mirror of
https://github.com/zadam/trilium.git
synced 2025-11-14 17:25:52 +01:00
Compare commits
2 Commits
migrate_pa
...
react/prom
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54c8322960 | ||
|
|
3d0d1fa36e |
@@ -38,7 +38,7 @@
|
||||
"@playwright/test": "1.56.1",
|
||||
"@stylistic/eslint-plugin": "5.5.0",
|
||||
"@types/express": "5.0.5",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/node": "24.10.0",
|
||||
"@types/yargs": "17.0.34",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.39.1",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"keywords": [],
|
||||
"author": "Elian Doran <contact@eliandoran.me>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.22.0",
|
||||
"packageManager": "pnpm@10.21.0",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.11.1",
|
||||
"archiver": "7.0.1",
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.27.2",
|
||||
"react-i18next": "16.3.1",
|
||||
"react-i18next": "16.2.4",
|
||||
"reveal.js": "5.2.1",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
|
||||
@@ -6,7 +6,6 @@ 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";
|
||||
@@ -256,21 +255,6 @@ 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()) {
|
||||
@@ -855,7 +839,8 @@ export default class FNote {
|
||||
return [];
|
||||
}
|
||||
|
||||
const promotedAttrs = this.getAttributeDefinitions()
|
||||
const promotedAttrs = this.getAttributes()
|
||||
.filter((attr) => attr.isDefinition())
|
||||
.filter((attr) => {
|
||||
const def = attr.getDefinition();
|
||||
|
||||
@@ -875,11 +860,6 @@ 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;
|
||||
|
||||
@@ -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";
|
||||
@@ -21,7 +21,6 @@ import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import options from "../services/options.js";
|
||||
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
|
||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
||||
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
||||
@@ -31,7 +30,6 @@ import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import ScrollPadding from "../widgets/scroll_padding.js";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import SharedInfo from "../widgets/shared_info.jsx";
|
||||
import OriginInfo from "../widgets/note_origin.jsx";
|
||||
import SpacerWidget from "../widgets/spacer.js";
|
||||
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import SqlResults from "../widgets/sql_result.js";
|
||||
@@ -45,6 +43,7 @@ import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
||||
import utils from "../services/utils.js";
|
||||
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
|
||||
|
||||
export default class DesktopLayout {
|
||||
|
||||
@@ -139,10 +138,9 @@ export default class DesktopLayout {
|
||||
.filling()
|
||||
.child(new ContentHeader()
|
||||
.child(<ReadOnlyNoteInfoBar />)
|
||||
.child(<OriginInfo />)
|
||||
.child(<SharedInfo />)
|
||||
)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(<PromotedAttributes />)
|
||||
.child(<SqlTableSchemas />)
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" />)
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -90,8 +90,7 @@ const HIDDEN_ATTRIBUTES = [
|
||||
"viewType",
|
||||
"geolocation",
|
||||
"docName",
|
||||
"webViewSrc",
|
||||
"archived"
|
||||
"webViewSrc"
|
||||
];
|
||||
|
||||
async function renderNormalAttributes(note: FNote) {
|
||||
|
||||
@@ -76,11 +76,6 @@ function getHue(color: ColorInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getReadableTextColor(bgColor: string) {
|
||||
const colorInstance = Color(bgColor);
|
||||
return colorInstance.isLight() ? "#000" : "#fff";
|
||||
}
|
||||
|
||||
export default {
|
||||
createClassForColor
|
||||
};
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
"title": "الملاحظات المعدلة"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": ""
|
||||
"info": "معلومات"
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "تحديث"
|
||||
|
||||
@@ -690,8 +690,7 @@
|
||||
"convert_into_attachment_failed": "笔记 '{{title}}' 转换失败。",
|
||||
"convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。",
|
||||
"convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?",
|
||||
"print_pdf": "导出为 PDF...",
|
||||
"open_note_on_server": "在服务器上打开笔记"
|
||||
"print_pdf": "导出为 PDF..."
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序"
|
||||
@@ -837,7 +836,8 @@
|
||||
"search": "搜索"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "笔记来源:"
|
||||
"this_note_was_originally_taken_from": "笔记来源:",
|
||||
"info": "信息"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "拥有的属性"
|
||||
@@ -1110,8 +1110,7 @@
|
||||
"title": "内容宽度",
|
||||
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
|
||||
"max_width_label": "内容最大宽度(像素)",
|
||||
"max_width_unit": "像素",
|
||||
"centerContent": "保持内容居中"
|
||||
"max_width_unit": "像素"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "原生标题栏(需要重新启动应用)",
|
||||
@@ -2083,11 +2082,5 @@
|
||||
},
|
||||
"calendar_view": {
|
||||
"delete_note": "删除笔记..."
|
||||
},
|
||||
"read-only-info": {
|
||||
"read-only-note": "当前正在查看一个只读笔记。",
|
||||
"auto-read-only-note": "这条笔记以只读模式显示便于快速加载。",
|
||||
"auto-read-only-learn-more": "了解更多",
|
||||
"edit-note": "编辑笔记"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,7 +830,8 @@
|
||||
"search": "Suchen"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Diese Notiz stammt ursprünglich aus:"
|
||||
"this_note_was_originally_taken_from": "Diese Notiz stammt ursprünglich aus:",
|
||||
"info": "Info"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Eigene Attribute"
|
||||
|
||||
@@ -837,7 +837,8 @@
|
||||
"search": "Search"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "This note was originally taken from:"
|
||||
"this_note_was_originally_taken_from": "This note was originally taken from:",
|
||||
"info": "Info"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Owned Attributes"
|
||||
|
||||
@@ -836,7 +836,8 @@
|
||||
"search": "Buscar"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Esta nota fue tomada originalmente de:"
|
||||
"this_note_was_originally_taken_from": "Esta nota fue tomada originalmente de:",
|
||||
"info": "Información"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Atributos propios"
|
||||
|
||||
@@ -832,7 +832,8 @@
|
||||
"search": "Recherche"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Cette note est initialement extraite de :"
|
||||
"this_note_was_originally_taken_from": "Cette note est initialement extraite de :",
|
||||
"info": "Infos"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Attributs propres"
|
||||
|
||||
@@ -1408,7 +1408,8 @@
|
||||
"search": "Ricerca"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Questa nota è stata originariamente tratta da:"
|
||||
"this_note_was_originally_taken_from": "Questa nota è stata originariamente tratta da:",
|
||||
"info": "Informazioni"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Attributi posseduti"
|
||||
|
||||
@@ -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,8 +456,7 @@
|
||||
"convert_into_attachment_failed": "ノート '{{title}}' の変換に失敗しました。",
|
||||
"convert_into_attachment_successful": "ノート '{{title}}' は添付ファイルに変換されました。",
|
||||
"convert_into_attachment_prompt": "本当にノート '{{title}}' を親ノートの添付ファイルに変換しますか?",
|
||||
"note_attachments": "ノートの添付ファイル",
|
||||
"open_note_on_server": "サーバー上のノートを開く"
|
||||
"note_attachments": "ノートの添付ファイル"
|
||||
},
|
||||
"command_palette": {
|
||||
"export_note_title": "ノートをエクスポート",
|
||||
@@ -688,6 +687,7 @@
|
||||
"outside_hoisted": "このパスはホイストされたノートの外側にあるため、ホイストを解除する必要があります。"
|
||||
},
|
||||
"note_properties": {
|
||||
"info": "情報",
|
||||
"this_note_was_originally_taken_from": "このノートは元々以下から引用したものです:"
|
||||
},
|
||||
"similar_notes": {
|
||||
|
||||
@@ -448,7 +448,8 @@
|
||||
"search": "Szukaj"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Ta notatka oryginalnie została wzięta z:"
|
||||
"this_note_was_originally_taken_from": "Ta notatka oryginalnie została wzięta z:",
|
||||
"info": "Info"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Posiadane atrybuty"
|
||||
|
||||
@@ -810,7 +810,8 @@
|
||||
"search": "Pesquisar"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Esta nota foi originalmente obtida de:"
|
||||
"this_note_was_originally_taken_from": "Esta nota foi originalmente obtida de:",
|
||||
"info": "Informações"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Atributos próprios"
|
||||
|
||||
@@ -1076,7 +1076,8 @@
|
||||
"outside_hoisted": "Este caminho está fora de uma nota fixada e você teria que desafixar."
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Esta nota foi originalmente obtida de:"
|
||||
"this_note_was_originally_taken_from": "Esta nota foi originalmente obtida de:",
|
||||
"info": "Informações"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
"promoted_attributes": "Atributos Promovidos",
|
||||
|
||||
@@ -897,6 +897,7 @@
|
||||
"title": "Căile notiței"
|
||||
},
|
||||
"note_properties": {
|
||||
"info": "Informații",
|
||||
"this_note_was_originally_taken_from": "Această notiță a fost preluată original de la:"
|
||||
},
|
||||
"note_type_chooser": {
|
||||
|
||||
@@ -1067,6 +1067,7 @@
|
||||
"archived": "Архивировано"
|
||||
},
|
||||
"note_properties": {
|
||||
"info": "Информация",
|
||||
"this_note_was_originally_taken_from": "Эта заметка была первоначально взята из:"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
|
||||
@@ -687,8 +687,7 @@
|
||||
"convert_into_attachment_failed": "筆記 '{{title}}' 轉換失敗。",
|
||||
"convert_into_attachment_successful": "筆記 '{{title}}' 已成功轉換為附件。",
|
||||
"convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?",
|
||||
"print_pdf": "匯出為 PDF…",
|
||||
"open_note_on_server": "在伺服器上開啟筆記"
|
||||
"print_pdf": "匯出為 PDF…"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
|
||||
@@ -834,7 +833,8 @@
|
||||
"search": "搜尋"
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "筆記來源:"
|
||||
"this_note_was_originally_taken_from": "筆記來源:",
|
||||
"info": "資訊"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "自有屬性"
|
||||
@@ -1107,8 +1107,7 @@
|
||||
"title": "內容寬度",
|
||||
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
|
||||
"max_width_label": "內容最大寬度(像素)",
|
||||
"max_width_unit": "像素",
|
||||
"centerContent": "將內容置中"
|
||||
"max_width_unit": "像素"
|
||||
},
|
||||
"native_title_bar": {
|
||||
"title": "原生標題列(需要重新啟動程式)",
|
||||
@@ -2083,11 +2082,5 @@
|
||||
},
|
||||
"calendar_view": {
|
||||
"delete_note": "刪除筆記…"
|
||||
},
|
||||
"read-only-info": {
|
||||
"read-only-note": "目前正在檢視唯讀筆記。",
|
||||
"auto-read-only-note": "此筆記以唯讀模式顯示以加快載入速度。",
|
||||
"auto-read-only-learn-more": "了解更多",
|
||||
"edit-note": "編輯筆記"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,7 +939,8 @@
|
||||
"outside_hoisted": "Цей шлях знаходиться поза межами закріпленої нотатки і вам доведеться відкріпити."
|
||||
},
|
||||
"note_properties": {
|
||||
"this_note_was_originally_taken_from": "Цю нотатку було спочатку взято з:"
|
||||
"this_note_was_originally_taken_from": "Цю нотатку було спочатку взято з:",
|
||||
"info": "Інформація"
|
||||
},
|
||||
"owned_attribute_list": {
|
||||
"owned_attributes": "Власні Атрибути"
|
||||
|
||||
91
apps/client/src/widgets/PromotedAttributes.css
Normal file
91
apps/client/src/widgets/PromotedAttributes.css
Normal file
@@ -0,0 +1,91 @@
|
||||
body.mobile .promoted-attributes-widget {
|
||||
/* https://github.com/zadam/trilium/issues/4468 */
|
||||
flex-shrink: 0.4;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.component.promoted-attributes-widget {
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.promoted-attributes-container {
|
||||
margin: 0 1.5em;
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
flex-wrap: wrap;
|
||||
display: table;
|
||||
}
|
||||
.promoted-attribute-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
display: table-row;
|
||||
}
|
||||
.promoted-attribute-cell > label {
|
||||
user-select: none;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.promoted-attribute-cell > * {
|
||||
display: table-cell;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell div.input-group {
|
||||
margin-inline-start: 10px;
|
||||
display: flex;
|
||||
min-height: 40px;
|
||||
}
|
||||
.promoted-attribute-cell strong {
|
||||
word-break:keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="checkbox"] {
|
||||
width: 22px !important;
|
||||
flex-grow: 0;
|
||||
width: unset;
|
||||
}
|
||||
|
||||
/* Restore default apperance */
|
||||
.promoted-attribute-cell input[type="number"],
|
||||
.promoted-attribute-cell input[type="checkbox"] {
|
||||
appearance: auto;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="color"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-top: 2px;
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
border-radius: 25% !important;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="color"]::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="color"]::-webkit-color-swatch {
|
||||
border: none;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"] {
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"]:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
inset-inline-start: 0px;
|
||||
inset-inline-end: 0;
|
||||
height: 2px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
transform: rotate(45deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
120
apps/client/src/widgets/PromotedAttributes.tsx
Normal file
120
apps/client/src/widgets/PromotedAttributes.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import "./PromotedAttributes.css";
|
||||
import { useNoteContext, useNoteLabel } from "./react/hooks";
|
||||
import { Attribute } from "../services/attribute_parser";
|
||||
import { ComponentChild } from "preact";
|
||||
import FAttribute from "../entities/fattribute";
|
||||
import { t } from "../services/i18n";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
|
||||
export default function PromotedAttributes() {
|
||||
const { note } = useNoteContext();
|
||||
const [ promotedAttributes, setPromotedAttributes ] = useState<ComponentChild[]>();
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
|
||||
useEffect(() => {
|
||||
if (!note) {
|
||||
setPromotedAttributes([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
||||
const ownedAttributes = note.getOwnedAttributes();
|
||||
// attrs are not resorted if position changes after the initial load
|
||||
// promoted attrs are sorted primarily by order of definitions, but with multi-valued promoted attrs
|
||||
// the order of attributes is important as well
|
||||
ownedAttributes.sort((a, b) => a.position - b.position);
|
||||
|
||||
let promotedAttributes: ComponentChild[] = [];
|
||||
for (const definitionAttr of promotedDefAttrs) {
|
||||
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";
|
||||
const valueName = definitionAttr.name.substr(valueType.length + 1);
|
||||
|
||||
let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType) as Attribute[];
|
||||
|
||||
if (valueAttrs.length === 0) {
|
||||
valueAttrs.push({
|
||||
attributeId: "",
|
||||
type: valueType,
|
||||
name: valueName,
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
|
||||
if (definitionAttr.getDefinition().multiplicity === "single") {
|
||||
valueAttrs = valueAttrs.slice(0, 1);
|
||||
}
|
||||
|
||||
for (const valueAttr of valueAttrs) {
|
||||
promotedAttributes.push(<PromotedAttributeCell
|
||||
noteId={note.noteId}
|
||||
definitionAttr={definitionAttr}
|
||||
valueAttr={valueAttr} valueName={valueName} />)
|
||||
}
|
||||
}
|
||||
setPromotedAttributes(promotedAttributes);
|
||||
console.log("Got ", promotedAttributes);
|
||||
}, [ note ]);
|
||||
|
||||
return (
|
||||
<div className="promoted-attributes-widget">
|
||||
{viewType !== "table" && (
|
||||
<div className="promoted-attributes-container">
|
||||
{promotedAttributes}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PromotedAttributeCell({ noteId, definitionAttr, valueAttr, valueName }: {
|
||||
noteId: string;
|
||||
definitionAttr: FAttribute;
|
||||
valueAttr: Attribute;
|
||||
valueName: string;
|
||||
}) {
|
||||
const definition = definitionAttr.getDefinition();
|
||||
const id = `value-${valueAttr.attributeId}`;
|
||||
|
||||
return (
|
||||
<div className="promoted-attribute-cell">
|
||||
<label
|
||||
for={id}
|
||||
>{definition.promotedAlias ?? valueName}</label>
|
||||
|
||||
<div className="input-group">
|
||||
<input
|
||||
className="form-control promoted-attribute-input"
|
||||
tabindex={200 + definitionAttr.position}
|
||||
id={id}
|
||||
// if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||
data-attribute-id={valueAttr.noteId === noteId ? valueAttr.attributeId ?? "" : ""}
|
||||
data-attribute-type={valueAttr.type}
|
||||
data-attribute-name={valueAttr.name}
|
||||
value={valueAttr.value}
|
||||
placeholder={t("promoted_attributes.unset-field-placeholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div />
|
||||
|
||||
{definition.multiplicity === "multi" && (
|
||||
<td className="multiplicity">
|
||||
<ActionButton
|
||||
icon="bx bx-plus"
|
||||
className="pointer tn-tool-button"
|
||||
text={t("promoted_attributes.add_new_attribute")}
|
||||
noIconActionClass
|
||||
/>
|
||||
|
||||
<ActionButton
|
||||
icon="bx bx-trash"
|
||||
className="pointer tn-tool-button"
|
||||
text={t("promoted_attributes.remove_this_attribute")}
|
||||
noIconActionClass
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -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 await note.getChildNoteIdsWithArchiveFiltering(includeArchived);
|
||||
return note.getChildNoteIds();
|
||||
} else {
|
||||
return await note.getSubtreeNoteIds(includeArchived);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class BoardApi {
|
||||
private byColumn: ColumnMap | undefined,
|
||||
public columns: string[],
|
||||
private parentNote: FNote,
|
||||
readonly statusAttribute: string,
|
||||
private statusAttribute: string,
|
||||
private viewConfig: BoardViewData,
|
||||
private saveConfig: (newConfig: BoardViewData) => void,
|
||||
private setBranchIdToEdit: (branchId: string | undefined) => void
|
||||
|
||||
@@ -6,7 +6,6 @@ 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";
|
||||
|
||||
@@ -109,7 +108,6 @@ export default function Card({
|
||||
title={t("board_view.edit-note-title")}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<PromotedAttributesDisplay note={note} ignoredAttributes={[api.statusAttribute]} />
|
||||
</>
|
||||
) : (
|
||||
<TitleEditor
|
||||
|
||||
@@ -104,8 +104,6 @@ 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")}
|
||||
|
||||
@@ -53,16 +53,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.board-view-container .board-column h3 > .title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.note-book-card.archived {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.note-book-card:not(.expanded) .note-book-content {
|
||||
padding: 10px
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ function ListNoteCard({ note, parentNote, expand, highlightedTokens }: { note: F
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""} ${note.isArchived ? "archived" : ""}`}
|
||||
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""}`}
|
||||
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 ${note.isArchived ? "archived" : ""}`}
|
||||
className={`note-book-card no-tooltip-preview block-link`}
|
||||
data-href={`#${notePath}`}
|
||||
data-note-id={note.noteId}
|
||||
onClick={(e) => link.goToLink(e)}
|
||||
|
||||
@@ -15,7 +15,6 @@ 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));
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { t } from "../services/i18n";
|
||||
import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import attributes from "../services/attributes";
|
||||
import InfoBar from "./react/InfoBar";
|
||||
import RawHtml from "./react/RawHtml";
|
||||
import FNote from "../entities/fnote";
|
||||
|
||||
export default function OriginInfo() {
|
||||
const { note } = useNoteContext();
|
||||
const [link, setLink] = useState<string>();
|
||||
|
||||
function refresh() {
|
||||
if (!note) return;
|
||||
const pageUrl = getPageUrl(note);
|
||||
if (!pageUrl) {
|
||||
setLink(undefined);
|
||||
return;
|
||||
}
|
||||
setLink(`<a href="${pageUrl}" class="external tn-link">${pageUrl}</a>`);
|
||||
}
|
||||
|
||||
useEffect(refresh, [note]);
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows().find((attr) => attr.type === "label" && attr.name?.toString() === "pageUrl" && attributes.isAffecting(attr, note))) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<InfoBar className="origin-info-widget" type="subtle" style={{ display: (!link) ? "none" : undefined }}>
|
||||
{link && (
|
||||
<RawHtml
|
||||
html={`${t("note_properties.this_note_was_originally_taken_from")} ${link}`}
|
||||
/>
|
||||
)}
|
||||
</InfoBar>
|
||||
)
|
||||
}
|
||||
|
||||
function getPageUrl(note: FNote) {
|
||||
return note.getOwnedLabelValue("pageUrl");
|
||||
}
|
||||
@@ -12,102 +12,6 @@ import type { Attribute } from "../services/attribute_parser.js";
|
||||
import type FAttribute from "../entities/fattribute.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="promoted-attributes-widget">
|
||||
<style>
|
||||
body.mobile .promoted-attributes-widget {
|
||||
/* https://github.com/zadam/trilium/issues/4468 */
|
||||
flex-shrink: 0.4;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.promoted-attributes-container {
|
||||
margin: 0 1.5em;
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
flex-wrap: wrap;
|
||||
display: table;
|
||||
}
|
||||
.promoted-attribute-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
display: table-row;
|
||||
}
|
||||
.promoted-attribute-cell > label {
|
||||
user-select: none;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.promoted-attribute-cell > * {
|
||||
display: table-cell;
|
||||
padding: 1px 0;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell div.input-group {
|
||||
margin-inline-start: 10px;
|
||||
display: flex;
|
||||
min-height: 40px;
|
||||
}
|
||||
.promoted-attribute-cell strong {
|
||||
word-break:keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="checkbox"] {
|
||||
width: 22px !important;
|
||||
flex-grow: 0;
|
||||
width: unset;
|
||||
}
|
||||
|
||||
/* Restore default apperance */
|
||||
.promoted-attribute-cell input[type="number"],
|
||||
.promoted-attribute-cell input[type="checkbox"] {
|
||||
appearance: auto;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="color"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-top: 2px;
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
border-radius: 25% !important;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="color"]::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.promoted-attribute-cell input[type="color"]::-webkit-color-swatch {
|
||||
border: none;
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"] {
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"]:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
inset-inline-start: 0px;
|
||||
inset-inline-end: 0;
|
||||
height: 2px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
transform: rotate(45deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="promoted-attributes-container"></div>
|
||||
</div>`;
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface AttributeResult {
|
||||
attributeId: string;
|
||||
@@ -117,115 +21,17 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $container!: JQuery<HTMLElement>;
|
||||
|
||||
get name() {
|
||||
return "promotedAttributes";
|
||||
}
|
||||
|
||||
get toggleCommand() {
|
||||
return "toggleRibbonTabPromotedAttributes";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget = $("");
|
||||
this.contentSized();
|
||||
this.$container = this.$widget.find(".promoted-attributes-container");
|
||||
}
|
||||
|
||||
getTitle(note: FNote) {
|
||||
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
||||
|
||||
if (promotedDefAttrs.length === 0) {
|
||||
return { show: false };
|
||||
}
|
||||
|
||||
return {
|
||||
show: true,
|
||||
activate: options.is("promotedAttributesOpenInRibbon"),
|
||||
title: t("promoted_attributes.promoted_attributes"),
|
||||
icon: "bx bx-table"
|
||||
};
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$container.empty();
|
||||
|
||||
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
|
||||
const ownedAttributes = note.getOwnedAttributes();
|
||||
// attrs are not resorted if position changes after the initial load
|
||||
// promoted attrs are sorted primarily by order of definitions, but with multi-valued promoted attrs
|
||||
// the order of attributes is important as well
|
||||
ownedAttributes.sort((a, b) => a.position - b.position);
|
||||
|
||||
if (promotedDefAttrs.length === 0 || note.getLabelValue("viewType") === "table") {
|
||||
this.toggleInt(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const $cells: JQuery<HTMLElement>[] = [];
|
||||
|
||||
for (const definitionAttr of promotedDefAttrs) {
|
||||
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";
|
||||
const valueName = definitionAttr.name.substr(valueType.length + 1);
|
||||
|
||||
let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType) as Attribute[];
|
||||
|
||||
if (valueAttrs.length === 0) {
|
||||
valueAttrs.push({
|
||||
attributeId: "",
|
||||
type: valueType,
|
||||
name: valueName,
|
||||
value: ""
|
||||
});
|
||||
}
|
||||
|
||||
if (definitionAttr.getDefinition().multiplicity === "single") {
|
||||
valueAttrs = valueAttrs.slice(0, 1);
|
||||
}
|
||||
|
||||
for (const valueAttr of valueAttrs) {
|
||||
const $cell = await this.createPromotedAttributeCell(definitionAttr, valueAttr, valueName);
|
||||
|
||||
if ($cell) {
|
||||
$cells.push($cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we replace the whole content in one step, so there can't be any race conditions
|
||||
// (previously we saw promoted attributes doubling)
|
||||
this.$container.empty().append(...$cells);
|
||||
this.toggleInt(true);
|
||||
}
|
||||
|
||||
async createPromotedAttributeCell(definitionAttr: FAttribute, valueAttr: Attribute, valueName: string) {
|
||||
const definition = definitionAttr.getDefinition();
|
||||
const id = `value-${valueAttr.attributeId}`;
|
||||
// .on("change", (event) => this.promotedAttributeChanged(event));
|
||||
|
||||
const $input = $("<input>")
|
||||
.prop("tabindex", 200 + definitionAttr.position)
|
||||
.prop("id", id)
|
||||
.attr("data-attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId ?? "" : "") // if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||
.attr("data-attribute-type", valueAttr.type)
|
||||
.attr("data-attribute-name", valueAttr.name)
|
||||
.prop("value", valueAttr.value)
|
||||
.prop("placeholder", t("promoted_attributes.unset-field-placeholder"))
|
||||
.addClass("form-control")
|
||||
.addClass("promoted-attribute-input")
|
||||
.on("change", (event) => this.promotedAttributeChanged(event));
|
||||
|
||||
const $actionCell = $("<div>");
|
||||
const $multiplicityCell = $("<td>").addClass("multiplicity").attr("nowrap", "true");
|
||||
|
||||
const $wrapper = $('<div class="promoted-attribute-cell">')
|
||||
.append(
|
||||
$("<label>")
|
||||
.prop("for", id)
|
||||
.text(definition.promotedAlias ?? valueName)
|
||||
)
|
||||
.append($("<div>").addClass("input-group").append($input))
|
||||
.append($actionCell)
|
||||
.append($multiplicityCell);
|
||||
|
||||
if (valueAttr.type === "label") {
|
||||
$wrapper.addClass(`promoted-attribute-label-${definition.labelType}`);
|
||||
if (definition.labelType === "text") {
|
||||
@@ -359,8 +165,6 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
||||
|
||||
if (definition.multiplicity === "multi") {
|
||||
const $addButton = $("<span>")
|
||||
.addClass("bx bx-plus pointer tn-tool-button")
|
||||
.prop("title", t("promoted_attributes.add_new_attribute"))
|
||||
.on("click", async () => {
|
||||
const $new = await this.createPromotedAttributeCell(
|
||||
definitionAttr,
|
||||
|
||||
@@ -406,17 +406,14 @@ export function useNoteLabelWithDefault(note: FNote | undefined | null, labelNam
|
||||
}
|
||||
|
||||
export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: FilterLabelsByType<boolean>): [ boolean, (newValue: boolean) => void] {
|
||||
const [, forceRender] = useState({});
|
||||
const [ labelValue, setLabelValue ] = useState<boolean>(!!note?.hasLabel(labelName));
|
||||
|
||||
useEffect(() => {
|
||||
forceRender({});
|
||||
}, [ note ]);
|
||||
useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]);
|
||||
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
for (const attr of loadResults.getAttributeRows()) {
|
||||
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
|
||||
forceRender({});
|
||||
break;
|
||||
setLabelValue(!attr.isDeleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -433,7 +430,6 @@ export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: F
|
||||
|
||||
useDebugValue(labelName);
|
||||
|
||||
const labelValue = !!note?.hasLabel(labelName);
|
||||
return [ labelValue, setter ] as const;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,12 +59,13 @@ 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",
|
||||
@@ -72,6 +73,7 @@ function BookProperties({ viewType, note, properties }: { viewType: ViewTypeOpti
|
||||
type: "checkbox"
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
20
apps/client/src/widgets/ribbon/NotePropertiesTab.tsx
Normal file
20
apps/client/src/widgets/ribbon/NotePropertiesTab.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { t } from "../../services/i18n";
|
||||
import { useNoteLabel } from "../react/hooks";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
|
||||
/**
|
||||
* TODO: figure out better name or conceptualize better.
|
||||
*/
|
||||
export default function NotePropertiesTab({ note }: TabContext) {
|
||||
const [ pageUrl ] = useNoteLabel(note, "pageUrl");
|
||||
|
||||
return (
|
||||
<div className="note-properties-widget" style={{ padding: "12px", color: "var(--muted-text-color)" }}>
|
||||
{ pageUrl && (
|
||||
<div style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{t("note_properties.this_note_was_originally_taken_from")} <a href={pageUrl} class="page-url external">{pageUrl}</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import ScriptTab from "./ScriptTab";
|
||||
import EditedNotesTab from "./EditedNotesTab";
|
||||
import NotePropertiesTab from "./NotePropertiesTab";
|
||||
import NoteInfoTab from "./NoteInfoTab";
|
||||
import SimilarNotesTab from "./SimilarNotesTab";
|
||||
import FilePropertiesTab from "./FilePropertiesTab";
|
||||
@@ -58,6 +59,13 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
|
||||
show: ({ note }) => note?.type === "book" || note?.type === "search",
|
||||
toggleCommand: "toggleRibbonTabBookProperties"
|
||||
},
|
||||
{
|
||||
title: t("note_properties.info"),
|
||||
icon: "bx bx-info-square",
|
||||
content: NotePropertiesTab,
|
||||
show: ({ note }) => !!note?.getLabelValue("pageUrl"),
|
||||
activate: true
|
||||
},
|
||||
{
|
||||
title: t("file_properties.title"),
|
||||
icon: "bx bx-file",
|
||||
|
||||
@@ -81,7 +81,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
||||
await attributes.removeAttributeById(noteId, expandedAttr.attributeId);
|
||||
}
|
||||
|
||||
triggerCommand("refreshNoteList", { noteId });
|
||||
triggerCommand("refreshNoteList", { noteId: noteId });
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.11.1-bullseye-slim AS builder
|
||||
FROM node:24.11.0-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.1-bullseye-slim
|
||||
FROM node:24.11.0-bullseye-slim
|
||||
# Install only runtime dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.11.1-alpine AS builder
|
||||
FROM node:24.11.0-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.1-alpine
|
||||
FROM node:24.11.0-alpine
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.11.1-alpine AS builder
|
||||
FROM node:24.11.0-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.1-alpine
|
||||
FROM node:24.11.0-alpine
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.11.1-bullseye-slim AS builder
|
||||
FROM node:24.11.0-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.1-bullseye-slim
|
||||
FROM node:24.11.0-bullseye-slim
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
"is-animated": "2.0.2",
|
||||
"is-svg": "6.1.0",
|
||||
"jimp": "1.6.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"marked": "16.4.2",
|
||||
"mime-types": "3.0.1",
|
||||
"multer": "2.0.2",
|
||||
|
||||
@@ -258,9 +258,7 @@
|
||||
"jump-to-note-title": "跳转至...",
|
||||
"llm-chat-title": "与笔记聊天",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"inbox-title": "收件箱",
|
||||
"command-palette": "打开命令面板",
|
||||
"zen-mode": "禅模式"
|
||||
"inbox-title": "收件箱"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "新建笔记",
|
||||
|
||||
@@ -43,26 +43,6 @@
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"zen-mode": "젠 모드",
|
||||
"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": "고급"
|
||||
"open-today-journal-note-title": "오늘의 일지 기록 열기"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,9 +355,7 @@
|
||||
"visible-launchers-title": "可見啟動器",
|
||||
"user-guide": "用戶說明",
|
||||
"localization": "語言和區域",
|
||||
"inbox-title": "收件匣",
|
||||
"command-palette": "打開命令面板",
|
||||
"zen-mode": "禪模式"
|
||||
"inbox-title": "收件匣"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "新增筆記",
|
||||
|
||||
@@ -91,12 +91,9 @@ function validateUtcDateTime(str: string | undefined) {
|
||||
}
|
||||
|
||||
export default {
|
||||
LOCAL_DATETIME_FORMAT,
|
||||
UTC_DATETIME_FORMAT,
|
||||
utcNowDateTime,
|
||||
localNowDateTime,
|
||||
localNowDate,
|
||||
|
||||
utcDateStr,
|
||||
utcDateTimeStr,
|
||||
parseDateTime,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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";
|
||||
@@ -237,8 +235,6 @@ 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
|
||||
@@ -247,7 +243,7 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
|
||||
dateModified = ?,
|
||||
utcDateModified = ?
|
||||
WHERE noteId = ?`,
|
||||
[dateCreated, utcDateCreated, dateModified, utcDateModified, note.noteId]
|
||||
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, note.noteId]
|
||||
);
|
||||
|
||||
sql.execute(
|
||||
@@ -411,21 +407,4 @@ 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 };
|
||||
|
||||
@@ -113,16 +113,7 @@ class NoteContentFulltextExp extends Expression {
|
||||
const normalizedFlatText = normalizeSearchText(flatText);
|
||||
|
||||
// Check if =phrase appears in flatText (indicates attribute value match)
|
||||
// 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}`);
|
||||
}
|
||||
matches = normalizedFlatText.includes(`=${normalizedPhrase}`);
|
||||
|
||||
if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) {
|
||||
resultNoteSet.add(noteFromBecca);
|
||||
@@ -133,17 +124,6 @@ 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
|
||||
@@ -159,8 +139,9 @@ class NoteContentFulltextExp extends Expression {
|
||||
return normalizedContent.includes(normalizedToken);
|
||||
}
|
||||
|
||||
// For single words, use exact word matching to avoid substring matches
|
||||
return this.exactWordMatch(normalizedToken, normalizedContent);
|
||||
// For single words, split content into words and check for exact match
|
||||
const words = normalizedContent.split(/\s+/);
|
||||
return words.some(word => word === normalizedToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,14 +155,7 @@ class NoteContentFulltextExp extends Expression {
|
||||
// Join tokens with single space to form the phrase
|
||||
const phrase = normalizedTokens.join(" ");
|
||||
|
||||
// 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
|
||||
// Check if the phrase appears as a substring (consecutive words)
|
||||
if (normalizedContent.includes(phrase)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"preact": "10.27.2",
|
||||
"preact-iso": "2.11.0",
|
||||
"preact-render-to-string": "6.6.3",
|
||||
"react-i18next": "16.3.1"
|
||||
"react-i18next": "16.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
|
||||
@@ -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> i <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>, and <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
8
docs/README-sv.md
vendored
@@ -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.
|
||||
|
||||
Se [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) för en
|
||||
snabb överblick:
|
||||
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for
|
||||
quick overview:
|
||||
|
||||
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
## Ladda ner
|
||||
## ⏬ Download
|
||||
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) –
|
||||
stabil version, rekommenderas för dom flesta användare.
|
||||
stable version, recommended for most users.
|
||||
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) –
|
||||
unstable development version, updated daily with the latest features and
|
||||
fixes.
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@playwright/test": "1.56.1",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"@types/express": "5.0.5",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/node": "24.10.0",
|
||||
"@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.22.0",
|
||||
"packageManager": "pnpm@10.21.0",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"ckeditor5-premium-features": "47.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@smithy/middleware-retry": "4.4.10",
|
||||
"@smithy/middleware-retry": "4.4.7",
|
||||
"@types/jquery": "3.5.33"
|
||||
}
|
||||
}
|
||||
|
||||
773
pnpm-lock.yaml
generated
773
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user