Compare commits

...

57 Commits

Author SHA1 Message Date
Elian Doran
1ca46e3505 Fix enex import stores wrong format in database for dateCreated, dateModified (#7718) 2025-11-13 20:48:09 +02:00
Elian Doran
6b6dc47f2b Translations update from Hosted Weblate (#7714) 2025-11-13 20:43:39 +02:00
contributor
98b5b81d7d add typing and improve readability 2025-11-13 20:33:03 +02:00
contributor
48a20500f8 explicit param to keep or convert local date for enex import 2025-11-13 20:13:46 +02:00
contributor
a3bd15e102 move date conversion function to enex directly
to protect from future potential refactoring
2025-11-13 17:41:45 +00:00
contributor
f728b2b0e7 fix enex import saves local dates in wrong format (with Z, like UTC fields)
the proper format for dateCreated, dateModified is: +0000
2025-11-13 17:41:45 +00:00
Magnus Johansson
50dfd1d329 Translated using Weblate (Swedish)
Currently translated at 4.2% (5 of 118 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/sv/
2025-11-13 13:47:27 +00:00
kamykO
c0375b34fd Translated using Weblate (Polish)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pl/
2025-11-13 13:47:27 +00:00
Dong-ha, Lee
d8e7832f07 Translated using Weblate (Korean)
Currently translated at 15.6% (61 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ko/
2025-11-13 13:47:26 +00:00
green
f2415916aa Translated using Weblate (Japanese)
Currently translated at 100.0% (1624 of 1624 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-11-13 13:47:25 +00:00
Francis C.
7f67b2a1ee Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/zh_Hant/
2025-11-13 13:47:23 +00:00
Francis C.
bc9ad5012e Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1624 of 1624 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-11-13 13:47:23 +00:00
Francis C.
096deda23c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/zh_Hans/
2025-11-13 13:47:22 +00:00
qingmo_dev
8b5544268b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1624 of 1624 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-11-13 13:47:21 +00:00
Francis C.
d492c0e091 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1624 of 1624 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-11-13 13:47:20 +00:00
Elian Doran
20b301ac0e fix(collections/list): archived notes not shown on first render 2025-11-13 10:59:56 +02:00
Elian Doran
bacbe9f47c chore(collections/list): style archived notes differently 2025-11-13 10:35:39 +02:00
Elian Doran
4ecb693be5 chore(collections/list): use more performant mechanism for searching by note ID 2025-11-13 10:26:47 +02:00
Elian Doran
454310c3e4 feat(collections/list): enable "Show archived notes" checkmark in collections 2025-11-13 10:24:57 +02:00
Elian Doran
e51daad5da feat(collections/list): filter archived notes 2025-11-13 10:21:45 +02:00
Elian Doran
b13c0fe7a2 refactor(board/promoted_attributes): reduce duplication 2025-11-13 09:56:47 +02:00
Elian Doran
3036d18df5 feat(board/promoted_attributes): improve rendering for color badge 2025-11-13 09:56:47 +02:00
Elian Doran
5dbe9e7da6 feat(board/promoted_attributes): render non-promoted attribute defs too 2025-11-13 09:56:47 +02:00
Elian Doran
fd9b6e9e67 fix(board/promoted_attributes): status attribute displayed when it shouldn't 2025-11-13 09:56:46 +02:00
Elian Doran
4f580a37a3 fix(quick_search): resolve word issues to not trigger substring matches (#7708) 2025-11-13 09:53:14 +02:00
Elian Doran
f64d11b7c8 chore(deps): update dependency @smithy/middleware-retry to v4.4.10 (#7709) 2025-11-13 09:42:30 +02:00
Elian Doran
2bf9e0edd9 chore(deps): update dependency js-yaml to v4.1.1 (#7710) 2025-11-13 09:42:18 +02:00
Elian Doran
b807079c55 chore: add class name to content-header (#7713) 2025-11-13 09:41:35 +02:00
SiriusXT
2cc5b896e0 Merge branch 'main' into share 2025-11-13 14:24:04 +08:00
SiriusXT
7c79caba78 chore: add class name to content-header 2025-11-13 14:21:07 +08:00
Elian Doran
fbf4a910fa Merge branch 'main' into renovate/smithy-middleware-retry-4.x 2025-11-13 08:07:46 +02:00
Elian Doran
95947a9f8c chore(deps): update node.js to v24.11.1 (#7697) 2025-11-13 07:43:55 +02:00
Elian Doran
1c05acf5ed fix(deps): update dependency react-i18next to v16.3.1 (#7712) 2025-11-13 07:43:18 +02:00
Elian Doran
5cc5f3ffae chore(deps): update pnpm to v10.22.0 (#7711) 2025-11-13 07:42:47 +02:00
renovate[bot]
54556c73e2 fix(deps): update dependency react-i18next to v16.3.1 2025-11-13 01:08:04 +00:00
renovate[bot]
5aa63ac50c chore(deps): update pnpm to v10.22.0 2025-11-13 01:07:17 +00:00
renovate[bot]
38eaa94a53 chore(deps): update node.js to v24.11.1 2025-11-13 01:07:05 +00:00
renovate[bot]
7810f6c8da chore(deps): update dependency js-yaml to v4.1.1 2025-11-13 01:06:16 +00:00
renovate[bot]
6d94efb6c8 chore(deps): update dependency @smithy/middleware-retry to v4.4.10 2025-11-13 01:05:36 +00:00
perfectra1n
e89646ee7c fix(quick_search): centralize the repeated exactWordMatch function, per Gemini's suggestion 2025-11-12 16:49:43 -08:00
perfectra1n
dee8c115ab fix(quick_search): resolve word issues to not trigger substring matches 2025-11-12 16:43:59 -08:00
Elian Doran
54d3936c7b feat(board/promoted_attributes): support multiple values 2025-11-12 21:06:44 +02:00
Elian Doran
02452a0513 feat(board/promoted_attributes): display relations with icon 2025-11-12 21:01:53 +02:00
Elian Doran
e9f40c48e3 feat(board/promoted_attributes): basic support for color attributes 2025-11-12 20:56:15 +02:00
Elian Doran
6b74b227cb feat(board/promoted_attributes): format URL 2025-11-12 20:50:01 +02:00
Elian Doran
00874840b7 feat(board/promoted_attributes): format time 2025-11-12 20:48:45 +02:00
Elian Doran
d79a23bc9e feat(board/promoted_attributes): format boolean 2025-11-12 20:42:30 +02:00
Elian Doran
3015576d7e feat(board/promoted_attributes): format date & time 2025-11-12 20:34:48 +02:00
Elian Doran
46c2e162f0 feat(board/promoted_attributes): format number with precision 2025-11-12 20:31:27 +02:00
Elian Doran
3c42577da4 feat(board/promoted_attributes): react to changes 2025-11-12 20:27:13 +02:00
Elian Doran
76f791da93 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-11-12 19:59:36 +02:00
Elian Doran
0c5adcee2d fix(board): no promoted attributes creates margin 2025-11-12 19:58:42 +02:00
Elian Doran
b11b3ff67f refactor(board): use hook for obtaining the list of attributes 2025-11-12 19:57:28 +02:00
Elian Doran
e006afc5a2 feat(board): display in chip format 2025-11-12 19:38:56 +02:00
Elian Doran
40dbb818c5 feat(board): ignore status attribute 2025-11-12 19:09:59 +02:00
Elian Doran
62dc570d38 feat(board): display promoted attributes 2025-11-12 19:07:23 +02:00
Elian Doran
b759c5e7d2 feat(board): display number of items in column 2025-11-12 18:47:29 +02:00
42 changed files with 701 additions and 488 deletions

2
.nvmrc
View File

@@ -1 +1 @@
24.11.0
24.11.1

View File

@@ -38,7 +38,7 @@
"@playwright/test": "1.56.1",
"@stylistic/eslint-plugin": "5.5.0",
"@types/express": "5.0.5",
"@types/node": "24.10.0",
"@types/node": "24.10.1",
"@types/yargs": "17.0.34",
"@vitest/coverage-v8": "3.2.4",
"eslint": "9.39.1",

View File

@@ -9,7 +9,7 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.21.0",
"packageManager": "pnpm@10.22.0",
"devDependencies": {
"@redocly/cli": "2.11.1",
"archiver": "7.0.1",

View File

@@ -59,7 +59,7 @@
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
"react-i18next": "16.2.4",
"react-i18next": "16.3.1",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",

View File

@@ -6,6 +6,7 @@ import type { Froca } from "../services/froca-interface.js";
import type FAttachment from "./fattachment.js";
import type { default as FAttribute, AttributeType } from "./fattribute.js";
import utils from "../services/utils.js";
import search from "../services/search.js";
const LABEL = "label";
const RELATION = "relation";
@@ -255,6 +256,21 @@ export default class FNote {
return this.children;
}
async getChildNoteIdsWithArchiveFiltering(includeArchived = false) {
if (!includeArchived) {
const unorderedIds = new Set(await search.searchForNoteIds(`note.parents.noteId="${this.noteId}" #!archived`));
const results: string[] = [];
for (const id of this.children) {
if (unorderedIds.has(id)) {
results.push(id);
}
}
return results;
} else {
return this.children;
}
}
async getSubtreeNoteIds(includeArchived = false) {
let noteIds: (string | string[])[] = [];
for (const child of await this.getChildNotes()) {
@@ -839,8 +855,7 @@ export default class FNote {
return [];
}
const promotedAttrs = this.getAttributes()
.filter((attr) => attr.isDefinition())
const promotedAttrs = this.getAttributeDefinitions()
.filter((attr) => {
const def = attr.getDefinition();
@@ -860,6 +875,11 @@ export default class FNote {
return promotedAttrs;
}
getAttributeDefinitions() {
return this.getAttributes()
.filter((attr) => attr.isDefinition());
}
hasAncestor(ancestorNoteId: string, followTemplates = false, visitedNoteIds: Set<string> | null = null) {
if (this.noteId === ancestorNoteId) {
return true;

View File

@@ -3,7 +3,7 @@ import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.
import ApiLog from "../widgets/api_log.jsx";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import ContentHeader from "../widgets/containers/content-header.js";
import ContentHeader from "../widgets/containers/content_header.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import FindWidget from "../widgets/find.js";
import FlexContainer from "../widgets/containers/flex_container.js";

View File

@@ -10,7 +10,7 @@ import LauncherContainer from "../widgets/containers/launcher_container.js";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import NoteList from "../widgets/collections/NoteList.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import ContentHeader from "../widgets/containers/content-header.js";
import ContentHeader from "../widgets/containers/content_header.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";

View File

@@ -90,7 +90,8 @@ const HIDDEN_ATTRIBUTES = [
"viewType",
"geolocation",
"docName",
"webViewSrc"
"webViewSrc",
"archived"
];
async function renderNormalAttributes(note: FNote) {

View File

@@ -76,6 +76,11 @@ function getHue(color: ColorInstance) {
}
}
export function getReadableTextColor(bgColor: string) {
const colorInstance = Color(bgColor);
return colorInstance.isLight() ? "#000" : "#fff";
}
export default {
createClassForColor
};

View File

@@ -690,7 +690,8 @@
"convert_into_attachment_failed": "笔记 '{{title}}' 转换失败。",
"convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。",
"convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?",
"print_pdf": "导出为 PDF..."
"print_pdf": "导出为 PDF...",
"open_note_on_server": "在服务器上打开笔记"
},
"onclick_button": {
"no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序"
@@ -1110,7 +1111,8 @@
"title": "内容宽度",
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
"max_width_label": "内容最大宽度(像素)",
"max_width_unit": "像素"
"max_width_unit": "像素",
"centerContent": "保持内容居中"
},
"native_title_bar": {
"title": "原生标题栏(需要重新启动应用)",
@@ -2082,5 +2084,11 @@
},
"calendar_view": {
"delete_note": "删除笔记..."
},
"read-only-info": {
"read-only-note": "当前正在查看一个只读笔记。",
"auto-read-only-note": "这条笔记以只读模式显示便于快速加载。",
"auto-read-only-learn-more": "了解更多",
"edit-note": "编辑笔记"
}
}

View File

@@ -41,7 +41,7 @@
"prefix": "接頭辞: ",
"branch_prefix_saved": "ブランチの接頭辞が保存されました。",
"edit_branch_prefix_multiple": "{{count}} ブランチのブランチ接頭辞を編集",
"branch_prefix_saved_multiple": "{{count}} 個のブランチのブランチ接頭辞が保存されました。",
"branch_prefix_saved_multiple": "{{count}} ブランチのブランチ接頭辞が保存されました。",
"affected_branches": "影響を受けるブランチ {{count}}:"
},
"global_menu": {
@@ -456,7 +456,8 @@
"convert_into_attachment_failed": "ノート '{{title}}' の変換に失敗しました。",
"convert_into_attachment_successful": "ノート '{{title}}' は添付ファイルに変換されました。",
"convert_into_attachment_prompt": "本当にノート '{{title}}' を親ノートの添付ファイルに変換しますか?",
"note_attachments": "ノートの添付ファイル"
"note_attachments": "ノートの添付ファイル",
"open_note_on_server": "サーバー上のノートを開く"
},
"command_palette": {
"export_note_title": "ノートをエクスポート",

View File

@@ -687,7 +687,8 @@
"convert_into_attachment_failed": "筆記 '{{title}}' 轉換失敗。",
"convert_into_attachment_successful": "筆記 '{{title}}' 已成功轉換為附件。",
"convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?",
"print_pdf": "匯出為 PDF…"
"print_pdf": "匯出為 PDF…",
"open_note_on_server": "在伺服器上開啟筆記"
},
"onclick_button": {
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
@@ -1107,7 +1108,8 @@
"title": "內容寬度",
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
"max_width_label": "內容最大寬度(像素)",
"max_width_unit": "像素"
"max_width_unit": "像素",
"centerContent": "將內容置中"
},
"native_title_bar": {
"title": "原生標題列(需要重新啟動程式)",
@@ -2082,5 +2084,11 @@
},
"calendar_view": {
"delete_note": "刪除筆記…"
},
"read-only-info": {
"read-only-note": "目前正在檢視唯讀筆記。",
"auto-read-only-note": "此筆記以唯讀模式顯示以加快載入速度。",
"auto-read-only-learn-more": "了解更多",
"edit-note": "編輯筆記"
}
}

View File

@@ -0,0 +1,31 @@
.promoted-attributes {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-top: 8px;
}
.promoted-attributes .promoted-attribute {
padding: 2px 10px;
border-radius: 9999px;
white-space: nowrap;
background-color: var(--chip-bg, rgba(0, 0, 0, 0.08));
color: var(--chip-fg, inherit);
border: 1px solid var(--chip-border, rgba(0, 0, 0, 0.15));
font-size: 12px;
line-height: 1.2;
}
.promoted-attributes .promoted-attribute:hover {
background-color: var(--chip-bg-hover, rgba(0, 0, 0, 0.12));
border-color: var(--chip-border-hover, rgba(0, 0, 0, 0.22));
}
.promoted-attributes .promoted-attribute .name {
font-weight: 600;
}
.promoted-attributes .promoted-attribute .value {
opacity: 0.9;
}

View File

@@ -0,0 +1,134 @@
import { useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import "./PromotedAttributesDisplay.css";
import { useTriliumEvent } from "../react/hooks";
import attributes from "../../services/attributes";
import { DefinitionObject } from "../../services/promoted_attribute_definition_parser";
import { formatDateTime } from "../../utils/formatters";
import { ComponentChild, ComponentChildren, CSSProperties } from "preact";
import Icon from "../react/Icon";
import NoteLink from "../react/NoteLink";
import { getReadableTextColor } from "../../services/css_class_manager";
interface PromotedAttributesDisplayProps {
note: FNote;
ignoredAttributes?: string[];
}
interface AttributeWithDefinitions {
friendlyName: string;
name: string;
type: string;
value: string;
def: DefinitionObject;
}
export default function PromotedAttributesDisplay({ note, ignoredAttributes }: PromotedAttributesDisplayProps) {
const promotedDefinitionAttributes = useNoteAttributesWithDefinitions(note, ignoredAttributes);
return promotedDefinitionAttributes?.length > 0 && (
<div className="promoted-attributes">
{promotedDefinitionAttributes?.map(attr => buildPromotedAttribute(attr))}
</div>
)
}
function useNoteAttributesWithDefinitions(note: FNote, attributesToIgnore: string[] = []): AttributeWithDefinitions[] {
const [ promotedDefinitionAttributes, setPromotedDefinitionAttributes ] = useState<AttributeWithDefinitions[]>(getAttributesWithDefinitions(note, attributesToIgnore));
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
setPromotedDefinitionAttributes(getAttributesWithDefinitions(note, attributesToIgnore));
}
});
return promotedDefinitionAttributes;
}
function PromotedAttribute({ attr, children, style }: { attr: AttributeWithDefinitions, children: ComponentChildren, style?: CSSProperties }) {
const className = `${attr.type === "label" ? "label" + " " + attr.def.labelType : "relation"}`;
return (
<span key={attr.friendlyName} className={`promoted-attribute type-${className}`} style={style}>
{children}
</span>
)
}
function buildPromotedAttribute(attr: AttributeWithDefinitions): ComponentChildren {
const defaultLabel = <><strong>{attr.friendlyName}:</strong>{" "}</>;
let content: ComponentChildren;
let style: CSSProperties | undefined;
if (attr.type === "label") {
let value = attr.value;
switch (attr.def.labelType) {
case "number":
let formattedValue = value;
const numberValue = Number(value);
if (!Number.isNaN(numberValue) && attr.def.numberPrecision) formattedValue = numberValue.toFixed(attr.def.numberPrecision);
content = <>{defaultLabel}{formattedValue}</>;
break;
case "date":
case "datetime": {
const date = new Date(value);
const timeFormat = attr.def.labelType !== "date" ? "short" : "none";
const formattedValue = formatDateTime(date, "short", timeFormat);
content = <>{defaultLabel}{formattedValue}</>;
break;
}
case "time": {
const date = new Date(`1970-01-01T${value}Z`);
const formattedValue = formatDateTime(date, "none", "short");
content = <>{defaultLabel}{formattedValue}</>;
break;
}
case "boolean":
content = <><Icon icon={value === "true" ? "bx bx-check-square" : "bx bx-square"} />{" "}<strong>{attr.friendlyName}</strong></>;
break;
case "url":
content = <a href={value} target="_blank" rel="noopener noreferrer">{attr.friendlyName}</a>;
break;
case "color":
style = { backgroundColor: value, color: getReadableTextColor(value) };
content = <>{attr.friendlyName}</>;
break;
case "text":
default:
content = <>{defaultLabel}{value}</>;
break;
}
} else if (attr.type === "relation") {
content = <>{defaultLabel}<NoteLink notePath={attr.value} showNoteIcon /></>;
}
return <PromotedAttribute attr={attr} style={style}>{content}</PromotedAttribute>
}
function getAttributesWithDefinitions(note: FNote, attributesToIgnore: string[] = []): AttributeWithDefinitions[] {
const promotedDefinitionAttributes = note.getAttributeDefinitions();
const result: AttributeWithDefinitions[] = [];
for (const attr of promotedDefinitionAttributes) {
const def = attr.getDefinition();
const [ type, name ] = attr.name.split(":", 2);
const friendlyName = def?.promotedAlias || name;
const props: Omit<AttributeWithDefinitions, "value"> = { def, name, type, friendlyName };
if (attributesToIgnore.includes(name)) continue;
if (type === "label") {
const labels = note.getLabels(name);
for (const label of labels) {
if (!label.value) continue;
result.push({ ...props, value: label.value } );
}
} else if (type === "relation") {
const relations = note.getRelations(name);
for (const relation of relations) {
if (!relation.value) continue;
result.push({ ...props, value: relation.value } );
}
}
}
return result;
}

View File

@@ -141,7 +141,7 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt
async function getNoteIds(note: FNote) {
if (viewType === "list" || viewType === "grid" || viewType === "table" || note.type === "search") {
return note.getChildNoteIds();
return await note.getChildNoteIdsWithArchiveFiltering(includeArchived);
} else {
return await note.getSubtreeNoteIds(includeArchived);
}

View File

@@ -16,7 +16,7 @@ export default class BoardApi {
private byColumn: ColumnMap | undefined,
public columns: string[],
private parentNote: FNote,
private statusAttribute: string,
readonly statusAttribute: string,
private viewConfig: BoardViewData,
private saveConfig: (newConfig: BoardViewData) => void,
private setBranchIdToEdit: (branchId: string | undefined) => void

View File

@@ -6,6 +6,7 @@ import { BoardViewContext, TitleEditor } from ".";
import { ContextMenuEvent } from "../../../menus/context_menu";
import { openNoteContextMenu } from "./context_menu";
import { t } from "../../../services/i18n";
import PromotedAttributesDisplay from "../../attribute_widgets/PromotedAttributesDisplay";
export const CARD_CLIPBOARD_TYPE = "trilium/board-card";
@@ -108,6 +109,7 @@ export default function Card({
title={t("board_view.edit-note-title")}
onClick={handleEdit}
/>
<PromotedAttributesDisplay note={note} ignoredAttributes={[api.statusAttribute]} />
</>
) : (
<TitleEditor

View File

@@ -104,6 +104,8 @@ export default function Column({
{!isEditing ? (
<>
<span className="title">{column}</span>
<span className="counter-badge">{columnItems?.length ?? 0}</span>
<div className="spacer" />
<span
className="edit-icon icon bx bx-edit-alt"
title={t("board_view.edit-column-title")}

View File

@@ -53,7 +53,16 @@
align-items: center;
}
.board-view-container .board-column h3 > .title {
.board-view-container .board-column h3 .counter-badge {
background-color: var(--muted-text-color);
color: var(--main-background-color);
border-radius: 12px;
padding: 0.1em 0.6em;
font-size: 0.75em;
margin-inline-start: 0.5em;
}
.board-view-container .board-column h3 > .spacer {
flex-grow: 1;
}

View File

@@ -16,6 +16,10 @@
flex-grow: 1;
}
.note-book-card.archived {
opacity: 0.5;
}
.note-book-card:not(.expanded) .note-book-content {
padding: 10px
}

View File

@@ -64,7 +64,7 @@ function ListNoteCard({ note, parentNote, expand, highlightedTokens }: { note: F
return (
<div
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""}`}
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""} ${note.isArchived ? "archived" : ""}`}
data-note-id={note.noteId}
>
<h5 className="note-book-header">
@@ -100,7 +100,7 @@ function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, pa
return (
<div
className={`note-book-card no-tooltip-preview block-link`}
className={`note-book-card no-tooltip-preview block-link ${note.isArchived ? "archived" : ""}`}
data-href={`#${notePath}`}
data-note-id={note.noteId}
onClick={(e) => link.goToLink(e)}

View File

@@ -15,6 +15,7 @@ export default class ContentHeader extends Container<BasicWidget> {
constructor() {
super();
this.class("content-header-widget");
this.css("contain", "unset");
this.resizeObserver = new ResizeObserver(this.onResize.bind(this));
}

View File

@@ -406,14 +406,17 @@ export function useNoteLabelWithDefault(note: FNote | undefined | null, labelNam
}
export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: FilterLabelsByType<boolean>): [ boolean, (newValue: boolean) => void] {
const [ labelValue, setLabelValue ] = useState<boolean>(!!note?.hasLabel(labelName));
const [, forceRender] = useState({});
useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]);
useEffect(() => {
forceRender({});
}, [ note ]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
for (const attr of loadResults.getAttributeRows()) {
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
setLabelValue(!attr.isDeleted);
forceRender({});
break;
}
}
});
@@ -430,6 +433,7 @@ export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: F
useDebugValue(labelName);
const labelValue = !!note?.hasLabel(labelName);
return [ labelValue, setter ] as const;
}

View File

@@ -59,13 +59,12 @@ function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, s
function BookProperties({ viewType, note, properties }: { viewType: ViewTypeOptions, note: FNote, properties: BookProperty[] }) {
return (
<>
{properties.map(property => (
{properties.map(property => (
<div className={`type-${property}`}>
{mapPropertyView({ note, property })}
{mapPropertyView({ note, property })}
</div>
))}
))}
{viewType !== "list" && viewType !== "grid" && (
<CheckboxPropertyView
note={note} property={{
bindToLabel: "includeArchived",
@@ -73,7 +72,6 @@ function BookProperties({ viewType, note, properties }: { viewType: ViewTypeOpti
type: "checkbox"
}}
/>
)}
</>
)
}

View File

@@ -81,7 +81,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
await attributes.removeAttributeById(noteId, expandedAttr.attributeId);
}
triggerCommand("refreshNoteList", { noteId: noteId });
triggerCommand("refreshNoteList", { noteId });
},
},
{

View File

@@ -1,4 +1,4 @@
FROM node:24.11.0-bullseye-slim AS builder
FROM node:24.11.1-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.11.0-bullseye-slim
FROM node:24.11.1-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM node:24.11.0-alpine AS builder
FROM node:24.11.1-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.11.0-alpine
FROM node:24.11.1-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow

View File

@@ -1,4 +1,4 @@
FROM node:24.11.0-alpine AS builder
FROM node:24.11.1-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.11.0-alpine
FROM node:24.11.1-alpine
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -1,4 +1,4 @@
FROM node:24.11.0-bullseye-slim AS builder
FROM node:24.11.1-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.11.0-bullseye-slim
FROM node:24.11.1-bullseye-slim
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -104,7 +104,7 @@
"is-animated": "2.0.2",
"is-svg": "6.1.0",
"jimp": "1.6.0",
"js-yaml": "4.1.0",
"js-yaml": "4.1.1",
"marked": "16.4.2",
"mime-types": "3.0.1",
"multer": "2.0.2",

View File

@@ -258,7 +258,9 @@
"jump-to-note-title": "跳转至...",
"llm-chat-title": "与笔记聊天",
"ai-llm-title": "AI/LLM",
"inbox-title": "收件箱"
"inbox-title": "收件箱",
"command-palette": "打开命令面板",
"zen-mode": "禅模式"
},
"notes": {
"new-note": "新建笔记",

View File

@@ -43,6 +43,26 @@
},
"hidden-subtree": {
"zen-mode": "젠 모드",
"open-today-journal-note-title": "오늘의 일지 기록 열기"
"open-today-journal-note-title": "오늘의 일지 기록 열기",
"quick-search-title": "빠른 검색",
"protected-session-title": "보호된 세션",
"sync-status-title": "동기화 상태",
"settings-title": "설정",
"llm-chat-title": "기록과 대화하기",
"options-title": "옵션",
"appearance-title": "모양",
"shortcuts-title": "바로가기",
"text-notes": "텍스트 노트",
"code-notes-title": "코드 노트",
"images-title": "그림",
"spellcheck-title": "맞춤법 검사",
"password-title": "암호",
"multi-factor-authentication-title": "다중 인증",
"etapi-title": "ETAPI",
"backup-title": "백업",
"sync-title": "동기화",
"ai-llm-title": "AI/LLM",
"other": "기타",
"advanced-title": "고급"
}
}

View File

@@ -355,7 +355,9 @@
"visible-launchers-title": "可見啟動器",
"user-guide": "用戶說明",
"localization": "語言和區域",
"inbox-title": "收件匣"
"inbox-title": "收件匣",
"command-palette": "打開命令面板",
"zen-mode": "禪模式"
},
"notes": {
"new-note": "新增筆記",

View File

@@ -91,9 +91,12 @@ function validateUtcDateTime(str: string | undefined) {
}
export default {
LOCAL_DATETIME_FORMAT,
UTC_DATETIME_FORMAT,
utcNowDateTime,
localNowDateTime,
localNowDate,
utcDateStr,
utcDateTimeStr,
parseDateTime,

View File

@@ -1,8 +1,10 @@
import dayjs from "dayjs";
import sax from "sax";
import stream from "stream";
import { Throttle } from "stream-throttle";
import log from "../log.js";
import { md5, escapeHtml, fromBase64 } from "../utils.js";
import date_utils from "../date_utils.js";
import sql from "../sql.js";
import noteService from "../notes.js";
import imageService from "../image.js";
@@ -235,6 +237,8 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
function updateDates(note: BNote, utcDateCreated?: string, utcDateModified?: string) {
// it's difficult to force custom dateCreated and dateModified to Note entity, so we do it post-creation with SQL
const dateCreated = formatDateTimeToLocalDbFormat(utcDateCreated, false);
const dateModified = formatDateTimeToLocalDbFormat(utcDateModified, false);
sql.execute(
`
UPDATE notes
@@ -243,7 +247,7 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
dateModified = ?,
utcDateModified = ?
WHERE noteId = ?`,
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, note.noteId]
[dateCreated, utcDateCreated, dateModified, utcDateModified, note.noteId]
);
sql.execute(
@@ -407,4 +411,21 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
});
}
function formatDateTimeToLocalDbFormat(
utcDateFromEnex: Date | string | null | undefined,
keepUtc: boolean
): string | undefined {
if (!utcDateFromEnex) {
return undefined;
}
const parsedDate = dayjs(utcDateFromEnex);
if (!parsedDate.isValid()) {
return undefined;
}
return (keepUtc ? parsedDate.utc() : parsedDate).format(date_utils.LOCAL_DATETIME_FORMAT);
}
export default { importEnex };

View File

@@ -113,7 +113,16 @@ class NoteContentFulltextExp extends Expression {
const normalizedFlatText = normalizeSearchText(flatText);
// Check if =phrase appears in flatText (indicates attribute value match)
matches = normalizedFlatText.includes(`=${normalizedPhrase}`);
// For single words, use word-boundary matching to avoid substring matches
if (!normalizedPhrase.includes(' ')) {
// Single word: look for =word with word boundaries
// Split by = to get attribute values, then check each value for exact word match
const parts = normalizedFlatText.split('=');
matches = parts.slice(1).some(part => this.exactWordMatch(normalizedPhrase, part));
} else {
// Multi-word phrase: check for substring match
matches = normalizedFlatText.includes(`=${normalizedPhrase}`);
}
if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) {
resultNoteSet.add(noteFromBecca);
@@ -124,6 +133,17 @@ class NoteContentFulltextExp extends Expression {
return resultNoteSet;
}
/**
* Helper method to check if a single word appears as an exact match in text
* @param wordToFind - The word to search for (should be normalized)
* @param text - The text to search in (should be normalized)
* @returns true if the word is found as an exact match (not substring)
*/
private exactWordMatch(wordToFind: string, text: string): boolean {
const words = text.split(/\s+/);
return words.some(word => word === wordToFind);
}
/**
* Checks if content contains the exact word (with word boundaries) or exact phrase
* This is case-insensitive since content and token are already normalized
@@ -139,9 +159,8 @@ class NoteContentFulltextExp extends Expression {
return normalizedContent.includes(normalizedToken);
}
// For single words, split content into words and check for exact match
const words = normalizedContent.split(/\s+/);
return words.some(word => word === normalizedToken);
// For single words, use exact word matching to avoid substring matches
return this.exactWordMatch(normalizedToken, normalizedContent);
}
/**
@@ -155,7 +174,14 @@ class NoteContentFulltextExp extends Expression {
// Join tokens with single space to form the phrase
const phrase = normalizedTokens.join(" ");
// Check if the phrase appears as a substring (consecutive words)
// For single-word phrases, use word-boundary matching to avoid substring matches
// e.g., "asd" should not match "asdfasdf"
if (!phrase.includes(' ')) {
// Single word: use exact word matching to avoid substring matches
return this.exactWordMatch(phrase, normalizedContent);
}
// For multi-word phrases, check if the phrase appears as consecutive words
if (normalizedContent.includes(phrase)) {
return true;
}

View File

@@ -14,7 +14,7 @@
"preact": "10.27.2",
"preact-iso": "2.11.0",
"preact-render-to-string": "6.6.3",
"react-i18next": "16.2.4"
"react-i18next": "16.3.1"
},
"devDependencies": {
"@preact/preset-vite": "2.10.2",

View File

@@ -51,7 +51,7 @@
"mermaid_description": "Twórz diagramy, takie jak schematy blokowe, diagramy klas i sekwencyjne, wykresy Gantta i wiele innych, korzystając z składni Mermaid.",
"mindmap_title": "Mapy myśli",
"mindmap_description": "Organizuj wizualnie swoje myśli albo przeprowadź sesję burzy mózgów.",
"others_list": "I wiele innych: <0>mapa notatek</0>, <1>mapa powiązań</1>, <2>zapisane wyszukiwania</2>, <3>renderowane notatki</3>, and <4>podgląd stron www</4>.",
"others_list": "I wiele innych: <0>mapa notatek</0>, <1>mapa powiązań</1>, <2>zapisane wyszukiwania</2>, <3>renderowane notatki</3> i <4>podgląd stron www</4>.",
"title": "Wiele sposobów przedstawienia Twoich informacji"
},
"extensibility_benefits": {
@@ -132,7 +132,7 @@
"title": "Inne sposoby wsparcia",
"way_translate": "Przetłumacz aplikacja na swój natywny język przez <Link>Weblate</Link>.",
"way_community": "Dołącz do społeczności na <Discussions>GitHub Discussions</Discussions> lub na <Matrix>Matrix</Matrix>.",
"way_reports": "Zgłoś błędy przez<Link>GitHub issues</Link>.",
"way_reports": "Zgłoś błędy przez <Link>GitHub issues</Link>.",
"way_document": "Pomóż nam w doskonaleniu dokumentacji przez informowanie nas o lukach albo sam pomóż w tworzeniu treści (dokumentacja, FAQ, poradniki).",
"way_market": "Powiedz o nas swoim znajomym, na blogu albo na social mediach."
},

8
docs/README-sv.md vendored
View File

@@ -28,14 +28,14 @@ status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted
Trilium Notes is a free and open-source, cross-platform hierarchical note taking
application with focus on building large personal knowledge bases.
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for
quick overview:
Se [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) för en
snabb överblick:
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
## ⏬ Download
## Ladda ner
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest)
stable version, recommended for most users.
stabil version, rekommenderas för dom flesta användare.
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly)
unstable development version, updated daily with the latest features and
fixes.

View File

@@ -43,7 +43,7 @@
"@playwright/test": "1.56.1",
"@triliumnext/server": "workspace:*",
"@types/express": "5.0.5",
"@types/node": "24.10.0",
"@types/node": "24.10.1",
"@vitest/coverage-v8": "3.2.4",
"@vitest/ui": "3.2.4",
"chalk": "5.6.2",
@@ -83,7 +83,7 @@
"url": "https://github.com/TriliumNext/Trilium/issues"
},
"homepage": "https://triliumnotes.org",
"packageManager": "pnpm@10.21.0",
"packageManager": "pnpm@10.22.0",
"pnpm": {
"patchedDependencies": {
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",

View File

@@ -15,7 +15,7 @@
"ckeditor5-premium-features": "47.2.0"
},
"devDependencies": {
"@smithy/middleware-retry": "4.4.7",
"@smithy/middleware-retry": "4.4.10",
"@types/jquery": "3.5.33"
}
}

773
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff