Compare commits

...

22 Commits

Author SHA1 Message Date
Elian Doran
e4dcc0f768 chore(client): fix typecheck issues 2026-01-02 20:45:28 +02:00
Elian Doran
74ab591214 chore(package): automatically build share theme & PDF viewer 2026-01-02 20:38:18 +02:00
Elian Doran
7bd7996893 feat(revisions): use customized PDF viewer 2026-01-02 20:17:27 +02:00
Elian Doran
505ae4eeb5 chore(revisions): remove "Preview" heading 2026-01-02 20:02:39 +02:00
Elian Doran
951d6d3ce3 feat(revisions): display PDF preview for revisions 2026-01-02 20:02:13 +02:00
Elian Doran
5ff7764699 style(revisions): prevent revision list from overflowing 2026-01-02 19:49:20 +02:00
Elian Doran
0d74998625 style(revisions): prevent buttons from overflowing 2026-01-02 19:47:07 +02:00
Elian Doran
29b70a12bd feat(revisions): display video preview for revisions 2026-01-02 19:44:23 +02:00
Elian Doran
d84150e97b feat(revisions): display audio preview for revisions 2026-01-02 19:21:51 +02:00
Elian Doran
2b2ef4251f style(revisions): minor spacing adjustments to file table 2026-01-02 18:44:06 +02:00
Elian Doran
2840ea0f38 chore(revisions): display a message when a preview is not available 2026-01-02 18:42:05 +02:00
Elian Doran
542d485267 fix(revisions): missing meta information about revisions 2026-01-02 18:34:45 +02:00
Elian Doran
cdd4fbc81d refactor(client): fix lint warnings in revisions modal 2026-01-02 18:23:04 +02:00
Elian Doran
bfdddab0a0 refactor(client): format revisions dialog 2026-01-02 18:20:55 +02:00
Elian Doran
44d1d01105 fix(pdfjs): preferences don't account for ntxId or noteId 2026-01-02 18:08:25 +02:00
Elian Doran
120bb09171 fix(pdfjs): saving doesn't account for ntxId or noteId 2026-01-02 17:57:43 +02:00
Elian Doran
b7af99c671 refactor(pdfjs): add type safety for messages 2026-01-02 17:57:28 +02:00
Elian Doran
869e0b3973 docs(user): mention updates to the new PDF functions 2026-01-02 12:49:30 +02:00
Elian Doran
b68613dee4 feat(share): integrate custom pdf.js viewer 2026-01-02 12:13:31 +02:00
Elian Doran
ce0f32e7d5 chore(client/pdfjs): remove open file 2026-01-02 11:44:33 +02:00
Elian Doran
78bc9b59c2 chore(client/pdfjs): remove download button from toolbar 2026-01-02 11:41:58 +02:00
Elian Doran
23cf3d2923 feat(client/pdfjs): rewrite download button 2026-01-02 11:40:32 +02:00
53 changed files with 1054 additions and 557 deletions

View File

@@ -382,7 +382,8 @@ export type CommandMappings = {
reloadTextEditor: CommandData;
chooseNoteType: CommandData & {
callback: ChooseNoteTypeCallback
}
};
customDownload: CommandData;
};
type EventMappings = {

View File

@@ -32,11 +32,11 @@ export interface NoteContextDataMap {
requestThumbnail(page: number): void;
};
pdfAttachments: {
attachments: Array<{ filename: string; size: number }>;
attachments: PdfAttachment[];
downloadAttachment(filename: string): void;
};
pdfLayers: {
layers: Array<{ id: string; name: string; visible: boolean }>;
layers: PdfLayer[];
toggleLayer(layerId: string, visible: boolean): void;
};
}

View File

@@ -1,35 +1,35 @@
import { applyModals } from "./layout_commons.js";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import FlexContainer from "../widgets/containers/flex_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import type AppContext from "../components/app_context.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import NoteList from "../widgets/collections/NoteList.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import ContentHeader from "../widgets/containers/content_header.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import QuickSearchWidget from "../widgets/quick_search.js";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import RootContainer from "../widgets/containers/root_container.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfoWidget from "../widgets/shared_info.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import TabRowWidget from "../widgets/tab_row.js";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import type AppContext from "../components/app_context.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
import { applyModals } from "./layout_commons.js";
const MOBILE_CSS = `
<style>
@@ -194,11 +194,11 @@ export default class MobileLayout {
}
function FilePropertiesWrapper() {
const { note } = useNoteContext();
const { note, ntxId } = useNoteContext();
return (
<div>
{note?.type === "file" && <FilePropertiesTab note={note} />}
{note?.type === "file" && <FilePropertiesTab note={note} ntxId={ntxId} />}
</div>
);
}

View File

@@ -1,18 +1,19 @@
import renderService from "./render.js";
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
import WheelZoom from 'vanilla-js-wheel-zoom';
import FAttachment from "../entities/fattachment.js";
import FNote from "../entities/fnote.js";
import imageContextMenuService from "../menus/image_context_menu.js";
import { t } from "../services/i18n.js";
import renderText from "./content_renderer_text.js";
import renderDoc from "./doc_renderer.js";
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
import openService from "./open.js";
import protectedSessionService from "./protected_session.js";
import protectedSessionHolder from "./protected_session_holder.js";
import openService from "./open.js";
import utils from "./utils.js";
import FNote from "../entities/fnote.js";
import FAttachment from "../entities/fattachment.js";
import imageContextMenuService from "../menus/image_context_menu.js";
import renderService from "./render.js";
import { applySingleBlockSyntaxHighlight } from "./syntax_highlight.js";
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
import renderDoc from "./doc_renderer.js";
import { t } from "../services/i18n.js";
import WheelZoom from 'vanilla-js-wheel-zoom';
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
import renderText from "./content_renderer_text.js";
import utils from "./utils.js";
let idCounter = 1;
@@ -152,7 +153,7 @@ function renderImage(entity: FNote | FAttachment, $renderedContent: JQuery<HTMLE
const $img = $("<img>")
.attr("src", url || "")
.attr("id", "attachment-image-" + idCounter++)
.attr("id", `attachment-image-${ idCounter++}`)
.css("max-width", "100%");
$renderedContent.append($img);
@@ -231,14 +232,14 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
$downloadButton.on("click", (e) => {
e.stopPropagation();
openService.downloadFileNote(entity.noteId)
openService.downloadFileNote(entity, null, null);
});
$openButton.on("click", async (e) => {
const iconEl = $openButton.find("> .bx");
iconEl.removeClass("bx bx-link-external");
iconEl.addClass("bx bx-loader spin");
e.stopPropagation();
await openService.openNoteExternally(entity.noteId, entity.mime)
await openService.openNoteExternally(entity.noteId, entity.mime);
iconEl.removeClass("bx bx-loader spin");
iconEl.addClass("bx bx-link-external");
});
@@ -266,7 +267,7 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery
try {
await loadElkIfNeeded(mermaid, content);
const { svg } = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content);
const { svg } = await mermaid.mermaidAPI.render(`in-mermaid-graph-${ idCounter++}`, content);
$renderedContent.append($(postprocessMermaidSvg(svg)));
} catch (e) {

View File

@@ -1,6 +1,8 @@
import utils from "./utils.js";
import Component from "../components/component.js";
import FNote from "../entities/fnote.js";
import options from "./options.js";
import server from "./server.js";
import utils from "./utils.js";
type ExecFunction = (command: string, cb: (err: string, stdout: string, stderror: string) => void) => void;
@@ -36,9 +38,14 @@ function download(url: string) {
}
}
export function downloadFileNote(noteId: string) {
const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
export function downloadFileNote(note: FNote, parentComponent: Component | null, ntxId: string | null | undefined) {
if (note.type === "file" && note.mime === "application/pdf" && parentComponent) {
// Special handling, manages its own downloading process.
parentComponent.triggerEvent("customDownload", { ntxId });
return;
}
const url = `${getFileUrl("notes", note.noteId)}?${Date.now()}`; // don't use cache
download(url);
}
@@ -97,7 +104,7 @@ async function openCustom(type: string, entityId: string, mime: string) {
// Note that the path separator must be \ instead of /
filePath = filePath.replace(/\//g, "\\");
}
const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ` + filePath;
const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ${ filePath}`;
exec(command, (err, stdout, stderr) => {
if (err) {
console.error("Open Note custom: ", err);
@@ -131,10 +138,10 @@ export function getUrlForDownload(url: string) {
if (utils.isElectron()) {
// electron needs absolute URL, so we extract current host, port, protocol
return `${getHost()}/${url}`;
} else {
// web server can be deployed on subdomain, so we need to use a relative path
return url;
}
// web server can be deployed on subdomain, so we need to use a relative path
return url;
}
function canOpenInBrowser(mime: string) {

View File

@@ -295,7 +295,6 @@
"download_button": "Download",
"mime": "MIME: ",
"file_size": "File size:",
"preview": "Preview:",
"preview_not_available": "Preview isn't available for this note type."
},
"sort_child_notes": {

View File

@@ -1,3 +1,15 @@
type HistoryData = {
files: {
fingerprint: string;
page: number;
zoom: string;
scrollLeft: number;
scrollTop: number;
rotation: number;
sidebarView: number;
}[];
};
interface Window {
/**
* By default, pdf.js will try to store information about the opened PDFs such as zoom and scroll position in local storage.
@@ -5,11 +17,100 @@ interface Window {
* This variable represents the direct content used by the pdf.js viewer in its local storage key, but in plain JS object format.
* The variable must be set early at startup, before pdf.js fully initializes.
*/
TRILIUM_VIEW_HISTORY_STORE?: object;
TRILIUM_VIEW_HISTORY_STORE?: HistoryData;
/**
* If set to true, hides the pdf.js viewer default sidebar containing the outline, page navigation, etc.
* This needs to be set early in the main method.
*/
TRILIUM_HIDE_SIDEBAR?: boolean;
TRILIUM_NOTE_ID: string;
TRILIUM_NTX_ID: string | null | undefined;
}
interface PdfOutlineItem {
title: string;
level: number;
dest: unknown;
id: string;
items: PdfOutlineItem[];
}
interface WithContext {
ntxId: string;
noteId: string | null | undefined;
}
interface PdfDocumentModifiedMessage extends WithContext {
type: "pdfjs-viewer-document-modified";
data: Uint8Array<ArrayBufferLike>;
}
interface PdfSaveViewHistoryMessage extends WithContext {
type: "pdfjs-viewer-save-view-history";
data: string;
}
interface PdfViewerTocMessage {
type: "pdfjs-viewer-toc";
data: PdfOutlineItem[];
}
interface PdfViewerActiveHeadingMessage {
type: "pdfjs-viewer-active-heading";
headingId: string;
}
interface PdfViewerPageInfoMessage {
type: "pdfjs-viewer-page-info";
totalPages: number;
currentPage: number;
}
interface PdfViewerCurrentPageMessage {
type: "pdfjs-viewer-current-page";
currentPage: number;
}
interface PdfViewerThumbnailMessage {
type: "pdfjs-viewer-thumbnail";
pageNumber: number;
dataUrl: string;
}
interface PdfAttachment {
filename: string;
size: number;
}
interface PdfViewerAttachmentsMessage {
type: "pdfjs-viewer-attachments";
attachments: PdfAttachment[];
downloadAttachment?: (fileName: string) => void;
}
interface PdfLayer {
id: string;
name: string;
visible: boolean;
}
interface PdfViewerLayersMessage {
type: "pdfjs-viewer-layers";
layers: PdfLayer[];
toggleLayer?: (layerId: string, visible: boolean) => void;
}
type PdfMessageEvent = MessageEvent<
PdfDocumentModifiedMessage
| PdfSaveViewHistoryMessage
| PdfViewerTocMessage
| PdfViewerActiveHeadingMessage
| PdfViewerPageInfoMessage
| PdfViewerCurrentPageMessage
| PdfViewerThumbnailMessage
| PdfViewerAttachmentsMessage
| PdfViewerLayersMessage
>;

View File

@@ -14,7 +14,7 @@ body.mobile .revisions-dialog {
flex-grow: 1;
width: 100%;
}
.modal-body {
height: fit-content !important;
flex-direction: column;
@@ -24,7 +24,7 @@ body.mobile .revisions-dialog {
.modal-footer {
font-size: 0.9em;
}
.revision-list {
height: fit-content !important;
max-height: 20vh;
@@ -32,7 +32,7 @@ body.mobile .revisions-dialog {
padding: 0 1em;
flex-shrink: 0;
}
.modal-body > .revision-content-wrapper {
flex-grow: 1;
max-width: unset !important;
@@ -40,24 +40,68 @@ body.mobile .revisions-dialog {
margin: 0;
display: block !important;
}
.modal-body > .revision-content-wrapper > div:first-of-type {
flex-direction: column;
}
.revision-title {
font-size: 1rem;
}
.revision-title-buttons {
text-align: center;
display: flex;
gap: 0.25em;
flex-wrap: wrap;
}
.revision-content {
padding: 0.5em;
height: fit-content;
}
}
}
.revisions-dialog {
.revision-title-buttons {
flex-shrink: 0;
}
.revision-list {
flex-shrink: 0;
}
.revision-content.type-file {
display: flex;
min-width: 0;
min-height: 0;
flex-grow: 1;
.file-preview-table {
th,
td {
padding: 0.25em 0;
}
}
.revision-file-preview {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
flex-grow: 1;
}
.revision-file-preview-content {
flex-grow: 1;
min-height: 0;
display: flex;
flex-direction: column;
> * {
height: 100%;
}
}
}
}

View File

@@ -1,26 +1,31 @@
import type { RevisionPojo, RevisionItem } from "@triliumnext/commons";
import "./revisions.css";
import type { RevisionItem,RevisionPojo } from "@triliumnext/commons";
import clsx from "clsx";
import { diffWords } from "diff";
import type { CSSProperties } from "preact/compat";
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import FNote from "../../entities/fnote";
import dialog from "../../services/dialog";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { renderMathInElement } from "../../services/math";
import open from "../../services/open";
import options from "../../services/options";
import protected_session_holder from "../../services/protected_session_holder";
import server from "../../services/server";
import toast from "../../services/toast";
import Button from "../react/Button";
import FormToggle from "../react/FormToggle";
import Modal from "../react/Modal";
import FormList, { FormListItem } from "../react/FormList";
import utils from "../../services/utils";
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
import protected_session_holder from "../../services/protected_session_holder";
import { renderMathInElement } from "../../services/math";
import type { CSSProperties } from "preact/compat";
import open from "../../services/open";
import ActionButton from "../react/ActionButton";
import options from "../../services/options";
import Button from "../react/Button";
import FormList, { FormListItem } from "../react/FormList";
import FormToggle from "../react/FormToggle";
import { useTriliumEvent } from "../react/hooks";
import { diffWords } from "diff";
import "./revisions.css";
import Modal from "../react/Modal";
import { RawHtmlBlock } from "../react/RawHtml";
import PdfViewer from "../type_widgets/file/PdfViewer";
export default function RevisionsDialog() {
const [ note, setNote ] = useState<FNote>();
@@ -47,7 +52,7 @@ export default function RevisionsDialog() {
setRevisions(undefined);
setNoteContent(undefined);
}
}, [ note?.noteId, refreshCounter ]);
}, [ note, refreshCounter ]);
if (revisions?.length && !currentRevision) {
setCurrentRevision(revisions[0]);
@@ -102,38 +107,38 @@ export default function RevisionsDialog() {
setRevisions(undefined);
}}
show={shown}
>
<RevisionsList
revisions={revisions ?? []}
onSelect={(revisionId) => {
const correspondingRevision = (revisions ?? []).find((r) => r.revisionId === revisionId);
if (correspondingRevision) {
setCurrentRevision(correspondingRevision);
}
}}
currentRevision={currentRevision}
/>
>
<RevisionsList
revisions={revisions ?? []}
onSelect={(revisionId) => {
const correspondingRevision = (revisions ?? []).find((r) => r.revisionId === revisionId);
if (correspondingRevision) {
setCurrentRevision(correspondingRevision);
}
}}
currentRevision={currentRevision}
/>
<div className="revision-content-wrapper" style={{
flexGrow: "1",
marginInlineStart: "20px",
display: "flex",
flexDirection: "column",
maxWidth: "calc(100% - 150px)",
minWidth: 0
}}>
<RevisionPreview
noteContent={noteContent}
revisionItem={currentRevision}
showDiff={showDiff}
setShown={setShown}
onRevisionDeleted={() => {
setRefreshCounter(c => c + 1);
setCurrentRevision(undefined);
}} />
</div>
<div className="revision-content-wrapper" style={{
flexGrow: "1",
marginInlineStart: "20px",
display: "flex",
flexDirection: "column",
maxWidth: "calc(100% - 150px)",
minWidth: 0
}}>
<RevisionPreview
noteContent={noteContent}
revisionItem={currentRevision}
showDiff={showDiff}
setShown={setShown}
onRevisionDeleted={() => {
setRefreshCounter(c => c + 1);
setCurrentRevision(undefined);
}} />
</div>
</Modal>
)
);
}
function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: RevisionItem[], onSelect: (val: string) => void, currentRevision?: RevisionItem }) {
@@ -141,6 +146,7 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re
<FormList onSelect={onSelect} fullHeight wrapperClassName="revision-list">
{revisions.map((item) =>
<FormListItem
key={item.revisionId}
value={item.revisionId}
active={currentRevision && item.revisionId === currentRevision.revisionId}
>
@@ -202,14 +208,17 @@ function RevisionPreview({noteContent, revisionItem, showDiff, setShown, onRevis
text={t("revisions.download_button")}
onClick={() => {
if (revisionItem.revisionId) {
open.downloadRevision(revisionItem.noteId, revisionItem.revisionId)}
}
open.downloadRevision(revisionItem.noteId, revisionItem.revisionId);}
}
}/>
</>
}
</div>)}
</div>
<div className="revision-content use-tn-links selectable-text" style={{ overflow: "auto", wordBreak: "break-word" }}>
<div
className={clsx("revision-content use-tn-links selectable-text", `type-${revisionItem?.type}`)}
style={{ overflow: "auto", wordBreak: "break-word" }}
>
<RevisionContent noteContent={noteContent} revisionItem={revisionItem} fullRevision={fullRevision} showDiff={showDiff}/>
</div>
</>
@@ -230,16 +239,16 @@ const CODE_STYLE: CSSProperties = {
function RevisionContent({ noteContent, revisionItem, fullRevision, showDiff }: { noteContent?:string, revisionItem?: RevisionItem, fullRevision?: RevisionPojo, showDiff: boolean}) {
const content = fullRevision?.content;
if (!revisionItem || !content) {
if (!revisionItem || !fullRevision) {
return <></>;
}
if (showDiff) {
return <RevisionContentDiff noteContent={noteContent} itemContent={content} itemType={revisionItem.type}/>
return <RevisionContentDiff noteContent={noteContent} itemContent={content} itemType={revisionItem.type}/>;
}
switch (revisionItem.type) {
case "text":
return <RevisionContentText content={content} />
return <RevisionContentText content={content} />;
case "code":
return <pre style={CODE_STYLE}>{content}</pre>;
case "image":
@@ -256,28 +265,11 @@ function RevisionContent({ noteContent, revisionItem, fullRevision, showDiff }:
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
return <img
src={`data:${fullRevision.mime};base64,${fullRevision.content}`}
style={IMAGE_STYLE} />
style={IMAGE_STYLE} />;
}
}
case "file":
return <table cellPadding="10">
<tr>
<th>{t("revisions.mime")}</th>
<td>{revisionItem.mime}</td>
</tr>
<tr>
<th>{t("revisions.file_size")}</th>
<td>{revisionItem.contentLength && utils.formatSize(revisionItem.contentLength)}</td>
</tr>
{fullRevision.content &&
<tr>
<td colspan={2}>
<strong>{t("revisions.preview")}</strong>
<pre className="file-preview-content" style={CODE_STYLE}>{fullRevision.content}</pre>
</td>
</tr>
}
</table>;
return <FilePreview fullRevision={fullRevision} revisionItem={revisionItem} />;
case "canvas":
case "mindMap":
case "mermaid": {
@@ -287,7 +279,7 @@ function RevisionContent({ noteContent, revisionItem, fullRevision, showDiff }:
style={IMAGE_STYLE} />;
}
default:
return <>{t("revisions.preview_not_available")}</>
return <>{t("revisions.preview_not_available")}</>;
}
}
@@ -298,7 +290,7 @@ function RevisionContentText({ content }: { content: string | Buffer<ArrayBuffer
renderMathInElement(contentRef.current, { trust: true });
}
}, [content]);
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
return <RawHtmlBlock containerRef={contentRef} className="ck-content" html={content as string} />;
}
function RevisionContentDiff({ noteContent, itemContent, itemType }: {
@@ -330,9 +322,9 @@ function RevisionContentDiff({ noteContent, itemContent, itemType }: {
return `<span class="revision-diff-added">${utils.escapeHtml(part.value)}</span>`;
} else if (part.removed) {
return `<span class="revision-diff-removed">${utils.escapeHtml(part.value)}</span>`;
} else {
return utils.escapeHtml(part.value);
}
return utils.escapeHtml(part.value);
}).join("");
if (contentRef.current) {
@@ -340,7 +332,7 @@ function RevisionContentDiff({ noteContent, itemContent, itemType }: {
}
}, [noteContent, itemContent, itemType]);
return <div ref={contentRef} className="ck-content" style={{ whiteSpace: "pre-wrap" }}></div>;
return <div ref={contentRef} className="ck-content" style={{ whiteSpace: "pre-wrap" }} />;
}
function RevisionFooter({ note }: { note?: FNote }) {
@@ -348,7 +340,7 @@ function RevisionFooter({ note }: { note?: FNote }) {
return <></>;
}
let revisionsNumberLimit: number | string = parseInt(note?.getLabelValue("versioningLimit") ?? "");
let revisionsNumberLimit: number | string = parseInt(note?.getLabelValue("versioningLimit") ?? "", 10);
if (!Number.isInteger(revisionsNumberLimit)) {
revisionsNumberLimit = options.getInt("revisionSnapshotNumberLimit") ?? 0;
}
@@ -370,10 +362,67 @@ function RevisionFooter({ note }: { note?: FNote }) {
</>;
}
function FilePreview({ revisionItem, fullRevision }: { revisionItem: RevisionItem, fullRevision: RevisionPojo }) {
return (
<div className="revision-file-preview">
<table className="file-preview-table">
<tbody>
<tr>
<th>{t("revisions.mime")}</th>
<td>{revisionItem.mime}</td>
</tr>
<tr>
<th>{t("revisions.file_size")}</th>
<td>{revisionItem.contentLength && utils.formatSize(revisionItem.contentLength)}</td>
</tr>
</tbody>
</table>
<div class="revision-file-preview-content">
<FilePreviewInner revisionItem={revisionItem} fullRevision={fullRevision} />
</div>
</div>
);
}
function FilePreviewInner({ revisionItem, fullRevision }: { revisionItem: RevisionItem, fullRevision: RevisionPojo }) {
if (revisionItem.mime.startsWith("audio/")) {
return (
<audio
src={`api/revisions/${revisionItem.revisionId}/download`}
controls
/>
);
}
if (revisionItem.mime.startsWith("video/")) {
return (
<video
src={`api/revisions/${revisionItem.revisionId}/download`}
controls
/>
);
}
if (revisionItem.mime === "application/pdf") {
return (
<PdfViewer
pdfUrl={`../../api/revisions/${revisionItem.revisionId}/download`}
/>
);
}
if (fullRevision.content) {
return <pre className="file-preview-content" style={CODE_STYLE}>{fullRevision.content}</pre>;
}
return t("revisions.preview_not_available");
}
async function getNote(noteId?: string | null) {
if (noteId) {
return await froca.getNote(noteId);
} else {
return appContext.tabManager.getActiveContextNote();
}
return appContext.tabManager.getActiveContextNote();
}

View File

@@ -1,3 +1,5 @@
import { useContext } from "preact/hooks";
import FNote from "../../entities/fnote";
import { t } from "../../services/i18n";
import { downloadFileNote, openNoteExternally } from "../../services/open";
@@ -8,11 +10,14 @@ import { formatSize } from "../../services/utils";
import Button from "../react/Button";
import { FormFileUploadButton } from "../react/FormFileUpload";
import { useNoteBlob, useNoteLabel } from "../react/hooks";
import { ParentComponent } from "../react/react_utils";
import { TabContext } from "./ribbon-interface";
export default function FilePropertiesTab({ note }: { note?: FNote | null }) {
export default function FilePropertiesTab({ note, ntxId }: Pick<TabContext, "note" | "ntxId">) {
const [ originalFileName ] = useNoteLabel(note, "originalFileName");
const canAccessProtectedNote = !note?.isProtected || protected_session_holder.isProtectedSessionAvailable();
const blob = useNoteBlob(note);
const parentComponent = useContext(ParentComponent);
return (
<div className="file-properties-widget">
@@ -40,7 +45,7 @@ export default function FilePropertiesTab({ note }: { note?: FNote | null }) {
text={t("file_properties.download")}
primary
disabled={!canAccessProtectedNote}
onClick={() => downloadFileNote(note.noteId)}
onClick={() => downloadFileNote(note, parentComponent, ntxId)}
/>
<Button

View File

@@ -44,7 +44,7 @@ export default function ImagePropertiesTab({ note, ntxId }: TabContext) {
text={t("image_properties.download")}
icon="bx bx-download"
primary
onClick={() => downloadFileNote(note.noteId)}
onClick={() => downloadFileNote(note, parentComponent, ntxId)}
/>
<Button

View File

@@ -135,13 +135,13 @@ function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) {
);
}
function DownloadFileButton({ note }: NoteActionsCustomInnerProps) {
function DownloadFileButton({ note, parentComponent, ntxId }: NoteActionsCustomInnerProps) {
return (
<ActionButton
icon="bx bx-download"
text={t("file_properties.download")}
disabled={!note.isContentAvailable()}
onClick={() => downloadFileNote(note.noteId)}
onClick={() => downloadFileNote(note, parentComponent, ntxId)}
/>
);
}

View File

@@ -1,5 +1,4 @@
import { RefObject } from "preact";
import { useCallback, useEffect, useRef } from "preact/hooks";
import { useEffect, useRef } from "preact/hooks";
import appContext from "../../../components/app_context";
import type NoteContext from "../../../components/note_context";
@@ -7,14 +6,8 @@ import FBlob from "../../../entities/fblob";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
import { useViewModeConfig } from "../../collections/NoteList";
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
const VARIABLE_WHITELIST = new Set([
"root-background",
"main-background-color",
"main-border-color",
"main-text-color"
]);
import { useTriliumEvent } from "../../react/hooks";
import PdfViewer from "./PdfViewer";
export default function PdfPreview({ note, blob, componentId, noteContext }: {
note: FNote;
@@ -23,20 +16,21 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
componentId: string | undefined;
}) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const { onLoad } = useStyleInjection(iframeRef);
const historyConfig = useViewModeConfig(note, "pdfHistory");
const [ locale ] = useTriliumOption("locale");
const [ newLayout ] = useTriliumOptionBool("newLayout");
const historyConfig = useViewModeConfig<HistoryData>(note, "pdfHistory");
useEffect(() => {
function handleMessage(event: MessageEvent) {
if (event.data?.type === "pdfjs-viewer-document-modified" && event.data?.data) {
const blob = new Blob([event.data.data], { type: note.mime });
server.upload(`notes/${note.noteId}/file`, new File([blob], note.title, { type: note.mime }), componentId);
function handleMessage(event: PdfMessageEvent) {
if (event.data?.type === "pdfjs-viewer-document-modified") {
const blob = new Blob([event.data.data as Uint8Array<ArrayBuffer>], { type: note.mime });
if (event.data.noteId === note.noteId && event.data.ntxId === noteContext.ntxId) {
server.upload(`notes/${note.noteId}/file`, new File([blob], note.title, { type: note.mime }), componentId);
}
}
if (event.data.type === "pdfjs-viewer-save-view-history" && event.data?.data) {
historyConfig?.storeFn(JSON.parse(event.data.data));
if (event.data.noteId === note.noteId && event.data.ntxId === noteContext.ntxId) {
historyConfig?.storeFn(JSON.parse(event.data.data));
}
}
if (event.data.type === "pdfjs-viewer-toc") {
@@ -170,83 +164,30 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
}
}, [ iframeRef.current?.contentWindow, noteContext ]);
useTriliumEvent("customDownload", ({ ntxId }) => {
if (ntxId !== noteContext.ntxId) return;
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-request-download"
});
});
return (historyConfig &&
<iframe
<PdfViewer
iframeRef={iframeRef}
tabIndex={300}
ref={iframeRef}
class="pdf-preview"
src={`pdfjs/web/viewer.html?file=../../api/notes/${note.noteId}/open&lang=${locale}&sidebar=${newLayout ? "0" : "1"}`}
pdfUrl={`../../api/notes/${note.noteId}/open`}
onLoad={() => {
const win = iframeRef.current?.contentWindow;
if (win) {
win.TRILIUM_VIEW_HISTORY_STORE = historyConfig.config;
win.TRILIUM_NOTE_ID = note.noteId;
win.TRILIUM_NTX_ID = noteContext.ntxId;
}
onLoad();
}}
/>
);
}
function useStyleInjection(iframeRef: RefObject<HTMLIFrameElement>) {
const styleRef = useRef<HTMLStyleElement | null>(null);
// First load.
const onLoad = useCallback(() => {
const doc = iframeRef.current?.contentDocument;
if (!doc) return;
const style = doc.createElement('style');
style.id = 'client-root-vars';
style.textContent = cssVarsToString(getRootCssVariables());
styleRef.current = style;
doc.head.appendChild(style);
}, [ iframeRef ]);
// React to changes.
useEffect(() => {
const listener = () => {
styleRef.current!.textContent = cssVarsToString(getRootCssVariables());
};
const media = window.matchMedia("(prefers-color-scheme: dark)");
media.addEventListener("change", listener);
return () => media.removeEventListener("change", listener);
}, [ iframeRef ]);
return {
onLoad
};
}
function getRootCssVariables() {
const styles = getComputedStyle(document.documentElement);
const vars: Record<string, string> = {};
for (let i = 0; i < styles.length; i++) {
const prop = styles[i];
if (prop.startsWith('--') && VARIABLE_WHITELIST.has(prop.substring(2))) {
vars[`--tn-${prop.substring(2)}`] = styles.getPropertyValue(prop).trim();
}
}
return vars;
}
function cssVarsToString(vars: Record<string, string>) {
return `:root {\n${Object.entries(vars)
.map(([k, v]) => ` ${k}: ${v};`)
.join('\n')}\n}`;
}
interface PdfOutlineItem {
title: string;
level: number;
dest: unknown;
id: string;
items: PdfOutlineItem[];
}
interface PdfHeading {
level: number;
text: string;

View File

@@ -0,0 +1,90 @@
import type { HTMLAttributes, RefObject } from "preact";
import { useCallback, useEffect, useRef } from "preact/hooks";
import { useSyncedRef, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
const VARIABLE_WHITELIST = new Set([
"root-background",
"main-background-color",
"main-border-color",
"main-text-color"
]);
interface PdfViewerProps extends Pick<HTMLAttributes<HTMLIFrameElement>, "tabIndex"> {
iframeRef?: RefObject<HTMLIFrameElement>;
/** Note: URLs are relative to /pdfjs/web. */
pdfUrl: string;
onLoad?(): void;
}
/**
* Reusable component displaying a PDF. The PDF needs to be provided via a URL.
*/
export default function PdfViewer({ iframeRef: externalIframeRef, pdfUrl, onLoad }: PdfViewerProps) {
const iframeRef = useSyncedRef(externalIframeRef, null);
const [ locale ] = useTriliumOption("locale");
const [ newLayout ] = useTriliumOptionBool("newLayout");
const injectStyles = useStyleInjection(iframeRef);
return (
<iframe
ref={iframeRef}
class="pdf-preview"
src={`pdfjs/web/viewer.html?file=${pdfUrl}&lang=${locale}&sidebar=${newLayout ? "0" : "1"}`}
onLoad={() => {
injectStyles();
onLoad?.();
}}
/>
);
}
function useStyleInjection(iframeRef: RefObject<HTMLIFrameElement>) {
const styleRef = useRef<HTMLStyleElement | null>(null);
// First load.
const onLoad = useCallback(() => {
const doc = iframeRef.current?.contentDocument;
if (!doc) return;
const style = doc.createElement('style');
style.id = 'client-root-vars';
style.textContent = cssVarsToString(getRootCssVariables());
styleRef.current = style;
doc.head.appendChild(style);
}, [ iframeRef ]);
// React to changes.
useEffect(() => {
const listener = () => {
styleRef.current!.textContent = cssVarsToString(getRootCssVariables());
};
const media = window.matchMedia("(prefers-color-scheme: dark)");
media.addEventListener("change", listener);
return () => media.removeEventListener("change", listener);
}, [ iframeRef ]);
return onLoad;
}
function getRootCssVariables() {
const styles = getComputedStyle(document.documentElement);
const vars: Record<string, string> = {};
for (let i = 0; i < styles.length; i++) {
const prop = styles[i];
if (prop.startsWith('--') && VARIABLE_WHITELIST.has(prop.substring(2))) {
vars[`--tn-${prop.substring(2)}`] = styles.getPropertyValue(prop).trim();
}
}
return vars;
}
function cssVarsToString(vars: Record<string, string>) {
return `:root {\n${Object.entries(vars)
.map(([k, v]) => ` ${k}: ${v};`)
.join('\n')}\n}`;
}

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,7 @@
<figcaption>Screenshot of the note contextual menu indicating the “Export as PDF”
option.</figcaption>
</figure>
<h2>Printing</h2>
<p>This feature allows printing of notes. It works on both the desktop client,
but also on the web.</p>
@@ -52,27 +53,18 @@ class="admonition note">
the default application might seem incorrect (such as opening in GIMP).
This is because it uses Gnome's “Recommended applications” list.</p>
<p>To solve this, you can change the recommended application for PDFs via
this command line. First, list the available applications via <code spellcheck="false">gio mime application/pdf</code> and
this command line. First, list the available applications via <code>gio mime application/pdf</code> and
then set the desired one. For example to use GNOME's Evince:</p><pre><code class="language-text-x-trilium-auto">gio mime application/pdf</code></pre>
<h3>Customizing exporting as PDF</h3>
<p>When exporting to PDF, there are no customizable settings such as page
orientation, size. However, there are a few&nbsp;<a class="reference-link"
href="#root/_help_zEY4DaJG4YT5">Attributes</a>&nbsp;to adjust some of the settings:</p>
<ul>
<li data-list-item-id="eec9214505f224e0ef7d50955357f6f52">To print in landscape mode instead of portrait (useful for big diagrams
or slides), add <code spellcheck="false">#printLandscape</code>.</li>
<li
data-list-item-id="ebc1a98e97b39978d0165f0befbf103a9">By default, the resulting PDF will be in Letter format. It is possible
to adjust it to another page size via the <code spellcheck="false">#printPageSize</code> attribute,
with one of the following values: <code spellcheck="false">A0</code>,
<code
spellcheck="false">A1</code>, <code spellcheck="false">A2</code>, <code spellcheck="false">A3</code>,
<code
spellcheck="false">A4</code>, <code spellcheck="false">A5</code>, <code spellcheck="false">A6</code>,
<code
spellcheck="false">Legal</code>, <code spellcheck="false">Letter</code>, <code spellcheck="false">Tabloid</code>,
<code
spellcheck="false">Ledger</code>.</li>
<li>To print in landscape mode instead of portrait (useful for big diagrams
or slides), add <code>#printLandscape</code>.</li>
<li>By default, the resulting PDF will be in Letter format. It is possible
to adjust it to another page size via the <code>#printPageSize</code> attribute,
with one of the following values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.</li>
</ul>
<aside class="admonition note">
<p>These options have no effect when used with the printing feature, since
@@ -82,10 +74,9 @@ class="admonition note">
<p>Since v0.100.0, it is possible to print more than one note at the time
by using&nbsp;<a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>:</p>
<ol>
<li data-list-item-id="e218b7d45c9a9ce882f16112aec9333be">First create a collection.</li>
<li data-list-item-id="e159ed2993b564448ee15f5796bba1d31">Configure it to use&nbsp;<a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a>.</li>
<li
data-list-item-id="ea82f390047ea239e7793145768738b97">Print the collection note normally.</li>
<li>First create a collection.</li>
<li>Configure it to use&nbsp;<a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a>.</li>
<li>Print the collection note normally.</li>
</ol>
<p>The resulting collection will contain all the children of the collection,
while maintaining the hierarchy.</p>
@@ -102,9 +93,9 @@ class="admonition note">
href="#root/_help_4TIF1oA4VQRO">Options</a>&nbsp;and assigning a key combination
for:</p>
<ul>
<li class="ck-list-marker-italic" data-list-item-id="e6b80294fd7a2d5f5d7cbf831691b53c8"><em>Print Active Note</em>
<li><em>Print Active Note</em>
</li>
<li class="ck-list-marker-italic" data-list-item-id="eb2cc694f51e4e25dcf1f814e64523df9"><em>Export Active Note as PDF</em>
<li><em>Export Active Note as PDF</em>
</li>
</ul>
<h2>Constraints &amp; limitations</h2>
@@ -112,44 +103,44 @@ class="admonition note">
supported when printing, in which case the <em>Print</em> and <em>Export as PDF</em> options
will be disabled.</p>
<ul>
<li data-list-item-id="e38dd484ec45f9bddf43bb9ecf708d339">For&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;notes:
<li>For&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;notes:
<ul>
<li data-list-item-id="e88456d3f86ed7e8656f60b837bf27f9f">Line numbers are not printed.</li>
<li data-list-item-id="e1e5e18b8dedac46c49cc46360b6a444f">Syntax highlighting is enabled, however a default theme (Visual Studio)
<li>Line numbers are not printed.</li>
<li>Syntax highlighting is enabled, however a default theme (Visual Studio)
is enforced.</li>
</ul>
</li>
<li data-list-item-id="e025b80306e7f736f3ae08e5693ab522e">For&nbsp;<a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>,
<li>For&nbsp;<a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>,
the following are supported:
<ul>
<li data-list-item-id="e3553e19731217829ba704c7bbacf86f4"><a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a>, allowing
<li><a class="reference-link" href="#root/_help_mULW0Q3VojwY">List View</a>, allowing
to print multiple notes at once while preserving hierarchy (similar to
a book).</li>
<li data-list-item-id="e96cab6a4cb4a18b1fa98d49f40d29496"><a class="reference-link" href="#root/_help_zP3PMqaG71Ct">Presentation</a>,
<li><a class="reference-link" href="#root/_help_zP3PMqaG71Ct">Presentation</a>,
where each slide/sub-note is displayed.
<ul>
<li data-list-item-id="ea145a7d22299304fbed7d8acb2cc05a0">Most note types are supported, especially the ones that have an image
representation such as&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_grjYqerjn243">Canvas</a>&nbsp;and&nbsp;
<li>Most note types are supported, especially the ones that have an image
representation such as&nbsp;<a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>&nbsp;and&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_gBbsAeiuUxI5">Mind Map</a>.</li>
class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>.</li>
</ul>
</li>
<li data-list-item-id="e6eecc96905c6fb3d05ab360fbf02c395"><a class="reference-link" href="#root/_help_2FvYrpmOXm29">Table</a>, where the
<li><a class="reference-link" href="#root/_help_2FvYrpmOXm29">Table</a>, where the
table is rendered in a print-friendly way.
<ul>
<li data-list-item-id="e44cd46142fff861654387e9b040bb051">Tables that are too complex (especially if they have multiple columns)
<li>Tables that are too complex (especially if they have multiple columns)
might not fit properly, however tables with a large number of rows are
supported thanks to pagination.</li>
<li data-list-item-id="edf28072a875e21fc21e18e7ddc0fec26">Consider printing in landscape mode, or using <code spellcheck="false">#printLandscape</code> if
<li>Consider printing in landscape mode, or using <code>#printLandscape</code> if
exporting to PDF.</li>
</ul>
</li>
<li data-list-item-id="e504297e0bafefa23c8c0ba28d67889e8">The rest of the collections are not supported, but we plan to add support
<li>The rest of the collections are not supported, but we plan to add support
for all the collection types at some point.</li>
</ul>
</li>
<li data-list-item-id="eb2113dc9bd86628162fe2eb47488f5f2">Using&nbsp;<a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>&nbsp;for
printing is no longer supported, instead a custom <code spellcheck="false">printCss</code> relation
<li>Using&nbsp;<a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>&nbsp;for
printing is no longer supported, instead a custom <code>printCss</code> relation
needs to be used (see below).</li>
</ul>
<h2>Customizing the print CSS</h2>
@@ -159,10 +150,10 @@ class="admonition note">
printing.</p>
<p>To do so:</p>
<ul>
<li data-list-item-id="e3df5625e58cea9d50974335203d99caa">Create a CSS <a href="#root/_help_6f9hih2hXXZk">code note</a>.</li>
<li data-list-item-id="e48c1388ee530c6301c75c8136617cf8f">On the note being printed, apply the <code spellcheck="false">~printCss</code> relation
to point to the newly created CSS code note.</li>
<li data-list-item-id="eaecc4fd4d22747aa15c85c988aab6d79">To apply the CSS to multiple notes, consider using <a href="#root/_help_bwZpz2ajCEwO">inheritable attributes</a> or&nbsp;
<li>Create a CSS <a href="#root/_help_6f9hih2hXXZk">code note</a>.</li>
<li>On the note being printed, apply the <code>~printCss</code> relation to
point to the newly created CSS code note.</li>
<li>To apply the CSS to multiple notes, consider using <a href="#root/_help_bwZpz2ajCEwO">inheritable attributes</a> or&nbsp;
<a
class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>.</li>
</ul>
@@ -173,23 +164,19 @@ class="admonition note">
}</code></pre>
<p>To remark:</p>
<ul>
<li data-list-item-id="ec9cd88d328b97422c11f4b25c1042ed5">Multiple CSS notes can be add by using multiple <code spellcheck="false">~printCss</code> relations.</li>
<li
data-list-item-id="e5ff782914a559055a1a72dda1be246bd">If the note pointing to the <code spellcheck="false">printCss</code> doesn't
have the right note type or mime type, it will be ignored.</li>
<li data-list-item-id="eab7b697428ef92d60ab6cd65bda76b69">If migrating from a previous version where&nbsp;<a class="reference-link"
href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>, there's no need for
<code
spellcheck="false">@media print {</code>since the style-sheet is used only for printing.</li>
<li>Multiple CSS notes can be add by using multiple <code>~printCss</code> relations.</li>
<li>If the note pointing to the <code>printCss</code> doesn't have the right
note type or mime type, it will be ignored.</li>
<li>If migrating from a previous version where&nbsp;<a class="reference-link"
href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>, there's no need for <code>@media print {</code> since
the style-sheet is used only for printing.</li>
</ul>
<h2>Under the hood</h2>
<p>Both printing and exporting as PDF use the same mechanism: a note is rendered
individually in a separate webpage that is then sent to the browser or
the Electron application either for printing or exporting as PDF.</p>
<p>The webpage that renders a single note can actually be accessed in a web
browser. For example <code spellcheck="false">http://localhost:8080/#root/WWRGzqHUfRln/RRZsE9Al8AIZ?ntxId=0o4fzk</code> becomes
<code
spellcheck="false">http://localhost:8080/?print#root/WWRGzqHUfRln/RRZsE9Al8AIZ</code>.</p>
browser. For example <code>http://localhost:8080/#root/WWRGzqHUfRln/RRZsE9Al8AIZ?ntxId=0o4fzk</code> becomes <code>http://localhost:8080/?print#root/WWRGzqHUfRln/RRZsE9Al8AIZ</code>.</p>
<p>Accessing the print note in a web browser allows for easy debugging to
understand why a particular note doesn't render well. The mechanism for
rendering is similar to the one used in&nbsp;<a class="reference-link"

View File

@@ -10,13 +10,11 @@
<p>Trilium supports custom user themes, allowing you to personalize the application's
appearance. To create a custom theme, follow these steps:</p>
<ol>
<li data-list-item-id="eca8bbfc636daa344eaabbbb94ea94c3f"><strong>Create a CSS Code Note</strong>: Start by creating a new <a href="#root/_help_6f9hih2hXXZk">code note</a> with
the <code spellcheck="false">CSS</code> type.</li>
<li data-list-item-id="e5ae35a0ee455e32c200fc095bf8415ac"><strong>Annotate with</strong> <code spellcheck="false">#appTheme</code>:
Add the <a href="#root/_help_zEY4DaJG4YT5">attribute</a> <code spellcheck="false">#appTheme=my-theme-name</code> to
your note, where <code spellcheck="false">my-theme-name</code> is the name
of your custom theme.</li>
<li data-list-item-id="e7cd8c79dcf2d9edc2d03c924b9d954e1"><strong>Define Your Styles</strong>: Write your custom CSS within the
<li><strong>Create a CSS Code Note</strong>: Start by creating a new <a href="#root/_help_6f9hih2hXXZk">code note</a> with
the <code>CSS</code> type.</li>
<li><strong>Annotate with</strong> <code>#appTheme</code>: Add the <a href="#root/_help_zEY4DaJG4YT5">attribute</a> <code>#appTheme=my-theme-name</code> to
your note, where <code>my-theme-name</code> is the name of your custom theme.</li>
<li><strong>Define Your Styles</strong>: Write your custom CSS within the
note. Below is an example of a custom theme:</li>
</ol><pre><code class="language-text-x-trilium-auto">@font-face {
font-family: 'Raleway';
@@ -74,20 +72,18 @@ body .CodeMirror {
<h3>Activating Your Custom Theme</h3>
<p>Once you've created your custom theme:</p>
<ol>
<li data-list-item-id="ee141db479e6972f78a12a2ece4be4d8f">Go to "Menu" -&gt; "Options" -&gt; "Appearance."</li>
<li data-list-item-id="e36a48638934829faceb5b68dececd6c7">In the theme selection dropdown, you should see your custom theme listed
under the name you provided with the <code spellcheck="false">#appTheme</code>
<a
href="#root/_help_zEY4DaJG4YT5">label</a>.</li>
<li data-list-item-id="eb7987a455a323d619c83e04a3f660cf9">Select your custom theme to activate it.</li>
<li>Go to "Menu" -&gt; "Options" -&gt; "Appearance."</li>
<li>In the theme selection dropdown, you should see your custom theme listed
under the name you provided with the <code>#appTheme</code> <a href="#root/_help_zEY4DaJG4YT5">label</a>.</li>
<li>Select your custom theme to activate it.</li>
</ol>
<p>If you make changes to your theme, press <kbd>Ctrl</kbd> + <kbd>R</kbd> to
reload the frontend and apply your updates.</p>
<h3>Sharing and Importing Themes</h3>
<p>Custom themes can be exported as <code spellcheck="false">.tar</code> archives,
which can be shared with other users. However, be cautious when importing
themes from untrusted sources, as they may contain executable scripts that
could pose security risks.</p>
<p>Custom themes can be exported as <code>.tar</code> archives, which can be
shared with other users. However, be cautious when importing themes from
untrusted sources, as they may contain executable scripts that could pose
security risks.</p>
<p>An example user theme, <em>Steel Blue</em>, is available in the demo document.</p>
<p>
<img src="Themes_steel-blue.png" alt="Steel Blue Theme">
@@ -100,19 +96,17 @@ body .CodeMirror {
<h3>Applying Custom CSS</h3>
<p>To use custom CSS:</p>
<ol>
<li data-list-item-id="e34f8b4bcba7c20fb1b7653fe36ded3da"><strong>Create a CSS Code Note</strong>: Create a new&nbsp;<a class="reference-link"
href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note with the <code spellcheck="false">CSS</code> type.</li>
<li
data-list-item-id="ec697ca6baf249b374caa04b0817a54f0"><strong>Add the</strong> <code spellcheck="false">appCss</code> <strong>Label</strong>:
Annotate the note with the <code spellcheck="false">#appCss</code> <a href="#root/_help_zEY4DaJG4YT5">label</a>.</li>
<li
data-list-item-id="e33d95fa67b19ef88f2561c3addecade8"><strong>Write Your CSS</strong>: Add your custom CSS rules to the note.</li>
<li><strong>Create a CSS Code Note</strong>: Create a new&nbsp;<a class="reference-link"
href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note with the <code>CSS</code> type.</li>
<li><strong>Add the</strong> <code>appCss</code> <strong>Label</strong>: Annotate
the note with the <code>#appCss</code> <a href="#root/_help_zEY4DaJG4YT5">label</a>.</li>
<li><strong>Write Your CSS</strong>: Add your custom CSS rules to the note.</li>
</ol>
<p>For example:</p><pre><code class="language-text-x-trilium-auto">/* Custom CSS to style specific elements */
.tree-item {
color: #ff6347; /* Change tree item color */
}</code></pre>
<p>When Trilium's frontend starts, all notes labeled with <code spellcheck="false">appCss</code> are
<p>When Trilium's frontend starts, all notes labeled with <code>appCss</code> are
automatically included in the style element of the HTML page.</p>
<p>After making changes, press <kbd>Ctrl</kbd> + <kbd>R</kbd> to reload the frontend
and apply your new styles.</p>
@@ -122,24 +116,22 @@ body .CodeMirror {
<h3>Styling Specific Notes in the Tree</h3>
<p>To apply specific styles to certain notes in the tree:</p>
<ul>
<li data-list-item-id="e885cbe8b8c440d4bf71c95d5f8c73340"><strong>Use the</strong> <code spellcheck="false">cssClass</code> <strong>Attribute</strong>:
Add the <code spellcheck="false">cssClass</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a> to
<li><strong>Use the</strong> <code>cssClass</code> <strong>Attribute</strong>:
Add the <code>cssClass</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a> to
a note, and assign it a value representing the desired CSS class.</li>
<li
data-list-item-id="e168c9c99d3bf9e7af0d0bd03ee6903f5"><strong>Define an</strong> <code spellcheck="false">iconClass</code>: You
can also define a custom icon for a note using the <code spellcheck="false">iconClass</code> attribute,
selecting from <a href="https://boxicons.com">Box Icons</a> or your own custom
classes.</li>
<li><strong>Define an</strong> <code>iconClass</code>: You can also define
a custom icon for a note using the <code>iconClass</code> attribute, selecting
from <a href="https://boxicons.com">Box Icons</a> or your own custom classes.</li>
</ul>
<p>For example, if you want to style notes of a specific type, such as notes
containing PNG images, you can target them with classes like <code spellcheck="false">type-image mime-image-png</code>.</p>
containing PNG images, you can target them with classes like <code>type-image mime-image-png</code>.</p>
<h3>User-Provided Themes</h3>
<p>A gallery of user-created themes is available, showcasing the variety
of customizations that the Trilium community has developed. For more information,
check the&nbsp;<a class="reference-link" href="#root/_help_VbjZvtUek0Ln">Theme Gallery</a>.</p>
<h3>Asset Path Management</h3>
<p>When referencing built-in assets like images in your custom themes or
CSS, you can avoid hardcoding version numbers by using the <code spellcheck="false">vX</code> alias.
For example, instead of specifying <code spellcheck="false">/assets/v0.57.0-beta/images/icon-grey.png</code>,
you can use <code spellcheck="false">/assets/vX/images/icon-grey.png</code> to
keep your theme compatible with future versions of Trilium.</p>
CSS, you can avoid hardcoding version numbers by using the <code>vX</code> alias.
For example, instead of specifying <code>/assets/v0.57.0-beta/images/icon-grey.png</code>,
you can use <code>/assets/vX/images/icon-grey.png</code> to keep your theme
compatible with future versions of Trilium.</p>

View File

@@ -3,22 +3,21 @@
scratch (see below) or imported from a ZIP file from a third-party developer.</p>
<aside
class="admonition note">
<p><strong>Icon packs are third-party content</strong>
<br>
<br>The Trilium maintainers are not responsible for keeping these icon packs
<p><strong>Icon packs are third-party content</strong>
</p>
<p>The Trilium maintainers are not responsible for keeping these icon packs
up to date. If you have an issue with a specific icon pack, then the issue
must be reported to the third-party developer responsible for it, not the
Trilium team.</p>
</aside>
<p>To import an icon pack:</p>
<ol>
<li data-list-item-id="e72cd89899a976ca5c54e089df2285d41">Ideally, create a dedicated spot in your note tree where to place the
<li>Ideally, create a dedicated spot in your note tree where to place the
icon packs.</li>
<li data-list-item-id="ed7f941fe865c5f14e1ae724831e49ba1">Right click the note where to put it and select <em>Import into note</em>.</li>
<li
data-list-item-id="e1b6980592dddab6a0235dd070b3b7067">Uncheck <em>Safe import</em>.</li>
<li data-list-item-id="edd29376be6fbf3b9c59275145457de3b">Select <em>Import</em>.</li>
<li data-list-item-id="ea9ae8004d6cd7b349ac648cde9141977"><a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_s8alTXmpFR61">Refresh the application</a>.</li>
<li>Right click the note where to put it and select <em>Import into note</em>.</li>
<li>Uncheck <em>Safe import</em>.</li>
<li>Select <em>Import</em>.</li>
<li><a href="#root/_help_s8alTXmpFR61">Refresh the application</a>.</li>
</ol>
<aside class="admonition warning">
<p>Since <em>Safe import</em> is disabled, make sure you trust the source as
@@ -30,11 +29,11 @@ class="admonition note">
<h2>Creating an icon pack</h2>
<p>Creating an icon pack requires some scripting knowledge outside Trilium
in order to generate the list of icons. For information, see&nbsp;<a class="reference-link"
href="#root/pKK96zzmvBGf/_help_g1mlRoU8CsqC">Creating an icon pack</a>.</p>
href="#root/_help_g1mlRoU8CsqC">Creating an icon pack</a>.</p>
<h2>Using an icon from an icon pack</h2>
<p>After <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_s8alTXmpFR61">refreshing the application</a>,
the icon pack should be enabled by default. To test this, simply select
an existing note or create a new one and try to change the note icon.</p>
<p>After <a href="#root/_help_s8alTXmpFR61">refreshing the application</a>, the
icon pack should be enabled by default. To test this, simply select an
existing note or create a new one and try to change the note icon.</p>
<p>There should be a <em>Filter</em> button to the right of the search bar
in the icon list. Clicking it allows filtering by icon pack and the newly
imported icon pack should be displayed there.</p>
@@ -42,35 +41,31 @@ class="admonition note">
<p>If the icon pack is missing from that list, then most likely there's something
wrong with it.</p>
<ul>
<li data-list-item-id="e0a36a4a12c83b68ed1affd67e7ffa850">Try checking the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/qzNzp9LYQyPT/_help_bnyigUA2UK7s">Backend (server) logs</a>&nbsp;for
clues and make sure that the icon pack has the <code spellcheck="false">#iconPack</code>
<li>Try checking the&nbsp;<a class="reference-link" href="#root/_help_bnyigUA2UK7s">Backend (server) logs</a>&nbsp;for
clues and make sure that the icon pack has the <code>#iconPack</code>
<a
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_HI6GBBIduIgv">label</a>with a value assigned to it (a prefix).</li>
<li data-list-item-id="e33510913f0656b1c14c5f0c7278093c7">Icon packs that are <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_bwg0e8ewQMak">protected</a> are
ignored.</li>
href="#root/_help_HI6GBBIduIgv">label</a>with a value assigned to it (a prefix).</li>
<li>Icon packs that are <a href="#root/_help_bwg0e8ewQMak">protected</a> are ignored.</li>
</ul>
</aside>
<h2>Integration with the share and export functionality</h2>
<p>Custom icon packs are also supported by the&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_R9pX4DGra2Vt">Sharing</a>&nbsp;feature,
where they will be shown in the note tree. However, in order for an icon
pack to be visible to the share function, the icon pack note must also
be shared.</p>
<p>If you are using a custom share theme, make sure it supports the
<code
spellcheck="false">iconPackCss</code>, otherwise icons will not show up. Check the original
share template source code for reference.</p>
href="#root/_help_R9pX4DGra2Vt">Sharing</a>&nbsp;feature, where they will be
shown in the note tree. However, in order for an icon pack to be visible
to the share function, the icon pack note must also be shared.</p>
<p>If you are using a custom share theme, make sure it supports the <code>iconPackCss</code>,
otherwise icons will not show up. Check the original share template source
code for reference.</p>
<p>Custom icon packs will also be preserved when&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/R9pX4DGra2Vt/_help_ycBFjKrrwE9p">Exporting static HTML for web publishing</a>.
href="#root/_help_ycBFjKrrwE9p">Exporting static HTML for web publishing</a>.
In this case, there's no requirement to make the icon pack shared.</p>
<h2>What happens if I remove an icon pack</h2>
<p>If an icon pack is removed or disabled (by removing or altering its
<code
spellcheck="false">#iconPack</code>label), all the notes that use this icon pack will show
in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;with
no icon. This won't cause any issues apart from looking strange.</p>
<p>If an icon pack is removed or disabled (by removing or altering its <code>#iconPack</code> label),
all the notes that use this icon pack will show in the&nbsp;<a class="reference-link"
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;with no icon. This won't cause
any issues apart from looking strange.</p>
<p>The solution is to replace the icons with some else, try using&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>&nbsp;which supports bulk actions, to identify the notes with
class="reference-link" href="#root/_help_eIg8jdvaoNNd">Search</a>&nbsp;which supports bulk actions, to identify the notes with
the now deleted icon pack (by looking for the prefix) and changing or removing
their <code spellcheck="false">iconClass</code>.</p>
their <code>iconClass</code>.</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -4,53 +4,35 @@
<p>Since these files come from an external source, it is not possible to
create a <em>File</em> note type directly:</p>
<ul>
<li>Drag a file into the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
<li>Right click a note and select <em>Import into note</em> and point it to
<li data-list-item-id="e838d2edcf20254924c6945bb0677bc35">Drag a file into the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
<li
data-list-item-id="e87405721a8eca37bf5258ce1a7d40847">Right click a note and select <em>Import into note</em> and point it to
one of the supported files.</li>
</ul>
<h2>Supported file types</h2>
<h3>PDFs</h3>
<figure class="image image-style-align-center image_resized" style="width:50%;">
<img style="aspect-ratio:933/666;" src="File_image.png"
width="933" height="666">
</figure>
<p>PDFs can be browsed directly from Trilium.</p>
<p>Interaction:</p>
<ul>
<li>Press the menu icon at the top-left to see a preview (thumbnail) of all
the pages, as well as a table of contents (if the PDF has this information).</li>
<li>See or edit the page number at the top.</li>
<li>Adjust the zoom using the buttons at the top or manually editing the value.</li>
<li>Rotate the document if it's in the wrong orientation.</li>
<li>In the contextual menu:
<ul>
<li>View two pages at once (great for books).</li>
<li>Toggle annotations (if present in the document).</li>
<li>View document properties.</li>
</ul>
</li>
</ul>
<p>See&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/W8vYD3Q1zjCR/_help_XJGJrpu7F9sh">PDFs</a>.</p>
<h3>Images</h3>
<figure class="image image-style-align-center image_resized" style="width:50%;">
<img style="aspect-ratio:879/766;" src="4_File_image.png"
<img style="aspect-ratio:879/766;" src="3_File_image.png"
width="879" height="766">
</figure>
<p>Interaction:</p>
<ul>
<li><em>Copy reference to clipboard</em>, for embedding the image within&nbsp;
<li data-list-item-id="e2a48d88d6adf1a0d7467cb8a1c70084a"><em>Copy reference to clipboard</em>, for embedding the image within&nbsp;
<a
class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a>&nbsp;notes.
<ul>
<li>See&nbsp;<a class="reference-link" href="#root/_help_0Ofbk1aSuVRu">Image references</a>&nbsp;for
<li data-list-item-id="ec686b380d3dc321b2d99d709315942d3">See&nbsp;<a class="reference-link" href="#root/_help_0Ofbk1aSuVRu">Image references</a>&nbsp;for
more information.</li>
<li>Alternatively, press the corresponding button from the&nbsp;<a class="reference-link"
<li data-list-item-id="e0502ddadf07a7e370462c08e9ef09ee4">Alternatively, press the corresponding button from the&nbsp;<a class="reference-link"
href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
</ul>
</li>
</ul>
<h3>Videos</h3>
<figure class="image image-style-align-center image_resized" style="width:50%;">
<img style="aspect-ratio:854/700;" src="1_File_image.png"
<img style="aspect-ratio:854/700;" src="File_image.png"
width="854" height="700">
</figure>
<p>Video files can be added in as well. The file is streamed directly, so
@@ -67,23 +49,23 @@
</aside>
<h3>Audio</h3>
<figure class="image image-style-align-center image_resized" style="width:50%;">
<img style="aspect-ratio:850/243;" src="3_File_image.png"
<img style="aspect-ratio:850/243;" src="2_File_image.png"
width="850" height="243">
</figure>
<p>Adding a supported audio file will reveal a basic audio player that can
be used to play it.</p>
<p>Interactions:</p>
<ul>
<li>The audio can be played/paused using the dedicated button.</li>
<li>Dragging the mouse across, or clicking the progress bar will seek through
<li data-list-item-id="e3361343c2087cd5fd90f446c2db3aceb">The audio can be played/paused using the dedicated button.</li>
<li data-list-item-id="e0681c0b088d2937257b1afc8d2ce3f04">Dragging the mouse across, or clicking the progress bar will seek through
the song.</li>
<li>The volume can be set.</li>
<li>The playback speed can be adjusted via the contextual menu next to the
<li data-list-item-id="e8d0e58bde64158d92b0a82e735299eb5">The volume can be set.</li>
<li data-list-item-id="e76438c11c21e615066915ef5289732ab">The playback speed can be adjusted via the contextual menu next to the
volume.</li>
</ul>
<h3>Text files</h3>
<figure class="image image-style-align-center image_resized" style="width:50%;">
<img style="aspect-ratio:926/347;" src="2_File_image.png"
<img style="aspect-ratio:926/347;" src="1_File_image.png"
width="926" height="347">
</figure>
<p>Files that are identified as containing text will show a preview of their
@@ -102,7 +84,7 @@
application.</p>
<h3>Unknown file types</h3>
<figure class="image image-style-align-center image_resized" style="width:50%;">
<img style="aspect-ratio:532/240;" src="5_File_image.png"
<img style="aspect-ratio:532/240;" src="4_File_image.png"
width="532" height="240">
</figure>
<p>If the file could not be identified as any of the supported file types
@@ -111,33 +93,35 @@
file externally, but there will be no preview of the content.</p>
<h2>Interaction</h2>
<ul>
<li>Regardless of the file type, a series of buttons will be displayed in
<li data-list-item-id="e4b886a9448519b3cdd4b972fbde1a8fb">Regardless of the file type, a series of buttons will be displayed in
the <em>Image</em> or <em>File</em> tab in the&nbsp;<a class="reference-link"
href="#root/_help_BlN9DFI679QC">Ribbon</a>.
<ul>
<li><em>Download</em>, which will download the file for local use.</li>
<li><em>Open</em>, will will open the file with the system-default application.</li>
<li>Upload new revision to replace the file with a new one.</li>
<li data-list-item-id="e50be8d9eea4871abc3d51a92ae05f136"><em>Download</em>, which will download the file for local use.</li>
<li
data-list-item-id="eec24704d1d2a5642d0881e38d5090b54"><em>Open</em>, will will open the file with the system-default application.</li>
<li
data-list-item-id="e5e576572cf69eb14753f33755f8a25b6">Upload new revision to replace the file with a new one.</li>
</ul>
</li>
<li>It is <strong>not</strong> possible to change the note type of a <em>File</em> note.</li>
<li>Convert into an <a href="#root/_help_0vhv7lsOLy82">attachment</a> from the <a href="#root/_help_8YBEPzcpUgxw">note menu</a>.</li>
</li>
<li data-list-item-id="ee5a73a8d726754636a45086dd1612616">It is <strong>not</strong> possible to change the note type of a <em>File</em> note.</li>
<li
data-list-item-id="e3f6f719f94482df13f687503378d15cc">Convert into an <a href="#root/_help_0vhv7lsOLy82">attachment</a> from the <a href="#root/_help_8YBEPzcpUgxw">note menu</a>.</li>
</ul>
<h2>Relation with other notes</h2>
<ul>
<li>
<li data-list-item-id="eb364a811b42ed28431d698b7e617523f">
<p>Files are also displayed in the&nbsp;<a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a>&nbsp;based
on their type:</p>
<img class="image_resized" style="aspect-ratio:853/315;width:50%;"
src="6_File_image.png" width="853" height="315">
</li>
<li>
<p>Non-image files can be embedded into text notes as read-only widgets via
the&nbsp;<a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>&nbsp;functionality.</p>
</li>
<li>
<p>Image files can be embedded into text notes like normal images via&nbsp;
<a
class="reference-link" href="#root/_help_0Ofbk1aSuVRu">Image references</a>.</p>
<p>
<img class="image_resized" style="aspect-ratio:853/315;width:50%;" src="5_File_image.png"
width="853" height="315">
</p>
</li>
<li data-list-item-id="e066146c6d5ab67fcd20d634f54a58a66">Non-image files can be embedded into text notes as read-only widgets via
the&nbsp;<a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a>&nbsp;functionality.</li>
<li
data-list-item-id="eaec442497ed868ee21da4ca456ead455">Image files can be embedded into text notes like normal images via&nbsp;
<a
class="reference-link" href="#root/_help_0Ofbk1aSuVRu">Image references</a>.</li>
</ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,128 @@
<figure class="image image_resized" style="width:74.34%;">
<img style="aspect-ratio:1360/698;" src="PDFs_image.png"
width="1360" height="698">
</figure>
<p>PDFs file can be uploaded in Trilium, where they will be displayed without
the need to download them first.</p>
<p>Since v0.102.0, PDFs will be rendered using Trilium's built-in PDF viewer,
which is a customization of <a href="https://mozilla.github.io/pdf.js/">Mozilla's PDF.js viewer</a> (also
built-in in the Mozilla Firefox browser). Versions prior to that render
PDFs using the browser's default PDF viewer.</p>
<h2>Storing last position and settings</h2>
<p>For every PDF, Trilium will remember the following information:</p>
<ul>
<li data-list-item-id="e800292c222a870700c853355e8f323a3">
<p>The current page.</p>
</li>
<li data-list-item-id="eafe1af464be4b531cc88170014c92cb9">
<p>The scroll position, within the current page.</p>
</li>
<li data-list-item-id="ed9b5e35df1c612b69742ff970c95a25b">
<p>The rotation of the page.</p>
</li>
</ul>
<p>This makes it useful when reading large documents since the position is
remembered automatically. This happens in the background, however it's
recorded only a few seconds after stopping any scroll actions.</p>
<aside
class="admonition tip">
<p>Technically, the information about the scroll position and rotation is
stored in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0vhv7lsOLy82">Attachments</a>&nbsp;section,
in a dedicated attachment called <code spellcheck="false">pdfHistory.json</code>.</p>
</aside>
<h2>Annotations</h2>
<p>Since v0.102.0 it's possible to annotate PDFs. To do so, look for the
annotation buttons on the right side of the PDF toolbar (
<img src="1_PDFs_image.png"
width="120" height="32">).</p>
<h3>Supported annotations</h3>
<p>The following annotation methods are supported:</p>
<ul>
<li data-list-item-id="e22b0fd64079f222a6edf716d1e287fcd"><strong>Highlight</strong>
<br>Allows highlighting text with one of the predefined colors.
<ul>
<li data-list-item-id="ed5816cda369a50d35356545b2e639106">The thickness is also adjustable.</li>
<li data-list-item-id="eacd49e75a9b3c6ca88dcc3aecb6e7780">It's also possible to highlight the blank space, turning the feature more
into a thicker pen.</li>
</ul>
</li>
<li data-list-item-id="ed150361858da21a3a043fe273f3a8082"><strong>Text</strong>
<br>Allows adding arbitrary text, with a custom color and size.</li>
<li data-list-item-id="e1684364a9a848f88610727d8d903113d"><strong>Pen</strong>
<br>Allows free drawing on the document, with variable color, thickness and
opacity.</li>
<li data-list-item-id="ebff3cc863fa39a4bf69ca9a3a25ed289"><strong>Image</strong>
<br>Allows inserting images from outside Trilium directly into the document.</li>
</ul>
<h3>Editing existing annotations</h3>
<p>Although annotations are stored in the PDF itself, they can be edited.
To edit an annotation, press one of the annotation buttons from the previous
section to enter edit mode and click on an existing annotation. This will
reveal a toolbar with options to customize the annotation (e.g. to change
a color), as well as the possibility to remove it.</p>
<h3>How are annotations stored</h3>
<p>Annotations are stored directly in the PDF. When modifications are made,
Trilium will replace the PDF with the new one.</p>
<p>Since modifications are automatically saved, there's no need to manually
save the document after making modifications to the annotations.</p>
<p>The benefit of “baked-in annotations” is that they are also accessible
if downloading (for external use outside Trilium) or sharing the note.</p>
<p>The downside is that the entire PDF needs to be sent back to the server,
which can slow down performance for larger documents. If you encounter
any issues from this system, feel free to <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_wy8So3yZZlH9">report it</a>.</p>
<h2>Filling out forms</h2>
<p>Similar to annotations, forms are also supported by Trilium since v0.102.0.
If the document has fields that can be filled-in, they will be indicated
with a colored background.</p>
<p>Simply type text in the forms and they will be automatically saved.</p>
<h2>Sidebar navigation</h2>
<aside class="admonition note">
<p>This feature is only available if&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_IjZS7iK5EXtb">New Layout</a>&nbsp;is
enabled. If you are using the old layout, these features are still available
by looking for a sidebar button in the PDF viewer toolbar.</p>
</aside>
<p>When a PDF file is opened in Trilium the&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_RnaPdbciOfeq">Right Sidebar</a>&nbsp;is
augmented with PDF-specific navigation, with the following features:</p>
<ul>
<li data-list-item-id="ec98e84135caf286b8c34b65403a7c36b">Table of contents/outline
<ul>
<li data-list-item-id="e3d034433ea04b042c9425d0d183174f0">All the headings and “bookmarks” will be displayed hierarchially.</li>
<li
data-list-item-id="e6113a5ba02d24a74d858d8b9e5cd529d">The heading on the current page is also highlighted (note that it can
be slightly offset depending on how many headings are on the same page).</li>
<li
data-list-item-id="e650d1274d9fcc67a61ed531be41664ba">Clicking on a heading will jump to the corresponding position in the PDF.</li>
</ul>
</li>
<li data-list-item-id="e7596d0db46b52000147e8c58035a9080">Pages
<ul>
<li data-list-item-id="e878951e72b24d63a5f427c09dbd5b43d">A preview of all the pages with a small thumbnail.</li>
<li data-list-item-id="ee2a88c436e666ad03396e16afc5e820d">Clicking on a page will automatically navigate to that page.</li>
</ul>
</li>
<li data-list-item-id="e69b90e09bec99dd22e1d51b3fd47ad30">Attachments
<ul>
<li data-list-item-id="e32d2f3ca07155778516311955aad6bc1">If the PDF has its own attachments (not to be confused with Trilium's&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0vhv7lsOLy82">Attachments</a>), they will be displayed in a list.</li>
<li data-list-item-id="e51eac1ae4ee919989c95a28940f77d1b">Some information such as the name and size of the attachment are displayed.</li>
<li
data-list-item-id="ee296a4cf55b2d4372496ab8e70691236">It's possible to download the attachment by clicking on the download button.</li>
</ul>
</li>
<li data-list-item-id="ef51c3a38777e3a1a11b7691de57c3eab">Layers
<ul>
<li data-list-item-id="e09e2c2c5f8fdd0f5756ca6345a275e5d">A less common feature, if the PDF has toggle-able layers, these layers
will be displayed in a list here.</li>
<li data-list-item-id="e35568c33af1e6bf80f9e5d9c66fa903a">It's possible to toggle the visibility for each individual layer.</li>
</ul>
</li>
</ul>
<h2>Share functionality</h2>
<p>PDFs can also be shared using the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_R9pX4DGra2Vt">Sharing</a>&nbsp;feature.
This will also use Trilium's customized PDF viewer.</p>
<p>If you are using a reverse proxy on your server with strict access limitations
for the share functionality, make sure that <code spellcheck="false">[host].com/pdfjs</code> directory
is accessible. Note that this directory is outside the <code spellcheck="false">/share</code> route
as it's common with the rest of the application.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 652 KiB

View File

@@ -8,62 +8,60 @@
into Trilium.</p>
<p>Trilium only supports <strong>font-based icon sets</strong>, with the following
formats:</p>
<figure class="table">
<table>
<thead>
<tr>
<th>Extension</th>
<th>MIME type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code spellcheck="false">.woff2</code>
</td>
<td><code spellcheck="false">font/woff2</code>
</td>
<td>Recommended due to great compression (low size).</td>
</tr>
<tr>
<td><code spellcheck="false">.woff</code>
</td>
<td><code spellcheck="false">font/woff</code>
</td>
<td>Higher compatibility, but the font file is bigger.</td>
</tr>
<tr>
<td><code spellcheck="false">.ttf</code>
</td>
<td><code spellcheck="false">font/ttf</code>
</td>
<td>Most common, but highest font size.</td>
</tr>
</tbody>
</table>
</figure>
<table>
<thead>
<tr>
<th>Extension</th>
<th>MIME type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>.woff2</code>
</td>
<td><code>font/woff2</code>
</td>
<td>Recommended due to great compression (low size).</td>
</tr>
<tr>
<td><code>.woff</code>
</td>
<td><code>font/woff</code>
</td>
<td>Higher compatibility, but the font file is bigger.</td>
</tr>
<tr>
<td><code>.ttf</code>
</td>
<td><code>font/ttf</code>
</td>
<td>Most common, but highest font size.</td>
</tr>
</tbody>
</table>
<h2>Unsupported formats</h2>
<p>Trilium <strong>does not</strong> support the following formats:</p>
<ul>
<li data-list-item-id="e82a131514827c3594823b95a1e032e55">SVG-based fonts.</li>
<li data-list-item-id="ea255621003bfddf0c27b138800b7461e">Individual SVGs.</li>
<li data-list-item-id="eb75d3b9b71a147f48925903844e878a0"><code spellcheck="false">.eot</code> fonts (legacy and proprietary).</li>
<li
data-list-item-id="e67532d402708b0d266f438de7afc617b">Duotone icons, since it requires a special CSS format that Trilium doesn't
<li>SVG-based fonts.</li>
<li>Individual SVGs.</li>
<li><code>.eot</code> fonts (legacy and proprietary).</li>
<li>Duotone icons, since it requires a special CSS format that Trilium doesn't
support.</li>
<li data-list-item-id="ee56cb0fb0ae77e969c44844a2b56ef94">Any other font format not specified in the <em>Supported formats</em> section.</li>
<li>Any other font format not specified in the <em>Supported formats</em> section.</li>
</ul>
<p>In this case, the font must be manually converted to one of the supported
formats (ideally <code spellcheck="false">.woff2</code>).</p>
formats (ideally <code>.woff2</code>).</p>
<h2>Prerequisites</h2>
<p>In order to create a new icon pack from a set of icons, it must meet the
following criteria:</p>
<ol>
<li data-list-item-id="eba517372b7686baf9c5ffbec386e7853">It must have a web font of the supported format (see above).</li>
<li data-list-item-id="e960457bf372dd1a2e3ec77140b38051c">It must have some kind of list, containing the name of each icon and the
<li>It must have a web font of the supported format (see above).</li>
<li>It must have some kind of list, containing the name of each icon and the
corresponding Unicode code point. If this is missing, icon fonts usually
ship with a <code spellcheck="false">.css</code> file that can be used to
extract the icon names from.</li>
ship with a <code>.css</code> file that can be used to extract the icon names
from.</li>
</ol>
<h2>Step-by-step process</h2>
<p>As an example throughout this page, we are going to go through the steps
@@ -71,7 +69,7 @@
<h3>Creating the manifest</h3>
<p>This is the most difficult part of creating an icon pack, since it requires
processing of the icon list to match Trilium's format.</p>
<p>The icon pack manifest is a JSON file with the following structure:</p><pre><code class="language-text-x-trilium-auto">{
<p>The icon pack manifest is a JSON file with the following structure:</p><pre><code class="language-application-ld-json">{
"icons": {
"bx-ball": {
"glyph": "\ue9c2",
@@ -84,20 +82,19 @@
}
}</code></pre>
<ul>
<li data-list-item-id="e421d9a5fbad061cc4bb80123d2b3b383">The JSON example is a sample from the Boxicons font.</li>
<li data-list-item-id="e61b71a1639566cc9ff760a99aca2279a">This is simply a mapping between the CSS classes (<code spellcheck="false">bx-ball</code>),
to its corresponding code point in the font (<code spellcheck="false">\ue9c2</code>)
and the terms/aliases used for search purposes.</li>
<li data-list-item-id="e7f407f989ab48267d31975f75c89d007">Note that it's also possible to use the unescaped glyph inside the JSON.
<li>The JSON example is a sample from the Boxicons font.</li>
<li>This is simply a mapping between the CSS classes (<code>bx-ball</code>),
to its corresponding code point in the font (<code>\ue9c2</code>) and the
terms/aliases used for search purposes.</li>
<li>Note that it's also possible to use the unescaped glyph inside the JSON.
It will appear strange (e.g. ), but it will be rendered properly regardless.</li>
<li
data-list-item-id="ec4b9b5ac1609500d36d35e659d61b61f">The first term is also considered the “name” of the icon, which is displayed
<li>The first term is also considered the “name” of the icon, which is displayed
while hovering over it in the icon selector.</li>
</ul>
<p>In order to generate this manifest, generally a script is needed that
processes an already existing list. In the case of Phosphor Icons, the
icon list comes in a file called <code spellcheck="false">selection.json</code> with
the following format:</p><pre><code class="language-application-json">{
icon list comes in a file called <code>selection.json</code> with the following
format:</p><pre><code class="language-application-ld-json">{
"icons": [
{
"icon": {
@@ -155,36 +152,34 @@ console.log(processIconPack("light"));</code></pre>
<aside class="admonition tip">
<p><strong>Mind the escape format when processing CSS</strong>
</p>
<p>The Unicode escape syntax is different in CSS (<code spellcheck="false">"\ea3f"</code>)
when compared to JSON (<code spellcheck="false">"\uea3f"</code>). Notice
how the JSON escape is <code spellcheck="false">\u</code> and not <code spellcheck="false">\</code>.</p>
<p>The Unicode escape syntax is different in CSS (<code>"\ea3f"</code>) when
compared to JSON (<code>"\uea3f"</code>). Notice how the JSON escape is <code>\u</code> and
not <code>\</code>.</p>
<p>As a more compact alternative, provide the un-escaped character directly,
as UTF-8 is supported.</p>
</aside>
<h3>Creating the icon pack</h3>
<ol>
<li data-list-item-id="e7d2cd4845de4760238012fc2ae8dc7a5">Create a note of type <em>Code</em>.</li>
<li data-list-item-id="ef8c38938d0e7f8db06bc1e48654ba3d7">Set the language to <em>JSON</em>.</li>
<li data-list-item-id="eb4d1eeb7b1af1474870e646096d5c71b">Copy and paste the manifest generated in the previous step as the content
<li>Create a note of type <em>Code</em>.</li>
<li>Set the language to <em>JSON</em>.</li>
<li>Copy and paste the manifest generated in the previous step as the content
of this note.</li>
<li data-list-item-id="e1e5a73c0ed4c207555912ccae863aa1c">Go to the <a href="#root/_help_0vhv7lsOLy82">note attachment</a> and upload the
font file (in <code spellcheck="false">.woff2</code>, <code spellcheck="false">.woff</code>,
<code
spellcheck="false">.ttf</code>) format.
<ol>
<li data-list-item-id="e2aaef242576df43f2c9259ec743593c4">Trilium identifies the font to use from attachments via the MIME type,
make sure the MIME type is displayed correctly after uploading the attachment
(for example <code spellcheck="false">font/woff2</code>).</li>
<li data-list-item-id="ed7e90b501e2503f559c3af48c3d1ae0c">Make sure the <code spellcheck="false">role</code> appears as <code spellcheck="false">file</code>,
otherwise the font will not be identified.</li>
<li data-list-item-id="e7fe120986469e80972cfff7e08095ce3">Multiple attachments are supported, but only one font will actually be
used in Trilium's order of preference: <code spellcheck="false">.woff2</code>,
<code
spellcheck="false">.woff</code>, <code spellcheck="false">.ttf</code>. As such, there's not
much reason to upload more than one font per icon pack.</li>
</ol>
<li>Go to the <a href="#root/_help_0vhv7lsOLy82">note attachment</a> and upload the
font file (in <code>.woff2</code>, <code>.woff</code>, <code>.ttf</code>)
format.
<ol>
<li>Trilium identifies the font to use from attachments via the MIME type,
make sure the MIME type is displayed correctly after uploading the attachment
(for example <code>font/woff2</code>).</li>
<li>Make sure the <code>role</code> appears as <code>file</code>, otherwise the
font will not be identified.</li>
<li>Multiple attachments are supported, but only one font will actually be
used in Trilium's order of preference: <code>.woff2</code>, <code>.woff</code>, <code>.ttf</code>.
As such, there's not much reason to upload more than one font per icon
pack.</li>
</ol>
</li>
<li data-list-item-id="e07ea13454ff47d3a80d140e435f6e31a">Go back to the note and rename it. The name of the note will also be the
<li>Go back to the note and rename it. The name of the note will also be the
name of the icon pack as displayed in the list of icons.</li>
</ol>
<h3>Assigning the prefix</h3>
@@ -193,53 +188,50 @@ console.log(processIconPack("light"));</code></pre>
the application.</p>
<p>To do so, Trilium makes use of the same format that was used for the internal
icon pack (Boxicons). For example, when an icon from Boxicons is set, it
looks like this: <code spellcheck="false">#iconClass="bx bxs-sushi"</code>.
In this case, the icon pack prefix is <code spellcheck="false">bx</code> and
the icon class name is <code spellcheck="false">bxs-sushi</code>.</p>
looks like this: <code>#iconClass="bx bxs-sushi"</code>. In this case, the
icon pack prefix is <code>bx</code> and the icon class name is <code>bxs-sushi</code>.</p>
<p>In order for an icon pack to be recognized, the prefix must be specified
in the <code spellcheck="false">#iconPack</code> label.&nbsp;</p>
<p>For our example with Phosphor Icons, we can use the <code spellcheck="false">ph</code> prefix
in the <code>#iconPack</code> label.&nbsp;</p>
<p>For our example with Phosphor Icons, we can use the <code>ph</code> prefix
since it also matches the prefix set in the original CSS. So in this case
it would be <code spellcheck="false">#iconPack=ph</code>.</p>
it would be <code>#iconPack=ph</code>.</p>
<aside class="admonition important">
<p>The prefix must consist of only alphanumeric characters, hyphens and underscore.
If the prefix doesn't match these constraints, the icon pack will be ignored
and an error will be logged in&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/qzNzp9LYQyPT/_help_bnyigUA2UK7s">Backend (server) logs</a>.</p>
and an error will be logged in&nbsp;<a class="reference-link" href="#root/_help_bnyigUA2UK7s">Backend (server) logs</a>.</p>
</aside>
<h3>Final steps</h3>
<ul>
<li data-list-item-id="ea0a195d13028ebcfdd45cd27468bae7d"><a href="#root/_help_s8alTXmpFR61">Refresh the client</a>
<li><a href="#root/_help_s8alTXmpFR61">Refresh the client</a>
<ul>
<li data-list-item-id="ecfbcbc9a17681cac7e86039dafa42280">Change the icon of the note and look for the <em>Filter</em> icon in the
<li>Change the icon of the note and look for the <em>Filter</em> icon in the
top-right side.</li>
<li data-list-item-id="e37c446c42f22aa5fa6417a2521b4ea3f">Check if the new icon pack is displayed there and click on it to see the
<li>Check if the new icon pack is displayed there and click on it to see the
full list of icons.</li>
<li data-list-item-id="e9546ed2a86e5debc1c3146972ce3ae5e">Go through most of the items to look for issues such as missing icon,
<li>Go through most of the items to look for issues such as missing icon,
wrong names (some icons have aliases/terms that can cause issues).</li>
</ul>
</li>
<li data-list-item-id="e6d48f2f0011e6dbbbf2aedce5a63ddd8">Optionally, assign an icon from the new icon pack to this note. This icon
<li>Optionally, assign an icon from the new icon pack to this note. This icon
will be used in the icon pack filter for a visual distinction.</li>
<li
data-list-item-id="e9b5f41994bb6beae98a2d9a1e396fba0">The icon pack can then be <a href="#root/_help_mHbBMPDPkVV5">exported as ZIP</a> in
<li>The icon pack can then be <a href="#root/_help_mHbBMPDPkVV5">exported as ZIP</a> in
order to be distributed to other users.
<ul>
<li data-list-item-id="e9a0f122846acf046541a084e505aba81">It's important to note that icon packs are considered “unsafe” by default,
<li>It's important to note that icon packs are considered “unsafe” by default,
so “Safe mode” must be disabled when importing the ZIP.</li>
<li data-list-item-id="e0e3990178843580147bb801c2c629a25">Consider linking new users to the&nbsp;<a class="reference-link" href="#root/_help_gOKqSJgXLcIj">Icon Packs</a>&nbsp;documentation
<li>Consider linking new users to the&nbsp;<a class="reference-link" href="#root/_help_gOKqSJgXLcIj">Icon Packs</a>&nbsp;documentation
in order to understand how to import and use an icon pack.</li>
</ul>
</li>
</li>
</ul>
<h3>Troubleshooting</h3>
<p>If the icon pack doesn't show up, look through the&nbsp;<a class="reference-link"
href="#root/_help_bnyigUA2UK7s">Backend (server) logs</a>&nbsp;for clues.</p>
<ul>
<li data-list-item-id="ea20b69c8a700cef8919d3c74cb564d4d">One example is if the font could not be retrieved: <code spellcheck="false">ERROR: Icon pack is missing WOFF/WOFF2/TTF attachment: Boxicons v3 400 (dup) (XRzqDQ67fHEK)</code>.</li>
<li
data-list-item-id="ec83897ba2caaeb0d906b29e9e4f52a77">Make sure the prefix is unique and not already taken by some other icon
<li>One example is if the font could not be retrieved: <code>ERROR: Icon pack is missing WOFF/WOFF2/TTF attachment: Boxicons v3 400 (dup) (XRzqDQ67fHEK)</code>.</li>
<li>Make sure the prefix is unique and not already taken by some other icon
pack. When there are two icon packs with the same prefix, only one is used.
The server logs will indicate if this situation occurs.</li>
<li data-list-item-id="ed42cfe9240d7ec8626e512f9322bf8b9">Make sure the prefix consists only of alphanumeric characters, hyphens
and underscore.</li>
<li>Make sure the prefix consists only of alphanumeric characters, hyphens
and underscore.</li>
</ul>

View File

@@ -481,7 +481,7 @@ function renderImage(result: Result, note: SNote | BNote) {
function renderFile(note: SNote | BNote, result: Result) {
if (note.mime === "application/pdf") {
result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`;
result.content = `<iframe class="pdf-view" src="../pdfjs/web/viewer.html?file=../../../share/api/notes/${note.noteId}/view"></iframe>`;
} else {
result.content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download'">Download file</button>`;
}

View File

@@ -1,5 +1,5 @@
# Documentation
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/elhSPST0fyf2/Documentation_image.png" width="205" height="162">
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/v280Ba21YUiA/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.

View File

@@ -10073,13 +10073,20 @@
"value": "bx bx-file-blank",
"isInheritable": false,
"position": 140
},
{
"type": "relation",
"name": "internalLink",
"value": "XJGJrpu7F9sh",
"isInheritable": false,
"position": 150
}
],
"format": "markdown",
"dataFileName": "File.md",
"attachments": [
{
"attachmentId": "82as0jgkDvVH",
"attachmentId": "FoEnowwOhzLT",
"title": "image.png",
"role": "image",
"mime": "image/png",
@@ -10087,7 +10094,7 @@
"dataFileName": "File_image.png"
},
{
"attachmentId": "FoEnowwOhzLT",
"attachmentId": "fZ7VMfQJWuLQ",
"title": "image.png",
"role": "image",
"mime": "image/png",
@@ -10095,7 +10102,7 @@
"dataFileName": "1_File_image.png"
},
{
"attachmentId": "fZ7VMfQJWuLQ",
"attachmentId": "hddkgf7kr9g4",
"title": "image.png",
"role": "image",
"mime": "image/png",
@@ -10103,7 +10110,7 @@
"dataFileName": "2_File_image.png"
},
{
"attachmentId": "hddkgf7kr9g4",
"attachmentId": "hIg9g5pgsjS3",
"title": "image.png",
"role": "image",
"mime": "image/png",
@@ -10111,28 +10118,103 @@
"dataFileName": "3_File_image.png"
},
{
"attachmentId": "hIg9g5pgsjS3",
"attachmentId": "IC0j8LFCOKka",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "4_File_image.png"
},
{
"attachmentId": "IC0j8LFCOKka",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "5_File_image.png"
},
{
"attachmentId": "wNHX24feZRAl",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "6_File_image.png"
"dataFileName": "5_File_image.png"
}
],
"dirFileName": "File",
"children": [
{
"isClone": false,
"noteId": "XJGJrpu7F9sh",
"notePath": [
"pOsGYCXsbNQG",
"KSZ04uQ2D1St",
"W8vYD3Q1zjCR",
"XJGJrpu7F9sh"
],
"title": "PDFs",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bxs-file-pdf",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "IjZS7iK5EXtb",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "RnaPdbciOfeq",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "0vhv7lsOLy82",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "wy8So3yZZlH9",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "R9pX4DGra2Vt",
"isInheritable": false,
"position": 80
}
],
"format": "markdown",
"dataFileName": "PDFs.md",
"attachments": [
{
"attachmentId": "6IIyelZjiGqC",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "PDFs_image.png"
},
{
"attachmentId": "LT2iTknjYoZi",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_PDFs_image.png"
}
]
}
]
}

View File

@@ -4,8 +4,8 @@
Icon packs are specific to Trilium, so they must either be created from scratch (see below) or imported from a ZIP file from a third-party developer.
> [!NOTE]
> **Icon packs are third-party content**
>
> **Icon packs are third-party content**
>
> The Trilium maintainers are not responsible for keeping these icon packs up to date. If you have an issue with a specific icon pack, then the issue must be reported to the third-party developer responsible for it, not the Trilium team.
To import an icon pack:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -12,24 +12,11 @@ Since these files come from an external source, it is not possible to create a _
### PDFs
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:933/666;" src="File_image.png" width="933" height="666"></figure>
PDFs can be browsed directly from Trilium.
Interaction:
* Press the menu icon at the top-left to see a preview (thumbnail) of all the pages, as well as a table of contents (if the PDF has this information).
* See or edit the page number at the top.
* Adjust the zoom using the buttons at the top or manually editing the value.
* Rotate the document if it's in the wrong orientation.
* In the contextual menu:
* View two pages at once (great for books).
* Toggle annotations (if present in the document).
* View document properties.
See <a class="reference-link" href="File/PDFs.md">PDFs</a>.
### Images
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:879/766;" src="4_File_image.png" width="879" height="766"></figure>
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:879/766;" src="3_File_image.png" width="879" height="766"></figure>
Interaction:
@@ -39,7 +26,7 @@ Interaction:
### Videos
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:854/700;" src="1_File_image.png" width="854" height="700"></figure>
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:854/700;" src="File_image.png" width="854" height="700"></figure>
Video files can be added in as well. The file is streamed directly, so when accessing the note from a server it doesn't have to download the entire video to start playing it.
@@ -48,7 +35,7 @@ Video files can be added in as well. The file is streamed directly, so when acce
### Audio
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:850/243;" src="3_File_image.png" width="850" height="243"></figure>
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:850/243;" src="2_File_image.png" width="850" height="243"></figure>
Adding a supported audio file will reveal a basic audio player that can be used to play it.
@@ -61,7 +48,7 @@ Interactions:
### Text files
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:926/347;" src="2_File_image.png" width="926" height="347"></figure>
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:926/347;" src="1_File_image.png" width="926" height="347"></figure>
Files that are identified as containing text will show a preview of their content. One common use case for this type of file is to embed text files whose content is not necessarily of interest to the user, such as third-party libraries or generated content, that can then be downloaded if needed.
@@ -71,7 +58,7 @@ Since one of the use cases for having files instead of notes is to display large
### Unknown file types
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:532/240;" src="5_File_image.png" width="532" height="240"></figure>
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:532/240;" src="4_File_image.png" width="532" height="240"></figure>
If the file could not be identified as any of the supported file types from above, it will be treated as an unknown file. In this case, all the default interactions will be available such as downloading or opening the file externally, but there will be no preview of the content.
@@ -88,6 +75,6 @@ If the file could not be identified as any of the supported file types from abov
* Files are also displayed in the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/Notes/Note%20List.md">Note List</a> based on their type:
<img class="image_resized" style="aspect-ratio:853/315;width:50%;" src="6_File_image.png" width="853" height="315">
<img class="image_resized" style="aspect-ratio:853/315;width:50%;" src="5_File_image.png" width="853" height="315">
* Non-image files can be embedded into text notes as read-only widgets via the <a class="reference-link" href="Text/Include%20Note.md">Include Note</a> functionality.
* Image files can be embedded into text notes like normal images via <a class="reference-link" href="Text/Images/Image%20references.md">Image references</a>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,86 @@
# PDFs
<figure class="image image_resized" style="width:74.34%;"><img style="aspect-ratio:1360/698;" src="PDFs_image.png" width="1360" height="698"></figure>
PDFs file can be uploaded in Trilium, where they will be displayed without the need to download them first.
Since v0.102.0, PDFs will be rendered using Trilium's built-in PDF viewer, which is a customization of [Mozilla's PDF.js viewer](https://mozilla.github.io/pdf.js/) (also built-in in the Mozilla Firefox browser). Versions prior to that render PDFs using the browser's default PDF viewer.
## Storing last position and settings
For every PDF, Trilium will remember the following information:
* The current page.
* The scroll position, within the current page.
* The rotation of the page.
This makes it useful when reading large documents since the position is remembered automatically. This happens in the background, however it's recorded only a few seconds after stopping any scroll actions.
> [!TIP]
> Technically, the information about the scroll position and rotation is stored in the <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/Notes/Attachments.md">Attachments</a> section, in a dedicated attachment called `pdfHistory.json`.
## Annotations
Since v0.102.0 it's possible to annotate PDFs. To do so, look for the annotation buttons on the right side of the PDF toolbar (<img src="1_PDFs_image.png" width="120" height="32">).
### Supported annotations
The following annotation methods are supported:
* **Highlight**
Allows highlighting text with one of the predefined colors.
* The thickness is also adjustable.
* It's also possible to highlight the blank space, turning the feature more into a thicker pen.
* **Text**
Allows adding arbitrary text, with a custom color and size.
* **Pen**
Allows free drawing on the document, with variable color, thickness and opacity.
* **Image**
Allows inserting images from outside Trilium directly into the document.
### Editing existing annotations
Although annotations are stored in the PDF itself, they can be edited. To edit an annotation, press one of the annotation buttons from the previous section to enter edit mode and click on an existing annotation. This will reveal a toolbar with options to customize the annotation (e.g. to change a color), as well as the possibility to remove it.
### How are annotations stored
Annotations are stored directly in the PDF. When modifications are made, Trilium will replace the PDF with the new one.
Since modifications are automatically saved, there's no need to manually save the document after making modifications to the annotations.
The benefit of “baked-in annotations” is that they are also accessible if downloading (for external use outside Trilium) or sharing the note.
The downside is that the entire PDF needs to be sent back to the server, which can slow down performance for larger documents. If you encounter any issues from this system, feel free to [report it](../../Troubleshooting/Reporting%20issues.md).
## Filling out forms
Similar to annotations, forms are also supported by Trilium since v0.102.0. If the document has fields that can be filled-in, they will be indicated with a colored background.
Simply type text in the forms and they will be automatically saved.
## Sidebar navigation
> [!NOTE]
> This feature is only available if <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/UI%20Elements/New%20Layout.md">New Layout</a> is enabled. If you are using the old layout, these features are still available by looking for a sidebar button in the PDF viewer toolbar.
When a PDF file is opened in Trilium the <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/UI%20Elements/Right%20Sidebar.md">Right Sidebar</a> is augmented with PDF-specific navigation, with the following features:
* Table of contents/outline
* All the headings and “bookmarks” will be displayed hierarchially.
* The heading on the current page is also highlighted (note that it can be slightly offset depending on how many headings are on the same page).
* Clicking on a heading will jump to the corresponding position in the PDF.
* Pages
* A preview of all the pages with a small thumbnail.
* Clicking on a page will automatically navigate to that page.
* Attachments
* If the PDF has its own attachments (not to be confused with Trilium's <a class="reference-link" href="../../Basic%20Concepts%20and%20Features/Notes/Attachments.md">Attachments</a>), they will be displayed in a list.
* Some information such as the name and size of the attachment are displayed.
* It's possible to download the attachment by clicking on the download button.
* Layers
* A less common feature, if the PDF has toggle-able layers, these layers will be displayed in a list here.
* It's possible to toggle the visibility for each individual layer.
## Share functionality
PDFs can also be shared using the <a class="reference-link" href="../../Advanced%20Usage/Sharing.md">Sharing</a> feature. This will also use Trilium's customized PDF viewer.
If you are using a reverse proxy on your server with strict access limitations for the share functionality, make sure that `[host].com/pdfjs` directory is accessible. Note that this directory is outside the `/share` route as it's common with the rest of the application.

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 652 KiB

View File

@@ -38,7 +38,8 @@
"dev:format-fix": "eslint -c eslint.format.config.mjs . --fix",
"dev:linter-check": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint .",
"dev:linter-fix": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint . --fix",
"postinstall": "tsx scripts/electron-rebuild.mts"
"postinstall": "tsx scripts/electron-rebuild.mts && pnpm prepare",
"prepare": "pnpm run --filter pdfjs-viewer --filter share-theme build"
},
"private": true,
"devDependencies": {

View File

@@ -24,7 +24,7 @@ async function extractAndSendAttachments() {
window.parent.postMessage({
type: "pdfjs-viewer-attachments",
attachments: []
}, window.location.origin);
} satisfies PdfViewerAttachmentsMessage, window.location.origin);
return;
}
@@ -42,13 +42,13 @@ async function extractAndSendAttachments() {
filename: att.filename,
size: att.size
}))
}, window.location.origin);
} satisfies PdfViewerAttachmentsMessage, window.location.origin);
} catch (error) {
console.error("Error extracting attachments:", error);
window.parent.postMessage({
type: "pdfjs-viewer-attachments",
attachments: []
}, window.location.origin);
} satisfies PdfViewerAttachmentsMessage, window.location.origin);
}
}

View File

@@ -12,3 +12,12 @@
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
}
}
#downloadButton,
#secondaryDownload {
display: none !important;
}
#secondaryOpenFile {
display: none !important;
}

View File

@@ -20,6 +20,7 @@ async function main() {
app.eventBus.on("documentloaded", () => {
manageSave();
manageDownload();
extractAndSendToc();
setupScrollToHeading();
setupActiveHeadingTracking();
@@ -65,8 +66,10 @@ function manageSave() {
const data = await app.pdfDocument.saveDocument();
window.parent.postMessage({
type: "pdfjs-viewer-document-modified",
data: data
}, window.location.origin);
data,
ntxId: window.TRILIUM_NTX_ID,
noteId: window.TRILIUM_NOTE_ID
} satisfies PdfDocumentModifiedMessage, window.location.origin);
storage.resetModified();
timeout = null;
}, 2_000);
@@ -85,4 +88,15 @@ function manageSave() {
});
}
function manageDownload() {
window.addEventListener("message", event => {
if (event.origin !== window.location.origin) return;
if (event.data?.type === "trilium-request-download") {
const app = window.PDFViewerApplication;
app.eventBus.dispatch("download", { source: window });
}
});
}
main();

View File

@@ -29,7 +29,7 @@ async function extractAndSendLayers() {
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers: []
}, window.location.origin);
} satisfies PdfViewerLayersMessage, window.location.origin);
return;
}
@@ -39,7 +39,7 @@ async function extractAndSendLayers() {
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers: []
}, window.location.origin);
} satisfies PdfViewerLayersMessage, window.location.origin);
return;
}
@@ -78,13 +78,13 @@ async function extractAndSendLayers() {
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers
}, window.location.origin);
} satisfies PdfViewerLayersMessage, window.location.origin);
} catch (error) {
console.error("Error extracting layers:", error);
window.parent.postMessage({
type: "pdfjs-viewer-layers",
layers: []
}, window.location.origin);
} satisfies PdfViewerLayersMessage, window.location.origin);
}
}

View File

@@ -16,7 +16,7 @@ export function setupPdfPages() {
window.parent.postMessage({
type: "pdfjs-viewer-current-page",
currentPage: evt.pageNumber
}, window.location.origin);
} satisfies PdfViewerCurrentPageMessage, window.location.origin);
});
window.addEventListener("message", async(event) => {
@@ -42,7 +42,7 @@ function sendPageInfo() {
type: "pdfjs-viewer-page-info",
totalPages: app.pdfDocument.numPages,
currentPage: app.pdfViewer.currentPageNumber
}, window.location.origin);
} satisfies PdfViewerPageInfoMessage, window.location.origin);
}
async function generateThumbnail(pageNumber: number) {
@@ -75,7 +75,7 @@ async function generateThumbnail(pageNumber: number) {
type: "pdfjs-viewer-thumbnail",
pageNumber,
dataUrl
}, window.location.origin);
} satisfies PdfViewerThumbnailMessage, window.location.origin);
} catch (error) {
console.error(`Error generating thumbnail for page %d:`, pageNumber, error);
}

View File

@@ -41,8 +41,10 @@ function saveHistory(value: string) {
window.parent.postMessage({
type: "pdfjs-viewer-save-view-history",
data: JSON.stringify(history)
}, window.location.origin);
data: JSON.stringify(history),
ntxId: window.TRILIUM_NTX_ID,
noteId: window.TRILIUM_NOTE_ID
} satisfies PdfSaveViewHistoryMessage, window.location.origin);
saveTimeout = null;
}, 2_000);
}

View File

@@ -11,7 +11,7 @@ export async function extractAndSendToc() {
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: null
}, window.location.origin);
} satisfies PdfViewerTocMessage, window.location.origin);
return;
}
@@ -26,12 +26,12 @@ export async function extractAndSendToc() {
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: toc
}, window.location.origin);
} satisfies PdfViewerTocMessage, window.location.origin);
} catch (error) {
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: null
}, window.location.origin);
} satisfies PdfViewerTocMessage, window.location.origin);
}
}
@@ -165,7 +165,7 @@ export function setupActiveHeadingTracking() {
window.parent.postMessage({
type: "pdfjs-viewer-active-heading",
headingId: activeHeadingId
}, window.location.origin);
} satisfies PdfViewerActiveHeadingMessage, window.location.origin);
}
}