Compare commits
2 Commits
siriusbcd_
...
react/prom
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54c8322960 | ||
|
|
3d0d1fa36e |
4
.github/workflows/main-docker.yml
vendored
@@ -155,10 +155,6 @@ jobs:
|
|||||||
- name: Update build info
|
- name: Update build info
|
||||||
run: pnpm run chore:update-build-info
|
run: pnpm run chore:update-build-info
|
||||||
|
|
||||||
- name: Update nightly version
|
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
|
||||||
run: pnpm run chore:ci-update-nightly-version
|
|
||||||
|
|
||||||
- name: Run the TypeScript build
|
- name: Run the TypeScript build
|
||||||
run: pnpm run server:build
|
run: pnpm run server:build
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/nightly.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
- name: Update nightly version
|
- name: Update nightly version
|
||||||
run: pnpm run chore:ci-update-nightly-version
|
run: npm run chore:ci-update-nightly-version
|
||||||
- name: Run the build
|
- name: Run the build
|
||||||
uses: ./.github/actions/build-electron
|
uses: ./.github/actions/build-electron
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -38,15 +38,15 @@
|
|||||||
"@playwright/test": "1.56.1",
|
"@playwright/test": "1.56.1",
|
||||||
"@stylistic/eslint-plugin": "5.5.0",
|
"@stylistic/eslint-plugin": "5.5.0",
|
||||||
"@types/express": "5.0.5",
|
"@types/express": "5.0.5",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.0",
|
||||||
"@types/yargs": "17.0.35",
|
"@types/yargs": "17.0.34",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.1",
|
||||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||||
"esm": "3.2.25",
|
"esm": "3.2.25",
|
||||||
"jsdoc": "4.0.5",
|
"jsdoc": "4.0.5",
|
||||||
"lorem-ipsum": "2.0.8",
|
"lorem-ipsum": "2.0.8",
|
||||||
"rcedit": "5.0.1",
|
"rcedit": "5.0.0",
|
||||||
"rimraf": "6.1.0",
|
"rimraf": "6.1.0",
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Elian Doran <contact@eliandoran.me>",
|
"author": "Elian Doran <contact@eliandoran.me>",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"packageManager": "pnpm@10.22.0",
|
"packageManager": "pnpm@10.21.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redocly/cli": "2.11.1",
|
"@redocly/cli": "2.11.1",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"@fullcalendar/timegrid": "6.1.19",
|
"@fullcalendar/timegrid": "6.1.19",
|
||||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||||
"@mermaid-js/layout-elk": "0.2.0",
|
"@mermaid-js/layout-elk": "0.2.0",
|
||||||
"@mind-elixir/node-menu": "5.0.1",
|
"@mind-elixir/node-menu": "5.0.0",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@triliumnext/ckeditor5": "workspace:*",
|
"@triliumnext/ckeditor5": "workspace:*",
|
||||||
"@triliumnext/codemirror": "workspace:*",
|
"@triliumnext/codemirror": "workspace:*",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"autocomplete.js": "0.38.1",
|
"autocomplete.js": "0.38.1",
|
||||||
"bootstrap": "5.3.8",
|
"bootstrap": "5.3.8",
|
||||||
"boxicons": "2.1.4",
|
"boxicons": "2.1.4",
|
||||||
"color": "5.0.3",
|
"color": "5.0.2",
|
||||||
"dayjs": "1.11.19",
|
"dayjs": "1.11.19",
|
||||||
"dayjs-plugin-utc": "0.1.2",
|
"dayjs-plugin-utc": "0.1.2",
|
||||||
"debounce": "3.0.0",
|
"debounce": "3.0.0",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
"preact": "10.27.2",
|
"preact": "10.27.2",
|
||||||
"react-i18next": "16.3.3",
|
"react-i18next": "16.2.4",
|
||||||
"reveal.js": "5.2.1",
|
"reveal.js": "5.2.1",
|
||||||
"svg-pan-zoom": "3.6.2",
|
"svg-pan-zoom": "3.6.2",
|
||||||
"tabulator-tables": "6.3.1",
|
"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 FAttachment from "./fattachment.js";
|
||||||
import type { default as FAttribute, AttributeType } from "./fattribute.js";
|
import type { default as FAttribute, AttributeType } from "./fattribute.js";
|
||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import search from "../services/search.js";
|
|
||||||
|
|
||||||
const LABEL = "label";
|
const LABEL = "label";
|
||||||
const RELATION = "relation";
|
const RELATION = "relation";
|
||||||
@@ -256,23 +255,6 @@ export default class FNote {
|
|||||||
return this.children;
|
return this.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildNoteIdsWithArchiveFiltering(includeArchived = false) {
|
|
||||||
const isHiddenNote = this.noteId.startsWith("_");
|
|
||||||
const isSearchNote = this.type === "search";
|
|
||||||
if (!includeArchived && !isHiddenNote && !isSearchNote) {
|
|
||||||
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) {
|
async getSubtreeNoteIds(includeArchived = false) {
|
||||||
let noteIds: (string | string[])[] = [];
|
let noteIds: (string | string[])[] = [];
|
||||||
for (const child of await this.getChildNotes()) {
|
for (const child of await this.getChildNotes()) {
|
||||||
@@ -806,16 +788,6 @@ export default class FNote {
|
|||||||
return this.getAttributeValue(LABEL, name);
|
return this.getAttributeValue(LABEL, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLabelOrRelation(nameWithPrefix: string) {
|
|
||||||
if (nameWithPrefix.startsWith("#")) {
|
|
||||||
return this.getLabelValue(nameWithPrefix.substring(1));
|
|
||||||
} else if (nameWithPrefix.startsWith("~")) {
|
|
||||||
return this.getRelationValue(nameWithPrefix.substring(1));
|
|
||||||
} else {
|
|
||||||
return this.getLabelValue(nameWithPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name - relation name
|
* @param name - relation name
|
||||||
* @returns relation value if relation exists, null otherwise
|
* @returns relation value if relation exists, null otherwise
|
||||||
@@ -867,7 +839,8 @@ export default class FNote {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const promotedAttrs = this.getAttributeDefinitions()
|
const promotedAttrs = this.getAttributes()
|
||||||
|
.filter((attr) => attr.isDefinition())
|
||||||
.filter((attr) => {
|
.filter((attr) => {
|
||||||
const def = attr.getDefinition();
|
const def = attr.getDefinition();
|
||||||
|
|
||||||
@@ -887,11 +860,6 @@ export default class FNote {
|
|||||||
return promotedAttrs;
|
return promotedAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAttributeDefinitions() {
|
|
||||||
return this.getAttributes()
|
|
||||||
.filter((attr) => attr.isDefinition());
|
|
||||||
}
|
|
||||||
|
|
||||||
hasAncestor(ancestorNoteId: string, followTemplates = false, visitedNoteIds: Set<string> | null = null) {
|
hasAncestor(ancestorNoteId: string, followTemplates = false, visitedNoteIds: Set<string> | null = null) {
|
||||||
if (this.noteId === ancestorNoteId) {
|
if (this.noteId === ancestorNoteId) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.
|
|||||||
import ApiLog from "../widgets/api_log.jsx";
|
import ApiLog from "../widgets/api_log.jsx";
|
||||||
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
|
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
|
||||||
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
|
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 CreatePaneButton from "../widgets/buttons/create_pane_button.js";
|
||||||
import FindWidget from "../widgets/find.js";
|
import FindWidget from "../widgets/find.js";
|
||||||
import FlexContainer from "../widgets/containers/flex_container.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 NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.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 QuickSearchWidget from "../widgets/quick_search.js";
|
||||||
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
||||||
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
|
||||||
@@ -44,6 +43,7 @@ import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
|
|||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
|
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
|
||||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||||
|
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
|
||||||
|
|
||||||
export default class DesktopLayout {
|
export default class DesktopLayout {
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ export default class DesktopLayout {
|
|||||||
.child(<ReadOnlyNoteInfoBar />)
|
.child(<ReadOnlyNoteInfoBar />)
|
||||||
.child(<SharedInfo />)
|
.child(<SharedInfo />)
|
||||||
)
|
)
|
||||||
.child(new PromotedAttributesWidget())
|
.child(<PromotedAttributes />)
|
||||||
.child(<SqlTableSchemas />)
|
.child(<SqlTableSchemas />)
|
||||||
.child(<NoteDetail />)
|
.child(<NoteDetail />)
|
||||||
.child(<NoteList media="screen" />)
|
.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 MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||||
import NoteTitleWidget from "../widgets/note_title.js";
|
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 NoteTreeWidget from "../widgets/note_tree.js";
|
||||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||||
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
||||||
|
|||||||
@@ -90,8 +90,7 @@ const HIDDEN_ATTRIBUTES = [
|
|||||||
"viewType",
|
"viewType",
|
||||||
"geolocation",
|
"geolocation",
|
||||||
"docName",
|
"docName",
|
||||||
"webViewSrc",
|
"webViewSrc"
|
||||||
"archived"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
async function renderNormalAttributes(note: FNote) {
|
async function renderNormalAttributes(note: FNote) {
|
||||||
|
|||||||
@@ -22,15 +22,6 @@ export async function setLabel(noteId: string, name: string, value: string = "",
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setRelation(noteId: string, name: string, value: string = "", isInheritable = false) {
|
|
||||||
await server.put(`notes/${noteId}/set-attribute`, {
|
|
||||||
type: "relation",
|
|
||||||
name: name,
|
|
||||||
value: value,
|
|
||||||
isInheritable
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeAttributeById(noteId: string, attributeId: string) {
|
async function removeAttributeById(noteId: string, attributeId: string) {
|
||||||
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
|
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
|
||||||
}
|
}
|
||||||
@@ -60,23 +51,6 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a relation identified by its name from the given note, if it exists. Note that the relation must be owned, i.e.
|
|
||||||
* it will not remove inherited attributes.
|
|
||||||
*
|
|
||||||
* @param note the note from which to remove the relation.
|
|
||||||
* @param relationName the name of the relation to remove.
|
|
||||||
* @returns `true` if an attribute was identified and removed, `false` otherwise.
|
|
||||||
*/
|
|
||||||
function removeOwnedRelationByName(note: FNote, relationName: string) {
|
|
||||||
const relation = note.getOwnedRelation(relationName);
|
|
||||||
if (relation) {
|
|
||||||
removeAttributeById(note.noteId, relation.attributeId);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the attribute of the given note to the provided value if its truthy, or removes the attribute if the value is falsy.
|
* Sets the attribute of the given note to the provided value if its truthy, or removes the attribute if the value is falsy.
|
||||||
* For an attribute with an empty value, pass an empty string instead.
|
* For an attribute with an empty value, pass an empty string instead.
|
||||||
@@ -142,10 +116,8 @@ function isAffecting(attrRow: AttributeRow, affectedNote: FNote | null | undefin
|
|||||||
export default {
|
export default {
|
||||||
addLabel,
|
addLabel,
|
||||||
setLabel,
|
setLabel,
|
||||||
setRelation,
|
|
||||||
setAttribute,
|
setAttribute,
|
||||||
removeAttributeById,
|
removeAttributeById,
|
||||||
removeOwnedLabelByName,
|
removeOwnedLabelByName,
|
||||||
removeOwnedRelationByName,
|
|
||||||
isAffecting
|
isAffecting
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,11 +76,6 @@ function getHue(color: ColorInstance) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReadableTextColor(bgColor: string) {
|
|
||||||
const colorInstance = Color(bgColor);
|
|
||||||
return colorInstance.isLight() ? "#000" : "#fff";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createClassForColor
|
createClassForColor
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import { applyReferenceLinks } from "../widgets/type_widgets/text/read_only_helper.js";
|
|
||||||
import { getCurrentLanguage } from "./i18n.js";
|
import { getCurrentLanguage } from "./i18n.js";
|
||||||
import { formatCodeBlocks } from "./syntax_highlight.js";
|
import { formatCodeBlocks } from "./syntax_highlight.js";
|
||||||
|
|
||||||
@@ -11,18 +10,18 @@ export default function renderDoc(note: FNote) {
|
|||||||
if (docName) {
|
if (docName) {
|
||||||
// find doc based on language
|
// find doc based on language
|
||||||
const url = getUrl(docName, getCurrentLanguage());
|
const url = getUrl(docName, getCurrentLanguage());
|
||||||
$content.load(url, async (response, status) => {
|
$content.load(url, (response, status) => {
|
||||||
// fallback to english doc if no translation available
|
// fallback to english doc if no translation available
|
||||||
if (status === "error") {
|
if (status === "error") {
|
||||||
const fallbackUrl = getUrl(docName, "en");
|
const fallbackUrl = getUrl(docName, "en");
|
||||||
$content.load(fallbackUrl, async () => {
|
$content.load(fallbackUrl, () => {
|
||||||
await processContent(fallbackUrl, $content)
|
processContent(fallbackUrl, $content)
|
||||||
resolve($content);
|
resolve($content);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await processContent(url, $content);
|
processContent(url, $content);
|
||||||
resolve($content);
|
resolve($content);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -33,7 +32,7 @@ export default function renderDoc(note: FNote) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processContent(url: string, $content: JQuery<HTMLElement>) {
|
function processContent(url: string, $content: JQuery<HTMLElement>) {
|
||||||
const dir = url.substring(0, url.lastIndexOf("/"));
|
const dir = url.substring(0, url.lastIndexOf("/"));
|
||||||
|
|
||||||
// Images are relative to the docnote but that will not work when rendered in the application since the path breaks.
|
// Images are relative to the docnote but that will not work when rendered in the application since the path breaks.
|
||||||
@@ -43,9 +42,6 @@ async function processContent(url: string, $content: JQuery<HTMLElement>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
formatCodeBlocks($content);
|
formatCodeBlocks($content);
|
||||||
|
|
||||||
// Apply reference links.
|
|
||||||
await applyReferenceLinks($content[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(docNameValue: string, language: string) {
|
function getUrl(docNameValue: string, language: string) {
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ async function getActionsForScope(scope: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, component: Component) {
|
async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, component: Component) {
|
||||||
if (!$el[0]) return [];
|
|
||||||
|
|
||||||
const actions = await getActionsForScope(scope);
|
const actions = await getActionsForScope(scope);
|
||||||
const bindings: ShortcutBinding[] = [];
|
const bindings: ShortcutBinding[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -150,16 +150,11 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
|
|||||||
$container.append($noteLink);
|
$container.append($noteLink);
|
||||||
|
|
||||||
if (showNotePath) {
|
if (showNotePath) {
|
||||||
let pathSegments: string[];
|
const resolvedPathSegments = (await treeService.resolveNotePathToSegments(notePath)) || [];
|
||||||
if (notePath == "root") {
|
resolvedPathSegments.pop(); // Remove last element
|
||||||
pathSegments = ["⌂"];
|
|
||||||
} else {
|
|
||||||
const resolvedPathSegments = (await treeService.resolveNotePathToSegments(notePath)) || [];
|
|
||||||
resolvedPathSegments.pop(); // Remove last element
|
|
||||||
|
|
||||||
const resolvedPath = resolvedPathSegments.join("/");
|
const resolvedPath = resolvedPathSegments.join("/");
|
||||||
pathSegments = await treeService.getNotePathTitleComponents(resolvedPath);
|
const pathSegments = await treeService.getNotePathTitleComponents(resolvedPath);
|
||||||
}
|
|
||||||
|
|
||||||
if (pathSegments) {
|
if (pathSegments) {
|
||||||
if (pathSegments.length) {
|
if (pathSegments.length) {
|
||||||
@@ -307,8 +302,7 @@ export function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDo
|
|||||||
// Right click is handled separately.
|
// Right click is handled separately.
|
||||||
const isMiddleClick = evt && "which" in evt && evt.which === 2;
|
const isMiddleClick = evt && "which" in evt && evt.which === 2;
|
||||||
const targetIsBlank = ($link?.attr("target") === "_blank");
|
const targetIsBlank = ($link?.attr("target") === "_blank");
|
||||||
const isDoubleClick = isLeftClick && evt?.type === "dblclick";
|
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
|
||||||
const openInNewTab = (isLeftClick && ctrlKey) || isDoubleClick || isMiddleClick || targetIsBlank;
|
|
||||||
const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey);
|
const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey);
|
||||||
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
||||||
|
|
||||||
@@ -329,18 +323,16 @@ export function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDo
|
|||||||
const withinEditLink = $link?.hasClass("ck-link-actions__preview");
|
const withinEditLink = $link?.hasClass("ck-link-actions__preview");
|
||||||
const outsideOfCKEditor = !$link || $link.closest("[contenteditable]").length === 0;
|
const outsideOfCKEditor = !$link || $link.closest("[contenteditable]").length === 0;
|
||||||
|
|
||||||
if (openInNewTab || openInNewWindow || (isLeftClick && (withinEditLink || outsideOfCKEditor))) {
|
if (openInNewTab || (withinEditLink && (isLeftClick || isMiddleClick)) || (outsideOfCKEditor && (isLeftClick || isMiddleClick))) {
|
||||||
if (hrefLink.toLowerCase().startsWith("http") || hrefLink.startsWith("api/")) {
|
if (hrefLink.toLowerCase().startsWith("http") || hrefLink.startsWith("api/")) {
|
||||||
window.open(hrefLink, "_blank");
|
window.open(hrefLink, "_blank");
|
||||||
|
} else if ((hrefLink.toLowerCase().startsWith("file:") || hrefLink.toLowerCase().startsWith("geo:")) && utils.isElectron()) {
|
||||||
|
const electron = utils.dynamicRequire("electron");
|
||||||
|
electron.shell.openPath(hrefLink);
|
||||||
} else {
|
} else {
|
||||||
// Enable protocols supported by CKEditor 5 to be clickable.
|
// Enable protocols supported by CKEditor 5 to be clickable.
|
||||||
if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(protocol + ":"))) {
|
if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(protocol + ":"))) {
|
||||||
if ( utils.isElectron()) {
|
window.open(hrefLink, "_blank");
|
||||||
const electron = utils.dynamicRequire("electron");
|
|
||||||
electron.shell.openExternal(hrefLink);
|
|
||||||
} else {
|
|
||||||
window.open(hrefLink, "_blank");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,9 +468,18 @@ $(document).on("auxclick", "a", goToLink); // to handle the middle button
|
|||||||
// TODO: Check why the event is not supported.
|
// TODO: Check why the event is not supported.
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
$(document).on("contextmenu", "a", linkContextMenu);
|
$(document).on("contextmenu", "a", linkContextMenu);
|
||||||
// TODO: Check why the event is not supported.
|
$(document).on("dblclick", "a", (e) => {
|
||||||
//@ts-ignore
|
e.preventDefault();
|
||||||
$(document).on("dblclick", "a", goToLink);
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const $link = $(e.target).closest("a");
|
||||||
|
|
||||||
|
const address = $link.attr("href");
|
||||||
|
|
||||||
|
if (address && address.startsWith("http")) {
|
||||||
|
window.open(address, "_blank");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on("mousedown", "a", (e) => {
|
$(document).on("mousedown", "a", (e) => {
|
||||||
if (e.which === 2) {
|
if (e.which === 2) {
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
import type { AttachmentRow, EtapiTokenRow, NoteType, OptionNames } from "@triliumnext/commons";
|
import type { AttachmentRow, EtapiTokenRow, OptionNames } from "@triliumnext/commons";
|
||||||
import type { AttributeType } from "../entities/fattribute.js";
|
import type { AttributeType } from "../entities/fattribute.js";
|
||||||
import type { EntityChange } from "../server_types.js";
|
import type { EntityChange } from "../server_types.js";
|
||||||
|
|
||||||
// TODO: Deduplicate with server.
|
// TODO: Deduplicate with server.
|
||||||
|
|
||||||
interface NoteRow {
|
interface NoteRow {
|
||||||
blobId: string;
|
|
||||||
dateCreated: string;
|
|
||||||
dateModified: string;
|
|
||||||
isDeleted?: boolean;
|
isDeleted?: boolean;
|
||||||
isProtected?: boolean;
|
|
||||||
mime: string;
|
|
||||||
noteId: string;
|
|
||||||
title: string;
|
|
||||||
type: NoteType;
|
|
||||||
utcDateCreated: string;
|
|
||||||
utcDateModified: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Deduplicate with BranchRow from `rows.ts`/
|
// TODO: Deduplicate with BranchRow from `rows.ts`/
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ function closePersistent(id: string) {
|
|||||||
$(`#toast-${id}`).remove();
|
$(`#toast-${id}`).remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMessage(message: string, delay = 2000, icon = "check") {
|
function showMessage(message: string, delay = 2000) {
|
||||||
console.debug(utils.now(), "message:", message);
|
console.debug(utils.now(), "message:", message);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
icon,
|
icon: "check",
|
||||||
message: message,
|
message: message,
|
||||||
autohide: true,
|
autohide: true,
|
||||||
delay
|
delay
|
||||||
|
|||||||
@@ -16,10 +16,6 @@
|
|||||||
background-color: var(--root-background);
|
background-color: var(--root-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mobile #root-widget {
|
|
||||||
background-color: var(--main-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
--native-titlebar-darwin-x-offset: 10;
|
--native-titlebar-darwin-x-offset: 10;
|
||||||
--native-titlebar-darwin-y-offset: 12 !important;
|
--native-titlebar-darwin-y-offset: 12 !important;
|
||||||
|
|||||||
@@ -690,8 +690,7 @@
|
|||||||
"convert_into_attachment_failed": "笔记 '{{title}}' 转换失败。",
|
"convert_into_attachment_failed": "笔记 '{{title}}' 转换失败。",
|
||||||
"convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。",
|
"convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。",
|
||||||
"convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?",
|
"convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?",
|
||||||
"print_pdf": "导出为 PDF...",
|
"print_pdf": "导出为 PDF..."
|
||||||
"open_note_on_server": "在服务器上打开笔记"
|
|
||||||
},
|
},
|
||||||
"onclick_button": {
|
"onclick_button": {
|
||||||
"no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序"
|
"no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序"
|
||||||
@@ -1111,8 +1110,7 @@
|
|||||||
"title": "内容宽度",
|
"title": "内容宽度",
|
||||||
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
|
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
|
||||||
"max_width_label": "内容最大宽度(像素)",
|
"max_width_label": "内容最大宽度(像素)",
|
||||||
"max_width_unit": "像素",
|
"max_width_unit": "像素"
|
||||||
"centerContent": "保持内容居中"
|
|
||||||
},
|
},
|
||||||
"native_title_bar": {
|
"native_title_bar": {
|
||||||
"title": "原生标题栏(需要重新启动应用)",
|
"title": "原生标题栏(需要重新启动应用)",
|
||||||
@@ -2084,11 +2082,5 @@
|
|||||||
},
|
},
|
||||||
"calendar_view": {
|
"calendar_view": {
|
||||||
"delete_note": "删除笔记..."
|
"delete_note": "删除笔记..."
|
||||||
},
|
|
||||||
"read-only-info": {
|
|
||||||
"read-only-note": "当前正在查看一个只读笔记。",
|
|
||||||
"auto-read-only-note": "这条笔记以只读模式显示便于快速加载。",
|
|
||||||
"auto-read-only-learn-more": "了解更多",
|
|
||||||
"edit-note": "编辑笔记"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2035,8 +2035,7 @@
|
|||||||
"add-column": "Add Column",
|
"add-column": "Add Column",
|
||||||
"add-column-placeholder": "Enter column name...",
|
"add-column-placeholder": "Enter column name...",
|
||||||
"edit-note-title": "Click to edit note title",
|
"edit-note-title": "Click to edit note title",
|
||||||
"edit-column-title": "Click to edit column title",
|
"edit-column-title": "Click to edit column title"
|
||||||
"column-already-exists": "This column already exists on the board."
|
|
||||||
},
|
},
|
||||||
"presentation_view": {
|
"presentation_view": {
|
||||||
"edit-slide": "Edit this slide",
|
"edit-slide": "Edit this slide",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"prefix": "接頭辞: ",
|
"prefix": "接頭辞: ",
|
||||||
"branch_prefix_saved": "ブランチの接頭辞が保存されました。",
|
"branch_prefix_saved": "ブランチの接頭辞が保存されました。",
|
||||||
"edit_branch_prefix_multiple": "{{count}} ブランチのブランチ接頭辞を編集",
|
"edit_branch_prefix_multiple": "{{count}} ブランチのブランチ接頭辞を編集",
|
||||||
"branch_prefix_saved_multiple": "{{count}} ブランチのブランチ接頭辞が保存されました。",
|
"branch_prefix_saved_multiple": "{{count}} 個のブランチのブランチ接頭辞が保存されました。",
|
||||||
"affected_branches": "影響を受けるブランチ {{count}}:"
|
"affected_branches": "影響を受けるブランチ {{count}}:"
|
||||||
},
|
},
|
||||||
"global_menu": {
|
"global_menu": {
|
||||||
@@ -456,8 +456,7 @@
|
|||||||
"convert_into_attachment_failed": "ノート '{{title}}' の変換に失敗しました。",
|
"convert_into_attachment_failed": "ノート '{{title}}' の変換に失敗しました。",
|
||||||
"convert_into_attachment_successful": "ノート '{{title}}' は添付ファイルに変換されました。",
|
"convert_into_attachment_successful": "ノート '{{title}}' は添付ファイルに変換されました。",
|
||||||
"convert_into_attachment_prompt": "本当にノート '{{title}}' を親ノートの添付ファイルに変換しますか?",
|
"convert_into_attachment_prompt": "本当にノート '{{title}}' を親ノートの添付ファイルに変換しますか?",
|
||||||
"note_attachments": "ノートの添付ファイル",
|
"note_attachments": "ノートの添付ファイル"
|
||||||
"open_note_on_server": "サーバー上のノートを開く"
|
|
||||||
},
|
},
|
||||||
"command_palette": {
|
"command_palette": {
|
||||||
"export_note_title": "ノートをエクスポート",
|
"export_note_title": "ノートをエクスポート",
|
||||||
|
|||||||
@@ -687,8 +687,7 @@
|
|||||||
"convert_into_attachment_failed": "筆記 '{{title}}' 轉換失敗。",
|
"convert_into_attachment_failed": "筆記 '{{title}}' 轉換失敗。",
|
||||||
"convert_into_attachment_successful": "筆記 '{{title}}' 已成功轉換為附件。",
|
"convert_into_attachment_successful": "筆記 '{{title}}' 已成功轉換為附件。",
|
||||||
"convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?",
|
"convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?",
|
||||||
"print_pdf": "匯出為 PDF…",
|
"print_pdf": "匯出為 PDF…"
|
||||||
"open_note_on_server": "在伺服器上開啟筆記"
|
|
||||||
},
|
},
|
||||||
"onclick_button": {
|
"onclick_button": {
|
||||||
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
|
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
|
||||||
@@ -1108,8 +1107,7 @@
|
|||||||
"title": "內容寬度",
|
"title": "內容寬度",
|
||||||
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
|
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
|
||||||
"max_width_label": "內容最大寬度(像素)",
|
"max_width_label": "內容最大寬度(像素)",
|
||||||
"max_width_unit": "像素",
|
"max_width_unit": "像素"
|
||||||
"centerContent": "將內容置中"
|
|
||||||
},
|
},
|
||||||
"native_title_bar": {
|
"native_title_bar": {
|
||||||
"title": "原生標題列(需要重新啟動程式)",
|
"title": "原生標題列(需要重新啟動程式)",
|
||||||
@@ -2084,11 +2082,5 @@
|
|||||||
},
|
},
|
||||||
"calendar_view": {
|
"calendar_view": {
|
||||||
"delete_note": "刪除筆記…"
|
"delete_note": "刪除筆記…"
|
||||||
},
|
|
||||||
"read-only-info": {
|
|
||||||
"read-only-note": "目前正在檢視唯讀筆記。",
|
|
||||||
"auto-read-only-note": "此筆記以唯讀模式顯示以加快載入速度。",
|
|
||||||
"auto-read-only-learn-more": "了解更多",
|
|
||||||
"edit-note": "編輯筆記"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
@@ -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 @@
|
|||||||
.user-attributes {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-attributes .user-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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-attributes .user-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));
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-attributes .user-attribute .name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-attributes .user-attribute .value {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import { useState } from "preact/hooks";
|
|
||||||
import FNote from "../../entities/fnote";
|
|
||||||
import "./UserAttributesList.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 { ComponentChildren, CSSProperties } from "preact";
|
|
||||||
import Icon from "../react/Icon";
|
|
||||||
import NoteLink from "../react/NoteLink";
|
|
||||||
import { getReadableTextColor } from "../../services/css_class_manager";
|
|
||||||
|
|
||||||
interface UserAttributesListProps {
|
|
||||||
note: FNote;
|
|
||||||
ignoredAttributes?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AttributeWithDefinitions {
|
|
||||||
friendlyName: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
value: string;
|
|
||||||
def: DefinitionObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function UserAttributesDisplay({ note, ignoredAttributes }: UserAttributesListProps) {
|
|
||||||
const userAttributes = useNoteAttributesWithDefinitions(note, ignoredAttributes);
|
|
||||||
return userAttributes?.length > 0 && (
|
|
||||||
<div className="user-attributes">
|
|
||||||
{userAttributes?.map(attr => buildUserAttribute(attr))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function useNoteAttributesWithDefinitions(note: FNote, attributesToIgnore: string[] = []): AttributeWithDefinitions[] {
|
|
||||||
const [ userAttributes, setUserAttributes ] = useState<AttributeWithDefinitions[]>(getAttributesWithDefinitions(note, attributesToIgnore));
|
|
||||||
|
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
|
||||||
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
|
|
||||||
setUserAttributes(getAttributesWithDefinitions(note, attributesToIgnore));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return userAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function UserAttribute({ 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={`user-attribute type-${className}`} style={style}>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildUserAttribute(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 <UserAttribute attr={attr} style={style}>{content}</UserAttribute>
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAttributesWithDefinitions(note: FNote, attributesToIgnore: string[] = []): AttributeWithDefinitions[] {
|
|
||||||
const attributeDefintions = note.getAttributeDefinitions();
|
|
||||||
const result: AttributeWithDefinitions[] = [];
|
|
||||||
for (const attr of attributeDefintions) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { t } from "../../services/i18n";
|
import { t } from "../../services/i18n";
|
||||||
import ActionButton from "../react/ActionButton";
|
import ActionButton from "../react/ActionButton";
|
||||||
import { useNoteContext, useTriliumEvents } from "../react/hooks";
|
import { useNoteContext, useTriliumEvent } from "../react/hooks";
|
||||||
import appContext from "../../components/app_context";
|
|
||||||
|
|
||||||
export default function ClosePaneButton() {
|
export default function ClosePaneButton() {
|
||||||
const { noteContext, ntxId, parentComponent } = useNoteContext();
|
const { noteContext, ntxId, parentComponent } = useNoteContext();
|
||||||
const [isEnabled, setIsEnabled] = useState(false);
|
const [ isEnabled, setIsEnabled ] = useState(false);
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
const isMainOfSomeContext = appContext.tabManager.noteContexts.some(c => c.mainNtxId === ntxId);
|
setIsEnabled(!!(noteContext && !!noteContext.mainNtxId));
|
||||||
setIsEnabled(!!(noteContext && (!!noteContext.mainNtxId || isMainOfSomeContext)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useTriliumEvents(["noteContextRemoved", "noteContextReorder", "newNoteContextCreated"], refresh);
|
useTriliumEvent("noteContextReorder", refresh);
|
||||||
useEffect(refresh, [ntxId]);
|
useEffect(refresh, [ ntxId ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ export function CustomNoteList<T extends object>({ note, isEnabled: shouldEnable
|
|||||||
props = {
|
props = {
|
||||||
note, noteIds, notePath,
|
note, noteIds, notePath,
|
||||||
highlightedTokens,
|
highlightedTokens,
|
||||||
viewConfig: viewModeConfig.config,
|
viewConfig: viewModeConfig[0],
|
||||||
saveConfig: viewModeConfig.storeFn,
|
saveConfig: viewModeConfig[1],
|
||||||
onReady: onReady ?? (() => {}),
|
onReady: onReady ?? (() => {}),
|
||||||
...restProps
|
...restProps
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt
|
|||||||
|
|
||||||
async function getNoteIds(note: FNote) {
|
async function getNoteIds(note: FNote) {
|
||||||
if (viewType === "list" || viewType === "grid" || viewType === "table" || note.type === "search") {
|
if (viewType === "list" || viewType === "grid" || viewType === "table" || note.type === "search") {
|
||||||
return await note.getChildNoteIdsWithArchiveFiltering(includeArchived);
|
return note.getChildNoteIds();
|
||||||
} else {
|
} else {
|
||||||
return await note.getSubtreeNoteIds(includeArchived);
|
return await note.getSubtreeNoteIds(includeArchived);
|
||||||
}
|
}
|
||||||
@@ -192,11 +192,7 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useViewModeConfig<T extends object>(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) {
|
export function useViewModeConfig<T extends object>(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) {
|
||||||
const [ viewConfig, setViewConfig ] = useState<{
|
const [ viewConfig, setViewConfig ] = useState<[T | undefined, (data: T) => void]>();
|
||||||
config: T | undefined;
|
|
||||||
storeFn: (data: T) => void;
|
|
||||||
note: FNote;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!note || !viewType) return;
|
if (!note || !viewType) return;
|
||||||
@@ -204,14 +200,12 @@ export function useViewModeConfig<T extends object>(note: FNote | null | undefin
|
|||||||
const viewStorage = new ViewModeStorage<T>(note, viewType);
|
const viewStorage = new ViewModeStorage<T>(note, viewType);
|
||||||
viewStorage.restore().then(config => {
|
viewStorage.restore().then(config => {
|
||||||
const storeFn = (config: T) => {
|
const storeFn = (config: T) => {
|
||||||
setViewConfig({ note, config, storeFn });
|
setViewConfig([ config, storeFn ]);
|
||||||
viewStorage.store(config);
|
viewStorage.store(config);
|
||||||
};
|
};
|
||||||
setViewConfig({ note, config, storeFn });
|
setViewConfig([ config, storeFn ]);
|
||||||
});
|
});
|
||||||
}, [ note, viewType ]);
|
}, [ note, viewType ]);
|
||||||
|
|
||||||
// Only expose config for the current note, avoid leaking notes when switching between them.
|
|
||||||
if (viewConfig?.note !== note) return undefined;
|
|
||||||
return viewConfig;
|
return viewConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BulkAction } from "@triliumnext/commons";
|
|
||||||
import { BoardViewData } from ".";
|
import { BoardViewData } from ".";
|
||||||
import appContext from "../../../components/app_context";
|
import appContext from "../../../components/app_context";
|
||||||
import FNote from "../../../entities/fnote";
|
import FNote from "../../../entities/fnote";
|
||||||
@@ -13,25 +12,15 @@ import { ColumnMap } from "./data";
|
|||||||
|
|
||||||
export default class BoardApi {
|
export default class BoardApi {
|
||||||
|
|
||||||
private isRelationMode: boolean;
|
|
||||||
statusAttribute: string;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private byColumn: ColumnMap | undefined,
|
private byColumn: ColumnMap | undefined,
|
||||||
public columns: string[],
|
public columns: string[],
|
||||||
private parentNote: FNote,
|
private parentNote: FNote,
|
||||||
statusAttribute: string,
|
private statusAttribute: string,
|
||||||
private viewConfig: BoardViewData,
|
private viewConfig: BoardViewData,
|
||||||
private saveConfig: (newConfig: BoardViewData) => void,
|
private saveConfig: (newConfig: BoardViewData) => void,
|
||||||
private setBranchIdToEdit: (branchId: string | undefined) => void
|
private setBranchIdToEdit: (branchId: string | undefined) => void
|
||||||
) {
|
) {};
|
||||||
this.isRelationMode = statusAttribute.startsWith("~");
|
|
||||||
|
|
||||||
if (statusAttribute.startsWith("~") || statusAttribute.startsWith("#")) {
|
|
||||||
statusAttribute = statusAttribute.substring(1);
|
|
||||||
}
|
|
||||||
this.statusAttribute = statusAttribute;
|
|
||||||
};
|
|
||||||
|
|
||||||
async createNewItem(column: string, title: string) {
|
async createNewItem(column: string, title: string) {
|
||||||
try {
|
try {
|
||||||
@@ -53,11 +42,7 @@ export default class BoardApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async changeColumn(noteId: string, newColumn: string) {
|
async changeColumn(noteId: string, newColumn: string) {
|
||||||
if (this.isRelationMode) {
|
await attributes.setLabel(noteId, this.statusAttribute, newColumn);
|
||||||
await attributes.setRelation(noteId, this.statusAttribute, newColumn);
|
|
||||||
} else {
|
|
||||||
await attributes.setLabel(noteId, this.statusAttribute, newColumn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addNewColumn(columnName: string) {
|
async addNewColumn(columnName: string) {
|
||||||
@@ -75,20 +60,22 @@ export default class BoardApi {
|
|||||||
|
|
||||||
// Add the new column to persisted data if it doesn't exist
|
// Add the new column to persisted data if it doesn't exist
|
||||||
const existingColumn = this.viewConfig.columns.find(col => col.value === columnName);
|
const existingColumn = this.viewConfig.columns.find(col => col.value === columnName);
|
||||||
if (existingColumn) return false;
|
if (!existingColumn) {
|
||||||
this.viewConfig.columns.push({ value: columnName });
|
this.viewConfig.columns.push({ value: columnName });
|
||||||
this.saveConfig(this.viewConfig);
|
this.saveConfig(this.viewConfig);
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeColumn(column: string) {
|
async removeColumn(column: string) {
|
||||||
// Remove the value from the notes.
|
// Remove the value from the notes.
|
||||||
const noteIds = this.byColumn?.get(column)?.map(item => item.note.noteId) || [];
|
const noteIds = this.byColumn?.get(column)?.map(item => item.note.noteId) || [];
|
||||||
|
await executeBulkActions(noteIds, [
|
||||||
|
{
|
||||||
|
name: "deleteLabel",
|
||||||
|
labelName: this.statusAttribute
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
const action: BulkAction = this.isRelationMode
|
|
||||||
? { name: "deleteRelation", relationName: this.statusAttribute }
|
|
||||||
: { name: "deleteLabel", labelName: this.statusAttribute }
|
|
||||||
await executeBulkActions(noteIds, [ action ]);
|
|
||||||
this.viewConfig.columns = (this.viewConfig.columns ?? []).filter(col => col.value !== column);
|
this.viewConfig.columns = (this.viewConfig.columns ?? []).filter(col => col.value !== column);
|
||||||
this.saveConfig(this.viewConfig);
|
this.saveConfig(this.viewConfig);
|
||||||
}
|
}
|
||||||
@@ -97,10 +84,13 @@ export default class BoardApi {
|
|||||||
const noteIds = this.byColumn?.get(oldValue)?.map(item => item.note.noteId) || [];
|
const noteIds = this.byColumn?.get(oldValue)?.map(item => item.note.noteId) || [];
|
||||||
|
|
||||||
// Change the value in the notes.
|
// Change the value in the notes.
|
||||||
const action: BulkAction = this.isRelationMode
|
await executeBulkActions(noteIds, [
|
||||||
? { name: "updateRelationTarget", relationName: this.statusAttribute, targetNoteId: newValue }
|
{
|
||||||
: { name: "updateLabelValue", labelName: this.statusAttribute, labelValue: newValue }
|
name: "updateLabelValue",
|
||||||
await executeBulkActions(noteIds, [ action ]);
|
labelName: this.statusAttribute,
|
||||||
|
labelValue: newValue
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
// Rename the column in the persisted data.
|
// Rename the column in the persisted data.
|
||||||
for (const column of this.viewConfig.columns || []) {
|
for (const column of this.viewConfig.columns || []) {
|
||||||
@@ -177,11 +167,7 @@ export default class BoardApi {
|
|||||||
removeFromBoard(noteId: string) {
|
removeFromBoard(noteId: string) {
|
||||||
const note = froca.getNoteFromCache(noteId);
|
const note = froca.getNoteFromCache(noteId);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
if (this.isRelationMode) {
|
return attributes.removeOwnedLabelByName(note, this.statusAttribute);
|
||||||
return attributes.removeOwnedRelationByName(note, this.statusAttribute);
|
|
||||||
} else {
|
|
||||||
return attributes.removeOwnedLabelByName(note, this.statusAttribute);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveWithinBoard(noteId: string, sourceBranchId: string, sourceIndex: number, targetIndex: number, sourceColumn: string, targetColumn: string) {
|
async moveWithinBoard(noteId: string, sourceBranchId: string, sourceIndex: number, targetIndex: number, sourceColumn: string, targetColumn: string) {
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { BoardViewContext, TitleEditor } from ".";
|
|||||||
import { ContextMenuEvent } from "../../../menus/context_menu";
|
import { ContextMenuEvent } from "../../../menus/context_menu";
|
||||||
import { openNoteContextMenu } from "./context_menu";
|
import { openNoteContextMenu } from "./context_menu";
|
||||||
import { t } from "../../../services/i18n";
|
import { t } from "../../../services/i18n";
|
||||||
import UserAttributesDisplay from "../../attribute_widgets/UserAttributesList";
|
|
||||||
import { useTriliumEvent } from "../../react/hooks";
|
|
||||||
|
|
||||||
export const CARD_CLIPBOARD_TYPE = "trilium/board-card";
|
export const CARD_CLIPBOARD_TYPE = "trilium/board-card";
|
||||||
|
|
||||||
@@ -41,13 +39,6 @@ export default function Card({
|
|||||||
const [ isVisible, setVisible ] = useState(true);
|
const [ isVisible, setVisible ] = useState(true);
|
||||||
const [ title, setTitle ] = useState(note.title);
|
const [ title, setTitle ] = useState(note.title);
|
||||||
|
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
|
||||||
const row = loadResults.getEntityRow("notes", note.noteId);
|
|
||||||
if (row) {
|
|
||||||
setTitle(row.title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDragStart = useCallback((e: DragEvent) => {
|
const handleDragStart = useCallback((e: DragEvent) => {
|
||||||
e.dataTransfer!.effectAllowed = 'move';
|
e.dataTransfer!.effectAllowed = 'move';
|
||||||
const data: CardDragData = { noteId: note.noteId, branchId: branch.branchId, fromColumn: column, index };
|
const data: CardDragData = { noteId: note.noteId, branchId: branch.branchId, fromColumn: column, index };
|
||||||
@@ -117,7 +108,6 @@ export default function Card({
|
|||||||
title={t("board_view.edit-note-title")}
|
title={t("board_view.edit-note-title")}
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
/>
|
/>
|
||||||
<UserAttributesDisplay note={note} ignoredAttributes={[api.statusAttribute]} />
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<TitleEditor
|
<TitleEditor
|
||||||
@@ -127,7 +117,7 @@ export default function Card({
|
|||||||
setTitle(newTitle);
|
setTitle(newTitle);
|
||||||
}}
|
}}
|
||||||
dismiss={() => api.dismissEditingTitle()}
|
dismiss={() => api.dismissEditingTitle()}
|
||||||
mode="multiline"
|
multiline
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import Card, { CARD_CLIPBOARD_TYPE, CardDragData } from "./card";
|
|||||||
import { JSX } from "preact/jsx-runtime";
|
import { JSX } from "preact/jsx-runtime";
|
||||||
import froca from "../../../services/froca";
|
import froca from "../../../services/froca";
|
||||||
import { DragData, TREE_CLIPBOARD_TYPE } from "../../note_tree";
|
import { DragData, TREE_CLIPBOARD_TYPE } from "../../note_tree";
|
||||||
import NoteLink from "../../react/NoteLink";
|
|
||||||
|
|
||||||
interface DragContext {
|
interface DragContext {
|
||||||
column: string;
|
column: string;
|
||||||
@@ -28,14 +27,12 @@ export default function Column({
|
|||||||
api,
|
api,
|
||||||
onColumnHover,
|
onColumnHover,
|
||||||
isAnyColumnDragging,
|
isAnyColumnDragging,
|
||||||
isInRelationMode
|
|
||||||
}: {
|
}: {
|
||||||
columnItems?: { note: FNote, branch: FBranch }[];
|
columnItems?: { note: FNote, branch: FBranch }[];
|
||||||
isDraggingColumn: boolean,
|
isDraggingColumn: boolean,
|
||||||
api: BoardApi,
|
api: BoardApi,
|
||||||
onColumnHover?: (index: number, mouseX: number, rect: DOMRect) => void,
|
onColumnHover?: (index: number, mouseX: number, rect: DOMRect) => void,
|
||||||
isAnyColumnDragging?: boolean,
|
isAnyColumnDragging?: boolean
|
||||||
isInRelationMode: boolean
|
|
||||||
} & DragContext) {
|
} & DragContext) {
|
||||||
const [ isVisible, setVisible ] = useState(true);
|
const [ isVisible, setVisible ] = useState(true);
|
||||||
const { columnNameToEdit, setColumnNameToEdit, dropTarget, draggedCard, dropPosition } = useContext(BoardViewContext)!;
|
const { columnNameToEdit, setColumnNameToEdit, dropTarget, draggedCard, dropPosition } = useContext(BoardViewContext)!;
|
||||||
@@ -106,13 +103,7 @@ export default function Column({
|
|||||||
>
|
>
|
||||||
{!isEditing ? (
|
{!isEditing ? (
|
||||||
<>
|
<>
|
||||||
<span className="title">
|
<span className="title">{column}</span>
|
||||||
{isInRelationMode
|
|
||||||
? <NoteLink notePath={column} showNoteIcon />
|
|
||||||
: column}
|
|
||||||
</span>
|
|
||||||
<span className="counter-badge">{columnItems?.length ?? 0}</span>
|
|
||||||
<div className="spacer" />
|
|
||||||
<span
|
<span
|
||||||
className="edit-icon icon bx bx-edit-alt"
|
className="edit-icon icon bx bx-edit-alt"
|
||||||
title={t("board_view.edit-column-title")}
|
title={t("board_view.edit-column-title")}
|
||||||
@@ -124,7 +115,6 @@ export default function Column({
|
|||||||
currentValue={column}
|
currentValue={column}
|
||||||
save={newTitle => api.renameColumn(column, newTitle)}
|
save={newTitle => api.renameColumn(column, newTitle)}
|
||||||
dismiss={() => setColumnNameToEdit?.(undefined)}
|
dismiss={() => setColumnNameToEdit?.(undefined)}
|
||||||
mode={isInRelationMode ? "relation" : "normal"}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -188,7 +178,7 @@ function AddNewItem({ column, api }: { column: string, api: BoardApi }) {
|
|||||||
placeholder={t("board_view.new-item-placeholder")}
|
placeholder={t("board_view.new-item-placeholder")}
|
||||||
save={(title) => api.createNewItem(column, title)}
|
save={(title) => api.createNewItem(column, title)}
|
||||||
dismiss={() => setIsCreatingNewItem(false)}
|
dismiss={() => setIsCreatingNewItem(false)}
|
||||||
mode="multiline" isNewItem
|
multiline isNewItem
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ export async function getBoardData(parentNote: FNote, groupByColumn: string, per
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
byColumn,
|
byColumn,
|
||||||
newPersistedData,
|
newPersistedData
|
||||||
isInRelationMode: groupByColumn.startsWith("~")
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +70,7 @@ async function recursiveGroupBy(branches: FBranch[], byColumn: ColumnMap, groupB
|
|||||||
await recursiveGroupBy(note.getChildBranches(), byColumn, groupByColumn, includeArchived, seenNoteIds);
|
await recursiveGroupBy(note.getChildBranches(), byColumn, groupByColumn, includeArchived, seenNoteIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = note.getLabelOrRelation(groupByColumn);
|
const group = note.getLabelValue(groupByColumn);
|
||||||
if (!group || seenNoteIds.has(note.noteId)) {
|
if (!group || seenNoteIds.has(note.noteId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,6 @@
|
|||||||
--card-padding: 0.6em;
|
--card-padding: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mobile .board-view {
|
|
||||||
scroll-snap-type: x mandatory;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.board-view-container {
|
.board-view-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -37,12 +31,6 @@ body.mobile .board-view {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mobile .board-view-container .board-column {
|
|
||||||
width: 75vw;
|
|
||||||
max-width: 300px;
|
|
||||||
scroll-snap-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.board-view-container .board-column.drag-over {
|
.board-view-container .board-column.drag-over {
|
||||||
border-color: var(--main-text-color);
|
border-color: var(--main-text-color);
|
||||||
background-color: var(--hover-item-background-color);
|
background-color: var(--hover-item-background-color);
|
||||||
@@ -65,21 +53,7 @@ body.mobile .board-view-container .board-column {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-view-container .board-column h3 a {
|
.board-view-container .board-column h3 > .title {
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import Column from "./column";
|
|||||||
import BoardApi from "./api";
|
import BoardApi from "./api";
|
||||||
import FormTextArea from "../../react/FormTextArea";
|
import FormTextArea from "../../react/FormTextArea";
|
||||||
import FNote from "../../../entities/fnote";
|
import FNote from "../../../entities/fnote";
|
||||||
import NoteAutocomplete from "../../react/NoteAutocomplete";
|
|
||||||
import toast from "../../../services/toast";
|
|
||||||
|
|
||||||
export interface BoardViewData {
|
export interface BoardViewData {
|
||||||
columns?: BoardColumnData[];
|
columns?: BoardColumnData[];
|
||||||
@@ -44,11 +42,10 @@ interface BoardViewContextData {
|
|||||||
export const BoardViewContext = createContext<BoardViewContextData | undefined>(undefined);
|
export const BoardViewContext = createContext<BoardViewContextData | undefined>(undefined);
|
||||||
|
|
||||||
export default function BoardView({ note: parentNote, noteIds, viewConfig, saveConfig }: ViewModeProps<BoardViewData>) {
|
export default function BoardView({ note: parentNote, noteIds, viewConfig, saveConfig }: ViewModeProps<BoardViewData>) {
|
||||||
const [ statusAttributeWithPrefix ] = useNoteLabelWithDefault(parentNote, "board:groupBy", "status");
|
const [ statusAttribute ] = useNoteLabelWithDefault(parentNote, "board:groupBy", "status");
|
||||||
const [ includeArchived ] = useNoteLabelBoolean(parentNote, "includeArchived");
|
const [ includeArchived ] = useNoteLabelBoolean(parentNote, "includeArchived");
|
||||||
const [ byColumn, setByColumn ] = useState<ColumnMap>();
|
const [ byColumn, setByColumn ] = useState<ColumnMap>();
|
||||||
const [ columns, setColumns ] = useState<string[]>();
|
const [ columns, setColumns ] = useState<string[]>();
|
||||||
const [ isInRelationMode, setIsRelationMode ] = useState(false);
|
|
||||||
const [ draggedCard, setDraggedCard ] = useState<{ noteId: string, branchId: string, fromColumn: string, index: number } | null>(null);
|
const [ draggedCard, setDraggedCard ] = useState<{ noteId: string, branchId: string, fromColumn: string, index: number } | null>(null);
|
||||||
const [ dropTarget, setDropTarget ] = useState<string | null>(null);
|
const [ dropTarget, setDropTarget ] = useState<string | null>(null);
|
||||||
const [ dropPosition, setDropPosition ] = useState<{ column: string, index: number } | null>(null);
|
const [ dropPosition, setDropPosition ] = useState<{ column: string, index: number } | null>(null);
|
||||||
@@ -58,8 +55,8 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
|||||||
const [ branchIdToEdit, setBranchIdToEdit ] = useState<string>();
|
const [ branchIdToEdit, setBranchIdToEdit ] = useState<string>();
|
||||||
const [ columnNameToEdit, setColumnNameToEdit ] = useState<string>();
|
const [ columnNameToEdit, setColumnNameToEdit ] = useState<string>();
|
||||||
const api = useMemo(() => {
|
const api = useMemo(() => {
|
||||||
return new Api(byColumn, columns ?? [], parentNote, statusAttributeWithPrefix, viewConfig ?? {}, saveConfig, setBranchIdToEdit );
|
return new Api(byColumn, columns ?? [], parentNote, statusAttribute, viewConfig ?? {}, saveConfig, setBranchIdToEdit );
|
||||||
}, [ byColumn, columns, parentNote, statusAttributeWithPrefix, viewConfig, saveConfig, setBranchIdToEdit ]);
|
}, [ byColumn, columns, parentNote, statusAttribute, viewConfig, saveConfig, setBranchIdToEdit ]);
|
||||||
const boardViewContext = useMemo<BoardViewContextData>(() => ({
|
const boardViewContext = useMemo<BoardViewContextData>(() => ({
|
||||||
api,
|
api,
|
||||||
parentNote,
|
parentNote,
|
||||||
@@ -81,9 +78,8 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
getBoardData(parentNote, statusAttributeWithPrefix, viewConfig ?? {}, includeArchived).then(({ byColumn, newPersistedData, isInRelationMode }) => {
|
getBoardData(parentNote, statusAttribute, viewConfig ?? {}, includeArchived).then(({ byColumn, newPersistedData }) => {
|
||||||
setByColumn(byColumn);
|
setByColumn(byColumn);
|
||||||
setIsRelationMode(isInRelationMode);
|
|
||||||
|
|
||||||
if (newPersistedData) {
|
if (newPersistedData) {
|
||||||
viewConfig = { ...newPersistedData };
|
viewConfig = { ...newPersistedData };
|
||||||
@@ -98,7 +94,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(refresh, [ parentNote, noteIds, viewConfig, statusAttributeWithPrefix ]);
|
useEffect(refresh, [ parentNote, noteIds, viewConfig ]);
|
||||||
|
|
||||||
const handleColumnDrop = useCallback((fromIndex: number, toIndex: number) => {
|
const handleColumnDrop = useCallback((fromIndex: number, toIndex: number) => {
|
||||||
const newColumns = api.reorderColumn(fromIndex, toIndex);
|
const newColumns = api.reorderColumn(fromIndex, toIndex);
|
||||||
@@ -114,7 +110,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
|||||||
// Check if any changes affect our board
|
// Check if any changes affect our board
|
||||||
const hasRelevantChanges =
|
const hasRelevantChanges =
|
||||||
// React to changes in status attribute for notes in this board
|
// React to changes in status attribute for notes in this board
|
||||||
loadResults.getAttributeRows().some(attr => attr.name === api.statusAttribute && noteIds.includes(attr.noteId!)) ||
|
loadResults.getAttributeRows().some(attr => attr.name === statusAttribute && noteIds.includes(attr.noteId!)) ||
|
||||||
// React to changes in note title
|
// React to changes in note title
|
||||||
loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) ||
|
loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) ||
|
||||||
// React to changes in branches for subchildren (e.g., moved, added, or removed notes)
|
// React to changes in branches for subchildren (e.g., moved, added, or removed notes)
|
||||||
@@ -175,7 +171,6 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
|||||||
<div className="column-drop-placeholder show" />
|
<div className="column-drop-placeholder show" />
|
||||||
)}
|
)}
|
||||||
<Column
|
<Column
|
||||||
isInRelationMode={isInRelationMode}
|
|
||||||
api={api}
|
api={api}
|
||||||
column={column}
|
column={column}
|
||||||
columnIndex={index}
|
columnIndex={index}
|
||||||
@@ -190,14 +185,14 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
|||||||
<div className="column-drop-placeholder show" />
|
<div className="column-drop-placeholder show" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<AddNewColumn api={api} isInRelationMode={isInRelationMode} />
|
<AddNewColumn api={api} />
|
||||||
</div>
|
</div>
|
||||||
</BoardViewContext.Provider>
|
</BoardViewContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMode: boolean }) {
|
function AddNewColumn({ api }: { api: BoardApi }) {
|
||||||
const [ isCreatingNewColumn, setIsCreatingNewColumn ] = useState(false);
|
const [ isCreatingNewColumn, setIsCreatingNewColumn ] = useState(false);
|
||||||
|
|
||||||
const addColumnCallback = useCallback(() => {
|
const addColumnCallback = useCallback(() => {
|
||||||
@@ -214,28 +209,22 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo
|
|||||||
: (
|
: (
|
||||||
<TitleEditor
|
<TitleEditor
|
||||||
placeholder={t("board_view.add-column-placeholder")}
|
placeholder={t("board_view.add-column-placeholder")}
|
||||||
save={async (columnName) => {
|
save={(columnName) => api.addNewColumn(columnName)}
|
||||||
const created = await api.addNewColumn(columnName);
|
|
||||||
if (!created) {
|
|
||||||
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
dismiss={() => setIsCreatingNewColumn(false)}
|
dismiss={() => setIsCreatingNewColumn(false)}
|
||||||
isNewItem
|
isNewItem
|
||||||
mode={isInRelationMode ? "relation" : "normal"}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
|
export function TitleEditor({ currentValue, placeholder, save, dismiss, multiline, isNewItem }: {
|
||||||
currentValue?: string;
|
currentValue?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
save: (newValue: string) => void;
|
save: (newValue: string) => void;
|
||||||
dismiss: () => void;
|
dismiss: () => void;
|
||||||
|
multiline?: boolean;
|
||||||
isNewItem?: boolean;
|
isNewItem?: boolean;
|
||||||
mode?: "normal" | "multiline" | "relation";
|
|
||||||
}) {
|
}) {
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
const focusElRef = useRef<Element>(null);
|
const focusElRef = useRef<Element>(null);
|
||||||
@@ -243,11 +232,13 @@ export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, is
|
|||||||
const shouldDismiss = useRef(false);
|
const shouldDismiss = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
focusElRef.current = document.activeElement !== document.body ? document.activeElement : null;
|
focusElRef.current = document.activeElement;
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
inputRef.current?.select();
|
inputRef.current?.select();
|
||||||
}, [ inputRef ]);
|
}, [ inputRef ]);
|
||||||
|
|
||||||
|
const Element = multiline ? FormTextArea : FormTextBox;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dismissOnNextRefreshRef.current) {
|
if (dismissOnNextRefreshRef.current) {
|
||||||
dismiss();
|
dismiss();
|
||||||
@@ -255,62 +246,31 @@ export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, is
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onKeyDown = (e: TargetedKeyboardEvent<HTMLInputElement | HTMLTextAreaElement> | KeyboardEvent) => {
|
return (
|
||||||
if (e.key === "Enter" || e.key === "Escape") {
|
<Element
|
||||||
e.preventDefault();
|
inputRef={inputRef}
|
||||||
e.stopPropagation();
|
currentValue={currentValue ?? ""}
|
||||||
if (focusElRef.current instanceof HTMLElement) {
|
placeholder={placeholder}
|
||||||
shouldDismiss.current = (e.key === "Escape");
|
autoComplete="trilium-title-entry" // forces the auto-fill off better than the "off" value.
|
||||||
focusElRef.current.focus();
|
rows={multiline ? 4 : undefined}
|
||||||
} else {
|
onKeyDown={(e: TargetedKeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
dismiss();
|
if (e.key === "Enter" || e.key === "Escape") {
|
||||||
}
|
e.preventDefault();
|
||||||
}
|
e.stopPropagation();
|
||||||
};
|
shouldDismiss.current = (e.key === "Escape");
|
||||||
|
if (focusElRef.current instanceof HTMLElement) {
|
||||||
const onBlur = (newValue: string) => {
|
focusElRef.current.focus();
|
||||||
if (!shouldDismiss.current && newValue.trim() && (newValue !== currentValue || isNewItem)) {
|
|
||||||
save(newValue);
|
|
||||||
dismissOnNextRefreshRef.current = true;
|
|
||||||
} else {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mode !== "relation") {
|
|
||||||
const Element = mode === "multiline" ? FormTextArea : FormTextBox;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Element
|
|
||||||
inputRef={inputRef}
|
|
||||||
currentValue={currentValue ?? ""}
|
|
||||||
placeholder={placeholder}
|
|
||||||
autoComplete="trilium-title-entry" // forces the auto-fill off better than the "off" value.
|
|
||||||
rows={mode === "multiline" ? 4 : undefined}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
onBlur={onBlur}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<NoteAutocomplete
|
|
||||||
inputRef={inputRef}
|
|
||||||
noteId={currentValue ?? ""}
|
|
||||||
opts={{
|
|
||||||
hideAllButtons: true,
|
|
||||||
allowCreatingNotes: true
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
dismiss();
|
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
onBlur={() => dismiss()}
|
}}
|
||||||
noteIdChanged={(newValue) => {
|
onBlur={(newValue) => {
|
||||||
|
if (!shouldDismiss.current && newValue.trim() && (newValue !== currentValue || isNewItem)) {
|
||||||
save(newValue);
|
save(newValue);
|
||||||
|
dismissOnNextRefreshRef.current = true;
|
||||||
|
} else {
|
||||||
dismiss();
|
dismiss();
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import FNote from "../../../entities/fnote";
|
|||||||
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
|
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
|
||||||
import link_context_menu from "../../../menus/link_context_menu";
|
import link_context_menu from "../../../menus/link_context_menu";
|
||||||
import branches from "../../../services/branches";
|
import branches from "../../../services/branches";
|
||||||
import froca from "../../../services/froca";
|
|
||||||
import { t } from "../../../services/i18n";
|
import { t } from "../../../services/i18n";
|
||||||
|
|
||||||
export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, parentNote: FNote) {
|
export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, parentNote: FNote) {
|
||||||
@@ -19,20 +18,8 @@ export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, par
|
|||||||
title: t("calendar_view.delete_note"),
|
title: t("calendar_view.delete_note"),
|
||||||
uiIcon: "bx bx-trash",
|
uiIcon: "bx bx-trash",
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
const noteToDelete = await froca.getNote(noteId);
|
const branchId = parentNote.childToBranch[noteId];
|
||||||
if (!noteToDelete) return;
|
await branches.deleteNotes([ branchId ], false, false);
|
||||||
|
|
||||||
let branchIdToDelete: string | null = null;
|
|
||||||
for (const parentBranch of noteToDelete.getParentBranches()) {
|
|
||||||
const parentNote = await parentBranch.getNote();
|
|
||||||
if (parentNote?.hasAncestor(parentNote.noteId)) {
|
|
||||||
branchIdToDelete = parentBranch.branchId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branchIdToDelete) {
|
|
||||||
await branches.deleteNotes([ branchIdToDelete ], false, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,10 +16,6 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-book-card.archived {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-book-card:not(.expanded) .note-book-content {
|
.note-book-card:not(.expanded) .note-book-content {
|
||||||
padding: 10px
|
padding: 10px
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ function ListNoteCard({ note, parentNote, expand, highlightedTokens }: { note: F
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}
|
data-note-id={note.noteId}
|
||||||
>
|
>
|
||||||
<h5 className="note-book-header">
|
<h5 className="note-book-header">
|
||||||
@@ -100,7 +100,7 @@ function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, pa
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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-href={`#${notePath}`}
|
||||||
data-note-id={note.noteId}
|
data-note-id={note.noteId}
|
||||||
onClick={(e) => link.goToLink(e)}
|
onClick={(e) => link.goToLink(e)}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export default class ContentHeader extends Container<BasicWidget> {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.class("content-header-widget");
|
|
||||||
this.css("contain", "unset");
|
this.css("contain", "unset");
|
||||||
this.resizeObserver = new ResizeObserver(this.onResize.bind(this));
|
this.resizeObserver = new ResizeObserver(this.onResize.bind(this));
|
||||||
}
|
}
|
||||||
@@ -100,23 +100,9 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
|
async closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
|
||||||
if (!ntxId) return;
|
if (ntxId) {
|
||||||
const contexts = appContext.tabManager.noteContexts;
|
await appContext.tabManager.removeNoteContext(ntxId);
|
||||||
|
|
||||||
const currentIndex = contexts.findIndex((c) => c.ntxId === ntxId);
|
|
||||||
if (currentIndex === -1) return;
|
|
||||||
|
|
||||||
const isRemoveMainContext = !contexts[currentIndex].mainNtxId;
|
|
||||||
if (isRemoveMainContext && currentIndex + 1 <= contexts.length) {
|
|
||||||
const ntxIds = contexts.map((c) => c.ntxId).filter((c) => !!c) as string[];
|
|
||||||
this.triggerCommand("noteContextReorder", {
|
|
||||||
ntxIdsInOrder: ntxIds,
|
|
||||||
oldMainNtxId: ntxId,
|
|
||||||
newMainNtxId: ntxIds[currentIndex + 1]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await appContext.tabManager.removeNoteContext(ntxId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveThisNoteSplitCommand({ ntxId, isMovingLeft }: CommandListenerData<"moveThisNoteSplit">) {
|
async moveThisNoteSplitCommand({ ntxId, isMovingLeft }: CommandListenerData<"moveThisNoteSplit">) {
|
||||||
|
|||||||
@@ -12,102 +12,6 @@ import type { Attribute } from "../services/attribute_parser.js";
|
|||||||
import type FAttribute from "../entities/fattribute.js";
|
import type FAttribute from "../entities/fattribute.js";
|
||||||
import type { EventData } from "../components/app_context.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
|
// TODO: Deduplicate
|
||||||
interface AttributeResult {
|
interface AttributeResult {
|
||||||
attributeId: string;
|
attributeId: string;
|
||||||
@@ -117,115 +21,17 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
private $container!: JQuery<HTMLElement>;
|
private $container!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
get name() {
|
|
||||||
return "promotedAttributes";
|
|
||||||
}
|
|
||||||
|
|
||||||
get toggleCommand() {
|
|
||||||
return "toggleRibbonTabPromotedAttributes";
|
|
||||||
}
|
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $("");
|
||||||
this.contentSized();
|
this.contentSized();
|
||||||
this.$container = this.$widget.find(".promoted-attributes-container");
|
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) {
|
async createPromotedAttributeCell(definitionAttr: FAttribute, valueAttr: Attribute, valueName: string) {
|
||||||
const definition = definitionAttr.getDefinition();
|
// .on("change", (event) => this.promotedAttributeChanged(event));
|
||||||
const id = `value-${valueAttr.attributeId}`;
|
|
||||||
|
|
||||||
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 $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") {
|
if (valueAttr.type === "label") {
|
||||||
$wrapper.addClass(`promoted-attribute-label-${definition.labelType}`);
|
$wrapper.addClass(`promoted-attribute-label-${definition.labelType}`);
|
||||||
if (definition.labelType === "text") {
|
if (definition.labelType === "text") {
|
||||||
@@ -359,8 +165,6 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
if (definition.multiplicity === "multi") {
|
if (definition.multiplicity === "multi") {
|
||||||
const $addButton = $("<span>")
|
const $addButton = $("<span>")
|
||||||
.addClass("bx bx-plus pointer tn-tool-button")
|
|
||||||
.prop("title", t("promoted_attributes.add_new_attribute"))
|
|
||||||
.on("click", async () => {
|
.on("click", async () => {
|
||||||
const $new = await this.createPromotedAttributeCell(
|
const $new = await this.createPromotedAttributeCell(
|
||||||
definitionAttr,
|
definitionAttr,
|
||||||
|
|||||||
@@ -15,13 +15,11 @@ interface NoteAutocompleteProps {
|
|||||||
opts?: Omit<Options, "container">;
|
opts?: Omit<Options, "container">;
|
||||||
onChange?: (suggestion: Suggestion | null) => void;
|
onChange?: (suggestion: Suggestion | null) => void;
|
||||||
onTextChange?: (text: string) => void;
|
onTextChange?: (text: string) => void;
|
||||||
onKeyDown?: (e: KeyboardEvent) => void;
|
|
||||||
onBlur?: (newValue: string) => void;
|
|
||||||
noteIdChanged?: (noteId: string) => void;
|
noteIdChanged?: (noteId: string) => void;
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NoteAutocomplete({ id, inputRef: externalInputRef, text, placeholder, onChange, onTextChange, container, containerStyle, opts, noteId, noteIdChanged, onKeyDown, onBlur }: NoteAutocompleteProps) {
|
export default function NoteAutocomplete({ id, inputRef: externalInputRef, text, placeholder, onChange, onTextChange, container, containerStyle, opts, noteId, noteIdChanged }: NoteAutocompleteProps) {
|
||||||
const ref = useSyncedRef<HTMLInputElement>(externalInputRef);
|
const ref = useSyncedRef<HTMLInputElement>(externalInputRef);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -59,12 +57,6 @@ export default function NoteAutocomplete({ id, inputRef: externalInputRef, text,
|
|||||||
if (onTextChange) {
|
if (onTextChange) {
|
||||||
$autoComplete.on("input", () => onTextChange($autoComplete[0].value));
|
$autoComplete.on("input", () => onTextChange($autoComplete[0].value));
|
||||||
}
|
}
|
||||||
if (onKeyDown) {
|
|
||||||
$autoComplete.on("keydown", (e) => e.originalEvent && onKeyDown(e.originalEvent));
|
|
||||||
}
|
|
||||||
if (onBlur) {
|
|
||||||
$autoComplete.on("blur", () => onBlur($autoComplete.getSelectedNoteId() ?? ""));
|
|
||||||
}
|
|
||||||
}, [opts, container?.current]);
|
}, [opts, container?.current]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
import link, { ViewScope } from "../../services/link";
|
import link, { ViewScope } from "../../services/link";
|
||||||
import { useImperativeSearchHighlighlighting, useTriliumEvent } from "./hooks";
|
import { useImperativeSearchHighlighlighting } from "./hooks";
|
||||||
|
|
||||||
interface NoteLinkOpts {
|
interface NoteLinkOpts {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -19,11 +19,9 @@ interface NoteLinkOpts {
|
|||||||
|
|
||||||
export default function NoteLink({ className, notePath, showNotePath, showNoteIcon, style, noPreview, noTnLink, highlightedTokens, title, viewScope, noContextMenu }: NoteLinkOpts) {
|
export default function NoteLink({ className, notePath, showNotePath, showNoteIcon, style, noPreview, noTnLink, highlightedTokens, title, viewScope, noContextMenu }: NoteLinkOpts) {
|
||||||
const stringifiedNotePath = Array.isArray(notePath) ? notePath.join("/") : notePath;
|
const stringifiedNotePath = Array.isArray(notePath) ? notePath.join("/") : notePath;
|
||||||
const noteId = stringifiedNotePath.split("/").at(-1);
|
|
||||||
const ref = useRef<HTMLSpanElement>(null);
|
const ref = useRef<HTMLSpanElement>(null);
|
||||||
const [ jqueryEl, setJqueryEl ] = useState<JQuery<HTMLElement>>();
|
const [ jqueryEl, setJqueryEl ] = useState<JQuery<HTMLElement>>();
|
||||||
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
|
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
|
||||||
const [ noteTitle, setNoteTitle ] = useState<string>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
link.createLink(stringifiedNotePath, {
|
link.createLink(stringifiedNotePath, {
|
||||||
@@ -32,7 +30,7 @@ export default function NoteLink({ className, notePath, showNotePath, showNoteIc
|
|||||||
showNoteIcon,
|
showNoteIcon,
|
||||||
viewScope
|
viewScope
|
||||||
}).then(setJqueryEl);
|
}).then(setJqueryEl);
|
||||||
}, [ stringifiedNotePath, showNotePath, title, viewScope, noteTitle ]);
|
}, [ stringifiedNotePath, showNotePath, title, viewScope ]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current || !jqueryEl) return;
|
if (!ref.current || !jqueryEl) return;
|
||||||
@@ -40,16 +38,6 @@ export default function NoteLink({ className, notePath, showNotePath, showNoteIc
|
|||||||
highlightSearch(ref.current);
|
highlightSearch(ref.current);
|
||||||
}, [ jqueryEl, highlightedTokens ]);
|
}, [ jqueryEl, highlightedTokens ]);
|
||||||
|
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
|
||||||
// React to note title changes, but only if the title is not overwritten.
|
|
||||||
if (!title && noteId) {
|
|
||||||
const entityRow = loadResults.getEntityRow("notes", noteId);
|
|
||||||
if (entityRow) {
|
|
||||||
setNoteTitle(entityRow.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (style) {
|
if (style) {
|
||||||
jqueryEl?.css(style);
|
jqueryEl?.css(style);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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] {
|
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(() => {
|
useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]);
|
||||||
forceRender({});
|
|
||||||
}, [ note ]);
|
|
||||||
|
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||||
for (const attr of loadResults.getAttributeRows()) {
|
for (const attr of loadResults.getAttributeRows()) {
|
||||||
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
|
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
|
||||||
forceRender({});
|
setLabelValue(!attr.isDeleted);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -433,7 +430,6 @@ export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: F
|
|||||||
|
|
||||||
useDebugValue(labelName);
|
useDebugValue(labelName);
|
||||||
|
|
||||||
const labelValue = !!note?.hasLabel(labelName);
|
|
||||||
return [ labelValue, setter ] as const;
|
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[] }) {
|
function BookProperties({ viewType, note, properties }: { viewType: ViewTypeOptions, note: FNote, properties: BookProperty[] }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{properties.map(property => (
|
{properties.map(property => (
|
||||||
<div className={`type-${property}`}>
|
<div className={`type-${property}`}>
|
||||||
{mapPropertyView({ note, property })}
|
{mapPropertyView({ note, property })}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{viewType !== "list" && viewType !== "grid" && (
|
||||||
<CheckboxPropertyView
|
<CheckboxPropertyView
|
||||||
note={note} property={{
|
note={note} property={{
|
||||||
bindToLabel: "includeArchived",
|
bindToLabel: "includeArchived",
|
||||||
@@ -72,6 +73,7 @@ function BookProperties({ viewType, note, properties }: { viewType: ViewTypeOpti
|
|||||||
type: "checkbox"
|
type: "checkbox"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
|
|||||||
await attributes.removeAttributeById(noteId, expandedAttr.attributeId);
|
await attributes.removeAttributeById(noteId, expandedAttr.attributeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerCommand("refreshNoteList", { noteId });
|
triggerCommand("refreshNoteList", { noteId: noteId });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useTriliumEvent } from "../react/hooks";
|
|||||||
import { refToJQuerySelector } from "../react/react_utils";
|
import { refToJQuerySelector } from "../react/react_utils";
|
||||||
|
|
||||||
export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) {
|
export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) {
|
||||||
|
const [ html, setHtml ] = useState<string>();
|
||||||
const initialized = useRef<Promise<void> | null>(null);
|
const initialized = useRef<Promise<void> | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) {
|
|||||||
if (!note) return;
|
if (!note) return;
|
||||||
|
|
||||||
initialized.current = renderDoc(note).then($content => {
|
initialized.current = renderDoc(note).then($content => {
|
||||||
containerRef.current?.replaceChildren(...$content);
|
setHtml($content.html());
|
||||||
});
|
});
|
||||||
}, [ note ]);
|
}, [ note ]);
|
||||||
|
|
||||||
@@ -25,9 +26,10 @@ export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<RawHtmlBlock
|
||||||
ref={containerRef}
|
containerRef={containerRef}
|
||||||
className={`note-detail-doc-content ck-content ${viewScope?.viewMode === "contextual-help" ? "contextual-help" : ""}`}
|
className={`note-detail-doc-content ck-content ${viewScope?.viewMode === "contextual-help" ? "contextual-help" : ""}`}
|
||||||
|
html={html}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ function Font({ title, fontFamilyOption, fontSizeOption }: { title: string, font
|
|||||||
<FormTextBoxWithUnit
|
<FormTextBoxWithUnit
|
||||||
name="tree-font-size"
|
name="tree-font-size"
|
||||||
type="number" min={50} max={200} step={10}
|
type="number" min={50} max={200} step={10}
|
||||||
currentValue={fontSize} onBlur={setFontSize}
|
currentValue={fontSize} onChange={setFontSize}
|
||||||
unit={t("units.percentage")}
|
unit={t("units.percentage")}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat";
|
import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat";
|
||||||
import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor, TemplateDefinition } from "@triliumnext/ckeditor5";
|
import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor, TemplateDefinition } from "@triliumnext/ckeditor5";
|
||||||
import { buildConfig, BuildEditorOptions } from "./config";
|
import { buildConfig, BuildEditorOptions } from "./config";
|
||||||
import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef } from "../../react/hooks";
|
import { useLegacyImperativeHandlers, useSyncedRef } from "../../react/hooks";
|
||||||
import link from "../../../services/link";
|
import link from "../../../services/link";
|
||||||
import froca from "../../../services/froca";
|
import froca from "../../../services/froca";
|
||||||
|
|
||||||
@@ -38,9 +38,6 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
|
|||||||
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
|
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
|
||||||
const watchdogRef = useRef<EditorWatchdog>(null);
|
const watchdogRef = useRef<EditorWatchdog>(null);
|
||||||
const [ editor, setEditor ] = useState<CKTextEditor>();
|
const [ editor, setEditor ] = useState<CKTextEditor>();
|
||||||
const { parentComponent } = useNoteContext();
|
|
||||||
|
|
||||||
useKeyboardShortcuts("text-detail", containerRef, parentComponent);
|
|
||||||
|
|
||||||
useImperativeHandle(editorApi, () => ({
|
useImperativeHandle(editorApi, () => ({
|
||||||
hasSelection() {
|
hasSelection() {
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ body.mobile .note-detail-editable-text {
|
|||||||
.note-detail-editable-text h5 { font-size: 1.1em; }
|
.note-detail-editable-text h5 { font-size: 1.1em; }
|
||||||
.note-detail-editable-text h6 { font-size: 1.0em; }
|
.note-detail-editable-text h6 { font-size: 1.0em; }
|
||||||
|
|
||||||
body.heading-style-markdown .note-detail-editable-text h2::before { content: "##\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-editable-text h2::before { content: "##\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-editable-text h3::before { content: "###\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-editable-text h3::before { content: "###\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-editable-text h4:not(.include-note-title)::before { content: "####\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-editable-text h4:not(.include-note-title)::before { content: "####\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-editable-text h5::before { content: "#####\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-editable-text h5::before { content: "#####\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-editable-text h6::before { content: "######\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-editable-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
|
||||||
|
|
||||||
body.heading-style-underline .note-detail-editable-text h2 { border-bottom: 1px solid var(--main-border-color); }
|
body.heading-style-underline .note-detail-editable-text h2 { border-bottom: 1px solid var(--main-border-color); }
|
||||||
body.heading-style-underline .note-detail-editable-text h3 { border-bottom: 1px solid var(--main-border-color); }
|
body.heading-style-underline .note-detail-editable-text h3 { border-bottom: 1px solid var(--main-border-color); }
|
||||||
|
|||||||
@@ -196,12 +196,14 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useKeyboardShortcuts("text-detail", containerRef, parentComponent);
|
||||||
useTriliumEvent("insertDateTimeToText", ({ ntxId: eventNtxId }) => {
|
useTriliumEvent("insertDateTimeToText", ({ ntxId: eventNtxId }) => {
|
||||||
if (eventNtxId !== ntxId) return;
|
if (eventNtxId !== ntxId) return;
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const customDateTimeFormat = options.get("customDateTimeFormat");
|
const customDateTimeFormat = options.get("customDateTimeFormat");
|
||||||
const dateString = utils.formatDateTime(date, customDateTimeFormat);
|
const dateString = utils.formatDateTime(date, customDateTimeFormat);
|
||||||
|
|
||||||
|
console.log("Insert text ", ntxId, eventNtxId, dateString);
|
||||||
addTextToEditor(dateString);
|
addTextToEditor(dateString);
|
||||||
});
|
});
|
||||||
useTriliumEvent("addTextToActiveEditor", ({ text }) => {
|
useTriliumEvent("addTextToActiveEditor", ({ text }) => {
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
.note-detail-readonly-text h5 { font-size: 1.1em; }
|
.note-detail-readonly-text h5 { font-size: 1.1em; }
|
||||||
.note-detail-readonly-text h6 { font-size: 1.0em; }
|
.note-detail-readonly-text h6 { font-size: 1.0em; }
|
||||||
|
|
||||||
body.heading-style-markdown .note-detail-readonly-text h1::before { content: "#\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-readonly-text h1::before { content: "#\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-readonly-text h2::before { content: "##\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-readonly-text h2::before { content: "##\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-readonly-text h3::before { content: "###\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-readonly-text h3::before { content: "###\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-readonly-text h4:not(.include-note-title)::before { content: "####\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-readonly-text h4:not(.include-note-title)::before { content: "####\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-readonly-text h5::before { content: "#####\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-readonly-text h5::before { content: "#####\\2004"; color: var(--muted-text-color); }
|
||||||
body.heading-style-markdown .note-detail-readonly-text h6::before { content: "######\2004"; color: var(--muted-text-color); }
|
body.heading-style-markdown .note-detail-readonly-text h6::before { content: "######\\2004"; color: var(--muted-text-color); }
|
||||||
|
|
||||||
body.heading-style-underline .note-detail-readonly-text h1 { border-bottom: 1px solid var(--main-border-color); }
|
body.heading-style-underline .note-detail-readonly-text h1 { border-bottom: 1px solid var(--main-border-color); }
|
||||||
body.heading-style-underline .note-detail-readonly-text h2 { border-bottom: 1px solid var(--main-border-color); }
|
body.heading-style-underline .note-detail-readonly-text h2 { border-bottom: 1px solid var(--main-border-color); }
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import link from "../../../services/link";
|
|||||||
import { formatCodeBlocks } from "../../../services/syntax_highlight";
|
import { formatCodeBlocks } from "../../../services/syntax_highlight";
|
||||||
import TouchBar, { TouchBarButton, TouchBarSpacer } from "../../react/TouchBar";
|
import TouchBar, { TouchBarButton, TouchBarSpacer } from "../../react/TouchBar";
|
||||||
import appContext from "../../../components/app_context";
|
import appContext from "../../../components/app_context";
|
||||||
import { applyReferenceLinks } from "./read_only_helper";
|
|
||||||
|
|
||||||
export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) {
|
export default function ReadOnlyText({ note, noteContext, ntxId }: TypeWidgetProps) {
|
||||||
const blob = useNoteBlob(note);
|
const blob = useNoteBlob(note);
|
||||||
@@ -123,3 +122,10 @@ function applyMath(container: HTMLDivElement) {
|
|||||||
renderMathInElement(equation, { trust: true });
|
renderMathInElement(equation, { trust: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyReferenceLinks(container: HTMLDivElement) {
|
||||||
|
const referenceLinks = container.querySelectorAll<HTMLDivElement>("a.reference-link");
|
||||||
|
for (const referenceLink of referenceLinks) {
|
||||||
|
link.loadReferenceLinkTitle($(referenceLink));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import link from "../../../services/link";
|
|
||||||
|
|
||||||
export async function applyReferenceLinks(container: HTMLDivElement | HTMLElement) {
|
|
||||||
const referenceLinks = container.querySelectorAll<HTMLDivElement>("a.reference-link");
|
|
||||||
for (const referenceLink of referenceLinks) {
|
|
||||||
await link.loadReferenceLinkTitle($(referenceLink));
|
|
||||||
|
|
||||||
// Wrap in a <span> to match the design while in CKEditor.
|
|
||||||
const spanEl = document.createElement("span");
|
|
||||||
spanEl.replaceChildren(...referenceLink.childNodes);
|
|
||||||
referenceLink.replaceChildren(spanEl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"@triliumnext/commons": "workspace:*",
|
"@triliumnext/commons": "workspace:*",
|
||||||
"@triliumnext/server": "workspace:*",
|
"@triliumnext/server": "workspace:*",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "13.0.1",
|
||||||
"electron": "38.7.0",
|
"electron": "38.6.0",
|
||||||
"@electron-forge/cli": "7.10.2",
|
"@electron-forge/cli": "7.10.2",
|
||||||
"@electron-forge/maker-deb": "7.10.2",
|
"@electron-forge/maker-deb": "7.10.2",
|
||||||
"@electron-forge/maker-dmg": "7.10.2",
|
"@electron-forge/maker-dmg": "7.10.2",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
"@types/mime-types": "3.0.1",
|
"@types/mime-types": "3.0.1",
|
||||||
"@types/yargs": "17.0.35"
|
"@types/yargs": "17.0.34"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx src/main.ts",
|
"dev": "tsx src/main.ts",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"@triliumnext/desktop": "workspace:*",
|
"@triliumnext/desktop": "workspace:*",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "13.0.1",
|
||||||
"electron": "38.7.0",
|
"electron": "38.6.0",
|
||||||
"fs-extra": "11.3.2"
|
"fs-extra": "11.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# 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
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
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
|
# Install only runtime dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
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
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# 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
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:24.11.1-alpine
|
FROM node:24.11.0-alpine
|
||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
RUN apk add --no-cache su-exec shadow
|
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
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# 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
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
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
|
# Create a non-root user with configurable UID/GID
|
||||||
ARG USER=trilium
|
ARG USER=trilium
|
||||||
ARG UID=1001
|
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
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# 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
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
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
|
# Create a non-root user with configurable UID/GID
|
||||||
ARG USER=trilium
|
ARG USER=trilium
|
||||||
ARG UID=1001
|
ARG UID=1001
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"node-html-parser": "7.0.1"
|
"node-html-parser": "7.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@anthropic-ai/sdk": "0.69.0",
|
"@anthropic-ai/sdk": "0.68.0",
|
||||||
"@braintree/sanitize-url": "7.1.1",
|
"@braintree/sanitize-url": "7.1.1",
|
||||||
"@electron/remote": "2.1.3",
|
"@electron/remote": "2.1.3",
|
||||||
"@preact/preset-vite": "2.10.2",
|
"@preact/preset-vite": "2.10.2",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"debounce": "3.0.0",
|
"debounce": "3.0.0",
|
||||||
"debug": "4.4.3",
|
"debug": "4.4.3",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"electron": "38.7.0",
|
"electron": "38.6.0",
|
||||||
"electron-debug": "4.1.0",
|
"electron-debug": "4.1.0",
|
||||||
"electron-window-state": "5.0.3",
|
"electron-window-state": "5.0.3",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
@@ -98,19 +98,19 @@
|
|||||||
"http-proxy-agent": "7.0.2",
|
"http-proxy-agent": "7.0.2",
|
||||||
"https-proxy-agent": "7.0.6",
|
"https-proxy-agent": "7.0.6",
|
||||||
"i18next": "25.6.2",
|
"i18next": "25.6.2",
|
||||||
"i18next-fs-backend": "2.6.1",
|
"i18next-fs-backend": "2.6.0",
|
||||||
"image-type": "6.0.0",
|
"image-type": "6.0.0",
|
||||||
"ini": "6.0.0",
|
"ini": "6.0.0",
|
||||||
"is-animated": "2.0.2",
|
"is-animated": "2.0.2",
|
||||||
"is-svg": "6.1.0",
|
"is-svg": "6.1.0",
|
||||||
"jimp": "1.6.0",
|
"jimp": "1.6.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.0",
|
||||||
"marked": "16.4.2",
|
"marked": "16.4.2",
|
||||||
"mime-types": "3.0.1",
|
"mime-types": "3.0.1",
|
||||||
"multer": "2.0.2",
|
"multer": "2.0.2",
|
||||||
"normalize-strings": "1.1.1",
|
"normalize-strings": "1.1.1",
|
||||||
"ollama": "0.6.3",
|
"ollama": "0.6.2",
|
||||||
"openai": "6.9.0",
|
"openai": "6.8.1",
|
||||||
"rand-token": "1.0.1",
|
"rand-token": "1.0.1",
|
||||||
"safe-compare": "1.1.4",
|
"safe-compare": "1.1.4",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
|
|||||||
40
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html
generated
vendored
@@ -5,14 +5,14 @@
|
|||||||
<p>In Trilium, attributes are key-value pairs assigned to notes, providing
|
<p>In Trilium, attributes are key-value pairs assigned to notes, providing
|
||||||
additional metadata or functionality. There are two primary types of attributes:</p>
|
additional metadata or functionality. There are two primary types of attributes:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li data-list-item-id="ef9c097e5af906754a4056ace4d16dbee">
|
<li>
|
||||||
<p><a class="reference-link" href="#root/_help_HI6GBBIduIgv">Labels</a> can
|
<p><a class="reference-link" href="#root/_help_HI6GBBIduIgv">Labels</a> can
|
||||||
be used for a variety of purposes, such as storing metadata or configuring
|
be used for a variety of purposes, such as storing metadata or configuring
|
||||||
the behavior of notes. Labels are also searchable, enhancing note retrieval.</p>
|
the behavior of notes. Labels are also searchable, enhancing note retrieval.</p>
|
||||||
<p>For more information, including predefined labels, see <a class="reference-link"
|
<p>For more information, including predefined labels, see <a class="reference-link"
|
||||||
href="#root/_help_HI6GBBIduIgv">Labels</a>.</p>
|
href="#root/_help_HI6GBBIduIgv">Labels</a>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="e8416f6f5188a4d8a25917c610a1482c0">
|
<li>
|
||||||
<p><a class="reference-link" href="#root/_help_Cq5X6iKQop6R">Relations</a> define
|
<p><a class="reference-link" href="#root/_help_Cq5X6iKQop6R">Relations</a> define
|
||||||
connections between notes, similar to links. These can be used for metadata
|
connections between notes, similar to links. These can be used for metadata
|
||||||
and scripting purposes.</p>
|
and scripting purposes.</p>
|
||||||
@@ -23,30 +23,6 @@
|
|||||||
</ol>
|
</ol>
|
||||||
<p>These attributes play a crucial role in organizing, categorizing, and
|
<p>These attributes play a crucial role in organizing, categorizing, and
|
||||||
enhancing the functionality of notes.</p>
|
enhancing the functionality of notes.</p>
|
||||||
<h2>Types of attributes</h2>
|
|
||||||
<p>Conceptually there are two types of attributes (applying to both labels
|
|
||||||
and relations):</p>
|
|
||||||
<ol>
|
|
||||||
<li data-list-item-id="e778cee42c209e30e41f7d2c99895495d"><strong>System attributes</strong>
|
|
||||||
<br>As the name suggest, these attributes have a special meaning since they
|
|
||||||
are interpreted by Trilium. For example the <code>color</code> attribute
|
|
||||||
will change the color of the note as displayed in the <a class="reference-link"
|
|
||||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
|
||||||
links, and <code>iconClass</code> will change the icon of a note.
|
|
||||||
<br> </li>
|
|
||||||
<li data-list-item-id="e0ca0ab889b471e7c18e3d4f6ae6a61e0"><strong>User-defined attributes</strong>
|
|
||||||
<br>These are free-form labels or relations that can be used by the user.
|
|
||||||
They can be used purely for categorization purposes (especially if combined
|
|
||||||
with <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>),
|
|
||||||
or they can be given meaning through the use of <a class="reference-link"
|
|
||||||
href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">Scripting</a>.</li>
|
|
||||||
</ol>
|
|
||||||
<p>In practice, Trilium makes no direct distinction of whether an attribute
|
|
||||||
is a system one or a user-defined one. A label or relation is considered
|
|
||||||
a system attribute if it matches one of the built-in names (e.g. like the
|
|
||||||
aforementioned <code>iconClass</code>). Keep this in mind when creating
|
|
||||||
<a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> in
|
|
||||||
order not to accidentally alter a system attribute (unless intended).</p>
|
|
||||||
<h2>Viewing the list of attributes</h2>
|
<h2>Viewing the list of attributes</h2>
|
||||||
<p>Both the labels and relations for the current note are displayed in the <em>Owned Attributes</em> section
|
<p>Both the labels and relations for the current note are displayed in the <em>Owned Attributes</em> section
|
||||||
of the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
of the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||||
@@ -55,15 +31,13 @@
|
|||||||
only be viewed.</p>
|
only be viewed.</p>
|
||||||
<p>In the list of attributes, labels are prefixed with the <code>#</code> character
|
<p>In the list of attributes, labels are prefixed with the <code>#</code> character
|
||||||
whereas relations are prefixed with the <code>~</code> character.</p>
|
whereas relations are prefixed with the <code>~</code> character.</p>
|
||||||
<h2>Attribute Definitions and Promoted Attributes</h2>
|
|
||||||
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> create
|
|
||||||
a form-like editing experience for attributes, which makes it easy to enhancing
|
|
||||||
the organization and management of attributes</p>
|
|
||||||
<h2>Multiplicity</h2>
|
<h2>Multiplicity</h2>
|
||||||
<p>Attributes in Trilium can be "multi-valued", meaning multiple attributes
|
<p>Attributes in Trilium can be "multi-valued", meaning multiple attributes
|
||||||
with the same name can co-exist. This can be combined with <a class="reference-link"
|
with the same name can co-exist.</p>
|
||||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> to
|
<h2>Attribute Definitions and Promoted Attributes</h2>
|
||||||
easily add them.</p>
|
<p>Special labels create "label/attribute" definitions, enhancing the organization
|
||||||
|
and management of attributes. For more details, see <a class="reference-link"
|
||||||
|
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.</p>
|
||||||
<h2>Attribute Inheritance</h2>
|
<h2>Attribute Inheritance</h2>
|
||||||
<p>Trilium supports attribute inheritance, allowing child notes to inherit
|
<p>Trilium supports attribute inheritance, allowing child notes to inherit
|
||||||
attributes from their parents. For more information, see <a class="reference-link"
|
attributes from their parents. For more information, see <a class="reference-link"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,118 +1,31 @@
|
|||||||
<figure class="image image_resized" style="width:61.4%;">
|
|
||||||
<img style="aspect-ratio:938/368;" src="Promoted Attributes_image.png"
|
|
||||||
width="938" height="368">
|
|
||||||
</figure>
|
|
||||||
<p>Promoted attributes are <a href="#root/_help_zEY4DaJG4YT5">attributes</a> which
|
<p>Promoted attributes are <a href="#root/_help_zEY4DaJG4YT5">attributes</a> which
|
||||||
are displayed prominently in the UI which allow them to be easily viewed
|
are considered important and thus are "promoted" onto the main note UI.
|
||||||
and edited.</p>
|
See example below:</p>
|
||||||
<p>One way of seeing promoted attributes is as a kind of form with several
|
<p>
|
||||||
fields. Each field is just regular attribute, the only difference is that
|
<img src="Promoted Attributes_promot.png">
|
||||||
they appear on the note itself.</p>
|
</p>
|
||||||
|
<p>You can see the note having kind of form with several fields. Each of
|
||||||
|
these is just regular attribute, the only difference is that they appear
|
||||||
|
on the note itself.</p>
|
||||||
<p>Attributes can be pretty useful since they allow for querying and script
|
<p>Attributes can be pretty useful since they allow for querying and script
|
||||||
automation etc. but they are also inconveniently hidden. This allows you
|
automation etc. but they are also inconveniently hidden. This allows you
|
||||||
to select few of the important ones and push them to the front of the user.</p>
|
to select few of the important ones and push them to the front of the user.</p>
|
||||||
|
<p>Now, how do we make attribute to appear on the UI?</p>
|
||||||
<h2>Attribute definition</h2>
|
<h2>Attribute definition</h2>
|
||||||
<p>In order to have promoted attributes, there needs to be a way to define
|
<p>Attribute is always name-value pair where both name and value are strings.</p>
|
||||||
them.</p>
|
<p><em>Attribute definition</em> specifies how should this value be interpreted
|
||||||
<figure class="image image-style-align-right image_resized" style="width:38.82%;">
|
- is it just string, or is it a date? Should we allow multiple values or
|
||||||
<img style="aspect-ratio:492/346;" src="1_Promoted Attributes_image.png"
|
note? And importantly, should we <em>promote</em> the attribute or not?</p>
|
||||||
width="492" height="346">
|
<p>
|
||||||
</figure>
|
<img src="Promoted Attributes_image.png">
|
||||||
<p>Technically, attributes are only name-value pairs where both name and
|
</p>
|
||||||
value are strings.</p>
|
<p>You can notice tag attribute definition. These "definition" attributes
|
||||||
<p>The <em>Attribute definition</em> specifies how should this value be interpreted:</p>
|
define how the "value" attributes should behave.</p>
|
||||||
<ul>
|
|
||||||
<li data-list-item-id="e73e2d8dffca4fe59d009bfc5a245da88">Is it just string, or is it a date?</li>
|
|
||||||
<li data-list-item-id="e115a58c2d81149f481c385479cf06965">Should we allow multiple values or note?</li>
|
|
||||||
<li data-list-item-id="eb8cab0b2fdcefb7af9128f63b3b9f4ee">Should we <em>promote</em> the attribute or not?</li>
|
|
||||||
</ul>
|
|
||||||
<h2>Creating a new promoted attribute definition</h2>
|
|
||||||
<p>To create a new promoted attribute:</p>
|
|
||||||
<ol>
|
|
||||||
<li data-list-item-id="e118d34c98d285e3a7492876f73a49ed5">Go to a note.</li>
|
|
||||||
<li data-list-item-id="e480cc90d938f7f728011679df29f6ce0">Go to <em>Owned Attributes</em> in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_BlN9DFI679QC">Ribbon</a>.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e40a26a8a32714572e670d8c7c18d1355">Press the + button.</li>
|
|
||||||
<li data-list-item-id="e5ba4dcc8ae48c7f043de2c8fc5ec1c2c">Select either <em>Add new label definition</em> or <em>Add new relation definition</em>.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e14e894cf6d207a410adbe87c56143799">Select the name which will be name of the label or relation that will
|
|
||||||
be created when the promoted attribute is edited.</li>
|
|
||||||
<li data-list-item-id="e5d803cd5a37a7169a5aa770d769f2dd4">Ensure <em>Promoted</em> is checked in order to display it at the top of
|
|
||||||
notes.</li>
|
|
||||||
<li data-list-item-id="e50c81da6343840b2a06b46a20e21181e">Optionally, choose an <em>Alias</em> which will be displayed next to the
|
|
||||||
promoted attribute instead of the attribute name. Generally it's best to
|
|
||||||
choose a “user-friendly” name since it can contain spaces and other characters
|
|
||||||
which are not supported as attribute names.</li>
|
|
||||||
<li data-list-item-id="e0e08c63296b88ea8ec8ae17104eed366">Check <em>Inheritable</em> to apply it to this note and all its descendants.
|
|
||||||
To keep it only for the current note, un-check it.</li>
|
|
||||||
<li data-list-item-id="ededc09be314b651d919aebd026cc5b86">Press “Save & Close” to apply the changes.</li>
|
|
||||||
</ol>
|
|
||||||
<h2>How attribute definitions actually work</h2>
|
|
||||||
<p>When a new promoted attribute definition is created, it creates a corresponding
|
|
||||||
label prefixed with either <code>label</code> or <code>relation</code>, depending
|
|
||||||
on the definition type:</p><pre><code class="language-text-x-trilium-auto">#label:myColor(inheritable)="promoted,alias=Color,multi,color"</code></pre>
|
|
||||||
<p>The only purpose of the attribute definition is to set up a template.
|
|
||||||
If the attribute was marked as promoted, then it's also displayed to the
|
|
||||||
user for easy editing.</p>
|
|
||||||
<figure class="table" style="width:100%;">
|
|
||||||
<table class="ck-table-resized">
|
|
||||||
<colgroup>
|
|
||||||
<col style="width:47.64%;">
|
|
||||||
<col style="width:52.36%;">
|
|
||||||
</colgroup>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<figure class="image">
|
|
||||||
<img style="aspect-ratio:495/157;" src="2_Promoted Attributes_image.png"
|
|
||||||
width="495" height="157">
|
|
||||||
</figure>
|
|
||||||
</td>
|
|
||||||
<td>Notice how the promoted attribute definition only creates a “Due date”
|
|
||||||
box above the text content.</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<figure class="image">
|
|
||||||
<img style="aspect-ratio:663/160;" src="3_Promoted Attributes_image.png"
|
|
||||||
width="663" height="160">
|
|
||||||
</figure>
|
|
||||||
</td>
|
|
||||||
<td>Once a value is set by the user, a new label (or relation, depending on
|
|
||||||
the type) is created. The name of the attribute matches one set when creating
|
|
||||||
the promoted attribute.</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</figure>
|
|
||||||
<p>So there's one attribute for value and one for definition. But notice
|
<p>So there's one attribute for value and one for definition. But notice
|
||||||
how an definition attribute can be made <a href="#root/_help_bwZpz2ajCEwO">Inheritable</a>,
|
how definition attribute is <a href="#root/_help_bwZpz2ajCEwO">Inheritable</a>,
|
||||||
meaning that it's also applied to all descendant notes. In this case, the
|
meaning that it's also applied to all descendant note. So in a way, this
|
||||||
definition used for the whole sub-tree while "value" attributes are for
|
definition is used for the whole subtree while "value" attributes are applied
|
||||||
each not individually.</p>
|
only for this note.</p>
|
||||||
<h2>Using system attributes</h2>
|
|
||||||
<p>It's possible to create promoted attributes out of system attributes,
|
|
||||||
to be able to easily alter them.</p>
|
|
||||||
<p>Here are a few practical examples:</p>
|
|
||||||
<ul>
|
|
||||||
<li data-list-item-id="eb7e4e362b582d6bf480375d1e5648ac3"><a class="reference-link" href="#root/pOsGYCXsbNQG/_help_GTwFsgaA0lCt">Collections</a> already
|
|
||||||
make use of this practice, for example:
|
|
||||||
<ul>
|
|
||||||
<li data-list-item-id="e0831925d935c7a90f90bd5fed94d089e">Calendars add “Start Date”, “End Date”, “Start Time” and “End Time” as
|
|
||||||
promoted attributes. These map to system attributes such as <code>startDate</code> which
|
|
||||||
are then interpreted by the calendar view.</li>
|
|
||||||
<li data-list-item-id="e4a235f12a7934803ba7706e309511c82"><a class="reference-link" href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_zP3PMqaG71Ct">Presentation</a> adds
|
|
||||||
a “Background” promoted attribute for each of the slide to easily be able
|
|
||||||
to customize.</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li data-list-item-id="e9069ad779c177b7ed0d3ece786e73cd3">The Trilium documentation (which is edited in Trilium) uses a promoted
|
|
||||||
attribute to be able to easily edit the <code>#shareAlias</code> (see
|
|
||||||
<a
|
|
||||||
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_R9pX4DGra2Vt">Sharing</a>) in order to form clean URLs.</li>
|
|
||||||
<li data-list-item-id="ed14685a7279b42a1769be97ffe7f5e8f">If you always edit a particular system attribute such as <code>#color</code>,
|
|
||||||
simply create a promoted attribute for it to make it easier.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Inverse relation</h3>
|
<h3>Inverse relation</h3>
|
||||||
<p>Some relations always occur in pairs - my favorite example is on the family.
|
<p>Some relations always occur in pairs - my favorite example is on the family.
|
||||||
If you have a note representing husband and note representing wife, then
|
If you have a note representing husband and note representing wife, then
|
||||||
@@ -120,7 +33,7 @@
|
|||||||
This is bidirectional relationship - meaning that if a relation is pointing
|
This is bidirectional relationship - meaning that if a relation is pointing
|
||||||
from husband to wife then there should be always another relation pointing
|
from husband to wife then there should be always another relation pointing
|
||||||
from wife to husband.</p>
|
from wife to husband.</p>
|
||||||
<p>Another example is with parent-child relationship. Again these always
|
<p>Another example is with parent - child relationship. Again these always
|
||||||
occur in pairs, but in this case it's not exact same relation - the one
|
occur in pairs, but in this case it's not exact same relation - the one
|
||||||
going from parent to child might be called <code>isParentOf</code> and the
|
going from parent to child might be called <code>isParentOf</code> and the
|
||||||
other one going from child to parent might be called <code>isChildOf</code>.</p>
|
other one going from child to parent might be called <code>isChildOf</code>.</p>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 113 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes_promot.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB |
652
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Geo Map.html
generated
vendored
@@ -1,11 +1,8 @@
|
|||||||
<aside class="admonition important">
|
<aside class="admonition important">
|
||||||
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_zEY4DaJG4YT5">Attributes</a>
|
<p>Starting with Trilium v0.97.0, the geo map has been converted from a standalone
|
||||||
<a
|
<a
|
||||||
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a><a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_zEY4DaJG4YT5">Attributes</a>Starting
|
href="#root/_help_KSZ04uQ2D1St">note type</a>to a type of view for the <a class="reference-link"
|
||||||
with Trilium v0.97.0, the geo map has been converted from a standalone
|
href="#root/_help_0ESUbbAxVnoK">Note List</a>. </p>
|
||||||
<a
|
|
||||||
href="#root/_help_KSZ04uQ2D1St">note type</a>to a type of view for the <a class="reference-link"
|
|
||||||
href="#root/_help_0ESUbbAxVnoK">Note List</a>. </p>
|
|
||||||
</aside>
|
</aside>
|
||||||
<figure class="image image-style-align-center">
|
<figure class="image image-style-align-center">
|
||||||
<img style="aspect-ratio:892/675;" src="9_Geo Map_image.png"
|
<img style="aspect-ratio:892/675;" src="9_Geo Map_image.png"
|
||||||
@@ -15,127 +12,121 @@
|
|||||||
on an attribute. It is also possible to add new notes at a specific location
|
on an attribute. It is also possible to add new notes at a specific location
|
||||||
using the built-in interface.</p>
|
using the built-in interface.</p>
|
||||||
<h2>Creating a new geo map</h2>
|
<h2>Creating a new geo map</h2>
|
||||||
<figure class="table">
|
<table>
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td>1</td>
|
||||||
<td>1</td>
|
<td>
|
||||||
<td>
|
<figure class="image">
|
||||||
<figure class="image">
|
<img style="aspect-ratio:483/413;" src="15_Geo Map_image.png"
|
||||||
<img style="aspect-ratio:483/413;" src="15_Geo Map_image.png"
|
width="483" height="413">
|
||||||
width="483" height="413">
|
</figure>
|
||||||
</figure>
|
</td>
|
||||||
</td>
|
<td>Right click on any note on the note tree and select <em>Insert child note</em> → <em>Geo Map (beta)</em>.</td>
|
||||||
<td>Right click on any note on the note tree and select <em>Insert child note</em> → <em>Geo Map (beta)</em>.</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>2</td>
|
||||||
<td>2</td>
|
<td>
|
||||||
<td>
|
<figure class="image image-style-align-center image_resized" style="width:53.44%;">
|
||||||
<figure class="image image-style-align-center image_resized" style="width:53.44%;">
|
<img style="aspect-ratio:1720/1396;" src="8_Geo Map_image.png"
|
||||||
<img style="aspect-ratio:1720/1396;" src="8_Geo Map_image.png"
|
width="1720" height="1396">
|
||||||
width="1720" height="1396">
|
</figure>
|
||||||
</figure>
|
</td>
|
||||||
</td>
|
<td>By default the map will be empty and will show the entire world.</td>
|
||||||
<td>By default the map will be empty and will show the entire world.</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</figure>
|
|
||||||
<h2>Repositioning the map</h2>
|
<h2>Repositioning the map</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="ec3df228f80922b4531e2dd1a977d81b8">Click and drag the map in order to move across the map.</li>
|
<li>Click and drag the map in order to move across the map.</li>
|
||||||
<li data-list-item-id="ee505c643c5bacffffc42119cc2f19c60">Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons
|
<li>Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons
|
||||||
on the top-left to adjust the zoom.</li>
|
on the top-left to adjust the zoom.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>The position on the map and the zoom are saved inside the map note and
|
<p>The position on the map and the zoom are saved inside the map note and
|
||||||
restored when visiting again the note.</p>
|
restored when visiting again the note.</p>
|
||||||
<h2>Adding a marker using the map</h2>
|
<h2>Adding a marker using the map</h2>
|
||||||
<h3>Adding a new note using the plus button</h3>
|
<h3>Adding a new note using the plus button</h3>
|
||||||
<figure class="table">
|
<table>
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td>1</td>
|
||||||
<td>1</td>
|
<td>To create a marker, first navigate to the desired point on the map. Then
|
||||||
<td>To create a marker, first navigate to the desired point on the map. Then
|
press the
|
||||||
press the
|
<img src="10_Geo Map_image.png">button in the <a href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> (top-right)
|
||||||
<img src="10_Geo Map_image.png">button in the <a href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> (top-right)
|
area.
|
||||||
area.
|
<br>
|
||||||
<br>
|
<br>If the button is not visible, make sure the button section is visible
|
||||||
<br>If the button is not visible, make sure the button section is visible
|
by pressing the chevron button (
|
||||||
by pressing the chevron button (
|
<img src="17_Geo Map_image.png">) in the top-right of the map.</td>
|
||||||
<img src="17_Geo Map_image.png">) in the top-right of the map.</td>
|
<td></td>
|
||||||
<td> </td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>2</td>
|
||||||
<td>2</td>
|
<td>
|
||||||
<td>
|
<img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png"
|
||||||
<img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png"
|
width="1730" height="416">
|
||||||
width="1730" height="416">
|
</td>
|
||||||
</td>
|
<td>Once pressed, the map will enter in the insert mode, as illustrated by
|
||||||
<td>Once pressed, the map will enter in the insert mode, as illustrated by
|
the notification.
|
||||||
the notification.
|
<br>
|
||||||
<br>
|
<br>Simply click the point on the map where to place the marker, or the Escape
|
||||||
<br>Simply click the point on the map where to place the marker, or the Escape
|
key to cancel.</td>
|
||||||
key to cancel.</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>3</td>
|
||||||
<td>3</td>
|
<td>
|
||||||
<td>
|
<img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map_image.png"
|
||||||
<img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map_image.png"
|
width="1586" height="404">
|
||||||
width="1586" height="404">
|
</td>
|
||||||
</td>
|
<td>Enter the name of the marker/note to be created.</td>
|
||||||
<td>Enter the name of the marker/note to be created.</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>4</td>
|
||||||
<td>4</td>
|
<td>
|
||||||
<td>
|
<img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map_image.png"
|
||||||
<img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map_image.png"
|
width="1696" height="608">
|
||||||
width="1696" height="608">
|
</td>
|
||||||
</td>
|
<td>Once confirmed, the marker will show up on the map and it will also be
|
||||||
<td>Once confirmed, the marker will show up on the map and it will also be
|
displayed as a child note of the map.</td>
|
||||||
displayed as a child note of the map.</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</figure>
|
|
||||||
<h3>Adding a new note using the contextual menu</h3>
|
<h3>Adding a new note using the contextual menu</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li data-list-item-id="ea014287557d9fbca07dd30b32405f92a">Right click anywhere on the map, where to place the newly created marker
|
<li>Right click anywhere on the map, where to place the newly created marker
|
||||||
(and corresponding note).</li>
|
(and corresponding note).</li>
|
||||||
<li data-list-item-id="ef758bd1a52c2ced75e402fd68e3d1e67">Select <em>Add a marker at this location</em>.</li>
|
<li>Select <em>Add a marker at this location</em>.</li>
|
||||||
<li data-list-item-id="e6f1d66e9d872ac4908a7c11b401a9d80">Enter the name of the ne<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>wly
|
<li>Enter the name of the newly created note.</li>
|
||||||
created note.</li>
|
<li>The map should be updated with the new marker.</li>
|
||||||
<li data-list-item-id="eed9a3fbe500d61c76729f6147ceed925">The map should be updated with the new marker.</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
<h3>Adding an existing note on note from the note tree</h3>
|
<h3>Adding an existing note on note from the note tree</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li data-list-item-id="ecba60e77600a1660e947ce54c633df04">Select the desired note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
<li>Select the desired note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||||
<li
|
<li>Hold the mouse on the note and drag it to the map to the desired location.</li>
|
||||||
data-list-item-id="e00bb8f41cf79fef2b5ce215bc678f808">Hold the mouse on the note and drag it to the map to the desired location.</li>
|
<li>The map should be updated with the new marker.</li>
|
||||||
<li
|
|
||||||
data-list-item-id="e3fc69dc9e44db125872274e68223cd4e">The map should be updated with the new marker.</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
<p>This works for:</p>
|
<p>This works for:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="e0821dbcf20fa4a83a9cfada4bea95521">Notes that are not part of the geo map, case in which a <a href="#root/_help_IakOLONlIfGI">clone</a> will
|
<li>Notes that are not part of the geo map, case in which a <a href="#root/_help_IakOLONlIfGI">clone</a> will
|
||||||
be created.</li>
|
be created.</li>
|
||||||
<li data-list-item-id="e859aa76bcb592afd7414cccb60cdd3ed">Notes that are a child of the geo map but not yet positioned on the map.</li>
|
<li>Notes that are a child of the geo map but not yet positioned on the map.</li>
|
||||||
<li
|
<li>Notes that are a child of the geo map and also positioned, case in which
|
||||||
data-list-item-id="e74fb2b74919aba44e1a457a5f8aec6b8">Notes that are a child of the geo map and also positioned, case in which
|
|
||||||
the marker will be relocated to the new position.</li>
|
the marker will be relocated to the new position.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<aside class="admonition note">
|
<aside class="admonition note">
|
||||||
@@ -145,10 +136,8 @@
|
|||||||
<h2>How the location of the markers is stored</h2>
|
<h2>How the location of the markers is stored</h2>
|
||||||
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
||||||
of the child notes:</p>
|
of the child notes:</p>
|
||||||
<p>
|
<img src="18_Geo Map_image.png"
|
||||||
<img src="18_Geo Map_image.png" width="1288"
|
width="1288" height="278">
|
||||||
height="278">
|
|
||||||
</p>
|
|
||||||
<p>This value can be added manually if needed. The value of the attribute
|
<p>This value can be added manually if needed. The value of the attribute
|
||||||
is made up of the latitude and longitude separated by a comma.</p>
|
is made up of the latitude and longitude separated by a comma.</p>
|
||||||
<h2>Repositioning markers</h2>
|
<h2>Repositioning markers</h2>
|
||||||
@@ -160,17 +149,16 @@
|
|||||||
page (<kbd>Ctrl</kbd>+<kbd>R</kbd> ) to cancel it.</p>
|
page (<kbd>Ctrl</kbd>+<kbd>R</kbd> ) to cancel it.</p>
|
||||||
<h2>Interaction with the markers</h2>
|
<h2>Interaction with the markers</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="e953e0022167e10d476bbf438b71029ee">Hovering over a marker will display a <a class="reference-link" href="#root/_help_lgKX7r3aL30x">Note Tooltip</a> with
|
<li>Hovering over a marker will display a <a class="reference-link" href="#root/_help_lgKX7r3aL30x">Note Tooltip</a> with
|
||||||
the content of the note it belongs to.
|
the content of the note it belongs to.
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="e24f6ec1307f470fc8a9c0681282dd424">Clicking on the note title in the tooltip will navigate to the note in
|
<li>Clicking on the note title in the tooltip will navigate to the note in
|
||||||
the current view.</li>
|
the current view.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="ea6f9b375776db475c00f3d308f170cb1">Middle-clicking the marker will open the note in a new tab.</li>
|
<li>Middle-clicking the marker will open the note in a new tab.</li>
|
||||||
<li data-list-item-id="e26942774d668ca9d49e1cfc797801516">Right-clicking the marker will open a contextual menu (as described below).</li>
|
<li>Right-clicking the marker will open a contextual menu (as described below).</li>
|
||||||
<li
|
<li>If the map is in read-only mode, clicking on a marker will open a
|
||||||
data-list-item-id="edcf8a4a4e252ae3757303fc28c6e549e">If the map is in read-only mode, clicking on a marker will open a
|
|
||||||
<a
|
<a
|
||||||
class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> popup for the corresponding note.</li>
|
class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> popup for the corresponding note.</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -178,24 +166,24 @@
|
|||||||
<p>It's possible to press the right mouse button to display a contextual
|
<p>It's possible to press the right mouse button to display a contextual
|
||||||
menu.</p>
|
menu.</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li data-list-item-id="e529cc53e555215e949135d9998ac4d06">If right-clicking an empty section of the map (not on a marker), it allows
|
<li>If right-clicking an empty section of the map (not on a marker), it allows
|
||||||
to:
|
to:
|
||||||
<ol>
|
<ol>
|
||||||
<li data-list-item-id="e18b27ec2d655ca2e29328c6cc899cccf">Displays the latitude and longitude. Clicking this option will copy them
|
<li>Displays the latitude and longitude. Clicking this option will copy them
|
||||||
to the clipboard.</li>
|
to the clipboard.</li>
|
||||||
<li data-list-item-id="ebecfc3495ce94589ebdaa3479e70a8ea">Open the location using an external application (if the operating system
|
<li>Open the location using an external application (if the operating system
|
||||||
supports it).</li>
|
supports it).</li>
|
||||||
<li data-list-item-id="e1e1e816904064ed2457553dbcf58f373">Adding a new marker at that location.</li>
|
<li>Adding a new marker at that location.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="eadf4de9e1e967bfcbf7274b8e7613c8e">If right-clicking on a marker, it allows to:
|
<li>If right-clicking on a marker, it allows to:
|
||||||
<ol>
|
<ol>
|
||||||
<li data-list-item-id="ebd32992e411b3a755008b1036448f7e9">Displays the latitude and longitude. Clicking this option will copy them
|
<li>Displays the latitude and longitude. Clicking this option will copy them
|
||||||
to the clipboard.</li>
|
to the clipboard.</li>
|
||||||
<li data-list-item-id="e5e8bf64b872e1bf32a53459b05d05263">Open the location using an external application (if the operating system
|
<li>Open the location using an external application (if the operating system
|
||||||
supports it).</li>
|
supports it).</li>
|
||||||
<li data-list-item-id="ec83b4469031db305a1d3f9a4d36b395d">Open the note in a new tab, split or window.</li>
|
<li>Open the note in a new tab, split or window.</li>
|
||||||
<li data-list-item-id="e49f6d549515bec0d6bd8bccf3d34d269">Remove the marker from the map, which will remove the <code>#geolocation</code> attribute
|
<li>Remove the marker from the map, which will remove the <code>#geolocation</code> attribute
|
||||||
of the note. To add it back again, the coordinates have to be manually
|
of the note. To add it back again, the coordinates have to be manually
|
||||||
added back in.</li>
|
added back in.</li>
|
||||||
</ol>
|
</ol>
|
||||||
@@ -215,215 +203,209 @@
|
|||||||
<p>The value of the attribute is made up of the latitude and longitude separated
|
<p>The value of the attribute is made up of the latitude and longitude separated
|
||||||
by a comma.</p>
|
by a comma.</p>
|
||||||
<h3>Adding from Google Maps</h3>
|
<h3>Adding from Google Maps</h3>
|
||||||
<figure class="table">
|
<table>
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td>1</td>
|
||||||
<td>1</td>
|
<td>
|
||||||
<td>
|
<figure class="image image-style-align-center image_resized" style="width:56.84%;">
|
||||||
<figure class="image image-style-align-center image_resized" style="width:56.84%;">
|
<img style="aspect-ratio:732/918;" src="12_Geo Map_image.png"
|
||||||
<img style="aspect-ratio:732/918;" src="12_Geo Map_image.png"
|
width="732" height="918">
|
||||||
width="732" height="918">
|
</figure>
|
||||||
</figure>
|
</td>
|
||||||
</td>
|
<td>Go to Google Maps on the web and look for a desired location, right click
|
||||||
<td>Go to Google Maps on the web and look for a desired location, right click
|
on it and a context menu will show up.
|
||||||
on it and a context menu will show up.
|
<br>
|
||||||
<br>
|
<br>Simply click on the first item displaying the coordinates and they will
|
||||||
<br>Simply click on the first item displaying the coordinates and they will
|
be copied to clipboard.
|
||||||
be copied to clipboard.
|
<br>
|
||||||
<br>
|
<br>Then paste the value inside the text box into the <code>#geolocation</code> attribute
|
||||||
<br>Then paste the value inside the text box into the <code>#geolocation</code> attribute
|
of a child note of the map (don't forget to surround the value with a <code>"</code> character).</td>
|
||||||
of a child note of the map (don't forget to surround the value with a <code>"</code> character).</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>2</td>
|
||||||
<td>2</td>
|
<td>
|
||||||
<td>
|
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
||||||
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
<img style="aspect-ratio:518/84;" src="4_Geo Map_image.png"
|
||||||
<img style="aspect-ratio:518/84;" src="4_Geo Map_image.png"
|
width="518" height="84">
|
||||||
width="518" height="84">
|
</figure>
|
||||||
</figure>
|
</td>
|
||||||
</td>
|
<td>In Trilium, create a child note under the map.</td>
|
||||||
<td>In Trilium, create a child note under the map.</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>3</td>
|
||||||
<td>3</td>
|
<td>
|
||||||
<td>
|
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
||||||
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
<img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png"
|
||||||
<img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png"
|
width="1074" height="276">
|
||||||
width="1074" height="276">
|
</figure>
|
||||||
</figure>
|
</td>
|
||||||
</td>
|
<td>And then go to Owned Attributes and type <code>#geolocation="</code>, then
|
||||||
<td>And then go to Owned Attributes and type <code>#geolocation="</code>, then
|
paste from the clipboard as-is and then add the ending <code>"</code> character.
|
||||||
paste from the clipboard as-is and then add the ending <code>"</code> character.
|
Press Enter to confirm and the map should now be updated to contain the
|
||||||
Press Enter to confirm and the map should now be updated to contain the
|
new note.</td>
|
||||||
new note.</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</figure>
|
|
||||||
<h3>Adding from OpenStreetMap</h3>
|
<h3>Adding from OpenStreetMap</h3>
|
||||||
<p>Similarly to the Google Maps approach:</p>
|
<p>Similarly to the Google Maps approach:</p>
|
||||||
<figure class="table">
|
<table>
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr>
|
<td>1</td>
|
||||||
<td>1</td>
|
<td>
|
||||||
<td>
|
<img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png"
|
||||||
<img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png"
|
width="562" height="454">
|
||||||
width="562" height="454">
|
</td>
|
||||||
</td>
|
<td>Go to any location on openstreetmap.org and right click to bring up the
|
||||||
<td>Go to any location on openstreetmap.org and right click to bring up the
|
context menu. Select the “Show address” item.</td>
|
||||||
context menu. Select the “Show address” item.</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>2</td>
|
||||||
<td>2</td>
|
<td>
|
||||||
<td>
|
<img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png"
|
||||||
<img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png"
|
width="696" height="480">
|
||||||
width="696" height="480">
|
</td>
|
||||||
</td>
|
<td>The address will be visible in the top-left of the screen, in the place
|
||||||
<td>The address will be visible in the top-left of the screen, in the place
|
of the search bar.
|
||||||
of the search bar.
|
<br>
|
||||||
<br>
|
<br>Select the coordinates and copy them into the clipboard.</td>
|
||||||
<br>Select the coordinates and copy them into the clipboard.</td>
|
</tr>
|
||||||
</tr>
|
<tr>
|
||||||
<tr>
|
<td>3</td>
|
||||||
<td>3</td>
|
<td>
|
||||||
<td>
|
<img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png"
|
||||||
<img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png"
|
width="640" height="276">
|
||||||
width="640" height="276">
|
</td>
|
||||||
</td>
|
<td>Simply paste the value inside the text box into the <code>#geolocation</code> attribute
|
||||||
<td>Simply paste the value inside the text box into the <code>#geolocation</code> attribute
|
of a child note of the map and then it should be displayed on the map.</td>
|
||||||
of a child note of the map and then it should be displayed on the map.</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</figure>
|
|
||||||
<h2>Adding GPS tracks (.gpx)</h2>
|
<h2>Adding GPS tracks (.gpx)</h2>
|
||||||
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
||||||
<figure
|
<table>
|
||||||
class="table">
|
<thead>
|
||||||
<table>
|
<tr>
|
||||||
<thead>
|
<th></th>
|
||||||
<tr>
|
<th></th>
|
||||||
<th> </th>
|
<th></th>
|
||||||
<th> </th>
|
</tr>
|
||||||
<th> </th>
|
</thead>
|
||||||
</tr>
|
<tbody>
|
||||||
</thead>
|
<tr>
|
||||||
<tbody>
|
<td>1</td>
|
||||||
<tr>
|
<td>
|
||||||
<td>1</td>
|
<figure class="image image-style-align-center">
|
||||||
<td>
|
<img style="aspect-ratio:226/74;" src="3_Geo Map_image.png"
|
||||||
<figure class="image image-style-align-center">
|
width="226" height="74">
|
||||||
<img style="aspect-ratio:226/74;" src="3_Geo Map_image.png"
|
</figure>
|
||||||
width="226" height="74">
|
</td>
|
||||||
</figure>
|
<td>To add a track, simply drag & drop a .gpx file inside the geo map
|
||||||
</td>
|
in the note tree.</td>
|
||||||
<td>To add a track, simply drag & drop a .gpx file inside the geo map
|
</tr>
|
||||||
in the note tree.</td>
|
<tr>
|
||||||
</tr>
|
<td>2</td>
|
||||||
<tr>
|
<td>
|
||||||
<td>2</td>
|
<figure class="image image-style-align-center">
|
||||||
<td>
|
<img style="aspect-ratio:322/222;" src="14_Geo Map_image.png"
|
||||||
<figure class="image image-style-align-center">
|
width="322" height="222">
|
||||||
<img style="aspect-ratio:322/222;" src="14_Geo Map_image.png"
|
</figure>
|
||||||
width="322" height="222">
|
</td>
|
||||||
</figure>
|
<td>In order for the file to be recognized as a GPS track, it needs to show
|
||||||
</td>
|
up as <code>application/gpx+xml</code> in the <em>File type</em> field.</td>
|
||||||
<td>In order for the file to be recognized as a GPS track, it needs to show
|
</tr>
|
||||||
up as <code>application/gpx+xml</code> in the <em>File type</em> field.</td>
|
<tr>
|
||||||
</tr>
|
<td>3</td>
|
||||||
<tr>
|
<td>
|
||||||
<td>3</td>
|
<figure class="image image-style-align-center">
|
||||||
<td>
|
<img style="aspect-ratio:620/530;" src="6_Geo Map_image.png"
|
||||||
<figure class="image image-style-align-center">
|
width="620" height="530">
|
||||||
<img style="aspect-ratio:620/530;" src="6_Geo Map_image.png"
|
</figure>
|
||||||
width="620" height="530">
|
</td>
|
||||||
</figure>
|
<td>When going back to the map, the track should now be visible.
|
||||||
</td>
|
<br>
|
||||||
<td>When going back to the map, the track should now be visible.
|
<br>The start and end points of the track are indicated by the two blue markers.</td>
|
||||||
<br>
|
</tr>
|
||||||
<br>The start and end points of the track are indicated by the two blue markers.</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
</tbody>
|
<aside class="admonition note">
|
||||||
</table>
|
<p>The starting point of the track will be displayed as a marker, with the
|
||||||
</figure>
|
name of the note underneath. The start marker will also respect the icon
|
||||||
<aside class="admonition note">
|
and the <code>color</code> of the note. The end marker is displayed with
|
||||||
<p>The starting point of the track will be displayed as a marker, with the
|
a distinct icon.</p>
|
||||||
name of the note underneath. The start marker will also respect the icon
|
<p>If the GPX contains waypoints, they will also be displayed. If they have
|
||||||
and the <code>color</code> of the note. The end marker is displayed with
|
a name, it is displayed when hovering over it with the mouse.</p>
|
||||||
a distinct icon.</p>
|
</aside>
|
||||||
<p>If the GPX contains waypoints, they will also be displayed. If they have
|
<h2>Read-only mode</h2>
|
||||||
a name, it is displayed when hovering over it with the mouse.</p>
|
<p>When a map is in read-only all editing features will be disabled such
|
||||||
</aside>
|
as:</p>
|
||||||
<h2>Read-only mode</h2>
|
<ul>
|
||||||
<p>When a map is in read-only all editing features will be disabled such
|
<li>The add button in the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
||||||
as:</p>
|
<li>Dragging markers.</li>
|
||||||
<ul>
|
<li>Editing from the contextual menu (removing locations or adding new items).</li>
|
||||||
<li data-list-item-id="e78bca8f945a953c13efaf287001d0edb">The add button in the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
</ul>
|
||||||
<li
|
<p>To enable read-only mode simply press the <em>Lock</em> icon from the
|
||||||
data-list-item-id="e022d4fc44c13cf5f529ea18729df3897">Dragging markers.</li>
|
<a
|
||||||
<li data-list-item-id="e0d024b930867c0253e98c322d3bea021">Editing from the contextual menu (removing locations or adding new items).</li>
|
class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
|
||||||
</ul>
|
<h2>Configuration</h2>
|
||||||
<p>To enable read-only mode simply press the <em>Lock</em> icon from the
|
<h3>Map Style</h3>
|
||||||
<a
|
<p>The styling of the map can be adjusted in the <em>Collection Properties</em> tab
|
||||||
class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
|
in the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a> or
|
||||||
<h2>Configuration</h2>
|
manually via the <code>#map:style</code> attribute.</p>
|
||||||
<h3>Map Style</h3>
|
<p>The geo map comes with two different types of styles:</p>
|
||||||
<p>The styling of the map can be adjusted in the <em>Collection Properties</em> tab
|
<ul>
|
||||||
in the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a> or
|
<li>Raster styles
|
||||||
manually via the <code>#map:style</code> attribute.</p>
|
<ul>
|
||||||
<p>The geo map comes with two different types of styles:</p>
|
<li>For these styles the map is represented as a grid of images at different
|
||||||
<ul>
|
zoom levels. This is the traditional way OpenStreetMap used to work.</li>
|
||||||
<li data-list-item-id="ee111daf471290ce8f16adb8ff6255e78">Raster styles
|
<li>Zoom is slightly restricted.</li>
|
||||||
<ul>
|
<li>Currently, the only raster theme is the original OpenStreetMap style.</li>
|
||||||
<li data-list-item-id="ebecb845ceda41068a106693c5a1244e3">For these styles the map is represented as a grid of images at different
|
|
||||||
zoom levels. This is the traditional way OpenStreetMap used to work.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e60a827a1f29105ea8321184d4fb4f0a2">Zoom is slightly restricted.</li>
|
|
||||||
<li data-list-item-id="e2eb8282db9496f32beeddd53a72fea2a">Currently, the only raster theme is the original OpenStreetMap style.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="e01494841aec47f71245855fe95ad4adb">Vector styles
|
<li>Vector styles
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="e87e7f879a4c7475769e73f1cfa1bc1b8">Vector styles are not represented as images, but as geometrical shapes.
|
<li>Vector styles are not represented as images, but as geometrical shapes.
|
||||||
This makes the rendering much smoother, especially when zooming and looking
|
This makes the rendering much smoother, especially when zooming and looking
|
||||||
at the building edges, for example.</li>
|
at the building edges, for example.</li>
|
||||||
<li data-list-item-id="ef95260bc6fcb49e63ff708077929b263">The map can be zoomed in much further.</li>
|
<li>The map can be zoomed in much further.</li>
|
||||||
<li data-list-item-id="e339157e4fdacc1b59f6c44cf29711032">These come both in a light and a dark version.</li>
|
<li>These come both in a light and a dark version.</li>
|
||||||
<li data-list-item-id="e9be34d3c44b5dc1ecaa8c856642fc610">The vector styles come from <a href="https://versatiles.org/">VersaTiles</a>,
|
<li>The vector styles come from <a href="https://versatiles.org/">VersaTiles</a>,
|
||||||
a free and open-source project providing map tiles based on OpenStreetMap.</li>
|
a free and open-source project providing map tiles based on OpenStreetMap.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<aside class="admonition note">
|
<aside class="admonition note">
|
||||||
<p>Currently it is not possible to use a custom map style.</p>
|
<p>Currently it is not possible to use a custom map style.</p>
|
||||||
</aside>
|
</aside>
|
||||||
<h3>Scale</h3>
|
<h3>Scale</h3>
|
||||||
<p>Activating this option via the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a> or
|
<p>Activating this option via the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a> or
|
||||||
manually via <code>#map:scale</code> will display an indicator in the bottom-left
|
manually via <code>#map:scale</code> will display an indicator in the bottom-left
|
||||||
of the scale of the map.</p>
|
of the scale of the map.</p>
|
||||||
<h2>Troubleshooting</h2>
|
<h2>Troubleshooting</h2>
|
||||||
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
|
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
|
||||||
<img style="aspect-ratio:678/499;" src="13_Geo Map_image.png"
|
<img style="aspect-ratio:678/499;" src="13_Geo Map_image.png"
|
||||||
width="678" height="499">
|
width="678" height="499">
|
||||||
</figure>
|
</figure>
|
||||||
<h3>Grid-like artifacts on the map</h3>
|
|
||||||
<p>This occurs if the application is not at 100% zoom which causes the pixels
|
<h3>Grid-like artifacts on the map</h3>
|
||||||
of the map to not render correctly due to fractional scaling. The only
|
<p>This occurs if the application is not at 100% zoom which causes the pixels
|
||||||
possible solution is to set the UI zoom at 100% (default keyboard shortcut
|
of the map to not render correctly due to fractional scaling. The only
|
||||||
is <kbd>Ctrl</kbd>+<kbd>0</kbd>).</p>
|
possible solution is to set the UI zoom at 100% (default keyboard shortcut
|
||||||
|
is <kbd>Ctrl</kbd>+<kbd>0</kbd>).</p>
|
||||||
169
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html
generated
vendored
@@ -1,5 +1,5 @@
|
|||||||
<figure class="image">
|
<figure class="image">
|
||||||
<img style="aspect-ratio:918/248;" src="2_Kanban Board_image.png"
|
<img style="aspect-ratio:918/248;" src="Kanban Board_image.png"
|
||||||
width="918" height="248">
|
width="918" height="248">
|
||||||
</figure>
|
</figure>
|
||||||
<p>The Board view presents sub-notes in columns for a Kanban-like experience.
|
<p>The Board view presents sub-notes in columns for a Kanban-like experience.
|
||||||
@@ -15,168 +15,83 @@
|
|||||||
<h2>Interaction</h2>
|
<h2>Interaction</h2>
|
||||||
<h3>Working with columns</h3>
|
<h3>Working with columns</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="ed78b9e2ac3200e097ae29e2d528c5e89">Create a new column by pressing <em>Add Column</em> near the last column.
|
<li>Create a new column by pressing <em>Add Column</em> near the last column.
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="ed5ddfffe11d0b328a8072e52be9c7492">Once pressed, a text box will be displayed to set the name of the column.
|
<li>Once pressed, a text box will be displayed to set the name of the column.
|
||||||
Press <kbd>Enter</kbd> to confirm, or <kbd>Escape</kbd> to dismiss.</li>
|
Press <kbd>Enter</kbd> to confirm, or <kbd>Escape</kbd> to dismiss.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="eeecd7ac7b32b1c29e7088ad172411862">To reorder a column, simply hold the mouse over the title and drag it
|
<li>To reorder a column, simply hold the mouse over the title and drag it
|
||||||
to the desired position.</li>
|
to the desired position.</li>
|
||||||
<li data-list-item-id="e6bc6a4b20236a9d2c382a9564eef528f">To delete a column, right click on its title and select <em>Delete column</em>.</li>
|
<li>To delete a column, right click on its title and select <em>Delete column</em>.</li>
|
||||||
<li
|
<li>To rename a column, click on the note title.
|
||||||
data-list-item-id="ea3bd7adf8521c4c2fb2f3ef0cf4def28">To rename a column, click on the note title.
|
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="eb1b0ee1c933e5ca2f4cb57a05b5c07eb">Press Enter to confirm.</li>
|
<li>Press Enter to confirm.</li>
|
||||||
<li data-list-item-id="eabddc4dae9e10189e9c7dc087496e846">Upon renaming a column, the corresponding status attribute of all its
|
<li>Upon renaming a column, the corresponding status attribute of all its
|
||||||
notes will be changed in bulk.</li>
|
notes will be changed in bulk.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="efc70bbc5f0d1dc25e919a5b9e41a54e2">If there are many columns, use the mouse wheel to scroll.</li>
|
<li>If there are many columns, use the mouse wheel to scroll.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Working with notes</h3>
|
<h3>Working with notes</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="ee1000666f3c92f251a0262d5e2e30cbf">Create a new note in any column by pressing <em>New item</em>
|
<li>Create a new note in any column by pressing <em>New item</em>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="ea3d86963b86f6820335c3994e3c7a00c">Enter the name of the note and press <kbd>Enter</kbd> or click away. To
|
<li>Enter the name of the note and press <kbd>Enter</kbd> or click away. To
|
||||||
dismiss the creation of a new note, simply press <kbd>Escape</kbd> or leave
|
dismiss the creation of a new note, simply press <kbd>Escape</kbd> or leave
|
||||||
the name empty.</li>
|
the name empty.</li>
|
||||||
<li data-list-item-id="ee8280e96edd38a1bc247ee34ea514c88">Once created, the new note will have an attribute (<code>status</code> label
|
<li>Once created, the new note will have an attribute (<code>status</code> label
|
||||||
by default) set to the name of the column.</li>
|
by default) set to the name of the column.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="eb03f96167236a14a55a7538b588bed05">To open the note, simply click on it.</li>
|
<li>To open the note, simply click on it.</li>
|
||||||
<li data-list-item-id="e29a6c1186c096f5ec6316edd9d44e626">To change the title of the note directly from the board, hover the mouse
|
<li>To change the title of the note directly from the board, hover the mouse
|
||||||
over its card and press the edit button on the right.</li>
|
over its card and press the edit button on the right.</li>
|
||||||
<li data-list-item-id="e201e4e28bad20f532c5e2c0ada6398f7">To change the state of a note, simply drag a note from one column to the
|
<li>To change the state of a note, simply drag a note from one column to the
|
||||||
other to change its state.</li>
|
other to change its state.</li>
|
||||||
<li data-list-item-id="e98766e577d03db3dfd8acb850ddf8268">The order of the notes in each column corresponds to their position in
|
<li>The order of the notes in each column corresponds to their position in
|
||||||
the tree.
|
the tree.
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="e905430d6d0a5e51e2596611ccb3a0f2a">It's possible to reorder notes simply by dragging them to the desired
|
<li>It's possible to reorder notes simply by dragging them to the desired
|
||||||
position within the same columns.</li>
|
position within the same columns.</li>
|
||||||
<li data-list-item-id="e66108d33aee9387b325a554e12a85a73">It's also possible to drag notes across columns, at the desired position.</li>
|
<li>It's also possible to drag notes across columns, at the desired position.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="ebb5b8df8459f8b32d2e1712d268b63df">For more options, right click on a note to display a context menu with
|
<li>For more options, right click on a note to display a context menu with
|
||||||
the following options:
|
the following options:
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="efe7840fa83c41d4a23759927bbe824d2">Open the note in a new tab/split/window or quick edit.</li>
|
<li>Open the note in a new tab/split/window or quick edit.</li>
|
||||||
<li data-list-item-id="ea44a2cd0895413620eff0720524e9938">Move the note to any column.</li>
|
<li>Move the note to any column.</li>
|
||||||
<li data-list-item-id="e505699fc18903f73f96ddfe29a4cc694">Insert a new note above/below the current one.</li>
|
<li>Insert a new note above/below the current one.</li>
|
||||||
<li data-list-item-id="e153bf42c4f955bb1421d586f9e7b5498">Archive/unarchive the current note.</li>
|
<li>Archive/unarchive the current note.</li>
|
||||||
<li data-list-item-id="e3c0a0113e5bb4a69a9dfe90862f5ee1e">Delete the current note.</li>
|
<li>Delete the current note.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li data-list-item-id="e1d6b1303eb3567b51fec1174993dcb71">If there are many notes within the column, move the mouse over the column
|
<li>If there are many notes within the column, move the mouse over the column
|
||||||
and use the mouse wheel to scroll.</li>
|
and use the mouse wheel to scroll.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Working with the note tree</h3>
|
<h2>Keyboard interaction</h2>
|
||||||
<p>It's also possible to add items on the board using the <a class="reference-link"
|
|
||||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
|
||||||
<ol>
|
|
||||||
<li data-list-item-id="ef31c436ab587f75725f224880891c063">Select the desired note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e4e3b01f6a772fe9f5ea2bbb91f2b1f6d">Hold the mouse on the note and drag it to the to the desired column.</li>
|
|
||||||
</ol>
|
|
||||||
<p>This works for:</p>
|
|
||||||
<ul>
|
|
||||||
<li data-list-item-id="e4b395d82d6e6220afbacdca93b80bea3">Notes that are not children of the board, case in which a <a href="#root/_help_IakOLONlIfGI">clone</a> will
|
|
||||||
be created.</li>
|
|
||||||
<li data-list-item-id="ef1eeff2b993c6285ed33440520254f7f">Notes that are children of the board, but not yet assigned on the board.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="ee4610f6d74645e030570bfa9420dad61">Notes that are children of the board, case in which they will be moved
|
|
||||||
to the new column.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Keyboard interaction</h3>
|
|
||||||
<p>The board view has mild support for keyboard-based navigation:</p>
|
<p>The board view has mild support for keyboard-based navigation:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li data-list-item-id="ef209d621d05adddb8648e11c1aff106a">Use <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> to navigate between
|
<li>Use <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> to navigate between
|
||||||
column titles, notes and the “New item” button for each of the columns,
|
column titles, notes and the “New item” button for each of the columns,
|
||||||
in sequential order.</li>
|
in sequential order.</li>
|
||||||
<li data-list-item-id="e48cffbf4ee1a7452c1e01277c8dd2cbf">To rename a column or a note, press <kbd>F2</kbd> while it is focused.</li>
|
<li>To rename a column or a note, press <kbd>F2</kbd> while it is focused.</li>
|
||||||
<li
|
<li>To open a specific note or create a new item, press <kbd>Enter</kbd> while
|
||||||
data-list-item-id="e02a6fb1c98d4e4b75e9e4245cf7cf6b1">To open a specific note or create a new item, press <kbd>Enter</kbd> while
|
|
||||||
it is focused.</li>
|
it is focused.</li>
|
||||||
<li data-list-item-id="eec6f2285fa4d8e95f6a3d40cf51e3d69">To dismiss a rename of a note or a column, press <kbd>Escape</kbd>.</li>
|
<li>To dismiss a rename of a note or a column, press <kbd>Escape</kbd>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Configuration</h2>
|
<h2>Configuration</h2>
|
||||||
<h3>Displaying custom attributes</h3>
|
<h3>Grouping by another attribute</h3>
|
||||||
<figure class="image image-style-align-center">
|
|
||||||
<img style="aspect-ratio:531/485;" src="Kanban Board_image.png"
|
|
||||||
width="531" height="485">
|
|
||||||
</figure>
|
|
||||||
<p>Note attributes can be displayed on the board to enhance it with custom
|
|
||||||
information such as adding a Due date for your tasks.</p>
|
|
||||||
<p>This feature works exclusively via attribute definitions (<a class="reference-link"
|
|
||||||
href="#root/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>). The easiest
|
|
||||||
way to add these is:</p>
|
|
||||||
<ol>
|
|
||||||
<li data-list-item-id="eb2220bf769a14b4d3c3763ddb412215a">Go to board note.</li>
|
|
||||||
<li data-list-item-id="ef2b3775a79f59cc97c77a69ca78a345e">In the ribbon select <em>Owned Attributes</em> → plus button → <em>Add new label/relation definition</em>.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e2565a4dfea04fba704b37927e6252ce4">Configure the attribute as desired.</li>
|
|
||||||
<li data-list-item-id="ee18605ea5b4a07f7b3f5529ba00b2a57">Check <em>Inheritable</em> to make it applicable to child notes automatically.</li>
|
|
||||||
</ol>
|
|
||||||
<p>After creating the attribute, click on a note and fill in the promoted
|
|
||||||
attributes which should then reflect inside the board.</p>
|
|
||||||
<p>Of note:</p>
|
|
||||||
<ul>
|
|
||||||
<li data-list-item-id="e5a19ed95dbafe9a850690bea201deba1">Both promoted and non-promoted attribute definitions are supported. The
|
|
||||||
only difference is that non-promoted attributes don't have an “Alias” for
|
|
||||||
assigning a custom name.</li>
|
|
||||||
<li data-list-item-id="e7ec69af7257cfe86e14bb803910872b4">Both “Single value” and “Multi value” attributes are supported. In case
|
|
||||||
of multi-value, a badge is displayed for every instance of the attribute.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e810a5dbe56db7e59f9091e45c545a611">All label types are supported, including dates, booleans and URLs.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e49414fe2d9d206692332cd7d7ff1d4c0">Relation attributes are also supported as well, showing a link with the
|
|
||||||
target note title and icon.</li>
|
|
||||||
<li data-list-item-id="e1c3fb07ae71ca498c04613917eca93d4">Currently, it's not possible to adjust which promoted attributes are displayed,
|
|
||||||
since all promoted attributes will be displayed (except the <code>board:groupBy</code> one).
|
|
||||||
There are plans to improve upon this being able to hide promoted attributes
|
|
||||||
individually.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Grouping by another label</h3>
|
|
||||||
<p>By default, the label used to group the notes is <code>#status</code>.
|
<p>By default, the label used to group the notes is <code>#status</code>.
|
||||||
It is possible to use a different label if needed by defining a label named <code>#board:groupBy</code> with
|
It is possible to use a different label if needed by defining a label named <code>#board:groupBy</code> with
|
||||||
the value being the attribute to use (with or without <code>#</code> attribute
|
the value being the attribute to use (without <code>#</code> attribute prefix).</p>
|
||||||
prefix).</p>
|
<aside
|
||||||
<h3>Grouping by relations</h3>
|
class="admonition note">
|
||||||
<figure class="image image-style-align-right">
|
<p>It's currently not possible to set a relation as the grouping criteria.
|
||||||
<img style="aspect-ratio:535/245;" src="1_Kanban Board_image.png"
|
There are plans to add support for it.</p>
|
||||||
width="535" height="245">
|
</aside>
|
||||||
</figure>
|
<h2>Limitations</h2>
|
||||||
<p>A more advanced use-case is grouping by <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_Cq5X6iKQop6R">Relations</a>.</p>
|
<ul>
|
||||||
<p>During this mode:</p>
|
<li>It is not possible yet to use group by a relation, only by label.</li>
|
||||||
<ul>
|
</ul>
|
||||||
<li data-list-item-id="e597b9ecddfae34bb858559de09ee1f6a">The columns represent the <em>target notes</em> of a relation.</li>
|
|
||||||
<li data-list-item-id="eb59f890b2223d7f3f43e7ebae747002d">When creating a new column, a note is selected instead of a column name.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e566f9ab556f5c47b00ce0854def37bea">The column icon will match the target note.</li>
|
|
||||||
<li data-list-item-id="e43d3786e7e6590a003f16f3eb437bba5">Moving notes between columns will change its relation.</li>
|
|
||||||
<li data-list-item-id="e5c2a414d0c80f0d1a67066078c7a4f79">Renaming an existing column will change the target note of all the notes
|
|
||||||
in that column.</li>
|
|
||||||
</ul>
|
|
||||||
<p>Using relations instead of labels has some benefits:</p>
|
|
||||||
<ul>
|
|
||||||
<li data-list-item-id="e580344f5638c09e80f566dc789db656a">The status/grouping of the notes is visible outside the Kanban board,
|
|
||||||
for example on the <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_bdUJEHsAPYQR">Note Map</a>.</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="e1d59fe7873950babd8e1f3dbcb913e1a">Columns can have icons.</li>
|
|
||||||
<li data-list-item-id="e9933195c1f7708326f434321482cd917">Renaming columns is less intensive since it simply involves changing the
|
|
||||||
note title of the target note instead of having to do a bulk rename.</li>
|
|
||||||
</ul>
|
|
||||||
<p>To do so:</p>
|
|
||||||
<ol>
|
|
||||||
<li data-list-item-id="e66855292cf2639a13ecd642482681653">First, create a Kanban board from scratch and not a template:</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="eaf16b94767674ca24de65ae102b84fc7">Assign <code>#viewType=board #hidePromotedAttributes</code> to emulate the
|
|
||||||
default template.</li>
|
|
||||||
<li data-list-item-id="e861d2859085e0dba83a44946fdc67c32">Set <code>#board:groupBy</code> to the name of a relation to group by, <strong>including the </strong><code><strong>~</strong></code><strong> prefix</strong> (e.g. <code>~status</code>).</li>
|
|
||||||
<li
|
|
||||||
data-list-item-id="efd300f3e766d485e28b7c8fd0a73364c">
|
|
||||||
<p>Optionally, use <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> for
|
|
||||||
easy status change within the note:</p><pre><code class="language-text-x-trilium-auto">#relation:status(inheritable)="promoted,alias=Status,single"</code></pre>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board_image.png
generated
vendored
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 18 KiB |
@@ -258,9 +258,7 @@
|
|||||||
"jump-to-note-title": "跳转至...",
|
"jump-to-note-title": "跳转至...",
|
||||||
"llm-chat-title": "与笔记聊天",
|
"llm-chat-title": "与笔记聊天",
|
||||||
"ai-llm-title": "AI/LLM",
|
"ai-llm-title": "AI/LLM",
|
||||||
"inbox-title": "收件箱",
|
"inbox-title": "收件箱"
|
||||||
"command-palette": "打开命令面板",
|
|
||||||
"zen-mode": "禅模式"
|
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"new-note": "新建笔记",
|
"new-note": "新建笔记",
|
||||||
|
|||||||
@@ -43,26 +43,6 @@
|
|||||||
},
|
},
|
||||||
"hidden-subtree": {
|
"hidden-subtree": {
|
||||||
"zen-mode": "젠 모드",
|
"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": "고급"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,9 +355,7 @@
|
|||||||
"visible-launchers-title": "可見啟動器",
|
"visible-launchers-title": "可見啟動器",
|
||||||
"user-guide": "用戶說明",
|
"user-guide": "用戶說明",
|
||||||
"localization": "語言和區域",
|
"localization": "語言和區域",
|
||||||
"inbox-title": "收件匣",
|
"inbox-title": "收件匣"
|
||||||
"command-palette": "打開命令面板",
|
|
||||||
"zen-mode": "禪模式"
|
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"new-note": "新增筆記",
|
"new-note": "新增筆記",
|
||||||
|
|||||||
@@ -91,12 +91,9 @@ function validateUtcDateTime(str: string | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
LOCAL_DATETIME_FORMAT,
|
|
||||||
UTC_DATETIME_FORMAT,
|
|
||||||
utcNowDateTime,
|
utcNowDateTime,
|
||||||
localNowDateTime,
|
localNowDateTime,
|
||||||
localNowDate,
|
localNowDate,
|
||||||
|
|
||||||
utcDateStr,
|
utcDateStr,
|
||||||
utcDateTimeStr,
|
utcDateTimeStr,
|
||||||
parseDateTime,
|
parseDateTime,
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
import sax from "sax";
|
import sax from "sax";
|
||||||
import stream from "stream";
|
import stream from "stream";
|
||||||
import { Throttle } from "stream-throttle";
|
import { Throttle } from "stream-throttle";
|
||||||
import log from "../log.js";
|
import log from "../log.js";
|
||||||
import { md5, escapeHtml, fromBase64 } from "../utils.js";
|
import { md5, escapeHtml, fromBase64 } from "../utils.js";
|
||||||
import date_utils from "../date_utils.js";
|
|
||||||
import sql from "../sql.js";
|
import sql from "../sql.js";
|
||||||
import noteService from "../notes.js";
|
import noteService from "../notes.js";
|
||||||
import imageService from "../image.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) {
|
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
|
// 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(
|
sql.execute(
|
||||||
`
|
`
|
||||||
UPDATE notes
|
UPDATE notes
|
||||||
@@ -247,7 +243,7 @@ function importEnex(taskContext: TaskContext<"importNotes">, file: File, parentN
|
|||||||
dateModified = ?,
|
dateModified = ?,
|
||||||
utcDateModified = ?
|
utcDateModified = ?
|
||||||
WHERE noteId = ?`,
|
WHERE noteId = ?`,
|
||||||
[dateCreated, utcDateCreated, dateModified, utcDateModified, note.noteId]
|
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, note.noteId]
|
||||||
);
|
);
|
||||||
|
|
||||||
sql.execute(
|
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 };
|
export default { importEnex };
|
||||||
|
|||||||
@@ -113,16 +113,7 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
const normalizedFlatText = normalizeSearchText(flatText);
|
const normalizedFlatText = normalizeSearchText(flatText);
|
||||||
|
|
||||||
// Check if =phrase appears in flatText (indicates attribute value match)
|
// Check if =phrase appears in flatText (indicates attribute value match)
|
||||||
// For single words, use word-boundary matching to avoid substring matches
|
matches = normalizedFlatText.includes(`=${normalizedPhrase}`);
|
||||||
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)) {
|
if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) {
|
||||||
resultNoteSet.add(noteFromBecca);
|
resultNoteSet.add(noteFromBecca);
|
||||||
@@ -133,17 +124,6 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
return resultNoteSet;
|
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
|
* Checks if content contains the exact word (with word boundaries) or exact phrase
|
||||||
* This is case-insensitive since content and token are already normalized
|
* This is case-insensitive since content and token are already normalized
|
||||||
@@ -159,8 +139,9 @@ class NoteContentFulltextExp extends Expression {
|
|||||||
return normalizedContent.includes(normalizedToken);
|
return normalizedContent.includes(normalizedToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For single words, use exact word matching to avoid substring matches
|
// For single words, split content into words and check for exact match
|
||||||
return this.exactWordMatch(normalizedToken, normalizedContent);
|
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
|
// Join tokens with single space to form the phrase
|
||||||
const phrase = normalizedTokens.join(" ");
|
const phrase = normalizedTokens.join(" ");
|
||||||
|
|
||||||
// For single-word phrases, use word-boundary matching to avoid substring matches
|
// Check if the phrase appears as a substring (consecutive words)
|
||||||
// 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)) {
|
if (normalizedContent.includes(phrase)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import BBranch from "../becca/entities/bbranch.js";
|
|||||||
import BNote from "../becca/entities/bnote.js";
|
import BNote from "../becca/entities/bnote.js";
|
||||||
import tree from "./tree.js";
|
import tree from "./tree.js";
|
||||||
import cls from "./cls.js";
|
import cls from "./cls.js";
|
||||||
import { buildNote } from "../test/becca_easy_mocking.js";
|
|
||||||
|
|
||||||
describe("Tree", () => {
|
describe("Tree", () => {
|
||||||
let rootNote!: NoteBuilder;
|
let rootNote!: NoteBuilder;
|
||||||
@@ -74,43 +73,4 @@ describe("Tree", () => {
|
|||||||
expect(order).toStrictEqual(expectedOrder);
|
expect(order).toStrictEqual(expectedOrder);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("pins to the top and bottom", () => {
|
|
||||||
const note = buildNote({
|
|
||||||
children: [
|
|
||||||
{ title: "bottom", "#bottom": "" },
|
|
||||||
{ title: "5" },
|
|
||||||
{ title: "3" },
|
|
||||||
{ title: "2" },
|
|
||||||
{ title: "1" },
|
|
||||||
{ title: "top", "#top": "" }
|
|
||||||
],
|
|
||||||
"#sorted": ""
|
|
||||||
});
|
|
||||||
cls.init(() => {
|
|
||||||
tree.sortNotesIfNeeded(note.noteId);
|
|
||||||
});
|
|
||||||
const orderedTitles = note.children.map((child) => child.title);
|
|
||||||
expect(orderedTitles).toStrictEqual([ "top", "1", "2", "3", "5", "bottom" ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("pins to the top and bottom in reverse order", () => {
|
|
||||||
const note = buildNote({
|
|
||||||
children: [
|
|
||||||
{ title: "bottom", "#bottom": "" },
|
|
||||||
{ title: "1" },
|
|
||||||
{ title: "2" },
|
|
||||||
{ title: "3" },
|
|
||||||
{ title: "5" },
|
|
||||||
{ title: "top", "#top": "" }
|
|
||||||
],
|
|
||||||
"#sorted": "",
|
|
||||||
"#sortDirection": "desc"
|
|
||||||
});
|
|
||||||
cls.init(() => {
|
|
||||||
tree.sortNotesIfNeeded(note.noteId);
|
|
||||||
});
|
|
||||||
const orderedTitles = note.children.map((child) => child.title);
|
|
||||||
expect(orderedTitles).toStrictEqual([ "top", "5", "3", "2", "1", "bottom" ]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -136,8 +136,8 @@ function sortNotes(parentNoteId: string, customSortBy: string = "title", reverse
|
|||||||
const topBEl = fetchValue(b, "top");
|
const topBEl = fetchValue(b, "top");
|
||||||
|
|
||||||
if (topAEl !== topBEl) {
|
if (topAEl !== topBEl) {
|
||||||
if (topAEl === null) return reverse ? -1 : 1;
|
if (topAEl === null) return 1;
|
||||||
if (topBEl === null) return reverse ? 1 : -1;
|
if (topBEl === null) return -1;
|
||||||
|
|
||||||
// since "top" should not be reversible, we'll reverse it once more to nullify this effect
|
// since "top" should not be reversible, we'll reverse it once more to nullify this effect
|
||||||
return compare(topAEl, topBEl) * (reverse ? -1 : 1);
|
return compare(topAEl, topBEl) * (reverse ? -1 : 1);
|
||||||
@@ -147,8 +147,8 @@ function sortNotes(parentNoteId: string, customSortBy: string = "title", reverse
|
|||||||
const bottomBEl = fetchValue(b, "bottom");
|
const bottomBEl = fetchValue(b, "bottom");
|
||||||
|
|
||||||
if (bottomAEl !== bottomBEl) {
|
if (bottomAEl !== bottomBEl) {
|
||||||
if (bottomAEl === null) return reverse ? 1 : -1;
|
if (bottomAEl === null) return -1;
|
||||||
if (bottomBEl === null) return reverse ? -1 : 1;
|
if (bottomBEl === null) return 1;
|
||||||
|
|
||||||
// since "bottom" should not be reversible, we'll reverse it once more to nullify this effect
|
// since "bottom" should not be reversible, we'll reverse it once more to nullify this effect
|
||||||
return compare(bottomBEl, bottomAEl) * (reverse ? -1 : 1);
|
return compare(bottomBEl, bottomAEl) * (reverse ? -1 : 1);
|
||||||
|
|||||||
@@ -342,11 +342,8 @@ async function registerGlobalShortcuts() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.actionName === "toggleTray") {
|
// window may be hidden / not in focus
|
||||||
targetWindow.focus();
|
showAndFocusWindow(targetWindow);
|
||||||
} else {
|
|
||||||
showAndFocusWindow(targetWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetWindow.webContents.send("globalShortcut", action.actionName);
|
targetWindow.webContents.send("globalShortcut", action.actionName);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"preact": "10.27.2",
|
"preact": "10.27.2",
|
||||||
"preact-iso": "2.11.0",
|
"preact-iso": "2.11.0",
|
||||||
"preact-render-to-string": "6.6.3",
|
"preact-render-to-string": "6.6.3",
|
||||||
"react-i18next": "16.3.3"
|
"react-i18next": "16.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@preact/preset-vite": "2.10.2",
|
"@preact/preset-vite": "2.10.2",
|
||||||
|
|||||||
@@ -1,20 +1,6 @@
|
|||||||
{
|
{
|
||||||
"hero_section": {
|
"hero_section": {
|
||||||
"github": "깃허브",
|
"github": "깃허브",
|
||||||
"dockerhub": "도커 허브",
|
"dockerhub": "도커 허브"
|
||||||
"get_started": "시작하기",
|
|
||||||
"title": "생각을 정리하고, 개인 지식 기반을 구축하세요.",
|
|
||||||
"subtitle": "Trilium은 개인 지식 베이스를 정리하고 노트를 작성하는 오픈소스 솔루션입니다. 데스크톱에서 로컬로 사용하거나, 자체 호스팅 서버와 동기화하여 어디에서나 노트를 보관할 수 있습니다.",
|
|
||||||
"screenshot_alt": "Trilium Notes 데스크톱 애플리케이션의 스크린샷"
|
|
||||||
},
|
|
||||||
"get-started": {
|
|
||||||
"title": "시작하기",
|
|
||||||
"desktop_title": "데스크탑 애플리케이션 내려받기 (v{{version}})",
|
|
||||||
"older_releases": "오래된 릴리즈 보기",
|
|
||||||
"architecture": "아키텍쳐:",
|
|
||||||
"server_title": "여러 기기에서 액세스할 수 있는 서버 설정"
|
|
||||||
},
|
|
||||||
"download_now": {
|
|
||||||
"text": "지금 내려받기 "
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.",
|
"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_title": "Mapy myśli",
|
||||||
"mindmap_description": "Organizuj wizualnie swoje myśli albo przeprowadź sesję burzy mózgów.",
|
"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"
|
"title": "Wiele sposobów przedstawienia Twoich informacji"
|
||||||
},
|
},
|
||||||
"extensibility_benefits": {
|
"extensibility_benefits": {
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
"title": "Inne sposoby wsparcia",
|
"title": "Inne sposoby wsparcia",
|
||||||
"way_translate": "Przetłumacz aplikacja na swój natywny język przez <Link>Weblate</Link>.",
|
"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_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_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."
|
"way_market": "Powiedz o nas swoim znajomym, na blogu albo na social mediach."
|
||||||
},
|
},
|
||||||
|
|||||||
32
docs/Developer Guide/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 2,
|
"formatVersion": 2,
|
||||||
"appVersion": "0.99.5",
|
"appVersion": "0.99.4",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"isClone": false,
|
"isClone": false,
|
||||||
@@ -110,13 +110,6 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"mime": "text/html",
|
"mime": "text/html",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "4nwtTJyjNDKd",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 10
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
"name": "iconClass",
|
"name": "iconClass",
|
||||||
@@ -124,6 +117,13 @@
|
|||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "relation",
|
||||||
|
"name": "internalLink",
|
||||||
|
"value": "4nwtTJyjNDKd",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 30
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
"name": "shareAlias",
|
"name": "shareAlias",
|
||||||
@@ -1260,19 +1260,12 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"mime": "text/html",
|
"mime": "text/html",
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "ccIoz7nqgDRK",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 10
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "zdQzavvHDl1k",
|
"value": "zdQzavvHDl1k",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@@ -1287,6 +1280,13 @@
|
|||||||
"value": "releasing",
|
"value": "releasing",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "relation",
|
||||||
|
"name": "internalLink",
|
||||||
|
"value": "ccIoz7nqgDRK",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 50
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/cJTTOrI5C1jn/Documentation_image.png" width="205" height="162">
|
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/E6pFkO6VwPFI/Documentation_image.png" width="205" height="162">
|
||||||
|
|
||||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||||
|
|||||||
51
docs/README-ko.md
vendored
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
# 트릴리움 노트
|
# Trilium Notes
|
||||||
|
|
||||||

|

|
||||||
\
|
\
|
||||||
@@ -20,32 +20,36 @@ releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\
|
|||||||
[](https://hosted.weblate.org/engage/trilium/)
|
status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
|
||||||
|
|
||||||
[영어](./README.md) | [중국어 (간체)](./docs/README-ZH_CN.md) | [중국어
|
[English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) |
|
||||||
(번체)](./docs/README-ZH_TW.md) | [러시아어](./docs/README-ru.md) |
|
[Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README-ru.md)
|
||||||
[일본어](./docs/README-ja.md) | [이탈리아어](./docs/README-it.md) |
|
| [Japanese](./docs/README-ja.md) | [Italian](./docs/README-it.md) |
|
||||||
[스페인어](./docs/README-es.md)
|
[Spanish](./docs/README-es.md)
|
||||||
|
|
||||||
Trilium Notes는 대규모 개인 지식 기반 구축에 중점을 둔 무료 오픈 소스 크로스 플랫폼 계층적 메모 작성 애플리케이션입니다.
|
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
|
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for
|
||||||
quick overview:
|
quick overview:
|
||||||
|
|
||||||
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
||||||
|
|
||||||
## ⏬ 내려받기
|
## ⏬ Download
|
||||||
- [최신 릴리스](https://github.com/TriliumNext/Trilium/releases/latest) – 안정된 버전으로
|
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) –
|
||||||
대부분의 사용자에게 권장됩니다.
|
stable version, recommended for most users.
|
||||||
- [야간 빌드](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – 불안정한 개발
|
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) –
|
||||||
버전으로, 최신 기능과 수정 사항이 매일 업데이트됩니다.
|
unstable development version, updated daily with the latest features and
|
||||||
|
fixes.
|
||||||
|
|
||||||
## 📚 문서
|
## 📚 Documentation
|
||||||
|
|
||||||
**[docs.triliumnotes.org](https://docs.triliumnotes.org/)에서 포괄적인 문서를 방문하세요**
|
**Visit our comprehensive documentation at
|
||||||
|
[docs.triliumnotes.org](https://docs.triliumnotes.org/)**
|
||||||
|
|
||||||
저희 문서는 다양한 형식으로 제공됩니다.
|
Our documentation is available in multiple formats:
|
||||||
- **온라인 문서**: [docs.triliumnotes.org](https://docs.triliumnotes.org/)에서 모든 문서를
|
- **Online Documentation**: Browse the full documentation at
|
||||||
보여줍니다
|
[docs.triliumnotes.org](https://docs.triliumnotes.org/)
|
||||||
- **도움말**: 트릴리움 어플리케이션에서 `F1` 버튼을 눌러 같은 문서를 직접 볼 수 있습니다
|
- **In-App Help**: Press `F1` within Trilium to access the same documentation
|
||||||
|
directly in the application
|
||||||
- **GitHub**: Navigate through the [User
|
- **GitHub**: Navigate through the [User
|
||||||
Guide](./docs/User%20Guide/User%20Guide/) in this repository
|
Guide](./docs/User%20Guide/User%20Guide/) in this repository
|
||||||
|
|
||||||
@@ -129,7 +133,7 @@ related goodies:
|
|||||||
themes, scripts, plugins and more.
|
themes, scripts, plugins and more.
|
||||||
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
||||||
|
|
||||||
## ❓왜 TriliumNext일까?
|
## ❓Why TriliumNext?
|
||||||
|
|
||||||
The original Trilium developer ([Zadam](https://github.com/zadam)) has
|
The original Trilium developer ([Zadam](https://github.com/zadam)) has
|
||||||
graciously given the Trilium repository to the community project which resides
|
graciously given the Trilium repository to the community project which resides
|
||||||
@@ -165,7 +169,7 @@ features, suggestions, or issues you may have!
|
|||||||
|
|
||||||
## 🏗 Installation
|
## 🏗 Installation
|
||||||
|
|
||||||
### 윈도우 / 맥OS
|
### Windows / MacOS
|
||||||
|
|
||||||
Download the binary release for your platform from the [latest release
|
Download the binary release for your platform from the [latest release
|
||||||
page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package
|
page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package
|
||||||
@@ -193,7 +197,7 @@ interface (which is almost identical to the desktop app).
|
|||||||
Currently only the latest versions of Chrome & Firefox are supported (and
|
Currently only the latest versions of Chrome & Firefox are supported (and
|
||||||
tested).
|
tested).
|
||||||
|
|
||||||
### 모바일
|
### Mobile
|
||||||
|
|
||||||
To use TriliumNext on a mobile device, you can use a mobile web browser to
|
To use TriliumNext on a mobile device, you can use a mobile web browser to
|
||||||
access the mobile interface of a server installation (see below).
|
access the mobile interface of a server installation (see below).
|
||||||
@@ -208,7 +212,7 @@ repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to
|
|||||||
disable automatic updates on your server installation (see below) when using
|
disable automatic updates on your server installation (see below) when using
|
||||||
TriliumDroid since the sync version must match between Trilium and TriliumDroid.
|
TriliumDroid since the sync version must match between Trilium and TriliumDroid.
|
||||||
|
|
||||||
### 서버
|
### Server
|
||||||
|
|
||||||
To install TriliumNext on your own server (including via Docker from
|
To install TriliumNext on your own server (including via Docker from
|
||||||
[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server
|
[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server
|
||||||
@@ -259,9 +263,8 @@ pnpm install
|
|||||||
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
|
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
|
||||||
```
|
```
|
||||||
|
|
||||||
자세한 내용은 [development
|
For more details, see the [development
|
||||||
docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide)를
|
docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
|
||||||
참고하세요.
|
|
||||||
|
|
||||||
### Developer Documentation
|
### Developer Documentation
|
||||||
|
|
||||||
|
|||||||
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
|
Trilium Notes is a free and open-source, cross-platform hierarchical note taking
|
||||||
application with focus on building large personal knowledge bases.
|
application with focus on building large personal knowledge bases.
|
||||||
|
|
||||||
Se [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) för en
|
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for
|
||||||
snabb överblick:
|
quick overview:
|
||||||
|
|
||||||
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
<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) –
|
- [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) –
|
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) –
|
||||||
unstable development version, updated daily with the latest features and
|
unstable development version, updated daily with the latest features and
|
||||||
fixes.
|
fixes.
|
||||||
|
|||||||
2
docs/Release Notes/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 2,
|
"formatVersion": 2,
|
||||||
"appVersion": "0.99.5",
|
"appVersion": "0.99.4",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"isClone": false,
|
"isClone": false,
|
||||||
|
|||||||
146
docs/User Guide/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"formatVersion": 2,
|
"formatVersion": 2,
|
||||||
"appVersion": "0.99.5",
|
"appVersion": "0.99.4",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"isClone": false,
|
"isClone": false,
|
||||||
@@ -9884,69 +9884,18 @@
|
|||||||
"value": "kanban-board",
|
"value": "kanban-board",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "Cq5X6iKQop6R",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "OFXdgB2nNk1F",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 40
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "bdUJEHsAPYQR",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "oPVyFC7WL2Lp",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "IakOLONlIfGI",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 70
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"dataFileName": "Kanban Board.md",
|
"dataFileName": "Kanban Board.md",
|
||||||
"attachments": [
|
"attachments": [
|
||||||
{
|
|
||||||
"attachmentId": "3ze7RpkjIWdW",
|
|
||||||
"title": "image.png",
|
|
||||||
"role": "image",
|
|
||||||
"mime": "image/png",
|
|
||||||
"position": 10,
|
|
||||||
"dataFileName": "Kanban Board_image.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"attachmentId": "IrIeh59VGjHq",
|
|
||||||
"title": "image.png",
|
|
||||||
"role": "image",
|
|
||||||
"mime": "image/png",
|
|
||||||
"position": 10,
|
|
||||||
"dataFileName": "1_Kanban Board_image.png"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"attachmentId": "usSSa0WI6dDK",
|
"attachmentId": "usSSa0WI6dDK",
|
||||||
"title": "image.png",
|
"title": "image.png",
|
||||||
"role": "image",
|
"role": "image",
|
||||||
"mime": "image/png",
|
"mime": "image/png",
|
||||||
"position": 10,
|
"position": 10,
|
||||||
"dataFileName": "2_Kanban Board_image.png"
|
"dataFileName": "Kanban Board_image.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -10034,20 +9983,6 @@
|
|||||||
"value": "geomap",
|
"value": "geomap",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 90
|
"position": 90
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "zEY4DaJG4YT5",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "OFXdgB2nNk1F",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 110
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@@ -11271,27 +11206,6 @@
|
|||||||
"value": "bx bx-list-check",
|
"value": "bx bx-list-check",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 110
|
"position": 110
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "oPVyFC7WL2Lp",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "eIg8jdvaoNNd",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 130
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "CdNpE2pqjmI6",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 140
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
@@ -11756,71 +11670,27 @@
|
|||||||
"name": "iconClass",
|
"name": "iconClass",
|
||||||
"value": "bx bx-table",
|
"value": "bx bx-table",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 40
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "BlN9DFI679QC",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "GTwFsgaA0lCt",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "zP3PMqaG71Ct",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 70
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "R9pX4DGra2Vt",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 80
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"format": "markdown",
|
"format": "markdown",
|
||||||
"dataFileName": "Promoted Attributes.md",
|
"dataFileName": "Promoted Attributes.md",
|
||||||
"attachments": [
|
"attachments": [
|
||||||
{
|
{
|
||||||
"attachmentId": "8ue55DaAJ82K",
|
"attachmentId": "4EcBRWF9iCk2",
|
||||||
"title": "image.png",
|
"title": "image.png",
|
||||||
"role": "image",
|
"role": "image",
|
||||||
"mime": "image/png",
|
"mime": "image/jpg",
|
||||||
"position": 10,
|
"position": 10,
|
||||||
"dataFileName": "Promoted Attributes_image.png"
|
"dataFileName": "Promoted Attributes_image.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"attachmentId": "bLMPNRtMAaKo",
|
"attachmentId": "Txf5Jdm2vqt2",
|
||||||
"title": "image.png",
|
"title": "promoted-attributes.png",
|
||||||
"role": "image",
|
"role": "image",
|
||||||
"mime": "image/png",
|
"mime": "image/png",
|
||||||
"position": 10,
|
"position": 10,
|
||||||
"dataFileName": "1_Promoted Attributes_image.png"
|
"dataFileName": "Promoted Attributes_promot.png"
|
||||||
},
|
|
||||||
{
|
|
||||||
"attachmentId": "FbNQB8xcY0Nu",
|
|
||||||
"title": "image.png",
|
|
||||||
"role": "image",
|
|
||||||
"mime": "image/png",
|
|
||||||
"position": 10,
|
|
||||||
"dataFileName": "2_Promoted Attributes_image.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"attachmentId": "yBIe1DyxuL2e",
|
|
||||||
"title": "image.png",
|
|
||||||
"role": "image",
|
|
||||||
"mime": "image/png",
|
|
||||||
"position": 10,
|
|
||||||
"dataFileName": "3_Promoted Attributes_image.png"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,30 +12,19 @@ In Trilium, attributes are key-value pairs assigned to notes, providing addition
|
|||||||
|
|
||||||
These attributes play a crucial role in organizing, categorizing, and enhancing the functionality of notes.
|
These attributes play a crucial role in organizing, categorizing, and enhancing the functionality of notes.
|
||||||
|
|
||||||
## Types of attributes
|
|
||||||
|
|
||||||
Conceptually there are two types of attributes (applying to both labels and relations):
|
|
||||||
|
|
||||||
1. **System attributes**
|
|
||||||
As the name suggest, these attributes have a special meaning since they are interpreted by Trilium. For example the `color` attribute will change the color of the note as displayed in the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Note%20Tree.md">Note Tree</a> and links, and `iconClass` will change the icon of a note.
|
|
||||||
2. **User-defined attributes**
|
|
||||||
These are free-form labels or relations that can be used by the user. They can be used purely for categorization purposes (especially if combined with <a class="reference-link" href="../Basic%20Concepts%20and%20Features/Navigation/Search.md">Search</a>), or they can be given meaning through the use of <a class="reference-link" href="../Scripting.md">Scripting</a>.
|
|
||||||
|
|
||||||
In practice, Trilium makes no direct distinction of whether an attribute is a system one or a user-defined one. A label or relation is considered a system attribute if it matches one of the built-in names (e.g. like the aforementioned `iconClass`). Keep this in mind when creating <a class="reference-link" href="Attributes/Promoted%20Attributes.md">Promoted Attributes</a> in order not to accidentally alter a system attribute (unless intended).
|
|
||||||
|
|
||||||
## Viewing the list of attributes
|
## Viewing the list of attributes
|
||||||
|
|
||||||
Both the labels and relations for the current note are displayed in the _Owned Attributes_ section of the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Ribbon.md">Ribbon</a>, where they can be viewed and edited. Inherited attributes are displayed in the _Inherited Attributes_ section of the ribbon, where they can only be viewed.
|
Both the labels and relations for the current note are displayed in the _Owned Attributes_ section of the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Ribbon.md">Ribbon</a>, where they can be viewed and edited. Inherited attributes are displayed in the _Inherited Attributes_ section of the ribbon, where they can only be viewed.
|
||||||
|
|
||||||
In the list of attributes, labels are prefixed with the `#` character whereas relations are prefixed with the `~` character.
|
In the list of attributes, labels are prefixed with the `#` character whereas relations are prefixed with the `~` character.
|
||||||
|
|
||||||
## Attribute Definitions and Promoted Attributes
|
|
||||||
|
|
||||||
<a class="reference-link" href="Attributes/Promoted%20Attributes.md">Promoted Attributes</a> create a form-like editing experience for attributes, which makes it easy to enhancing the organization and management of attributes
|
|
||||||
|
|
||||||
## Multiplicity
|
## Multiplicity
|
||||||
|
|
||||||
Attributes in Trilium can be "multi-valued", meaning multiple attributes with the same name can co-exist. This can be combined with <a class="reference-link" href="Attributes/Promoted%20Attributes.md">Promoted Attributes</a> to easily add them.
|
Attributes in Trilium can be "multi-valued", meaning multiple attributes with the same name can co-exist.
|
||||||
|
|
||||||
|
## Attribute Definitions and Promoted Attributes
|
||||||
|
|
||||||
|
Special labels create "label/attribute" definitions, enhancing the organization and management of attributes. For more details, see <a class="reference-link" href="Attributes/Promoted%20Attributes.md">Promoted Attributes</a>.
|
||||||
|
|
||||||
## Attribute Inheritance
|
## Attribute Inheritance
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,74 +1,31 @@
|
|||||||
# Promoted Attributes
|
# Promoted Attributes
|
||||||
<figure class="image image_resized" style="width:61.4%;"><img style="aspect-ratio:938/368;" src="Promoted Attributes_image.png" width="938" height="368"></figure>
|
Promoted attributes are [attributes](../Attributes.md) which are considered important and thus are "promoted" onto the main note UI. See example below:
|
||||||
|
|
||||||
Promoted attributes are [attributes](../Attributes.md) which are displayed prominently in the UI which allow them to be easily viewed and edited.
|

|
||||||
|
|
||||||
One way of seeing promoted attributes is as a kind of form with several fields. Each field is just regular attribute, the only difference is that they appear on the note itself.
|
You can see the note having kind of form with several fields. Each of these is just regular attribute, the only difference is that they appear on the note itself.
|
||||||
|
|
||||||
Attributes can be pretty useful since they allow for querying and script automation etc. but they are also inconveniently hidden. This allows you to select few of the important ones and push them to the front of the user.
|
Attributes can be pretty useful since they allow for querying and script automation etc. but they are also inconveniently hidden. This allows you to select few of the important ones and push them to the front of the user.
|
||||||
|
|
||||||
|
Now, how do we make attribute to appear on the UI?
|
||||||
|
|
||||||
## Attribute definition
|
## Attribute definition
|
||||||
|
|
||||||
In order to have promoted attributes, there needs to be a way to define them.
|
Attribute is always name-value pair where both name and value are strings.
|
||||||
|
|
||||||
<figure class="image image-style-align-right image_resized" style="width:38.82%;"><img style="aspect-ratio:492/346;" src="1_Promoted Attributes_image.png" width="492" height="346"></figure>
|
_Attribute definition_ specifies how should this value be interpreted - is it just string, or is it a date? Should we allow multiple values or note? And importantly, should we _promote_ the attribute or not?
|
||||||
|
|
||||||
Technically, attributes are only name-value pairs where both name and value are strings.
|

|
||||||
|
|
||||||
The _Attribute definition_ specifies how should this value be interpreted:
|
You can notice tag attribute definition. These "definition" attributes define how the "value" attributes should behave.
|
||||||
|
|
||||||
* Is it just string, or is it a date?
|
So there's one attribute for value and one for definition. But notice how definition attribute is [Inheritable](Attribute%20Inheritance.md), meaning that it's also applied to all descendant note. So in a way, this definition is used for the whole subtree while "value" attributes are applied only for this note.
|
||||||
* Should we allow multiple values or note?
|
|
||||||
* Should we _promote_ the attribute or not?
|
|
||||||
|
|
||||||
## Creating a new promoted attribute definition
|
|
||||||
|
|
||||||
To create a new promoted attribute:
|
|
||||||
|
|
||||||
1. Go to a note.
|
|
||||||
2. Go to _Owned Attributes_ in the <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/UI%20Elements/Ribbon.md">Ribbon</a>.
|
|
||||||
3. Press the + button.
|
|
||||||
4. Select either _Add new label definition_ or _Add new relation definition_.
|
|
||||||
5. Select the name which will be name of the label or relation that will be created when the promoted attribute is edited.
|
|
||||||
6. Ensure _Promoted_ is checked in order to display it at the top of notes.
|
|
||||||
7. Optionally, choose an _Alias_ which will be displayed next to the promoted attribute instead of the attribute name. Generally it's best to choose a “user-friendly” name since it can contain spaces and other characters which are not supported as attribute names.
|
|
||||||
8. Check _Inheritable_ to apply it to this note and all its descendants. To keep it only for the current note, un-check it.
|
|
||||||
9. Press “Save & Close” to apply the changes.
|
|
||||||
|
|
||||||
## How attribute definitions actually work
|
|
||||||
|
|
||||||
When a new promoted attribute definition is created, it creates a corresponding label prefixed with either `label` or `relation`, depending on the definition type:
|
|
||||||
|
|
||||||
```
|
|
||||||
#label:myColor(inheritable)="promoted,alias=Color,multi,color"
|
|
||||||
```
|
|
||||||
|
|
||||||
The only purpose of the attribute definition is to set up a template. If the attribute was marked as promoted, then it's also displayed to the user for easy editing.
|
|
||||||
|
|
||||||
| | |
|
|
||||||
| --- | --- |
|
|
||||||
| <figure class="image"><img style="aspect-ratio:495/157;" src="2_Promoted Attributes_image.png" width="495" height="157"></figure> | Notice how the promoted attribute definition only creates a “Due date” box above the text content. |
|
|
||||||
| <figure class="image"><img style="aspect-ratio:663/160;" src="3_Promoted Attributes_image.png" width="663" height="160"></figure> | Once a value is set by the user, a new label (or relation, depending on the type) is created. The name of the attribute matches one set when creating the promoted attribute. |
|
|
||||||
|
|
||||||
So there's one attribute for value and one for definition. But notice how an definition attribute can be made [Inheritable](Attribute%20Inheritance.md), meaning that it's also applied to all descendant notes. In this case, the definition used for the whole sub-tree while "value" attributes are for each not individually.
|
|
||||||
|
|
||||||
## Using system attributes
|
|
||||||
|
|
||||||
It's possible to create promoted attributes out of system attributes, to be able to easily alter them.
|
|
||||||
|
|
||||||
Here are a few practical examples:
|
|
||||||
|
|
||||||
* <a class="reference-link" href="../../Collections.md">Collections</a> already make use of this practice, for example:
|
|
||||||
* Calendars add “Start Date”, “End Date”, “Start Time” and “End Time” as promoted attributes. These map to system attributes such as `startDate` which are then interpreted by the calendar view.
|
|
||||||
* <a class="reference-link" href="../../Collections/Presentation.md">Presentation</a> adds a “Background” promoted attribute for each of the slide to easily be able to customize.
|
|
||||||
* The Trilium documentation (which is edited in Trilium) uses a promoted attribute to be able to easily edit the `#shareAlias` (see <a class="reference-link" href="../Sharing.md">Sharing</a>) in order to form clean URLs.
|
|
||||||
* If you always edit a particular system attribute such as `#color`, simply create a promoted attribute for it to make it easier.
|
|
||||||
|
|
||||||
### Inverse relation
|
### Inverse relation
|
||||||
|
|
||||||
Some relations always occur in pairs - my favorite example is on the family. If you have a note representing husband and note representing wife, then there might be a relation between those two of `isPartnerOf`. This is bidirectional relationship - meaning that if a relation is pointing from husband to wife then there should be always another relation pointing from wife to husband.
|
Some relations always occur in pairs - my favorite example is on the family. If you have a note representing husband and note representing wife, then there might be a relation between those two of `isPartnerOf`. This is bidirectional relationship - meaning that if a relation is pointing from husband to wife then there should be always another relation pointing from wife to husband.
|
||||||
|
|
||||||
Another example is with parent-child relationship. Again these always occur in pairs, but in this case it's not exact same relation - the one going from parent to child might be called `isParentOf` and the other one going from child to parent might be called `isChildOf`.
|
Another example is with parent - child relationship. Again these always occur in pairs, but in this case it's not exact same relation - the one going from parent to child might be called `isParentOf` and the other one going from child to parent might be called `isChildOf`.
|
||||||
|
|
||||||
Relation definition allows you to specify such "inverse relation" - for the relation you just define you specify which is the inverse relation. Note that in the second example we should have two relation definitions - one for `isParentOf` which defines `isChildOf` as inverse relation and then second relation definition for `isChildOf` which defines `isParentOf` as inverse relation.
|
Relation definition allows you to specify such "inverse relation" - for the relation you just define you specify which is the inverse relation. Note that in the second example we should have two relation definitions - one for `isParentOf` which defines `isChildOf` as inverse relation and then second relation definition for `isChildOf` which defines `isParentOf` as inverse relation.
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 113 KiB |
BIN
docs/User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes_promot.png
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB |