mirror of
https://github.com/zadam/trilium.git
synced 2025-12-29 18:54:29 +01:00
Minor tweaks (#8145)
Some checks failed
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Documentation / Build and Deploy Documentation (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
Deploy website / Build & deploy website (push) Has been cancelled
Some checks failed
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Documentation / Build and Deploy Documentation (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
Deploy website / Build & deploy website (push) Has been cancelled
This commit is contained in:
commit
d6d75a0e48
@ -46,17 +46,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
|
||||
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
|
||||
}.call(apiContext);
|
||||
} catch (e: any) {
|
||||
const note = await froca.getNote(bundle.noteId);
|
||||
toastService.showPersistent({
|
||||
id: `custom-script-failure-${note?.noteId}`,
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.bundle-error.message", {
|
||||
id: note?.noteId,
|
||||
title: note?.title,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
showErrorForScriptNote(bundle.noteId, t("toast.bundle-error.message", { message: e.message }));
|
||||
logError("Widget initialization failed: ", e);
|
||||
}
|
||||
}
|
||||
@ -151,17 +141,7 @@ async function getWidgetBundlesByParent() {
|
||||
}
|
||||
} catch (e: any) {
|
||||
const noteId = bundle.noteId;
|
||||
const note = await froca.getNote(noteId);
|
||||
toastService.showPersistent({
|
||||
id: `custom-script-failure-${noteId}`,
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.bundle-error.message", {
|
||||
id: noteId,
|
||||
title: note?.title,
|
||||
message: e.message
|
||||
})
|
||||
});
|
||||
showErrorForScriptNote(noteId, t("toast.bundle-error.message", { message: e.message }));
|
||||
|
||||
logError("Widget initialization failed: ", e);
|
||||
continue;
|
||||
|
||||
@ -69,7 +69,7 @@ export async function showErrorForScriptNote(noteId: string, message: string) {
|
||||
|
||||
showPersistent({
|
||||
id: `custom-widget-failure-${noteId}`,
|
||||
title: note?.title ?? "",
|
||||
title: t("toast.scripting-error", { title: note?.title ?? "" }),
|
||||
icon: note?.getIcon() ?? "bx bx-error-circle",
|
||||
message,
|
||||
timeout: 15_000,
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Failed to load a custom script",
|
||||
"message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}"
|
||||
"message": "Script could not be executed due to:\n\n{{message}}"
|
||||
},
|
||||
"widget-list-error": {
|
||||
"title": "Failed to obtain the list of widgets from the server"
|
||||
@ -30,7 +30,8 @@
|
||||
"title": "Failed to render a custom React widget"
|
||||
},
|
||||
"widget-missing-parent": "Custom widget does not have mandatory '{{property}}' property defined.",
|
||||
"open-script-note": "Open script note"
|
||||
"open-script-note": "Open script note",
|
||||
"scripting-error": "Custom script error: {{title}}"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Add link",
|
||||
@ -1779,7 +1780,8 @@
|
||||
"note_type_switcher_others": "Other note type",
|
||||
"note_type_switcher_templates": "Template",
|
||||
"note_type_switcher_collection": "Collection",
|
||||
"edited_notes": "Edited notes"
|
||||
"edited_notes": "Notes edited on this day",
|
||||
"promoted_attributes": "Promoted attributes"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "No notes have been found for given search parameters.",
|
||||
|
||||
@ -15,7 +15,7 @@ export default function TabHistoryNavigationButtons() {
|
||||
const legacyBackVisible = useLauncherVisibility("_lbBackInHistory");
|
||||
const legacyForwardVisible = useLauncherVisibility("_lbForwardInHistory");
|
||||
|
||||
return (isElectron() &&
|
||||
return (
|
||||
<div className="tab-history-navigation-buttons">
|
||||
{!legacyBackVisible && <ActionButton
|
||||
icon="bx bx-left-arrow-alt"
|
||||
|
||||
@ -54,6 +54,16 @@
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn {
|
||||
color: var(--bs-toast-color);
|
||||
background: var(--modal-control-button-background);
|
||||
|
||||
&:hover {
|
||||
background: var(--modal-control-button-hover-background);
|
||||
color: var(--bs-toast-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toast-progress {
|
||||
|
||||
@ -4,7 +4,14 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-split.type-code:not(.mime-text-x-sqlite) > .scrolling-container {
|
||||
background-color: var(--code-background-color);
|
||||
--scrollbar-background-color: var(--main-background-color);
|
||||
.note-split.type-code:not(.mime-text-x-sqlite) {
|
||||
&> .scrolling-container {
|
||||
background-color: var(--code-background-color);
|
||||
--scrollbar-background-color: var(--main-background-color);
|
||||
}
|
||||
|
||||
.inline-title,
|
||||
.title-actions {
|
||||
background-color: var(--main-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ import { ParentComponent } from "../react/react_utils";
|
||||
|
||||
const COLLAPSE_THRESHOLD = 5;
|
||||
const INITIAL_ITEMS = 2;
|
||||
const FINAL_ITEMS = 2;
|
||||
const FINAL_ITEMS = 3;
|
||||
|
||||
export default function Breadcrumb() {
|
||||
const { note, notePath, notePaths, noteContext } = useNotePaths();
|
||||
@ -65,8 +65,7 @@ export default function Breadcrumb() {
|
||||
? <BreadcrumbRoot noteContext={noteContext} />
|
||||
: <BreadcrumbItem index={index} notePath={item} notePathLength={notePaths.length} noteContext={noteContext} parentComponent={parentComponent} />
|
||||
}
|
||||
{(index < notePaths.length - 1 || note?.hasChildren()) &&
|
||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />}
|
||||
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />
|
||||
</Fragment>
|
||||
))
|
||||
)}
|
||||
@ -226,7 +225,7 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP
|
||||
</li>;
|
||||
})}
|
||||
|
||||
<FormDropdownDivider />
|
||||
{childNotes.length > 0 && <FormDropdownDivider />}
|
||||
<FormListItem
|
||||
icon="bx bx-plus"
|
||||
onClick={() => note_create.createNote(notePath, { activate: true })}
|
||||
|
||||
@ -75,10 +75,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.note-split.type-code:not(.mime-text-x-sqlite) .inline-title {
|
||||
background-color: var(--main-background-color);
|
||||
}
|
||||
|
||||
body.prefers-centered-content .inline-title {
|
||||
margin-inline: auto;
|
||||
}
|
||||
@ -99,58 +95,3 @@ body.prefers-centered-content .inline-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes note-type-switcher-intro {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.note-type-switcher {
|
||||
--badge-radius: 12px;
|
||||
|
||||
position: relative;
|
||||
top: 5px;
|
||||
padding: .25em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
min-height: 35px;
|
||||
|
||||
>* {
|
||||
flex-shrink: 0;
|
||||
animation: note-type-switcher-intro 200ms ease-in;
|
||||
}
|
||||
|
||||
.ext-badge {
|
||||
--color: var(--input-background-color);
|
||||
color: var(--main-text-color);
|
||||
font-size: 0.9rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edited-notes {
|
||||
padding: 1.5em 0;
|
||||
|
||||
.collapsible-inner-body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3em;
|
||||
|
||||
.badge {
|
||||
margin: 0;
|
||||
a.tn-link {
|
||||
color: inherit;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,28 +3,16 @@ import "./InlineTitle.css";
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import clsx from "clsx";
|
||||
import { ComponentChild } from "preact";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ViewScope } from "../../services/link";
|
||||
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
|
||||
import server from "../../services/server";
|
||||
import { formatDateTime } from "../../utils/formatters";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import SimpleBadge, { Badge, BadgeWithDropdown } from "../react/Badge";
|
||||
import Collapsible from "../react/Collapsible";
|
||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
||||
import { useNoteBlob, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { useNoteContext, useNoteProperty, useStaticTooltip } from "../react/hooks";
|
||||
import { joinElements } from "../react/react_utils";
|
||||
import { useEditedNotes } from "../ribbon/EditedNotesTab";
|
||||
import { useNoteMetadata } from "../ribbon/NoteInfoTab";
|
||||
import { onWheelHorizontalScroll } from "../widget_utils";
|
||||
|
||||
const supportedNoteTypes = new Set<NoteType>([
|
||||
"text", "code"
|
||||
@ -76,9 +64,6 @@ export default function InlineTitle() {
|
||||
<NoteTitleDetails />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditedNotes />
|
||||
<NoteTypeSwitcher />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -142,205 +127,4 @@ function TextWithValue({ i18nKey, value, valueTooltip }: {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Note type switcher
|
||||
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
|
||||
|
||||
function NoteTypeSwitcher() {
|
||||
const { note } = useNoteContext();
|
||||
const blob = useNoteBlob(note);
|
||||
const currentNoteType = useNoteProperty(note, "type");
|
||||
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
|
||||
const pinnedNoteTypes: NoteTypeMapping[] = [];
|
||||
const restNoteTypes: NoteTypeMapping[] = [];
|
||||
for (const noteType of NOTE_TYPES) {
|
||||
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
|
||||
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
|
||||
pinnedNoteTypes.push(noteType);
|
||||
} else {
|
||||
restNoteTypes.push(noteType);
|
||||
}
|
||||
}
|
||||
return { pinnedNoteTypes, restNoteTypes };
|
||||
}, []);
|
||||
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
|
||||
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
|
||||
|
||||
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
|
||||
<div
|
||||
className="note-type-switcher"
|
||||
onWheel={onWheelHorizontalScroll}
|
||||
>
|
||||
{note && blob?.contentLength === 0 && (
|
||||
<>
|
||||
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
|
||||
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
|
||||
<Badge
|
||||
key={noteType.type}
|
||||
text={noteType.title}
|
||||
icon={`bx ${noteType.icon}`}
|
||||
onClick={() => switchNoteType(note.noteId, noteType)}
|
||||
/>
|
||||
))}
|
||||
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
|
||||
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
|
||||
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_others")}
|
||||
icon="bx bx-dots-vertical-rounded"
|
||||
>
|
||||
{restNoteTypes.map(noteType => (
|
||||
<FormListItem
|
||||
key={noteType.type}
|
||||
icon={`bx ${noteType.icon}`}
|
||||
onClick={() => switchNoteType(noteId, noteType)}
|
||||
>{noteType.title}</FormListItem>
|
||||
))}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_collection")}
|
||||
icon="bx bx-book"
|
||||
>
|
||||
{collectionTemplates.map(collectionTemplate => (
|
||||
<FormListItem
|
||||
key={collectionTemplate.noteId}
|
||||
icon={collectionTemplate.getIcon()}
|
||||
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
|
||||
>{collectionTemplate.title}</FormListItem>
|
||||
))}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
|
||||
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
|
||||
|
||||
async function refreshTemplates() {
|
||||
const templateNoteIds = await server.get<string[]>("search-templates");
|
||||
const templateNotes = await froca.getNotes(templateNoteIds);
|
||||
setUserTemplates(templateNotes);
|
||||
}
|
||||
|
||||
// First load.
|
||||
useEffect(() => {
|
||||
refreshTemplates();
|
||||
}, []);
|
||||
|
||||
// React to external changes.
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
|
||||
refreshTemplates();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_templates")}
|
||||
icon="bx bx-copy-alt"
|
||||
>
|
||||
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
|
||||
{userTemplates.length > 0 && <FormDropdownDivider />}
|
||||
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
|
||||
return (
|
||||
<FormListItem
|
||||
icon={template.getIcon()}
|
||||
onClick={() => setTemplate(noteId, template.noteId)}
|
||||
>{template.title}</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
|
||||
return server.put(`notes/${noteId}/type`, { type, mime });
|
||||
}
|
||||
|
||||
function setTemplate(noteId: string, templateId: string) {
|
||||
return attributes.setRelation(noteId, "template", templateId);
|
||||
}
|
||||
|
||||
function useBuiltinTemplates() {
|
||||
const [ templates, setTemplates ] = useState<{
|
||||
builtinTemplates: FNote[];
|
||||
collectionTemplates: FNote[];
|
||||
}>({
|
||||
builtinTemplates: [],
|
||||
collectionTemplates: []
|
||||
});
|
||||
|
||||
async function loadBuiltinTemplates() {
|
||||
const templatesRoot = await froca.getNote("_templates");
|
||||
if (!templatesRoot) return;
|
||||
const childNotes = await templatesRoot.getChildNotes();
|
||||
const builtinTemplates: FNote[] = [];
|
||||
const collectionTemplates: FNote[] = [];
|
||||
for (const childNote of childNotes) {
|
||||
if (!childNote.hasLabel("template")) continue;
|
||||
if (childNote.hasLabel("collection")) {
|
||||
collectionTemplates.push(childNote);
|
||||
} else {
|
||||
builtinTemplates.push(childNote);
|
||||
}
|
||||
}
|
||||
setTemplates({ builtinTemplates, collectionTemplates });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadBuiltinTemplates();
|
||||
}, []);
|
||||
|
||||
return templates;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Edited Notes
|
||||
function EditedNotes() {
|
||||
const { note } = useNoteContext();
|
||||
const [ dateNote ] = useNoteLabel(note, "dateNote");
|
||||
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
|
||||
|
||||
return (note && dateNote &&
|
||||
<Collapsible
|
||||
className="edited-notes"
|
||||
title={t("note_title.edited_notes")}
|
||||
initiallyExpanded={editedNotesOpenInRibbon}
|
||||
>
|
||||
<EditedNotesContent note={note} />
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function EditedNotesContent({ note }: { note: FNote }) {
|
||||
const editedNotes = useEditedNotes(note);
|
||||
|
||||
return (editedNotes !== undefined &&
|
||||
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
|
||||
<SimpleBadge
|
||||
key={editedNote.noteId}
|
||||
title={(
|
||||
<NoteLink
|
||||
notePath={editedNote.noteId}
|
||||
showNoteIcon
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)) : (
|
||||
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
|
||||
)));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@ -16,6 +16,13 @@
|
||||
&.share-badge {--color: var(--badge-share-background-color);}
|
||||
&.clipped-note-badge {--color: var(--badge-clipped-note-background-color);}
|
||||
&.execute-badge {--color: var(--badge-execute-background-color);}
|
||||
min-width: 0;
|
||||
|
||||
.text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-badge {
|
||||
|
||||
@ -4,8 +4,41 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
|
||||
.title-actions {
|
||||
&.visible:not(:empty) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
&:not(:empty) {
|
||||
padding: 0.75em 15px;
|
||||
}
|
||||
|
||||
.edited-notes {
|
||||
.collapsible-inner-body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3em;
|
||||
|
||||
.badge {
|
||||
margin: 0;
|
||||
color: inherit;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: background-color 250ms ease-in, color 250ms ease-in;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--link-hover-background);
|
||||
color: var(--link-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.promoted-attributes-widget {
|
||||
.promoted-attributes-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "./NoteTitleActions.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
@ -9,29 +9,31 @@ import { t } from "../../services/i18n";
|
||||
import CollectionProperties from "../note_bars/CollectionProperties";
|
||||
import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
|
||||
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
||||
import SimpleBadge from "../react/Badge";
|
||||
import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible";
|
||||
import { useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
|
||||
import { useNoteContext, useNoteLabel, useNoteProperty, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
|
||||
import NoteLink, { NewNoteLink } from "../react/NoteLink";
|
||||
import { useEditedNotes } from "../ribbon/EditedNotesTab";
|
||||
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
|
||||
import NoteTypeSwitcher from "./NoteTypeSwitcher";
|
||||
|
||||
export default function NoteTitleActions() {
|
||||
const { note, ntxId, componentId, noteContext } = useNoteContext();
|
||||
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
|
||||
const items = [
|
||||
note && <PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />,
|
||||
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
|
||||
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
|
||||
].filter(Boolean);
|
||||
|
||||
return (
|
||||
<div className={clsx("title-actions", items.length > 0 && "visible")}>
|
||||
{items}
|
||||
<div className="title-actions">
|
||||
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
|
||||
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
|
||||
{!isHiddenNote && note && noteType === "book" && <CollectionProperties note={note} />}
|
||||
<EditedNotes />
|
||||
<NoteTypeSwitcher />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | undefined }) {
|
||||
function SearchProperties({ note, ntxId }: { note: FNote | null | undefined, ntxId: string | null | undefined }) {
|
||||
return (note &&
|
||||
<Collapsible
|
||||
title={t("search_definition.search_parameters")}
|
||||
@ -64,10 +66,43 @@ function PromotedAttributes({ note, componentId, noteContext }: {
|
||||
return (note && (
|
||||
<ExternallyControlledCollapsible
|
||||
key={note.noteId}
|
||||
title={t("promoted_attributes.promoted_attributes")}
|
||||
title={t("note_title.promoted_attributes")}
|
||||
expanded={expanded} setExpanded={setExpanded}
|
||||
>
|
||||
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
|
||||
</ExternallyControlledCollapsible>
|
||||
));
|
||||
}
|
||||
|
||||
//#region Edited Notes
|
||||
function EditedNotes() {
|
||||
const { note } = useNoteContext();
|
||||
const [ dateNote ] = useNoteLabel(note, "dateNote");
|
||||
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
|
||||
|
||||
return (note && dateNote &&
|
||||
<Collapsible
|
||||
className="edited-notes"
|
||||
title={t("note_title.edited_notes")}
|
||||
initiallyExpanded={editedNotesOpenInRibbon}
|
||||
>
|
||||
<EditedNotesContent note={note} />
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function EditedNotesContent({ note }: { note: FNote }) {
|
||||
const editedNotes = useEditedNotes(note);
|
||||
|
||||
return (editedNotes !== undefined &&
|
||||
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
|
||||
<NewNoteLink
|
||||
className="badge"
|
||||
notePath={editedNote.noteId}
|
||||
showNoteIcon
|
||||
/>
|
||||
)) : (
|
||||
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
|
||||
)));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
33
apps/client/src/widgets/layout/NoteTypeSwitcher.css
Normal file
33
apps/client/src/widgets/layout/NoteTypeSwitcher.css
Normal file
@ -0,0 +1,33 @@
|
||||
@keyframes note-type-switcher-intro {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.note-type-switcher {
|
||||
--badge-radius: 12px;
|
||||
|
||||
position: relative;
|
||||
top: 5px;
|
||||
padding: .25em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
min-height: 35px;
|
||||
|
||||
>* {
|
||||
flex-shrink: 0;
|
||||
animation: note-type-switcher-intro 200ms ease-in;
|
||||
}
|
||||
|
||||
.ext-badge {
|
||||
--color: var(--input-background-color);
|
||||
color: var(--main-text-color);
|
||||
font-size: 0.9rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
182
apps/client/src/widgets/layout/NoteTypeSwitcher.tsx
Normal file
182
apps/client/src/widgets/layout/NoteTypeSwitcher.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import "./NoteTypeSwitcher.css";
|
||||
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
|
||||
import server from "../../services/server";
|
||||
import { Badge, BadgeWithDropdown } from "../react/Badge";
|
||||
import { FormDropdownDivider, FormListItem } from "../react/FormList";
|
||||
import { useNoteBlob, useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
|
||||
import { onWheelHorizontalScroll } from "../widget_utils";
|
||||
|
||||
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
|
||||
const supportedNoteTypes = new Set<NoteType>([
|
||||
"text", "code"
|
||||
]);
|
||||
|
||||
export default function NoteTypeSwitcher() {
|
||||
const { note } = useNoteContext();
|
||||
const blob = useNoteBlob(note);
|
||||
const currentNoteType = useNoteProperty(note, "type");
|
||||
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
|
||||
const pinnedNoteTypes: NoteTypeMapping[] = [];
|
||||
const restNoteTypes: NoteTypeMapping[] = [];
|
||||
for (const noteType of NOTE_TYPES) {
|
||||
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
|
||||
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
|
||||
pinnedNoteTypes.push(noteType);
|
||||
} else {
|
||||
restNoteTypes.push(noteType);
|
||||
}
|
||||
}
|
||||
return { pinnedNoteTypes, restNoteTypes };
|
||||
}, []);
|
||||
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
|
||||
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
|
||||
|
||||
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
|
||||
<div
|
||||
className="note-type-switcher"
|
||||
onWheel={onWheelHorizontalScroll}
|
||||
>
|
||||
{note && blob?.contentLength === 0 && (
|
||||
<>
|
||||
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
|
||||
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
|
||||
<Badge
|
||||
key={noteType.type}
|
||||
text={noteType.title}
|
||||
icon={`bx ${noteType.icon}`}
|
||||
onClick={() => switchNoteType(note.noteId, noteType)}
|
||||
/>
|
||||
))}
|
||||
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
|
||||
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
|
||||
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_others")}
|
||||
icon="bx bx-dots-vertical-rounded"
|
||||
>
|
||||
{restNoteTypes.map(noteType => (
|
||||
<FormListItem
|
||||
key={noteType.type}
|
||||
icon={`bx ${noteType.icon}`}
|
||||
onClick={() => switchNoteType(noteId, noteType)}
|
||||
>{noteType.title}</FormListItem>
|
||||
))}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_collection")}
|
||||
icon="bx bx-book"
|
||||
>
|
||||
{collectionTemplates.map(collectionTemplate => (
|
||||
<FormListItem
|
||||
key={collectionTemplate.noteId}
|
||||
icon={collectionTemplate.getIcon()}
|
||||
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
|
||||
>{collectionTemplate.title}</FormListItem>
|
||||
))}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
|
||||
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
|
||||
|
||||
async function refreshTemplates() {
|
||||
const templateNoteIds = await server.get<string[]>("search-templates");
|
||||
const templateNotes = await froca.getNotes(templateNoteIds);
|
||||
setUserTemplates(templateNotes);
|
||||
}
|
||||
|
||||
// First load.
|
||||
useEffect(() => {
|
||||
refreshTemplates();
|
||||
}, []);
|
||||
|
||||
// React to external changes.
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
|
||||
refreshTemplates();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BadgeWithDropdown
|
||||
text={t("note_title.note_type_switcher_templates")}
|
||||
icon="bx bx-copy-alt"
|
||||
>
|
||||
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
|
||||
{userTemplates.length > 0 && <FormDropdownDivider />}
|
||||
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
|
||||
</BadgeWithDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
|
||||
return (
|
||||
<FormListItem
|
||||
icon={template.getIcon()}
|
||||
onClick={() => setTemplate(noteId, template.noteId)}
|
||||
>{template.title}</FormListItem>
|
||||
);
|
||||
}
|
||||
|
||||
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
|
||||
return server.put(`notes/${noteId}/type`, { type, mime });
|
||||
}
|
||||
|
||||
function setTemplate(noteId: string, templateId: string) {
|
||||
return attributes.setRelation(noteId, "template", templateId);
|
||||
}
|
||||
|
||||
function useBuiltinTemplates() {
|
||||
const [ templates, setTemplates ] = useState<{
|
||||
builtinTemplates: FNote[];
|
||||
collectionTemplates: FNote[];
|
||||
}>({
|
||||
builtinTemplates: [],
|
||||
collectionTemplates: []
|
||||
});
|
||||
|
||||
async function loadBuiltinTemplates() {
|
||||
const templatesRoot = await froca.getNote("_templates");
|
||||
if (!templatesRoot) return;
|
||||
const childNotes = await templatesRoot.getChildNotes();
|
||||
const builtinTemplates: FNote[] = [];
|
||||
const collectionTemplates: FNote[] = [];
|
||||
for (const childNote of childNotes) {
|
||||
if (!childNote.hasLabel("template")) continue;
|
||||
if (childNote.hasLabel("collection")) {
|
||||
collectionTemplates.push(childNote);
|
||||
} else {
|
||||
builtinTemplates.push(childNote);
|
||||
}
|
||||
}
|
||||
setTemplates({ builtinTemplates, collectionTemplates });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadBuiltinTemplates();
|
||||
}, []);
|
||||
|
||||
return templates;
|
||||
}
|
||||
@ -37,7 +37,7 @@ body.experimental-feature-new-layout {
|
||||
.title-row {
|
||||
container-type: size;
|
||||
transition: border 400ms ease-out;
|
||||
|
||||
|
||||
&.note-split-title {
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
|
||||
@ -56,13 +56,13 @@ body.experimental-feature-new-layout {
|
||||
--note-icon-container-padding-size: 6px;
|
||||
margin-inline: 0;
|
||||
}
|
||||
|
||||
|
||||
.note-title-widget {
|
||||
--note-title-size: 18px;
|
||||
--note-title-padding-inline: 0;
|
||||
}
|
||||
|
||||
@container (max-width: 700px) {
|
||||
@container (max-width: 600px) {
|
||||
.note-title-widget {
|
||||
--note-title-size: 1.25rem;
|
||||
--note-title-padding-inline: 4px;
|
||||
@ -80,7 +80,7 @@ body.experimental-feature-new-layout {
|
||||
.ext-badge .text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.ext-badge {
|
||||
--size: 2em;
|
||||
width: var(--size);
|
||||
|
||||
@ -50,16 +50,30 @@
|
||||
white-space: nowrap;
|
||||
border-radius: var(--badge-radius);
|
||||
|
||||
button,
|
||||
.btn {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ext-badge {
|
||||
border-radius: 0;
|
||||
|
||||
.text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
|
||||
.text-inner {
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 1.3em;
|
||||
margin-left: 0.25em;
|
||||
margin-inline-start: 0.25em;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,10 @@ export function BadgeWithDropdown({ text, children, tooltip, className, dropdown
|
||||
<Dropdown
|
||||
className={`dropdown-badge dropdown-${className}`}
|
||||
text={<Badge
|
||||
text={<>{text} <Icon className="arrow" icon="bx bx-chevron-down" /></>}
|
||||
text={<>
|
||||
<span class="text-inner">{text}</span>
|
||||
<Icon className="arrow" icon="bx bx-chevron-down" />
|
||||
</>}
|
||||
className={className}
|
||||
{...props}
|
||||
/>}
|
||||
|
||||
@ -2,10 +2,11 @@ import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap";
|
||||
import { ComponentChildren, HTMLAttributes } from "preact";
|
||||
import { CSSProperties, HTMLProps } from "preact/compat";
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { useTooltip, useUniqueName } from "./hooks";
|
||||
|
||||
type DataAttributes = {
|
||||
[key: `data-${string}`]: string | number | boolean | undefined;
|
||||
[key: `data-${string}`]: string | number | boolean | undefined;
|
||||
};
|
||||
|
||||
export interface DropdownProps extends Pick<HTMLProps<HTMLDivElement>, "id" | "className"> {
|
||||
@ -66,14 +67,14 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
dropdown.dispose();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onShown = useCallback(() => {
|
||||
setShown(true);
|
||||
externalOnShown?.();
|
||||
hideTooltip();
|
||||
}, [ hideTooltip ])
|
||||
}, [ hideTooltip ]);
|
||||
|
||||
const onHidden = useCallback(() => {
|
||||
setShown(false);
|
||||
@ -122,7 +123,7 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
||||
{...buttonProps}
|
||||
>
|
||||
{text}
|
||||
<span className="caret"></span>
|
||||
<span className="caret" />
|
||||
</button>
|
||||
|
||||
<ul
|
||||
@ -130,9 +131,15 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
||||
style={dropdownContainerStyle}
|
||||
aria-labelledby={ariaId}
|
||||
ref={dropdownContainerRef}
|
||||
onClick={(e) => {
|
||||
// Prevent clicks directly inside the dropdown from closing.
|
||||
if (e.target === dropdownContainerRef.current) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{shown && children}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -105,22 +105,17 @@ export function NewNoteLink({ notePath, viewScope, noContextMenu, showNoteIcon,
|
||||
const [ archived ] = useNoteLabelBoolean(note, "archived");
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span>
|
||||
{icon && <Icon icon={icon} />}
|
||||
|
||||
<a
|
||||
className={clsx("tn-link", colorClass, {
|
||||
"no-tooltip-preview": noPreview,
|
||||
archived
|
||||
})}
|
||||
href={calculateHash({ notePath, viewScope })}
|
||||
data-no-context-menu={noContextMenu}
|
||||
{...linkProps}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
<a
|
||||
className={clsx("tn-link", colorClass, {
|
||||
"no-tooltip-preview": noPreview,
|
||||
archived
|
||||
})}
|
||||
href={calculateHash({ notePath, viewScope })}
|
||||
data-no-context-menu={noContextMenu}
|
||||
{...linkProps}
|
||||
>
|
||||
{icon && <><Icon icon={icon} /> </>}
|
||||
{title}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@ -369,7 +369,8 @@ export function useActiveNoteContext() {
|
||||
|
||||
useEffect(() => {
|
||||
setNote(noteContext?.note);
|
||||
}, [ notePath ]);
|
||||
setNotePath(noteContext?.notePath);
|
||||
}, [ notePath, noteContext?.note, noteContext?.notePath ]);
|
||||
|
||||
useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], () => {
|
||||
const noteContext = appContext.tabManager.getActiveContext() ?? undefined;
|
||||
@ -634,7 +635,8 @@ export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, {
|
||||
|
||||
const renderedWidget = widget.render();
|
||||
return [ widget, renderedWidget ];
|
||||
}, [ noteContext, parentComponent, widgetFactory]);
|
||||
}, [ noteContext, parentComponent ]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
// widgetFactory() is intentionally left out
|
||||
|
||||
// Attach the widget to the parent.
|
||||
useEffect(() => {
|
||||
|
||||
@ -5,6 +5,7 @@ import { useEffect, useState } from "preact/hooks";
|
||||
import FNote from "../entities/fnote";
|
||||
import attributes from "../services/attributes";
|
||||
import { t } from "../services/i18n";
|
||||
import { isElectron } from "../services/utils";
|
||||
import HelpButton from "./react/HelpButton";
|
||||
import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks";
|
||||
import InfoBar from "./react/InfoBar";
|
||||
@ -68,7 +69,11 @@ export function useShareInfo(note: FNote | null | undefined) {
|
||||
}
|
||||
});
|
||||
|
||||
return { link, linkHref, isSharedExternally: !!syncServerHost };
|
||||
return {
|
||||
link,
|
||||
linkHref,
|
||||
isSharedExternally: !isElectron() || !!syncServerHost // on server we can't reliably detect if the note is shared locally or available publicly.
|
||||
};
|
||||
}
|
||||
|
||||
function getShareId(note: FNote) {
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
<p>When you run Trilium for the first time, it will generate a new database
|
||||
containing demo notes. These notes showcase its many features, such as:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e1332d0e5bd3075dff093a222c3396eba"><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
|
||||
<li><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
|
||||
</li>
|
||||
<li data-list-item-id="e15271c447822c455fcc523deb51fb9eb"><a class="reference-link" href="#root/_help_l0tKav7yLHGF">Day Notes</a>
|
||||
<li><a class="reference-link" href="#root/_help_l0tKav7yLHGF">Day Notes</a>
|
||||
</li>
|
||||
<li data-list-item-id="ee1a63745a43d7a8109f69dcdaf79ee28"><a class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a>
|
||||
<li><a class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a>
|
||||
</li>
|
||||
<li data-list-item-id="ed1b10c8014e5fbaab923dd9ed7e4b122"><a class="reference-link" href="#root/_help_xYjQUYhpbUEW">Task Manager</a>
|
||||
<li><a class="reference-link" href="#root/_help_xYjQUYhpbUEW">Task Manager</a>
|
||||
</li>
|
||||
<li data-list-item-id="edbd34e77d6f5c13372f6ff6135fcfb95"><a class="reference-link" href="#root/_help_Wy267RK4M69c">Themes</a>
|
||||
<li><a class="reference-link" href="#root/_help_Wy267RK4M69c">Themes</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Restoring Demo Notes</h3>
|
||||
@ -21,10 +21,10 @@
|
||||
<p>You can easily restore the demo notes by using Trilium's built-in import
|
||||
feature by importing them:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="eaa8db14b035c1cc6b11d54b19acf9803">Download <a href="https://github.com/TriliumNext/Trilium/raw/refs/heads/main/apps/server/src/assets/db/demo.zip">the .zip archive</a> with
|
||||
<li>Download <a href="https://github.com/TriliumNext/Trilium/raw/refs/heads/main/apps/server/src/assets/db/demo.zip">the .zip archive</a> with
|
||||
the latest version of the demo notes</li>
|
||||
<li data-list-item-id="e3aa9c23f36af5aa9931ecf72de36083f">Right click on any note in your tree under which you would like the demo
|
||||
<li>Right click on any note in your tree under which you would like the demo
|
||||
notes to be imported</li>
|
||||
<li data-list-item-id="e08987d861bb67c9457e73122c134a641">Click "Import into note"</li>
|
||||
<li data-list-item-id="ee8b17222734db674b929a1d2f55a6840">Select the .zip archive to import it</li>
|
||||
<li>Click "Import into note"</li>
|
||||
<li>Select the .zip archive to import it</li>
|
||||
</ul>
|
||||
@ -24,7 +24,7 @@ The current date & time is <span class="date"></span></code></pr
|
||||
<p>Now we need to add the script. Create another <a class="reference-link"
|
||||
href="#root/_help_6f9hih2hXXZk">Code</a>, but this time of JavaScript (frontend)
|
||||
language. Make sure the newly created note is a direct child of the HTML
|
||||
note created previously; with the following content:</p><pre><code class="language-text-x-trilium-auto">const $dateEl = api.$container.find(".date");
|
||||
note created previously; with the following content:</p><pre><code class="language-application-javascript-env-backend">const $dateEl = api.$container.find(".date");
|
||||
$dateEl.text(new Date());</code></pre>
|
||||
<p>Now create a render note at any place and set its <code>~renderNote</code> relation
|
||||
to point to the HTML note. When the render note is accessed it will display:</p>
|
||||
|
||||
@ -32,41 +32,30 @@
|
||||
<p>Let's start by creating a widget that shows a message near the content
|
||||
area. Follow the previous section to create a code note, and use the following
|
||||
content.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Legacy</th>
|
||||
<th>Preact (v0.101.0+)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.BasicWidget {
|
||||
<h3>Legacy version (jQuery)</h3><pre><code class="language-text-x-trilium-auto">class HelloCenterPane extends api.BasicWidget {
|
||||
|
||||
<pre><code class="language-text-x-trilium-auto">constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
get parentWidget() { return "center-pane" }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<span>Center pane</span>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
get parentWidget() { return "center-pane" }
|
||||
module.exports = new HelloCenterPane();</code></pre>
|
||||
<p><a href="#root/_help_s8alTXmpFR61">Refresh the application</a> and the widget
|
||||
should appear underneath the content area.</p>
|
||||
<h3>Preact version</h3><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";
|
||||
|
||||
doRender() {
|
||||
this.<!--FORMULA_INLINE_1766526977514_0-->("&lt;span&gt;Center pane&lt;/span&gt;");
|
||||
}</code></pre>
|
||||
<p>}</p>
|
||||
<p>module.exports = new HelloNoteDetail();</p>
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";<p></p><p>export default defineWidget({
|
||||
export default defineWidget({
|
||||
parent: "center-pane",
|
||||
render: () => <span>Center pane from Preact.</span>
|
||||
});</p></code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p></p>
|
||||
});</code></pre>
|
||||
<p><a href="#root/_help_s8alTXmpFR61">Refresh the application</a> and the widget
|
||||
should appear underneath the content area.</p>
|
||||
<h2>Widget location (parent widget)</h2>
|
||||
|
||||
@ -30,7 +30,7 @@ class NoteTitleWidget extends api.NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
module.exports = new NoteTitleWidget();</code></pre>
|
||||
<h2>Preact widget (v0.101.0+)</h2><pre><code class="language-text-x-trilium-auto">import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
|
||||
<h2>Preact widget (v0.101.0+)</h2><pre><code class="language-text-jsx">import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
|
||||
|
||||
export default defineLauncherWidget({
|
||||
render: () => {
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
support for JSX.</p>
|
||||
<p>Preact can be used for:</p>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>, where
|
||||
<li data-list-item-id="e5c224943359315528b8eb20e0e16bf02"><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>, where
|
||||
a JSX code note is used instead of a HTML one.</li>
|
||||
<li><a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
|
||||
<li data-list-item-id="eccf375685d8f35e66c9e62619f013b60"><a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
|
||||
where JSX can be used to replace the old, jQuery-based mechanism.</li>
|
||||
</ul>
|
||||
<p>To get started, the first step is to enable JSX in the list of Code languages.
|
||||
@ -20,22 +20,23 @@
|
||||
</aside>
|
||||
<h2>Import/exports</h2>
|
||||
<p>When using Preact with JSX, there is a special syntax which provides ES-like
|
||||
imports. This <code>import</code> syntax makes way for a more intuitive that
|
||||
doesn't make use of global objects and paves the way for better auto-completion
|
||||
support that might be introduced in the future. </p>
|
||||
imports. This <code spellcheck="false">import</code> syntax makes way for
|
||||
a more intuitive that doesn't make use of global objects and paves the
|
||||
way for better auto-completion support that might be introduced in the
|
||||
future. </p>
|
||||
<h3>API imports</h3>
|
||||
<p>Instead of:</p><pre><code class="language-text-x-trilium-auto">api.showMessage("Hello");</code></pre>
|
||||
<p>the JSX version looks like this:</p><pre><code class="language-text-x-trilium-auto">import { showMessage } from "trilium:api";
|
||||
<p>Instead of:</p><pre><code class="language-text-jsx">api.showMessage("Hello");</code></pre>
|
||||
<p>the JSX version looks like this:</p><pre><code class="language-text-jsx">import { showMessage } from "trilium:api";
|
||||
showMessage("hello");</code></pre>
|
||||
<h3>Preact API imports (hooks, components)</h3>
|
||||
<p>There's a new <a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a> dedicated
|
||||
to Preact, which provides shared components that are also used by Trilium
|
||||
internally as well as hooks, for example.</p><pre><code class="language-text-x-trilium-auto">import { useState } from "trilium:preact";
|
||||
internally as well as hooks, for example.</p><pre><code class="language-text-jsx">import { useState } from "trilium:preact";
|
||||
const [ myState, setMyState ] = useState("Hi");</code></pre>
|
||||
<h3>Exporting</h3>
|
||||
<p>JSX notes can export a component for use in <a class="reference-link"
|
||||
href="#root/_help_HcABDtFCkbFN">Render Note</a> or for <a class="reference-link"
|
||||
href="#root/_help_Bqde6BvPo05g">Component libraries</a>: </p><pre><code class="language-text-x-trilium-auto">export default function() {
|
||||
href="#root/_help_Bqde6BvPo05g">Component libraries</a>: </p><pre><code class="language-text-jsx">export default function() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello world.</p>
|
||||
@ -43,13 +44,15 @@ const [ myState, setMyState ] = useState("Hi");</code></pre>
|
||||
);
|
||||
}</code></pre>
|
||||
<h3>Import/export are not required</h3>
|
||||
<p>These imports are syntactic sugar meant to replace the usage for the <code>api</code> global
|
||||
object (see <a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a>). </p>
|
||||
<p>These imports are syntactic sugar meant to replace the usage for the
|
||||
<code
|
||||
spellcheck="false">api</code>global object (see <a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a>). </p>
|
||||
<aside
|
||||
class="admonition note">
|
||||
<p>The <code>import</code> and <code>export</code> syntax work only for JSX notes.
|
||||
Standard/jQuery code notes still need to use the <code>api</code> global
|
||||
and <code>module.exports</code>.</p>
|
||||
<p>The <code spellcheck="false">import</code> and <code spellcheck="false">export</code> syntax
|
||||
work only for JSX notes. Standard/jQuery code notes still need to use the
|
||||
<code
|
||||
spellcheck="false">api</code>global and <code spellcheck="false">module.exports</code>.</p>
|
||||
</aside>
|
||||
<h2>Under the hood</h2>
|
||||
<p>Unlike JavaScript, JSX requires pre-processing to turn it into JavaScript
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
also available to <a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a> and
|
||||
<a
|
||||
class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</p>
|
||||
<p>To use these components, simply import them from <code>trilium:preact</code>:</p><pre><code class="language-text-x-trilium-auto">import { ActionButton, Button, LinkButton } from "trilium:preact";</code></pre>
|
||||
<p>and then use them:</p><pre><code class="language-text-x-trilium-auto">export default function MyRenderNote() {
|
||||
<p>To use these components, simply import them from <code spellcheck="false">trilium:preact</code>:</p><pre><code class="language-text-jsx">import { ActionButton, Button, LinkButton } from "trilium:preact";</code></pre>
|
||||
<p>and then use them:</p><pre><code class="language-text-jsx">export default function MyRenderNote() {
|
||||
const onClick = () => showMessage("A button was pressed");
|
||||
|
||||
return (
|
||||
@ -33,12 +33,12 @@
|
||||
to custom widgets and JSX render notes.</p>
|
||||
<p>To use it, simply:</p>
|
||||
<ol>
|
||||
<li>Create a render note.</li>
|
||||
<li>Create a child code note of JSX type with the content of this file:
|
||||
<li data-list-item-id="e201c26633c3655e16c1408da3d277352">Create a render note.</li>
|
||||
<li data-list-item-id="e45bace7ccf33bec8fbe7103e28e21c7f">Create a child code note of JSX type with the content of this file:
|
||||
<a
|
||||
class="reference-link" href="#root/_help_i9B4IW7b6V6z">Widget showcase</a>
|
||||
</li>
|
||||
<li>Set the <code>~renderNote</code> relation of the parent note to the child
|
||||
note.</li>
|
||||
<li>Refresh the render note to see the results.</li>
|
||||
<li data-list-item-id="e35635c3e08829316c6a1b68b80ff8ebd">Set the <code spellcheck="false">~renderNote</code> relation of the parent
|
||||
note to the child note.</li>
|
||||
<li data-list-item-id="edd02eaa6bb298854826ae5c7cd2d32d3">Refresh the render note to see the results.</li>
|
||||
</ol>
|
||||
@ -1,4 +1,4 @@
|
||||
<h2>Inline styles</h2><pre><code class="language-text-x-trilium-auto"><div style={{
|
||||
<h2>Inline styles</h2><pre><code class="language-text-jsx"><div style={{
|
||||
display: "flex",
|
||||
height: "53px",
|
||||
width: "fit-content",
|
||||
@ -9,4 +9,4 @@
|
||||
<h2>Custom CSS file</h2>
|
||||
<p>Simply create a <a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>.
|
||||
Make sure the class names are unique enough to not intersect with other
|
||||
UI elements, consider adding a prefix (e.g. <code>x-mywidget-</code>).</p>
|
||||
UI elements, consider adding a prefix (e.g. <code spellcheck="false">x-mywidget-</code>).</p>
|
||||
@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/nfSRd3FnBEBK/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/LZ93RWFQwIO3/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 _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
@ -24,7 +24,7 @@ The current date & time is <span class="date"></span>
|
||||
|
||||
Now we need to add the script. Create another <a class="reference-link" href="Code.md">Code</a>, but this time of JavaScript (frontend) language. Make sure the newly created note is a direct child of the HTML note created previously; with the following content:
|
||||
|
||||
```
|
||||
```javascript
|
||||
const $dateEl = api.$container.find(".date");
|
||||
$dateEl.text(new Date());
|
||||
```
|
||||
|
||||
@ -23,21 +23,39 @@ Wherever possible, widget examples will be both in the legacy and Preact format.
|
||||
|
||||
Let's start by creating a widget that shows a message near the content area. Follow the previous section to create a code note, and use the following content.
|
||||
|
||||
<table><thead><tr><th>Legacy</th><th>Preact (v0.101.0+)</th></tr></thead><tbody><tr><td><pre><code class="language-text-x-trilium-auto">class HelloNoteDetail extends api.BasicWidget {
|
||||
### Legacy version (jQuery)
|
||||
|
||||
<pre><code class="language-text-x-trilium-auto">constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
```
|
||||
class HelloCenterPane extends api.BasicWidget {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
get parentWidget() { return "center-pane" }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<span>Center pane</span>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
get parentWidget() { return "center-pane" }
|
||||
module.exports = new HelloCenterPane();
|
||||
```
|
||||
|
||||
doRender() {
|
||||
this.<!--FORMULA_INLINE_1766526977514_0-->("&lt;span&gt;Center pane&lt;/span&gt;");
|
||||
}</code></pre><p>}</p><p>module.exports = new HelloNoteDetail();</p></code></pre></td><td><pre><code class="language-text-x-trilium-auto">import { defineWidget } from "trilium:preact";<p></p><p>export default defineWidget({
|
||||
[Refresh the application](../../Troubleshooting/Refreshing%20the%20application.md) and the widget should appear underneath the content area.
|
||||
|
||||
### Preact version
|
||||
|
||||
```
|
||||
import { defineWidget } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "center-pane",
|
||||
render: () => <span>Center pane from Preact.</span>
|
||||
});</p></code></pre></td></tr></tbody></table>
|
||||
render: () => <span>Center pane from Preact.</span>
|
||||
});
|
||||
```
|
||||
|
||||
[Refresh the application](../../Troubleshooting/Refreshing%20the%20application.md) and the widget should appear underneath the content area.
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ module.exports = new NoteTitleWidget();
|
||||
|
||||
## Preact widget (v0.101.0+)
|
||||
|
||||
```
|
||||
```jsx
|
||||
import { defineLauncherWidget, useActiveNoteContext } from "trilium:preact";
|
||||
|
||||
export default defineLauncherWidget({
|
||||
|
||||
@ -19,13 +19,13 @@ When using Preact with JSX, there is a special syntax which provides ES-like imp
|
||||
|
||||
Instead of:
|
||||
|
||||
```
|
||||
```jsx
|
||||
api.showMessage("Hello");
|
||||
```
|
||||
|
||||
the JSX version looks like this:
|
||||
|
||||
```
|
||||
```jsx
|
||||
import { showMessage } from "trilium:api";
|
||||
showMessage("hello");
|
||||
```
|
||||
@ -34,7 +34,7 @@ showMessage("hello");
|
||||
|
||||
There's a new <a class="reference-link" href="../Script%20API.md">Script API</a> dedicated to Preact, which provides shared components that are also used by Trilium internally as well as hooks, for example.
|
||||
|
||||
```
|
||||
```jsx
|
||||
import { useState } from "trilium:preact";
|
||||
const [ myState, setMyState ] = useState("Hi");
|
||||
```
|
||||
@ -43,7 +43,7 @@ const [ myState, setMyState ] = useState("Hi");
|
||||
|
||||
JSX notes can export a component for use in <a class="reference-link" href="../../Note%20Types/Render%20Note.md">Render Note</a> or for <a class="reference-link" href="Preact/Component%20libraries.md">Component libraries</a>:
|
||||
|
||||
```
|
||||
```jsx
|
||||
export default function() {
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -5,13 +5,13 @@ Trilium comes with its own set of Preact components, some of which are also avai
|
||||
|
||||
To use these components, simply import them from `trilium:preact`:
|
||||
|
||||
```
|
||||
```jsx
|
||||
import { ActionButton, Button, LinkButton } from "trilium:preact";
|
||||
```
|
||||
|
||||
and then use them:
|
||||
|
||||
```
|
||||
```jsx
|
||||
export default function MyRenderNote() {
|
||||
const onClick = () => showMessage("A button was pressed");
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# CSS
|
||||
## Inline styles
|
||||
|
||||
```
|
||||
```jsx
|
||||
<div style={{
|
||||
display: "flex",
|
||||
height: "53px",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user