Compare commits
121 Commits
renovate/k
...
renovate/h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cafcdf838f | ||
|
|
caa428c1a2 | ||
|
|
517c721664 | ||
|
|
a8cdaa69f7 | ||
|
|
53d221ef34 | ||
|
|
5450fde472 | ||
|
|
808446cef5 | ||
|
|
921c663199 | ||
|
|
1b8a75b615 | ||
|
|
f78ced5bc3 | ||
|
|
81bf5f4f3b | ||
|
|
aaed368670 | ||
|
|
5e8de14721 | ||
|
|
634ab5b5c0 | ||
|
|
906889a035 | ||
|
|
ab9d50b905 | ||
|
|
e61b7c7cfc | ||
|
|
1c628fba4c | ||
|
|
f8b4c6cb15 | ||
|
|
3edd8f6c5a | ||
|
|
7777f72893 | ||
|
|
9af85b767b | ||
|
|
73260b91eb | ||
|
|
2858f63873 | ||
|
|
15ca328727 | ||
|
|
5b3fbecc0f | ||
|
|
365d0f0aac | ||
|
|
e86d84c463 | ||
|
|
6b974c2ac7 | ||
|
|
d2afcbb98d | ||
|
|
68a122fcf5 | ||
|
|
92f0144b48 | ||
|
|
a5a345728c | ||
|
|
23890e64e9 | ||
|
|
3de712aca4 | ||
|
|
cb5b4d870f | ||
|
|
f81aef2de5 | ||
|
|
06aed16ea1 | ||
|
|
aa2d8af15c | ||
|
|
dc7b91433b | ||
|
|
72951386b1 | ||
|
|
db8df01d82 | ||
|
|
98713ed111 | ||
|
|
3e88fecb15 | ||
|
|
fe4255f2fc | ||
|
|
c046a57654 | ||
|
|
d8fc0d45a8 | ||
|
|
567b96cfb4 | ||
|
|
d25849d280 | ||
|
|
d4d73995db | ||
|
|
f4657b5da9 | ||
|
|
614f43cb8a | ||
|
|
ca2fbf8dba | ||
|
|
a421513442 | ||
|
|
a9599c471a | ||
|
|
415bcac641 | ||
|
|
9527017314 | ||
|
|
1d3d7c77f8 | ||
|
|
e868615fd5 | ||
|
|
80493a52be | ||
|
|
3fed2ba42e | ||
|
|
82592ada54 | ||
|
|
5528701744 | ||
|
|
0ca665fb85 | ||
|
|
7eb452ed8b | ||
|
|
d81dec94a9 | ||
|
|
6631a4a806 | ||
|
|
12f817c896 | ||
|
|
87229600d2 | ||
|
|
471a46a030 | ||
|
|
41220eebd5 | ||
|
|
755872277b | ||
|
|
2cb54d7021 | ||
|
|
5a16bafbbf | ||
|
|
fc6e9d89d9 | ||
|
|
8af35da279 | ||
|
|
7107fec1a4 | ||
|
|
4bb662c5fb | ||
|
|
89297b92f8 | ||
|
|
e019271e74 | ||
|
|
f6d61eefcc | ||
|
|
fabc07be42 | ||
|
|
bccfa7956c | ||
|
|
42a05f411b | ||
|
|
7ba7b98f5f | ||
|
|
2132c2ab38 | ||
|
|
2ce4d512e7 | ||
|
|
1258d32820 | ||
|
|
db763ba229 | ||
|
|
951fdaec70 | ||
|
|
4303f3687e | ||
|
|
540b0e0b83 | ||
|
|
08a0326cb0 | ||
|
|
8b0a45e4fd | ||
|
|
0e0ad2ed73 | ||
|
|
4c73f31aca | ||
|
|
6b2ae8fd12 | ||
|
|
88d84fae1e | ||
|
|
cdc46faaad | ||
|
|
24dbc79961 | ||
|
|
8cb58dcc45 | ||
|
|
fe70b8aee6 | ||
|
|
00f66cfb49 | ||
|
|
3a4b080765 | ||
|
|
41269ef987 | ||
|
|
e521c6a386 | ||
|
|
1c35a557c1 | ||
|
|
99eb8389c5 | ||
|
|
c5e560ef5b | ||
|
|
a7d7a078b1 | ||
|
|
a06fa5222f | ||
|
|
8d3e40a28a | ||
|
|
8e32f99790 | ||
|
|
57bce62e48 | ||
|
|
1c873394d5 | ||
|
|
aac4774326 | ||
|
|
7f32fe5ef7 | ||
|
|
d3337eab9c | ||
|
|
8128a8192a | ||
|
|
65514a6fd7 | ||
|
|
93a7f8c711 |
@@ -14,7 +14,7 @@
|
||||
"keywords": [],
|
||||
"author": "Elian Doran <contact@eliandoran.me>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.31.0",
|
||||
"packageManager": "pnpm@10.32.0",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.20.2",
|
||||
"archiver": "7.0.1",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@fullcalendar/rrule": "6.1.20",
|
||||
"@fullcalendar/timegrid": "6.1.20",
|
||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
"@mermaid-js/layout-elk": "0.2.1",
|
||||
"@mind-elixir/node-menu": "5.0.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@preact/signals": "2.8.2",
|
||||
@@ -43,7 +43,7 @@
|
||||
"@univerjs/preset-sheets-note": "0.16.1",
|
||||
"@univerjs/preset-sheets-sort": "0.16.1",
|
||||
"@univerjs/presets": "0.16.1",
|
||||
"@zumer/snapdom": "2.0.2",
|
||||
"@zumer/snapdom": "2.1.0",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
@@ -53,22 +53,22 @@
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.1",
|
||||
"globals": "17.4.0",
|
||||
"i18next": "25.8.14",
|
||||
"i18next": "25.8.17",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "4.0.0",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.38",
|
||||
"knockout": "3.5.2",
|
||||
"knockout": "3.5.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.4",
|
||||
"mermaid": "11.12.3",
|
||||
"mind-elixir": "5.9.2",
|
||||
"mind-elixir": "5.9.3",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.28.4",
|
||||
"preact": "10.29.0",
|
||||
"react-i18next": "16.5.6",
|
||||
"react-window": "2.2.7",
|
||||
"reveal.js": "5.2.1",
|
||||
@@ -89,7 +89,7 @@
|
||||
"@types/tabulator-tables": "6.3.1",
|
||||
"copy-webpack-plugin": "14.0.0",
|
||||
"happy-dom": "20.8.3",
|
||||
"lightningcss": "1.31.1",
|
||||
"lightningcss": "1.32.0",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.2.0"
|
||||
}
|
||||
|
||||
@@ -803,12 +803,13 @@
|
||||
"web-view": "عرض الويب",
|
||||
"mind-map": "خريطة ذهنية",
|
||||
"geo-map": "خريطة جغرافية",
|
||||
"task-list": "قائمة المهام"
|
||||
"task-list": "قائمة المهام",
|
||||
"spreadsheet": "جدول البيانات"
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "مشترك",
|
||||
"toggle-on-title": "مشاركة الملاحظة",
|
||||
"toggle-off-title": "الغاء مشاركة الملاحظة"
|
||||
"toggle-off-title": "إلغاء مشاركة الملاحظة"
|
||||
},
|
||||
"template_switch": {
|
||||
"template": "قالب"
|
||||
@@ -1286,8 +1287,10 @@
|
||||
"search-for": "بحث ل \"{{term}}\""
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-off": "ازالة الحماية عن الملاحظة",
|
||||
"toggle-on": "حماية الملاحظة"
|
||||
"toggle-off": "إزالة الحماية عن الملاحظة",
|
||||
"toggle-on": "حماية الملاحظة",
|
||||
"toggle-on-hint": "الملاحظة غير محمة، انقر لحمايتها",
|
||||
"toggle-off-hint": "الملاحظة محمية، انقر لإزالة الحماية منها"
|
||||
},
|
||||
"open-help-page": "فتح صفحة المساعدة",
|
||||
"empty": {
|
||||
|
||||
@@ -1036,6 +1036,25 @@
|
||||
"file_preview_not_available": "File preview is not available for this file format.",
|
||||
"too_big": "The preview only shows the first {{maxNumChars}} characters of the file for performance reasons. Download the file and open it externally to be able to see the entire content."
|
||||
},
|
||||
"media": {
|
||||
"play": "Play (Space)",
|
||||
"pause": "Pause (Space)",
|
||||
"back-10s": "Back 10s (Left arrow key)",
|
||||
"forward-30s": "Forward 30s",
|
||||
"mute": "Mute (M)",
|
||||
"unmute": "Unmute (M)",
|
||||
"playback-speed": "Playback speed",
|
||||
"loop": "Loop",
|
||||
"disable-loop": "Disable loop",
|
||||
"rotate": "Rotate",
|
||||
"picture-in-picture": "Picture-in-picture",
|
||||
"exit-picture-in-picture": "Exit picture-in-picture",
|
||||
"fullscreen": "Fullscreen (F)",
|
||||
"exit-fullscreen": "Exit fullscreen",
|
||||
"unsupported-format": "Media preview is not available for this file format:\n{{mime}}",
|
||||
"zoom-to-fit": "Zoom to fill",
|
||||
"zoom-reset": "Reset zoom to fill"
|
||||
},
|
||||
"protected_session": {
|
||||
"enter_password_instruction": "Showing protected note requires entering your password:",
|
||||
"start_session_button": "Start protected session",
|
||||
|
||||
@@ -1780,7 +1780,8 @@
|
||||
"ai-chat": "Czat AI",
|
||||
"task-list": "Lista zadań",
|
||||
"new-feature": "Nowość",
|
||||
"collections": "Kolekcje"
|
||||
"collections": "Kolekcje",
|
||||
"spreadsheet": "Arkusz"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Chroń notatkę",
|
||||
|
||||
@@ -41,7 +41,9 @@ export default function NoteDetail() {
|
||||
const hasFixedTree = note && noteContext?.hoistedNoteId === "_lbMobileRoot" && isMobile() && note.noteId.startsWith("_lbMobile");
|
||||
|
||||
// Defer loading for tabs that haven't been active yet (e.g. on app refresh).
|
||||
const [ hasTabBeenActive, setHasTabBeenActive ] = useState(() => noteContext?.isActive() ?? false);
|
||||
// Special contexts (ntxId starting with "_", e.g. popup editor) are always considered active.
|
||||
const isSpecialContext = ntxId?.startsWith("_") ?? false;
|
||||
const [ hasTabBeenActive, setHasTabBeenActive ] = useState(() => isSpecialContext || (noteContext?.isActive() ?? false));
|
||||
useEffect(() => {
|
||||
if (!hasTabBeenActive && noteContext?.isActive()) {
|
||||
setHasTabBeenActive(true);
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
margin-inline: var(--content-margin-inline);
|
||||
padding-block: 4px;
|
||||
padding: 4px var(--content-margin-inline);
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
}
|
||||
@@ -42,7 +41,11 @@ body.mobile .board-view-container {
|
||||
body.mobile .board-view-container .board-column {
|
||||
width: 75vw;
|
||||
max-width: 300px;
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
body.mobile .board-view-container .board-column,
|
||||
body.mobile .board-view-container .board-add-column {
|
||||
scroll-snap-align: center;
|
||||
}
|
||||
|
||||
.board-view-container .board-column.drag-over {
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
animation: fadeOut 250ms ease-in 5s forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body#trilium-app.motion-disabled &.saved {
|
||||
animation: fadeOut 0s 5s forwards !important;
|
||||
}
|
||||
}
|
||||
&.active-content-badge { --color: var(--badge-active-content-background-color); }
|
||||
&.active-content-badge.disabled {
|
||||
|
||||
@@ -83,7 +83,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (note.type === "file" && (note.mime === "application/pdf" || note.mime.startsWith("video/"))) {
|
||||
if (note.type === "file" && (note.mime === "application/pdf" || note.mime.startsWith("video/") || note.mime.startsWith("audio/"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (note.type === "file" && MIME_TYPES_WITH_BACKGROUND_EFFECTS.includes(note.mime)) {
|
||||
if (note.type === "file" && (MIME_TYPES_WITH_BACKGROUND_EFFECTS.includes(note.mime) || note.mime.startsWith("audio/"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
color: var(--muted-text-color);
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
|
||||
.tn-icon {
|
||||
font-size: 4em;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "./TableOfContents.css";
|
||||
|
||||
import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5";
|
||||
import { attributeChangeAffectsHeading, CKTextEditor, ModelElement } from "@triliumnext/ckeditor5";
|
||||
import clsx from "clsx";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -170,11 +170,14 @@ function EditableTextTableOfContents() {
|
||||
|
||||
const affectsHeadings = changes.some( change => {
|
||||
return (
|
||||
change.type === 'insert' || change.type === 'remove' || (change.type === 'attribute' && change.attributeKey === 'headingLevel')
|
||||
change.type === 'insert' || change.type === 'remove' ||
|
||||
(change.type === 'attribute' && attributeChangeAffectsHeading(change, textEditor))
|
||||
);
|
||||
});
|
||||
if (affectsHeadings) {
|
||||
setHeadings(extractTocFromTextEditor(textEditor));
|
||||
requestAnimationFrame(() => {
|
||||
setHeadings(extractTocFromTextEditor(textEditor));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.note-detail-file > .pdf-preview,
|
||||
.note-detail-file > .video-preview {
|
||||
.note-detail-file > .pdf-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 100;
|
||||
@@ -38,4 +37,4 @@
|
||||
right: 15px;
|
||||
width: calc(100% - 30px);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import "./File.css";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { getUrlForDownload } from "../../services/open";
|
||||
import Alert from "../react/Alert";
|
||||
import { useNoteBlob } from "../react/hooks";
|
||||
import AudioPreview from "./file/Audio";
|
||||
import PdfPreview from "./file/Pdf";
|
||||
import VideoPreview from "./file/Video";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
|
||||
const TEXT_MAX_NUM_CHARS = 5000;
|
||||
@@ -42,27 +42,6 @@ function TextPreview({ content }: { content: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function VideoPreview({ note }: { note: FNote }) {
|
||||
return (
|
||||
<video
|
||||
class="video-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
datatype={note?.mime}
|
||||
controls
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function AudioPreview({ note }: { note: FNote }) {
|
||||
return (
|
||||
<audio
|
||||
class="audio-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
controls
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function NoPreview() {
|
||||
return (
|
||||
<Alert className="file-preview-not-available" type="info">
|
||||
|
||||
112
apps/client/src/widgets/type_widgets/file/Audio.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import { getUrlForDownload } from "../../../services/open";
|
||||
import Icon from "../../react/Icon";
|
||||
import NoItems from "../../react/NoItems";
|
||||
import { LoopButton, PlaybackSpeed, PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer";
|
||||
|
||||
export default function AudioPreview({ note }: { note: FNote }) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const togglePlayback = useCallback(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
if (audio.paused) {
|
||||
audio.play();
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
}, []);
|
||||
const onKeyDown = useKeyboardShortcuts(audioRef, togglePlayback);
|
||||
|
||||
useEffect(() => setError(false), [note.noteId]);
|
||||
const onError = useCallback(() => setError(true), []);
|
||||
|
||||
if (error) {
|
||||
return <NoItems icon="bx bx-volume-mute" text={t("media.unsupported-format", { mime: note.mime.replace("/", "-") })} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="audio-preview-wrapper" onKeyDown={onKeyDown} tabIndex={0}>
|
||||
<audio
|
||||
class="audio-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
ref={audioRef}
|
||||
onPlay={() => setPlaying(true)}
|
||||
onPause={() => setPlaying(false)}
|
||||
onError={onError}
|
||||
/>
|
||||
<div className="audio-preview-icon-wrapper">
|
||||
<Icon icon="bx bx-music" className="audio-preview-icon" />
|
||||
</div>
|
||||
<div className="media-preview-controls">
|
||||
<SeekBar mediaRef={audioRef} />
|
||||
|
||||
<div class="media-buttons-row">
|
||||
<div className="left">
|
||||
<PlaybackSpeed mediaRef={audioRef} />
|
||||
</div>
|
||||
|
||||
<div className="center">
|
||||
<div className="spacer" />
|
||||
<SkipButton mediaRef={audioRef} seconds={-10} icon="bx bx-rewind" text={t("media.back-10s")} />
|
||||
<PlayPauseButton playing={playing} togglePlayback={togglePlayback} />
|
||||
<SkipButton mediaRef={audioRef} seconds={30} icon="bx bx-fast-forward" text={t("media.forward-30s")} />
|
||||
<LoopButton mediaRef={audioRef} />
|
||||
</div>
|
||||
|
||||
<div className="right">
|
||||
<VolumeControl mediaRef={audioRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useKeyboardShortcuts(audioRef: MutableRef<HTMLAudioElement | null>, togglePlayback: () => void) {
|
||||
return useCallback((e: KeyboardEvent) => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
|
||||
switch (e.key) {
|
||||
case " ":
|
||||
e.preventDefault();
|
||||
togglePlayback();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
e.preventDefault();
|
||||
audio.currentTime = Math.max(0, audio.currentTime - (e.ctrlKey ? 60 : 10));
|
||||
break;
|
||||
case "ArrowRight":
|
||||
e.preventDefault();
|
||||
audio.currentTime = Math.min(audio.duration, audio.currentTime + (e.ctrlKey ? 60 : 10));
|
||||
break;
|
||||
case "m":
|
||||
case "M":
|
||||
e.preventDefault();
|
||||
audio.muted = !audio.muted;
|
||||
break;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
audio.volume = Math.min(1, audio.volume + 0.05);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
audio.volume = Math.max(0, audio.volume - 0.05);
|
||||
break;
|
||||
case "Home":
|
||||
e.preventDefault();
|
||||
audio.currentTime = 0;
|
||||
break;
|
||||
case "End":
|
||||
e.preventDefault();
|
||||
audio.currentTime = audio.duration;
|
||||
break;
|
||||
}
|
||||
}, [ audioRef, togglePlayback ]);
|
||||
}
|
||||
98
apps/client/src/widgets/type_widgets/file/MediaPlayer.css
Normal file
@@ -0,0 +1,98 @@
|
||||
.media-preview-controls {
|
||||
padding: 1.25em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
.media-buttons-row {
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: var(--icon-button-size, 32px);
|
||||
height: var(--icon-button-size, 32px);
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
--icon-button-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-seekbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
.media-time {
|
||||
font-size: 0.85em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-trackbar {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.media-volume-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
|
||||
.media-volume-slider {
|
||||
width: 80px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.speed-dropdown {
|
||||
position: relative;
|
||||
|
||||
.tn-icon {
|
||||
transform: translateY(-10%);
|
||||
}
|
||||
|
||||
.media-speed-label {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(15%);
|
||||
text-align: center;
|
||||
font-size: 0.6rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.audio-preview-icon-wrapper {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
220
apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import "./MediaPlayer.css";
|
||||
|
||||
import { RefObject } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { t } from "../../../services/i18n";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import Dropdown from "../../react/Dropdown";
|
||||
import Icon from "../../react/Icon";
|
||||
|
||||
export function SeekBar({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
|
||||
const onTimeUpdate = () => setCurrentTime(media.currentTime);
|
||||
const onDurationChange = () => setDuration(media.duration);
|
||||
|
||||
media.addEventListener("timeupdate", onTimeUpdate);
|
||||
media.addEventListener("durationchange", onDurationChange);
|
||||
return () => {
|
||||
media.removeEventListener("timeupdate", onTimeUpdate);
|
||||
media.removeEventListener("durationchange", onDurationChange);
|
||||
};
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const onSeek = (e: Event) => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.currentTime = parseFloat((e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="media-seekbar-row">
|
||||
<span class="media-time">{formatTime(currentTime)}</span>
|
||||
<input
|
||||
type="range"
|
||||
class="media-trackbar"
|
||||
min={0}
|
||||
max={duration || 0}
|
||||
step={0.1}
|
||||
value={currentTime}
|
||||
onInput={onSeek}
|
||||
/>
|
||||
<span class="media-time">-{formatTime(Math.max(0, duration - currentTime))}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function PlayPauseButton({ playing, togglePlayback }: {
|
||||
playing: boolean,
|
||||
togglePlayback: () => void
|
||||
}) {
|
||||
return (
|
||||
<ActionButton
|
||||
className="play-button"
|
||||
icon={playing ? "bx bx-pause" : "bx bx-play"}
|
||||
text={playing ? t("media.pause") : t("media.play")}
|
||||
onClick={togglePlayback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function VolumeControl({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [volume, setVolume] = useState(() => mediaRef.current?.volume ?? 1);
|
||||
const [muted, setMuted] = useState(() => mediaRef.current?.muted ?? false);
|
||||
|
||||
// Sync state when the media element changes volume externally.
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
|
||||
setVolume(media.volume);
|
||||
setMuted(media.muted);
|
||||
|
||||
const onVolumeChange = () => {
|
||||
setVolume(media.volume);
|
||||
setMuted(media.muted);
|
||||
};
|
||||
media.addEventListener("volumechange", onVolumeChange);
|
||||
return () => media.removeEventListener("volumechange", onVolumeChange);
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const onVolumeChange = (e: Event) => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
const val = parseFloat((e.target as HTMLInputElement).value);
|
||||
media.volume = val;
|
||||
setVolume(val);
|
||||
if (val > 0 && media.muted) {
|
||||
media.muted = false;
|
||||
setMuted(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.muted = !media.muted;
|
||||
setMuted(media.muted);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="media-volume-row">
|
||||
<ActionButton
|
||||
icon={muted || volume === 0 ? "bx bx-volume-mute" : volume < 0.5 ? "bx bx-volume-low" : "bx bx-volume-full"}
|
||||
text={muted ? t("media.unmute") : t("media.mute")}
|
||||
onClick={toggleMute}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
class="media-volume-slider"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
value={muted ? 0 : volume}
|
||||
onInput={onVolumeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkipButton({ mediaRef, seconds, icon, text }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement>, seconds: number, icon: string, text: string }) {
|
||||
const skip = () => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.currentTime = Math.max(0, Math.min(media.duration, media.currentTime + seconds));
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton icon={icon} text={text} onClick={skip} />
|
||||
);
|
||||
}
|
||||
|
||||
export function LoopButton({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [loop, setLoop] = useState(() => mediaRef.current?.loop ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
setLoop(media.loop);
|
||||
|
||||
const observer = new MutationObserver(() => setLoop(media.loop));
|
||||
observer.observe(media, { attributes: true, attributeFilter: ["loop"] });
|
||||
return () => observer.disconnect();
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const toggle = () => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.loop = !media.loop;
|
||||
setLoop(media.loop);
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className={loop ? "active" : ""}
|
||||
icon="bx bx-repeat"
|
||||
text={loop ? t("media.disable-loop") : t("media.loop")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const PLAYBACK_SPEEDS = [0.5, 1, 1.25, 1.5, 2];
|
||||
|
||||
export function PlaybackSpeed({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [speed, setSpeed] = useState(() => mediaRef.current?.playbackRate ?? 1);
|
||||
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
|
||||
setSpeed(media.playbackRate);
|
||||
|
||||
const onRateChange = () => setSpeed(media.playbackRate);
|
||||
media.addEventListener("ratechange", onRateChange);
|
||||
return () => media.removeEventListener("ratechange", onRateChange);
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const selectSpeed = (rate: number) => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.playbackRate = rate;
|
||||
setSpeed(rate);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
iconAction
|
||||
hideToggleArrow
|
||||
buttonClassName="speed-dropdown"
|
||||
text={<>
|
||||
<Icon icon="bx bx-tachometer" />
|
||||
<span class="media-speed-label">{speed}x</span>
|
||||
</>}
|
||||
title={t("media.playback-speed")}
|
||||
>
|
||||
{PLAYBACK_SPEEDS.map((rate) => (
|
||||
<li key={rate}>
|
||||
<button
|
||||
class={`dropdown-item ${rate === speed ? "active" : ""}`}
|
||||
onClick={() => selectSpeed(rate)}
|
||||
>
|
||||
{rate}x
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
35
apps/client/src/widgets/type_widgets/file/Video.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.note-detail-file > .video-preview-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-color: black;
|
||||
|
||||
.video-preview {
|
||||
background-color: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.controls-hidden {
|
||||
cursor: pointer;
|
||||
|
||||
.media-preview-controls {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.media-preview-controls {
|
||||
--icon-button-hover-color: white;
|
||||
--icon-button-hover-background: rgba(255, 255, 255, 0.2);
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(6px);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
298
apps/client/src/widgets/type_widgets/file/Video.tsx
Normal file
@@ -0,0 +1,298 @@
|
||||
import "./Video.css";
|
||||
|
||||
import { RefObject } from "preact";
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import { getUrlForDownload } from "../../../services/open";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import NoItems from "../../react/NoItems";
|
||||
import { LoopButton, PlaybackSpeed, PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer";
|
||||
|
||||
const AUTO_HIDE_DELAY = 3000;
|
||||
|
||||
export default function VideoPreview({ note }: { note: FNote }) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const { visible: controlsVisible, onMouseMove, flash: flashControls } = useAutoHideControls(videoRef, playing);
|
||||
|
||||
useEffect(() => setError(false), [note.noteId]);
|
||||
const onError = useCallback(() => setError(true), []);
|
||||
|
||||
const togglePlayback = useCallback(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
if (video.paused) {
|
||||
video.play();
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onVideoClick = useCallback((e: MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest(".media-preview-controls")) return;
|
||||
togglePlayback();
|
||||
}, [togglePlayback]);
|
||||
|
||||
const onKeyDown = useKeyboardShortcuts(videoRef, wrapperRef, togglePlayback, flashControls);
|
||||
|
||||
if (error) {
|
||||
return <NoItems icon="bx bx-video-off" text={t("media.unsupported-format", { mime: note.mime.replace("/", "-") })} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className={`video-preview-wrapper ${controlsVisible ? "" : "controls-hidden"}`} tabIndex={0} onClick={onVideoClick} onKeyDown={onKeyDown} onMouseMove={onMouseMove}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
class="video-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
datatype={note?.mime}
|
||||
onPlay={() => setPlaying(true)}
|
||||
onPause={() => setPlaying(false)}
|
||||
onError={onError}
|
||||
/>
|
||||
|
||||
<div className="media-preview-controls">
|
||||
<SeekBar mediaRef={videoRef} />
|
||||
<div class="media-buttons-row">
|
||||
<div className="left">
|
||||
<PlaybackSpeed mediaRef={videoRef} />
|
||||
<RotateButton videoRef={videoRef} />
|
||||
</div>
|
||||
<div className="center">
|
||||
<div className="spacer" />
|
||||
<SkipButton mediaRef={videoRef} seconds={-10} icon="bx bx-rewind" text={t("media.back-10s")} />
|
||||
<PlayPauseButton playing={playing} togglePlayback={togglePlayback} />
|
||||
<SkipButton mediaRef={videoRef} seconds={30} icon="bx bx-fast-forward" text={t("media.forward-30s")} />
|
||||
<LoopButton mediaRef={videoRef} />
|
||||
</div>
|
||||
<div className="right">
|
||||
<VolumeControl mediaRef={videoRef} />
|
||||
<ZoomToFitButton videoRef={videoRef} />
|
||||
<PictureInPictureButton videoRef={videoRef} />
|
||||
<FullscreenButton targetRef={wrapperRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useKeyboardShortcuts(videoRef: MutableRef<HTMLVideoElement | null>, wrapperRef: MutableRef<HTMLDivElement | null>, togglePlayback: () => void, flashControls: () => void) {
|
||||
return useCallback((e: KeyboardEvent) => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
switch (e.key) {
|
||||
case " ":
|
||||
e.preventDefault();
|
||||
togglePlayback();
|
||||
flashControls();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
e.preventDefault();
|
||||
video.currentTime = Math.max(0, video.currentTime - (e.ctrlKey ? 60 : 10));
|
||||
flashControls();
|
||||
break;
|
||||
case "ArrowRight":
|
||||
e.preventDefault();
|
||||
video.currentTime = Math.min(video.duration, video.currentTime + (e.ctrlKey ? 60 : 10));
|
||||
flashControls();
|
||||
break;
|
||||
case "f":
|
||||
case "F":
|
||||
e.preventDefault();
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
wrapperRef.current?.requestFullscreen();
|
||||
}
|
||||
break;
|
||||
case "m":
|
||||
case "M":
|
||||
e.preventDefault();
|
||||
video.muted = !video.muted;
|
||||
flashControls();
|
||||
break;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
video.volume = Math.min(1, video.volume + 0.05);
|
||||
flashControls();
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
video.volume = Math.max(0, video.volume - 0.05);
|
||||
flashControls();
|
||||
break;
|
||||
case "Home":
|
||||
e.preventDefault();
|
||||
video.currentTime = 0;
|
||||
flashControls();
|
||||
break;
|
||||
case "End":
|
||||
e.preventDefault();
|
||||
video.currentTime = video.duration;
|
||||
flashControls();
|
||||
break;
|
||||
}
|
||||
}, [ wrapperRef, videoRef, togglePlayback, flashControls ]);
|
||||
}
|
||||
|
||||
function useAutoHideControls(videoRef: RefObject<HTMLVideoElement>, playing: boolean) {
|
||||
const [visible, setVisible] = useState(true);
|
||||
const hideTimerRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const scheduleHide = useCallback(() => {
|
||||
clearTimeout(hideTimerRef.current);
|
||||
if (videoRef.current && !videoRef.current.paused) {
|
||||
hideTimerRef.current = setTimeout(() => setVisible(false), AUTO_HIDE_DELAY);
|
||||
}
|
||||
}, [ videoRef]);
|
||||
|
||||
const onMouseMove = useCallback(() => {
|
||||
setVisible(true);
|
||||
scheduleHide();
|
||||
}, [scheduleHide]);
|
||||
|
||||
// Hide immediately when playback starts, show when paused.
|
||||
useEffect(() => {
|
||||
if (playing) {
|
||||
setVisible(false);
|
||||
} else {
|
||||
clearTimeout(hideTimerRef.current);
|
||||
setVisible(true);
|
||||
}
|
||||
return () => clearTimeout(hideTimerRef.current);
|
||||
}, [playing, scheduleHide]);
|
||||
|
||||
return { visible, onMouseMove, flash: onMouseMove };
|
||||
}
|
||||
|
||||
function RotateButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [rotation, setRotation] = useState(0);
|
||||
|
||||
const rotate = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
const next = (rotation + 90) % 360;
|
||||
setRotation(next);
|
||||
|
||||
const isSideways = next === 90 || next === 270;
|
||||
if (isSideways) {
|
||||
// Scale down so the rotated video fits within its container.
|
||||
const container = video.parentElement;
|
||||
if (container) {
|
||||
const ratio = container.clientWidth / container.clientHeight;
|
||||
video.style.transform = `rotate(${next}deg) scale(${1 / ratio})`;
|
||||
} else {
|
||||
video.style.transform = `rotate(${next}deg)`;
|
||||
}
|
||||
} else {
|
||||
video.style.transform = next === 0 ? "" : `rotate(${next}deg)`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
icon="bx bx-rotate-right"
|
||||
text={t("media.rotate")}
|
||||
onClick={rotate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ZoomToFitButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [fitted, setFitted] = useState(false);
|
||||
|
||||
const toggle = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
const next = !fitted;
|
||||
video.style.objectFit = next ? "cover" : "";
|
||||
setFitted(next);
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className={fitted ? "active" : ""}
|
||||
icon={fitted ? "bx bx-collapse" : "bx bx-expand"}
|
||||
text={fitted ? t("media.zoom-reset") : t("media.zoom-to-fit")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PictureInPictureButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [active, setActive] = useState(false);
|
||||
// The standard PiP API is only supported in Chromium-based browsers.
|
||||
// Firefox uses its own proprietary PiP implementation.
|
||||
const supported = "requestPictureInPicture" in HTMLVideoElement.prototype;
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video || !supported) return;
|
||||
|
||||
const onEnter = () => setActive(true);
|
||||
const onLeave = () => setActive(false);
|
||||
|
||||
video.addEventListener("enterpictureinpicture", onEnter);
|
||||
video.addEventListener("leavepictureinpicture", onLeave);
|
||||
return () => {
|
||||
video.removeEventListener("enterpictureinpicture", onEnter);
|
||||
video.removeEventListener("leavepictureinpicture", onLeave);
|
||||
};
|
||||
}, [ videoRef, supported ]);
|
||||
|
||||
if (!supported) return null;
|
||||
|
||||
const toggle = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
if (document.pictureInPictureElement) {
|
||||
document.exitPictureInPicture();
|
||||
} else {
|
||||
video.requestPictureInPicture();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
icon={active ? "bx bx-exit" : "bx bx-window-open"}
|
||||
text={active ? t("media.exit-picture-in-picture") : t("media.picture-in-picture")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FullscreenButton({ targetRef }: { targetRef: RefObject<HTMLElement> }) {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onFullscreenChange = () => setIsFullscreen(!!document.fullscreenElement);
|
||||
document.addEventListener("fullscreenchange", onFullscreenChange);
|
||||
return () => document.removeEventListener("fullscreenchange", onFullscreenChange);
|
||||
}, []);
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
const target = targetRef.current;
|
||||
if (!target) return;
|
||||
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
target.requestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
icon={isFullscreen ? "bx bx-exit-fullscreen" : "bx bx-fullscreen"}
|
||||
text={isFullscreen ? t("media.exit-fullscreen") : t("media.fullscreen")}
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -43,6 +43,7 @@ function SpreadsheetEditor({ note, noteContext, readOnly }: TypeWidgetProps & {
|
||||
useDarkMode(apiRef);
|
||||
usePersistence(note, noteContext, apiRef, containerRef, readOnly);
|
||||
useSearchIntegration(apiRef);
|
||||
useFixRadixPortals();
|
||||
|
||||
// Focus the spreadsheet when the note is focused.
|
||||
useTriliumEvent("focusOnDetail", () => {
|
||||
@@ -55,6 +56,38 @@ function SpreadsheetEditor({ note, noteContext, readOnly }: TypeWidgetProps & {
|
||||
return <div ref={containerRef} className="spreadsheet" />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Univer's design system uses Radix UI primitives whose DismissableLayer detects
|
||||
* "outside" clicks/focus via document-level pointerdown/focusin listeners combined
|
||||
* with a React capture-phase flag. In React, portal events bubble through the
|
||||
* component tree so onPointerDownCapture fires on the DismissableLayer, setting an
|
||||
* internal flag that suppresses the "outside" detection. With preact/compat, portal
|
||||
* events don't bubble through the React tree, so the flag never gets set and Radix
|
||||
* immediately dismisses popups.
|
||||
*
|
||||
* Radix dispatches cancelable custom events ("dismissableLayer.pointerDownOutside"
|
||||
* and "dismissableLayer.focusOutside") on the original event target before calling
|
||||
* onDismiss. The dismiss is skipped if defaultPrevented is true. This hook intercepts
|
||||
* those custom events in the capture phase and prevents default when the target is
|
||||
* inside a Radix portal, restoring the expected behavior.
|
||||
*/
|
||||
function useFixRadixPortals() {
|
||||
useEffect(() => {
|
||||
function preventDismiss(e: Event) {
|
||||
if (e.target instanceof HTMLElement && e.target.closest("[id^='radix-']")) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("dismissableLayer.pointerDownOutside", preventDismiss, true);
|
||||
document.addEventListener("dismissableLayer.focusOutside", preventDismiss, true);
|
||||
return () => {
|
||||
document.removeEventListener("dismissableLayer.pointerDownOutside", preventDismiss, true);
|
||||
document.removeEventListener("dismissableLayer.focusOutside", preventDismiss, true);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
function useInitializeSpreadsheet(containerRef: MutableRef<HTMLDivElement | null>, apiRef: MutableRef<FUniver | undefined>, readOnly: boolean) {
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"express": "5.2.1",
|
||||
"express-http-proxy": "2.1.2",
|
||||
"express-openid-connect": "2.19.4",
|
||||
"express-rate-limit": "8.3.0",
|
||||
"express-rate-limit": "8.3.1",
|
||||
"express-session": "1.19.0",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
"fs-extra": "11.3.4",
|
||||
@@ -97,8 +97,8 @@
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.8.14",
|
||||
"https-proxy-agent": "8.0.0",
|
||||
"i18next": "25.8.17",
|
||||
"i18next-fs-backend": "2.6.1",
|
||||
"image-type": "6.0.0",
|
||||
"ini": "6.0.0",
|
||||
@@ -128,6 +128,6 @@
|
||||
"vite": "7.3.1",
|
||||
"ws": "8.19.0",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "3.2.0"
|
||||
"yauzl": "3.2.1"
|
||||
}
|
||||
}
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
12
apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html
generated
vendored
@@ -4,21 +4,17 @@
|
||||
maintaining and supporting it long-term proved to be unsustainable.</p>
|
||||
<p>When upgrading to v0.102.0, your Chat notes will be preserved, but instead
|
||||
of the dedicated chat window they will be turned to a normal <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_6f9hih2hXXZk">Code</a> note,
|
||||
revealing the underlying JSON of the conversation.</p>
|
||||
href="#root/_help_6f9hih2hXXZk">Code</a> note, revealing the underlying
|
||||
JSON of the conversation.</p>
|
||||
<h2>Alternative solutions (MCP)</h2>
|
||||
<p>Given the recent advancements of the AI scene, MCP has grown to be more
|
||||
powerful and facilitates easier integrations with various application.</p>
|
||||
<p>As such, there are third-party solutions that integrate an MCP server
|
||||
that can be used with Trilium:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><a href="https://github.com/tan-yong-sheng/triliumnext-mcp">tan-yong-sheng/triliumnext-mcp</a>
|
||||
</p>
|
||||
<li><a href="https://github.com/tan-yong-sheng/triliumnext-mcp">tan-yong-sheng/triliumnext-mcp</a>
|
||||
</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/perfectra1n/triliumnext-mcp">perfectra1n/triliumnext-mcp</a>
|
||||
</p>
|
||||
<li><a href="https://github.com/perfectra1n/triliumnext-mcp">perfectra1n/triliumnext-mcp</a>
|
||||
</li>
|
||||
</ul>
|
||||
<aside class="admonition important">
|
||||
|
||||
44
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
@@ -209,7 +209,7 @@
|
||||
<tr>
|
||||
<td><code spellcheck="false">#calendar:color</code>
|
||||
</td>
|
||||
<td><strong>❌️ Removed since v0.100.0. Use</strong> <code spellcheck="false">**#color**</code> <strong>instead.</strong>
|
||||
<td><strong>❌️ Removed since v0.100.0. Use</strong> <code spellcheck="false">**#color**</code> <strong>instead.</strong>
|
||||
<br>
|
||||
<br>Similar to <code spellcheck="false">#color</code>, but applies the color
|
||||
only for the event in the calendar and not for other places such as the
|
||||
@@ -233,15 +233,15 @@
|
||||
<td><code spellcheck="false">#calendar:displayedAttributes</code>
|
||||
</td>
|
||||
<td>Allows displaying the value of one or more attributes in the calendar
|
||||
like this:
|
||||
like this:
|
||||
<br>
|
||||
<br>
|
||||
<img src="7_Calendar_image.png">
|
||||
<img src="7_Calendar_image.png">
|
||||
<br>
|
||||
<br><code spellcheck="false">#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>
|
||||
<br><code spellcheck="false">#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>
|
||||
<br>
|
||||
<br>It can also be used with relations, case in which it will display the
|
||||
title of the target note:
|
||||
title of the target note:
|
||||
<br>
|
||||
<br><code spellcheck="false">~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
|
||||
</td>
|
||||
@@ -294,44 +294,27 @@
|
||||
<p>When not used in a Journal, the calendar is recursive. That is, it will
|
||||
look for events not just in its child notes but also in the children of
|
||||
these child notes.</p>
|
||||
<p> </p>
|
||||
<h2>Recurrence</h2>
|
||||
<p>The built in calendar view also supports repeating tasks. If a child note
|
||||
of the calendar has a #recurrence label with a valid recurrence, that event
|
||||
will repeat on the calendar according to the recurrence string. </p>
|
||||
<p>For example, to make a note repeat on the calendar:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Every Day - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=1"</code>
|
||||
</p>
|
||||
<li>Every Day - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=1"</code>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 3 days - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=3"</code>
|
||||
</p>
|
||||
<li>Every 3 days - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=3"</code>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every week - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=1"</code>
|
||||
</p>
|
||||
<li>Every week - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=1"</code>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 2 weeks on Monday, Wednesday and Friday - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"</code>
|
||||
</p>
|
||||
<li>Every 2 weeks on Monday, Wednesday and Friday - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"</code>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 3 months - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=3"</code>
|
||||
</p>
|
||||
<li>Every 3 months - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=3"</code>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 2 months on the First Sunday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU"</code>
|
||||
</p>
|
||||
<li>Every 2 months on the First Sunday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU"</code>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every month on the Last Friday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>And so on.</p>
|
||||
<li>Every month on the Last Friday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR"</code>
|
||||
</li>
|
||||
<li>And so on.</li>
|
||||
</ul>
|
||||
<p>For other examples of valid <code spellcheck="false">RRULE</code> strings
|
||||
see <a href="https://icalendar.org/rrule-tool.html">https://icalendar.org/rrule-tool.html</a>
|
||||
@@ -352,7 +335,6 @@
|
||||
note ID and title of the note with the erroneous recurrence message. This
|
||||
note will not be added to the calendar</p>
|
||||
</aside>
|
||||
<p> </p>
|
||||
<h2>Use-cases</h2>
|
||||
<h3>Using with the Journal / calendar</h3>
|
||||
<p>It is possible to integrate the calendar view into the Journal with day
|
||||
|
||||
190
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types.html
generated
vendored
@@ -9,7 +9,8 @@
|
||||
note where to place the new one and select:</p>
|
||||
<ul>
|
||||
<li><em>Insert note after</em>, to put the new note underneath the one selected.</li>
|
||||
<li><em>Insert child note</em>, to insert the note as a child of the selected
|
||||
<li
|
||||
><em>Insert child note</em>, to insert the note as a child of the selected
|
||||
note.</li>
|
||||
</ul>
|
||||
<p>
|
||||
@@ -20,7 +21,8 @@
|
||||
<li>When adding a <a href="#root/_help_QEAPj01N5f7w">link</a> in a <a class="reference-link"
|
||||
href="#root/_help_iPIMuisry3hd">Text</a> note, type the desired title of
|
||||
the new note and press Enter. Afterwards the type of the note will be asked.</li>
|
||||
<li>Similarly, when creating a new tab, type the desired title and press Enter.</li>
|
||||
<li
|
||||
>Similarly, when creating a new tab, type the desired title and press Enter.</li>
|
||||
</ul>
|
||||
<h2>Changing the type of a note</h2>
|
||||
<p>It is possible to change the type of a note after it has been created
|
||||
@@ -30,94 +32,96 @@
|
||||
edit the <a href="#root/_help_4FahAwuGTAwC">source of a note</a>.</p>
|
||||
<h2>Supported note types</h2>
|
||||
<p>The following note types are supported by Trilium:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Note Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a>
|
||||
</td>
|
||||
<td>The default note type, which allows for rich text formatting, images,
|
||||
admonitions and right-to-left support.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>
|
||||
</td>
|
||||
<td>Uses a mono-space font and can be used to store larger chunks of code
|
||||
or plain text than a text note, and has better syntax highlighting.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>
|
||||
</td>
|
||||
<td>Stores the information about a search (the search text, criteria, etc.)
|
||||
for later use. Can be used for quick filtering of a large amount of notes,
|
||||
for example. The search can easily be triggered.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
|
||||
</td>
|
||||
<td>Allows easy creation of notes and relations between them. Can be used
|
||||
for mainly relational data such as a family tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_bdUJEHsAPYQR">Note Map</a>
|
||||
</td>
|
||||
<td>Displays the relationships between the notes, whether via relations or
|
||||
their hierarchical structure.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
|
||||
</td>
|
||||
<td>Used in <a class="reference-link" href="#root/_help_CdNpE2pqjmI6">Scripting</a>,
|
||||
it displays the HTML content of another note. This allows displaying any
|
||||
kind of content, provided there is a script behind it to generate it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
|
||||
</td>
|
||||
<td>Displays the children of the note either as a grid, a list, or for a more
|
||||
specialized case: a calendar.
|
||||
<br>
|
||||
<br>Generally useful for easy reading of short notes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>
|
||||
</td>
|
||||
<td>Displays diagrams such as bar charts, flow charts, state diagrams, etc.
|
||||
Requires a bit of technical knowledge since the diagrams are written in
|
||||
a specialized format.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>
|
||||
</td>
|
||||
<td>Allows easy drawing of sketches, diagrams, handwritten content. Uses the
|
||||
same technology behind <a href="https://excalidraw.com">excalidraw.com</a>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
|
||||
</td>
|
||||
<td>Displays the content of an external web page, similar to a browser.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
|
||||
</td>
|
||||
<td>Easy for brainstorming ideas, by placing them in a hierarchical layout.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map View</a>
|
||||
</td>
|
||||
<td>Displays the children of the note as a geographical map, one use-case
|
||||
would be to plan vacations. It even has basic support for tracks. Notes
|
||||
can also be created from it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a>
|
||||
</td>
|
||||
<td>Represents an uploaded file such as PDFs, images, video or audio files.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Note Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_iPIMuisry3hd">Text</a>
|
||||
</td>
|
||||
<td>The default note type, which allows for rich text formatting, images,
|
||||
admonitions and right-to-left support.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>
|
||||
</td>
|
||||
<td>Uses a mono-space font and can be used to store larger chunks of code
|
||||
or plain text than a text note, and has better syntax highlighting.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>
|
||||
</td>
|
||||
<td>Stores the information about a search (the search text, criteria, etc.)
|
||||
for later use. Can be used for quick filtering of a large amount of notes,
|
||||
for example. The search can easily be triggered.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_iRwzGnHPzonm">Relation Map</a>
|
||||
</td>
|
||||
<td>Allows easy creation of notes and relations between them. Can be used
|
||||
for mainly relational data such as a family tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_bdUJEHsAPYQR">Note Map</a>
|
||||
</td>
|
||||
<td>Displays the relationships between the notes, whether via relations or
|
||||
their hierarchical structure.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
|
||||
</td>
|
||||
<td>Used in <a class="reference-link" href="#root/_help_CdNpE2pqjmI6">Scripting</a>,
|
||||
it displays the HTML content of another note. This allows displaying any
|
||||
kind of content, provided there is a script behind it to generate it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>
|
||||
</td>
|
||||
<td>Displays the children of the note either as a grid, a list, or for a more
|
||||
specialized case: a calendar.
|
||||
<br>
|
||||
<br>Generally useful for easy reading of short notes.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a>
|
||||
</td>
|
||||
<td>Displays diagrams such as bar charts, flow charts, state diagrams, etc.
|
||||
Requires a bit of technical knowledge since the diagrams are written in
|
||||
a specialized format.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_grjYqerjn243">Canvas</a>
|
||||
</td>
|
||||
<td>Allows easy drawing of sketches, diagrams, handwritten content. Uses the
|
||||
same technology behind <a href="https://excalidraw.com">excalidraw.com</a>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
|
||||
</td>
|
||||
<td>Displays the content of an external web page, similar to a browser.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_gBbsAeiuUxI5">Mind Map</a>
|
||||
</td>
|
||||
<td>Easy for brainstorming ideas, by placing them in a hierarchical layout.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_81SGnPGMk7Xc">Geo Map</a>
|
||||
</td>
|
||||
<td>Displays the children of the note as a geographical map, one use-case
|
||||
would be to plan vacations. It even has basic support for tracks. Notes
|
||||
can also be created from it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a class="reference-link" href="#root/_help_W8vYD3Q1zjCR">File</a>
|
||||
</td>
|
||||
<td>Represents an uploaded file such as PDFs, images, video or audio files.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/1_File_image.png
generated
vendored
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/2_File_image.png
generated
vendored
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 612 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/3_File_image.png
generated
vendored
|
Before Width: | Height: | Size: 612 KiB After Width: | Height: | Size: 10 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/4_File_image.png
generated
vendored
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 15 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/5_File_image.png
generated
vendored
|
Before Width: | Height: | Size: 15 KiB |
27
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/File.html
generated
vendored
@@ -13,7 +13,7 @@
|
||||
<p>See <a class="reference-link" href="#root/_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="3_File_image.png"
|
||||
<img style="aspect-ratio:879/766;" src="2_File_image.png"
|
||||
width="879" height="766">
|
||||
</figure>
|
||||
<p>Interaction:</p>
|
||||
@@ -30,25 +30,10 @@
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Videos</h3>
|
||||
<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>
|
||||
<p>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.</p>
|
||||
<aside class="admonition caution">
|
||||
<p>Although Trilium offers support for videos, it is generally not meant
|
||||
to be used with very large files. Uploading large videos will cause the
|
||||
<a
|
||||
class="reference-link" href="#root/_help_wX4HbRucYSDD">Database</a> to balloon as well as the any <a class="reference-link"
|
||||
href="#root/_help_ODY7qQn5m2FT">Backup</a> of it. In addition to that, there
|
||||
might be slowdowns when first uploading the files. Otherwise, a large database
|
||||
should not impact the general performance of Trilium significantly.</p>
|
||||
</aside>
|
||||
<p>See <a class="reference-link" href="#root/_help_AjqEeiDUOzj4">Videos</a>.</p>
|
||||
<h3>Audio</h3>
|
||||
<figure class="image image-style-align-center image_resized" style="width:50%;">
|
||||
<img style="aspect-ratio:850/243;" src="2_File_image.png"
|
||||
<img style="aspect-ratio:850/243;" src="1_File_image.png"
|
||||
width="850" height="243">
|
||||
</figure>
|
||||
<p>Adding a supported audio file will reveal a basic audio player that can
|
||||
@@ -64,7 +49,7 @@
|
||||
</ul>
|
||||
<h3>Text files</h3>
|
||||
<figure class="image image-style-align-center image_resized" style="width:50%;">
|
||||
<img style="aspect-ratio:926/347;" src="1_File_image.png"
|
||||
<img style="aspect-ratio:926/347;" src="File_image.png"
|
||||
width="926" height="347">
|
||||
</figure>
|
||||
<p>Files that are identified as containing text will show a preview of their
|
||||
@@ -83,7 +68,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="4_File_image.png"
|
||||
<img style="aspect-ratio:532/240;" src="3_File_image.png"
|
||||
width="532" height="240">
|
||||
</figure>
|
||||
<p>If the file could not be identified as any of the supported file types
|
||||
@@ -110,7 +95,7 @@
|
||||
<p>Files are also displayed in the <a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a> based
|
||||
on their type:</p>
|
||||
<img class="image_resized" style="aspect-ratio:853/315;width:50%;"
|
||||
src="5_File_image.png" width="853" height="315">
|
||||
src="4_File_image.png" width="853" height="315">
|
||||
</li>
|
||||
<li>
|
||||
<p>Non-image files can be embedded into text notes as read-only widgets via
|
||||
|
||||
131
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/File/Videos.html
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
<figure class="image image-style-align-right image_resized" style="width:61.8%;">
|
||||
<img style="aspect-ratio:953/587;" src="Videos_image.png"
|
||||
width="953" height="587">
|
||||
</figure>
|
||||
<p>Starting with v0.103.0, Trilium has a custom video player which offers
|
||||
more features than the built-in video player.</p>
|
||||
<p>Versions prior to v0.103.0 also support videos, but using the built-in
|
||||
player.</p>
|
||||
<p>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.</p>
|
||||
<h2>Note on large video files</h2>
|
||||
<p>Although Trilium offers support for videos, it is generally not meant
|
||||
to be used with very large files. Uploading large videos will cause the
|
||||
<a
|
||||
class="reference-link" href="#root/_help_wX4HbRucYSDD">Database</a> to balloon as well as the any <a class="reference-link"
|
||||
href="#root/_help_ODY7qQn5m2FT">Backup</a> of it. In addition to that, there
|
||||
might be slowdowns when first uploading the files. Otherwise, a large database
|
||||
should not impact the general performance of Trilium significantly.</p>
|
||||
<h2>Supported formats</h2>
|
||||
<p>Trilium uses the built-in video decoding mechanism of the browser (or
|
||||
Electron/Chromium when running on the desktop). Starting with v0.103.0,
|
||||
a message will be displayed instead when a video format is not supported.</p>
|
||||
<h2>Interactions</h2>
|
||||
<p>To play/pause the video, simply click anywhere on the video.</p>
|
||||
<p>The controls at the bottom will hide automatically after playing, simply
|
||||
move the mouse to show them again.</p>
|
||||
<p>The bottom bar has the following features:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>A track bar to seek across the video.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>On the left of the track bar, the current time is indicated.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>On the right of the track bar, the remaining time is indicated.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>On the left side there are buttons to:</p>
|
||||
<ul>
|
||||
<li>Adjust the playback speed (e.g. 0.5x, 1x).</li>
|
||||
<li>Rotate the video by 90 degrees.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>In the center:</p>
|
||||
<ul>
|
||||
<li>Go back by 10s</li>
|
||||
<li>Play/pause</li>
|
||||
<li>Go forward by 30s</li>
|
||||
<li>Loop, which when enabled will restart the video once it reaches the end.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>On the right side:</p>
|
||||
<ul>
|
||||
<li>Mute button</li>
|
||||
<li>Volume adjustment</li>
|
||||
<li>Full screen</li>
|
||||
<li>Zoom to fill, which will crop the video so that it fills the entire window.</li>
|
||||
<li>Picture-in-picture (if the browser supports it).</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Keyboard shortcuts</h2>
|
||||
<p>The following keyboard shortcuts are supported by the video player:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><kbd>Space</kbd>
|
||||
</td>
|
||||
<td>Play/pause</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Left arrow key</kbd>
|
||||
</td>
|
||||
<td>Go back by 10s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Right arrow key</kbd>
|
||||
</td>
|
||||
<td>Go forward by 10s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Ctrl</kbd> + <kbd>Left arrow key</kbd>
|
||||
</td>
|
||||
<td>Go back by 1 min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Ctrl</kbd> + <kbd>Right arrow key</kbd>
|
||||
</td>
|
||||
<td>Go right by 1 min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>F</kbd>
|
||||
</td>
|
||||
<td>Toggle full-screen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>M</kbd>
|
||||
</td>
|
||||
<td>Mute/unmute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Home</kbd>
|
||||
</td>
|
||||
<td>Go to the beginning of the video</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>End</kbd>
|
||||
</td>
|
||||
<td>Go to the end of the video</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Up</kbd>
|
||||
</td>
|
||||
<td>Increase volume by 5%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Down</kbd>
|
||||
</td>
|
||||
<td>Decrease volume by 5%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/File/Videos_image.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 842 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/File_image.png
generated
vendored
|
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 15 KiB |
@@ -38,30 +38,34 @@
|
||||
<img src="1_Mermaid Diagrams_image.png">
|
||||
</li>
|
||||
<li>The preview can be moved around by holding the left mouse button and dragging.</li>
|
||||
<li>Zooming can also be done by using the scroll wheel.</li>
|
||||
<li>The zoom and position on the preview will remain fixed as the diagram
|
||||
changes, to be able to work more easily with large diagrams.</li>
|
||||
</ul>
|
||||
<li
|
||||
>Zooming can also be done by using the scroll wheel.</li>
|
||||
<li>The zoom and position on the preview will remain fixed as the diagram
|
||||
changes, to be able to work more easily with large diagrams.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>The size of the source/preview panes can be adjusted by hovering over
|
||||
the border between them and dragging it with the mouse.</li>
|
||||
<li>In the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> area:
|
||||
<ul>
|
||||
<li>The source/preview can be laid out left-right or bottom-top via the <em>Move editing pane to the left / bottom</em> option.</li>
|
||||
<li>Press <em>Lock editing</em> to automatically mark the note as read-only.
|
||||
<li
|
||||
>Press <em>Lock editing</em> to automatically mark the note as read-only.
|
||||
In this mode, the code pane is hidden and the diagram is displayed full-size.
|
||||
Similarly, press <em>Unlock editing</em> to mark a read-only note as editable.</li>
|
||||
<li>Press the <em>Copy image reference to the clipboard</em> to be able to insert
|
||||
the image representation of the diagram into a text note. See <a class="reference-link"
|
||||
href="#root/_help_0Ofbk1aSuVRu">Image references</a> for more information.</li>
|
||||
<li>Press the <em>Export diagram as SVG</em> to download a scalable/vector rendering
|
||||
of the diagram. Can be used to present the diagram without degrading when
|
||||
zooming.</li>
|
||||
<li
|
||||
>Press the <em>Copy image reference to the clipboard</em> to be able to insert
|
||||
the image representation of the diagram into a text note. See <a class="reference-link"
|
||||
href="#root/_help_0Ofbk1aSuVRu">Image references</a> for more information.</li>
|
||||
<li
|
||||
>Press the <em>Export diagram as SVG</em> to download a scalable/vector rendering
|
||||
of the diagram. Can be used to present the diagram without degrading when
|
||||
zooming.</li>
|
||||
<li>Press the <em>Export diagram as PNG</em> to download a normal image (at
|
||||
1x scale, raster) of the diagram. Can be used to send the diagram in more
|
||||
traditional channels such as e-mail.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Errors in the diagram</h2>
|
||||
<p>If there is an error in the source code, the error will be displayed in
|
||||
|
||||
111
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Spreadsheets.html
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1102/573;" src="Spreadsheets_image.png"
|
||||
width="1102" height="573">
|
||||
</figure>
|
||||
<aside class="admonition important">
|
||||
<p>Spreadsheets are a new type of note introduced in v0.103.0 and are currently
|
||||
considered experimental/beta. As such, expect major changes to occur to
|
||||
this note type.</p>
|
||||
</aside>
|
||||
<p>Spreadsheets provide a familiar experience to Microsoft Excel or LibreOffice
|
||||
Calc, with support for formulas, data validation and text formatting.</p>
|
||||
<h2>Spreadsheets vs. collections</h2>
|
||||
<p>There is a slight overlap between spreadsheets and the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_2FvYrpmOXm29">Table</a> collection.
|
||||
In general the table collection is useful to track meta-information about
|
||||
notes (for example a collection of people and their birthdays), whereas
|
||||
spreadsheets are quite useful for calculations since they support formulas.</p>
|
||||
<p>Spreadsheets also benefit from a wider range of features such as data
|
||||
validation, formatting and can work on a relatively large dataset.</p>
|
||||
<h2>Important statement regarding data format</h2>
|
||||
<p>For Trilium as a knowledge database, it is important that data is stored
|
||||
in a format that is easy to convert to something else. For example,
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_iPIMuisry3hd">Text</a> notes can be exported to either HTML or Markdown, making
|
||||
it relatively easy to migrate to another software or simply to stand the
|
||||
test of time.</p>
|
||||
<p>For spreadsheets, Trilium uses a technology called <a href="https://docs.univer.ai/">Univer Sheets</a>,
|
||||
developed by DreamNum Co., Ltd. Although this software library is quite
|
||||
powerful and has a good track record (starting with Luckysheet from 2020,
|
||||
becoming Univer somewhere in 2023), it uses its own JSON format to store
|
||||
the sheets.</p>
|
||||
<p>As such, if Univer were to become unmaintained or incompatible for some
|
||||
reason, your data might become vendor locked-in.</p>
|
||||
<p>With that in mind, spreadsheets can be really useful for quick calculations,
|
||||
but it's important not to have critical information on it that you might
|
||||
not want to need in a few years time.</p>
|
||||
<h2>Regarding data export</h2>
|
||||
<p>Currently, in Trilium there is no way to export the spreadsheets to CSV
|
||||
or Excel formats. We might manage to add support for it at some point,
|
||||
but currently this is not the case.</p>
|
||||
<h2>Supported features</h2>
|
||||
<p>The spreadsheet has support for the following features:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Filtering</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Sorting</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Data validation</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Conditional formatting</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Notes / annotations</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Find / replace</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>We might consider adding <a href="https://docs.univer.ai/guides/sheets/features/filter">other features</a> from
|
||||
Univer at some point. If there is a particular feature that can be added
|
||||
easily, it can be discussed over <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_wy8So3yZZlH9">GitHub Issues</a>.</p>
|
||||
<h2>Features not supported yet</h2>
|
||||
<h3>Regarding Pro features</h3>
|
||||
<p>Univer spreadsheets also feature a <a href="https://univer.ai/pro">Pro plan</a> which
|
||||
adds quite a lot of functionality such as charts, printing, pivot tables,
|
||||
export, etc.</p>
|
||||
<p>As the Pro plan needs a license, Trilium does not support any of the premium
|
||||
features. Theoretically, pro features can be used in trial mode with some
|
||||
limitations, we might explore this direction at some point.</p>
|
||||
<h3>Planned features</h3>
|
||||
<p>There are a few features that are already planned but are not supported
|
||||
yet:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Trilium-specific formulas (e.g. to obtain the title of a note).</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>User-defined formulas</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Cross-workbook calculation</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>If you would like us to work on these features, consider <a href="https://triliumnotes.org/en/support-us">supporting us</a>.</p>
|
||||
<h2>Known limitations</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<p>It is possible to share a spreadsheet, case in which a best-effort HTML
|
||||
rendering of the spreadsheet is done.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>For more advanced use cases, this will most likely not work as intended.
|
||||
Feel free to <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_wy8So3yZZlH9">report issues</a>,
|
||||
but keep in mind that we might not be able to have a complete feature parity
|
||||
with all the features of Univer.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>There is currently no export functionality, as stated previously.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>There is no dedicated mobile support. Mobile support is currently experimental
|
||||
in Univer and when it becomes stable, we could potentially integrate it
|
||||
into Trilium as well.</p>
|
||||
</li>
|
||||
</ul>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Spreadsheets_image.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 117 KiB |
@@ -10449,6 +10449,12 @@
|
||||
"terms": [
|
||||
"virus-block"
|
||||
]
|
||||
},
|
||||
"bx-empty": {
|
||||
"glyph": "",
|
||||
"terms": [
|
||||
"empty"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,7 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { processMindmapContent } from "./note_content_fulltext.js";
|
||||
import { describe, expect,it } from "vitest";
|
||||
|
||||
import NoteContentFulltextExp from "./note_content_fulltext.js";
|
||||
|
||||
describe("processMindmapContent", () => {
|
||||
it("supports empty JSON", () => {
|
||||
expect(processMindmapContent("{}")).toEqual("");
|
||||
});
|
||||
|
||||
it("supports blank text / invalid JSON", () => {
|
||||
expect(processMindmapContent("")).toEqual("");
|
||||
expect(processMindmapContent(`{ "node": " }`)).toEqual("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Fuzzy Search Operators", () => {
|
||||
it("~= operator works with typos", () => {
|
||||
// Test that the ~= operator can handle common typos
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
"use strict";
|
||||
|
||||
import type { NoteRow } from "@triliumnext/commons";
|
||||
import type SearchContext from "../search_context.js";
|
||||
|
||||
import Expression from "./expression.js";
|
||||
import NoteSet from "../note_set.js";
|
||||
import log from "../../log.js";
|
||||
import becca from "../../../becca/becca.js";
|
||||
import log from "../../log.js";
|
||||
import protectedSessionService from "../../protected_session.js";
|
||||
import striptags from "striptags";
|
||||
import { normalize } from "../../utils.js";
|
||||
import sql from "../../sql.js";
|
||||
import {
|
||||
normalizeSearchText,
|
||||
calculateOptimizedEditDistance,
|
||||
validateFuzzySearchTokens,
|
||||
validateAndPreprocessContent,
|
||||
import NoteSet from "../note_set.js";
|
||||
import type SearchContext from "../search_context.js";
|
||||
import {
|
||||
FUZZY_SEARCH_CONFIG,
|
||||
fuzzyMatchWord,
|
||||
FUZZY_SEARCH_CONFIG
|
||||
} from "../utils/text_utils.js";
|
||||
normalizeSearchText,
|
||||
validateAndPreprocessContent,
|
||||
validateFuzzySearchTokens} from "../utils/text_utils.js";
|
||||
import Expression from "./expression.js";
|
||||
import preprocessContent from "./note_content_fulltext_preprocessor.js";
|
||||
|
||||
const ALLOWED_OPERATORS = new Set(["=", "!=", "*=*", "*=", "=*", "%=", "~=", "~*"]);
|
||||
|
||||
@@ -218,7 +213,7 @@ class NoteContentFulltextExp extends Expression {
|
||||
return;
|
||||
}
|
||||
|
||||
content = this.preprocessContent(content, type, mime);
|
||||
content = preprocessContent(content, type, mime, this.raw);
|
||||
|
||||
// Apply content size validation and preprocessing
|
||||
const processedContent = validateAndPreprocessContent(content, noteId);
|
||||
@@ -295,59 +290,22 @@ class NoteContentFulltextExp extends Expression {
|
||||
return content;
|
||||
}
|
||||
|
||||
preprocessContent(content: string | Buffer, type: string, mime: string) {
|
||||
content = normalize(content.toString());
|
||||
|
||||
if (type === "text" && mime === "text/html") {
|
||||
if (!this.raw) {
|
||||
// Content size already filtered at DB level, safe to process
|
||||
content = this.stripTags(content);
|
||||
}
|
||||
|
||||
content = content.replace(/ /g, " ");
|
||||
} else if (type === "mindMap" && mime === "application/json") {
|
||||
content = processMindmapContent(content);
|
||||
} else if (type === "canvas" && mime === "application/json") {
|
||||
interface Element {
|
||||
type: string;
|
||||
text?: string; // Optional since not all objects have a `text` property
|
||||
id: string;
|
||||
[key: string]: any; // Other properties that may exist
|
||||
}
|
||||
|
||||
const canvasContent = JSON.parse(content);
|
||||
const elements = canvasContent.elements;
|
||||
|
||||
if (Array.isArray(elements)) {
|
||||
const texts = elements
|
||||
.filter((element: Element) => element.type === "text" && element.text) // Filter for 'text' type elements with a 'text' property
|
||||
.map((element: Element) => element.text!); // Use `!` to assert `text` is defined after filtering
|
||||
|
||||
content = normalize(texts.join(" "));
|
||||
} else {
|
||||
content = "";
|
||||
}
|
||||
}
|
||||
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a token matches content with optional fuzzy matching
|
||||
*/
|
||||
private tokenMatchesContent(token: string, content: string, noteId: string): boolean {
|
||||
const normalizedToken = normalizeSearchText(token);
|
||||
const normalizedContent = normalizeSearchText(content);
|
||||
|
||||
|
||||
if (normalizedContent.includes(normalizedToken)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Check flat text for default fulltext search
|
||||
if (!this.flatText || !becca.notes[noteId].getFlatText().includes(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -358,15 +316,15 @@ class NoteContentFulltextExp extends Expression {
|
||||
try {
|
||||
const normalizedContent = normalizeSearchText(content);
|
||||
const flatText = this.flatText ? normalizeSearchText(becca.notes[noteId].getFlatText()) : "";
|
||||
|
||||
|
||||
// For phrase matching, check if tokens appear within reasonable proximity
|
||||
if (this.tokens.length > 1) {
|
||||
return this.matchesPhrase(normalizedContent, flatText);
|
||||
}
|
||||
|
||||
|
||||
// Single token fuzzy matching
|
||||
const token = normalizeSearchText(this.tokens[0]);
|
||||
return this.fuzzyMatchToken(token, normalizedContent) ||
|
||||
return this.fuzzyMatchToken(token, normalizedContent) ||
|
||||
(this.flatText && this.fuzzyMatchToken(token, flatText));
|
||||
} catch (error) {
|
||||
log.error(`Error in fuzzy matching for note ${noteId}: ${error}`);
|
||||
@@ -379,45 +337,45 @@ class NoteContentFulltextExp extends Expression {
|
||||
*/
|
||||
private matchesPhrase(content: string, flatText: string): boolean {
|
||||
const searchText = this.flatText ? `${content} ${flatText}` : content;
|
||||
|
||||
|
||||
// Apply content size limits for phrase matching
|
||||
const limitedText = validateAndPreprocessContent(searchText);
|
||||
if (!limitedText) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const words = limitedText.toLowerCase().split(/\s+/);
|
||||
|
||||
|
||||
// Only skip phrase matching for truly extreme word counts that could crash the system
|
||||
if (words.length > FUZZY_SEARCH_CONFIG.ABSOLUTE_MAX_WORD_COUNT) {
|
||||
console.error(`Phrase matching skipped due to extreme word count that could cause system instability: ${words.length} words`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Warn about large word counts but still attempt matching
|
||||
if (words.length > FUZZY_SEARCH_CONFIG.PERFORMANCE_WARNING_WORDS) {
|
||||
console.info(`Large word count for phrase matching: ${words.length} words - may take longer but will attempt full matching`);
|
||||
}
|
||||
|
||||
|
||||
// Find positions of each token
|
||||
const tokenPositions: number[][] = this.tokens.map(token => {
|
||||
const normalizedToken = normalizeSearchText(token);
|
||||
const positions: number[] = [];
|
||||
|
||||
|
||||
words.forEach((word, index) => {
|
||||
if (this.fuzzyMatchSingle(normalizedToken, word)) {
|
||||
positions.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return positions;
|
||||
});
|
||||
|
||||
|
||||
// Check if we found all tokens
|
||||
if (tokenPositions.some(positions => positions.length === 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check for phrase proximity using configurable distance
|
||||
return this.hasProximityMatch(tokenPositions, FUZZY_SEARCH_CONFIG.MAX_PHRASE_PROXIMITY);
|
||||
}
|
||||
@@ -431,18 +389,18 @@ class NoteContentFulltextExp extends Expression {
|
||||
const [pos1, pos2] = tokenPositions;
|
||||
return pos1.some(p1 => pos2.some(p2 => Math.abs(p1 - p2) <= maxDistance));
|
||||
}
|
||||
|
||||
|
||||
// For more tokens, check if we can find a sequence where all tokens are within range
|
||||
const findSequence = (remaining: number[][], currentPos: number): boolean => {
|
||||
if (remaining.length === 0) return true;
|
||||
|
||||
|
||||
const [nextPositions, ...rest] = remaining;
|
||||
return nextPositions.some(pos =>
|
||||
Math.abs(pos - currentPos) <= maxDistance &&
|
||||
return nextPositions.some(pos =>
|
||||
Math.abs(pos - currentPos) <= maxDistance &&
|
||||
findSequence(rest, pos)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const [firstPositions, ...rest] = tokenPositions;
|
||||
return firstPositions.some(startPos => findSequence(rest, startPos));
|
||||
}
|
||||
@@ -455,12 +413,12 @@ class NoteContentFulltextExp extends Expression {
|
||||
// For short tokens, require exact match to avoid too many false positives
|
||||
return content.includes(token);
|
||||
}
|
||||
|
||||
|
||||
const words = content.split(/\s+/);
|
||||
|
||||
|
||||
// Only limit word processing for truly extreme cases to prevent system instability
|
||||
const limitedWords = words.slice(0, FUZZY_SEARCH_CONFIG.ABSOLUTE_MAX_WORD_COUNT);
|
||||
|
||||
|
||||
return limitedWords.some(word => this.fuzzyMatchSingle(token, word));
|
||||
}
|
||||
|
||||
@@ -471,83 +429,6 @@ class NoteContentFulltextExp extends Expression {
|
||||
// Use shared optimized fuzzy matching logic
|
||||
return fuzzyMatchWord(token, word, FUZZY_SEARCH_CONFIG.MAX_EDIT_DISTANCE);
|
||||
}
|
||||
|
||||
|
||||
stripTags(content: string) {
|
||||
// we want to allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412
|
||||
// we want to insert space in place of block tags (because they imply text separation)
|
||||
// but we don't want to insert text for typical formatting inline tags which can occur within one word
|
||||
const linkTag = "a";
|
||||
const inlineFormattingTags = ["b", "strong", "em", "i", "span", "big", "small", "font", "sub", "sup"];
|
||||
|
||||
// replace tags which imply text separation with a space
|
||||
content = striptags(content, [linkTag, ...inlineFormattingTags], " ");
|
||||
|
||||
// replace the inline formatting tags (but not links) without a space
|
||||
content = striptags(content, [linkTag], "");
|
||||
|
||||
// at least the closing link tag can be easily stripped
|
||||
return content.replace(/<\/a>/gi, "");
|
||||
}
|
||||
}
|
||||
|
||||
export function processMindmapContent(content: string) {
|
||||
let mindMapcontent;
|
||||
|
||||
try {
|
||||
mindMapcontent = JSON.parse(content);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Define interfaces for the JSON structure
|
||||
interface MindmapNode {
|
||||
id: string;
|
||||
topic: string;
|
||||
children: MindmapNode[]; // Recursive structure
|
||||
direction?: number;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
interface MindmapData {
|
||||
nodedata: MindmapNode;
|
||||
arrows: any[]; // If you know the structure, replace `any` with the correct type
|
||||
summaries: any[];
|
||||
direction: number;
|
||||
theme: {
|
||||
name: string;
|
||||
type: string;
|
||||
palette: string[];
|
||||
cssvar: Record<string, string>; // Object with string keys and string values
|
||||
};
|
||||
}
|
||||
|
||||
// Recursive function to collect all topics
|
||||
function collectTopics(node?: MindmapNode): string[] {
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collect the current node's topic
|
||||
let topics = [node.topic];
|
||||
|
||||
// If the node has children, collect topics recursively
|
||||
if (node.children && node.children.length > 0) {
|
||||
for (const child of node.children) {
|
||||
topics = topics.concat(collectTopics(child));
|
||||
}
|
||||
}
|
||||
|
||||
return topics;
|
||||
}
|
||||
|
||||
// Start extracting from the root node
|
||||
const topicsArray = collectTopics(mindMapcontent.nodedata);
|
||||
|
||||
// Combine topics into a single string
|
||||
const topicsString = topicsArray.join(", ");
|
||||
|
||||
return normalize(topicsString.toString());
|
||||
}
|
||||
|
||||
export default NoteContentFulltextExp;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import { describe, expect,it } from "vitest";
|
||||
|
||||
import preprocessContent from "./note_content_fulltext_preprocessor";
|
||||
|
||||
describe("Mind map preprocessing", () => {
|
||||
const type: NoteType = "mindMap";
|
||||
const mime = "application/json";
|
||||
|
||||
it("supports empty JSON", () => {
|
||||
expect(preprocessContent("{}", type, mime)).toEqual("");
|
||||
});
|
||||
|
||||
it("supports blank text / invalid JSON", () => {
|
||||
expect(preprocessContent("", type, mime)).toEqual("");
|
||||
expect(preprocessContent(`{ "node": " }`, type, mime)).toEqual("");
|
||||
});
|
||||
|
||||
it("reads data", () => {
|
||||
expect(preprocessContent(`{ "nodedata": { "topic": "Root", "children": [ { "topic": "Child 1" }, { "topic": "Child 2", "children": [ { "topic": "Grandchild" } ] } ] } }`, type, mime)).toEqual("root, child 1, child 2, grandchild");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Canvas preprocessing", () => {
|
||||
const type: NoteType = "canvas";
|
||||
const mime = "application/json";
|
||||
|
||||
it("supports empty JSON", () => {
|
||||
expect(preprocessContent("{}", type, mime)).toEqual("");
|
||||
});
|
||||
|
||||
it("supports blank text / invalid JSON", () => {
|
||||
expect(preprocessContent("", type, mime)).toEqual("");
|
||||
});
|
||||
|
||||
it("reads elements", () => {
|
||||
expect(preprocessContent(`{ "elements": [ { "type": "text", "text": "Hello" } ] }`, type, mime)).toEqual("hello");
|
||||
expect(preprocessContent(`{ "elements": [ { "type": "text" }, { "type": "text", "text": "World" }, { "type": "rectangle", "text": "Ignored" } ] }`, type, mime)).toEqual("world");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import striptags from "striptags";
|
||||
|
||||
import { normalize } from "../../utils.js";
|
||||
|
||||
export default function preprocessContent(content: string | Buffer, type: string, mime: string, raw?: boolean) {
|
||||
content = normalize(content.toString());
|
||||
|
||||
if (type === "text" && mime === "text/html") {
|
||||
if (!raw) {
|
||||
// Content size already filtered at DB level, safe to process
|
||||
content = stripTags(content);
|
||||
}
|
||||
|
||||
content = content.replace(/ /g, " ");
|
||||
} else if (type === "mindMap" && mime === "application/json") {
|
||||
content = processMindmapContent(content);
|
||||
} else if (type === "canvas" && mime === "application/json") {
|
||||
content = processCanvasContent(content);
|
||||
}
|
||||
|
||||
return content.trim();
|
||||
}
|
||||
|
||||
function processMindmapContent(content: string) {
|
||||
let mindMapcontent;
|
||||
|
||||
try {
|
||||
mindMapcontent = JSON.parse(content);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Define interfaces for the JSON structure
|
||||
interface MindmapNode {
|
||||
id: string;
|
||||
topic: string;
|
||||
children: MindmapNode[]; // Recursive structure
|
||||
direction?: number;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
interface MindmapData {
|
||||
nodedata: MindmapNode;
|
||||
arrows: any[]; // If you know the structure, replace `any` with the correct type
|
||||
summaries: any[];
|
||||
direction: number;
|
||||
theme: {
|
||||
name: string;
|
||||
type: string;
|
||||
palette: string[];
|
||||
cssvar: Record<string, string>; // Object with string keys and string values
|
||||
};
|
||||
}
|
||||
|
||||
// Recursive function to collect all topics
|
||||
function collectTopics(node?: MindmapNode): string[] {
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collect the current node's topic
|
||||
let topics = [node.topic];
|
||||
|
||||
// If the node has children, collect topics recursively
|
||||
if (node.children && node.children.length > 0) {
|
||||
for (const child of node.children) {
|
||||
topics = topics.concat(collectTopics(child));
|
||||
}
|
||||
}
|
||||
|
||||
return topics;
|
||||
}
|
||||
|
||||
// Start extracting from the root node
|
||||
const topicsArray = collectTopics(mindMapcontent.nodedata);
|
||||
|
||||
// Combine topics into a single string
|
||||
const topicsString = topicsArray.join(", ");
|
||||
|
||||
return normalize(topicsString.toString());
|
||||
}
|
||||
|
||||
function processCanvasContent(content: string) {
|
||||
interface Element {
|
||||
type: string;
|
||||
text?: string; // Optional since not all objects have a `text` property
|
||||
id: string;
|
||||
[key: string]: any; // Other properties that may exist
|
||||
}
|
||||
|
||||
let canvasContent;
|
||||
try {
|
||||
canvasContent = JSON.parse(content);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
const elements = canvasContent.elements;
|
||||
|
||||
if (Array.isArray(elements)) {
|
||||
const texts = elements
|
||||
.filter((element: Element) => element.type === "text" && element.text) // Filter for 'text' type elements with a 'text' property
|
||||
.map((element: Element) => element.text!); // Use `!` to assert `text` is defined after filtering
|
||||
|
||||
content = normalize(texts.join(" "));
|
||||
} else {
|
||||
content = "";
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function stripTags(content: string) {
|
||||
// we want to allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412
|
||||
// we want to insert space in place of block tags (because they imply text separation)
|
||||
// but we don't want to insert text for typical formatting inline tags which can occur within one word
|
||||
const linkTag = "a";
|
||||
const inlineFormattingTags = ["b", "strong", "em", "i", "span", "big", "small", "font", "sub", "sup"];
|
||||
|
||||
// replace tags which imply text separation with a space
|
||||
content = striptags(content, [linkTag, ...inlineFormattingTags], " ");
|
||||
|
||||
// replace the inline formatting tags (but not links) without a space
|
||||
content = striptags(content, [linkTag], "");
|
||||
|
||||
// at least the closing link tag can be easily stripped
|
||||
return content.replace(/<\/a>/gi, "");
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"keywords": [],
|
||||
"packageManager": "pnpm@10.31.0",
|
||||
"packageManager": "pnpm@10.32.0",
|
||||
"devDependencies": {
|
||||
"@wxt-dev/auto-icons": "1.1.1",
|
||||
"wxt": "0.20.18"
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"preview": "pnpm build && vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18next": "25.8.14",
|
||||
"i18next": "25.8.17",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"preact": "10.28.4",
|
||||
"preact": "10.29.0",
|
||||
"preact-iso": "2.11.1",
|
||||
"preact-render-to-string": "6.6.6",
|
||||
"react-i18next": "16.5.6"
|
||||
|
||||
@@ -197,5 +197,12 @@
|
||||
"description": "Trilium Notes는 간편한 접근 및 관리를 위해 유료 서비스인 PikaPods에서 호스팅할 수 있습니다. Trilium 팀과 직접 제휴되어있지는 않습니다.",
|
||||
"download_pikapod": "PikaPods에서 설치하기",
|
||||
"download_triliumcc": "또는 trilium.cc를 참조하세요"
|
||||
},
|
||||
"resources": {
|
||||
"title": "리소스",
|
||||
"icon_packs": "아이콘 팩",
|
||||
"icon_packs_intro": "아이콘 팩을 사용하여 노트에 사용할 수 있는 아이콘 종류를 늘려보세요. 아이콘 팩에 대한 자세한 내용은 <DocumentationLink>공식 문서</DocumentationLink>를 참조하세요.",
|
||||
"download": "다운로드",
|
||||
"website": "웹사이트"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
"canvas_description": "Розташовуйте фігури, зображення та текст на нескінченному полотні, використовуючи ту саму технологію, що й excalidraw.com. Ідеально підходить для діаграм, ескізів та візуального планування.",
|
||||
"mermaid_description": "Створюйте діаграми, такі як блок-схеми, діаграми класів та послідовностей, діаграми Ганта та багато іншого, використовуючи синтаксис Mermaid.",
|
||||
"others_list": "та інші: <0>карта нотаток</0>, <1>карта зв'язків</1>, <2>збережені пошуки</2>, <3>візуалізація нотаток</3> та <4>веб-перегляди</4>.",
|
||||
"mermaid_title": "Mermaid діаграми"
|
||||
"mermaid_title": "Mermaid діаграми",
|
||||
"mindmap_title": "Карта думок",
|
||||
"mindmap_description": "Візуально упорядкуйте свої думки або проведіть мозковий штурм."
|
||||
},
|
||||
"extensibility_benefits": {
|
||||
"title": "Спільне використання та розширюваність",
|
||||
@@ -59,7 +61,9 @@
|
||||
"share_title": "Діліться нотатками в Інтернеті",
|
||||
"share_description": "Якщо у Вас є сервер, Ви можете використати його, щоб поділитися частиною своїх нотаток з іншими людьми.",
|
||||
"api_title": "REST API",
|
||||
"api_description": "Взаємодійте з Trilium програмно, використовуючи його вбудований REST API."
|
||||
"api_description": "Взаємодійте з Trilium програмно, використовуючи його вбудований REST API.",
|
||||
"scripting_title": "Розширений скриптинг",
|
||||
"scripting_description": "Створюйте власні інтеграції в Trilium за допомогою користувацьких віджетів або серверної логіки."
|
||||
},
|
||||
"collections": {
|
||||
"title": "Колекції",
|
||||
@@ -108,7 +112,8 @@
|
||||
"header": {
|
||||
"get-started": "Почати",
|
||||
"documentation": "Документація",
|
||||
"support-us": "Підтримайте нас"
|
||||
"support-us": "Підтримайте нас",
|
||||
"resources": "Ресурси"
|
||||
},
|
||||
"footer": {
|
||||
"copyright_and_the": " і ",
|
||||
@@ -148,7 +153,8 @@
|
||||
"description_arm64": "Сумісний з пристроями ARM (наприклад, з Qualcomm Snapdragon).",
|
||||
"quick_start": "Щоб встановити через Winget:",
|
||||
"download_exe": "Завантажити інсталятор (.exe)",
|
||||
"download_zip": "Портативний (.zip)"
|
||||
"download_zip": "Портативний (.zip)",
|
||||
"download_scoop": "Scoop"
|
||||
},
|
||||
"download_helper_desktop_linux": {
|
||||
"title_x64": "Linux 64-bit",
|
||||
@@ -159,23 +165,44 @@
|
||||
"download_deb": ".deb",
|
||||
"download_rpm": ".rpm",
|
||||
"download_flatpak": ".flatpak",
|
||||
"download_nixpkgs": "nixpkgs"
|
||||
"download_nixpkgs": "nixpkgs",
|
||||
"download_zip": "Portable (.zip)",
|
||||
"download_aur": "AUR"
|
||||
},
|
||||
"download_helper_desktop_macos": {
|
||||
"title_x64": "macOS для Intel",
|
||||
"title_arm64": "macOS для Apple Silicon",
|
||||
"quick_start": "Для того, щоб встановити за допомогою Homebrew:",
|
||||
"download_homebrew_cask": "Homebrew Cask"
|
||||
"download_homebrew_cask": "Homebrew Cask",
|
||||
"description_x64": "Для комп’ютерів Mac на базі Intel з macOS Monterey або пізнішої версії.",
|
||||
"description_arm64": "Для комп'ютерів Apple Silicon Mac, таких як ті, що мають чіпи M1 та M2.",
|
||||
"download_dmg": "Завантажити інсталятор (.dmg)",
|
||||
"download_zip": "Portable (.zip)"
|
||||
},
|
||||
"download_helper_server_docker": {
|
||||
"download_dockerhub": "Docker Hub",
|
||||
"download_ghcr": "ghcr.io"
|
||||
"download_ghcr": "ghcr.io",
|
||||
"title": "Self-hosted using Docker",
|
||||
"description": "Легке розгортання на Windows, Linux або macOS за допомогою контейнера Docker."
|
||||
},
|
||||
"download_helper_server_linux": {
|
||||
"download_tar_x64": "x64 (.tar.xz)",
|
||||
"download_tar_arm64": "ARM (.tar.xz)"
|
||||
"download_tar_arm64": "ARM (.tar.xz)",
|
||||
"title": "Self-hosted on Linux",
|
||||
"description": "Розгорніть Trilium Notes на власному сервері або VPS, сумісному з більшістю дистрибутивів.",
|
||||
"download_nixos": "NixOS module"
|
||||
},
|
||||
"download_helper_server_hosted": {
|
||||
"title": "Платний хостинг"
|
||||
"title": "Платний хостинг",
|
||||
"description": "Нотатки Trilium розміщені на PikaPods, платному сервісі для легкого доступу та керування. Не пов'язаний безпосередньо з командою Trilium.",
|
||||
"download_pikapod": "Налаштування на PikaPods",
|
||||
"download_triliumcc": "Або див. trilium.cc"
|
||||
},
|
||||
"resources": {
|
||||
"title": "Ресурси",
|
||||
"icon_packs": "Пакети піктограм",
|
||||
"icon_packs_intro": "Розширте вибір доступних піктограм для ваших нотаток за допомогою пакету піктограм. Щоб отримати докладнішу інформацію про пакети піктограм, див. <DocumentationLink>офіційну документацію</DocumentationLink>.",
|
||||
"download": "Завантажити",
|
||||
"website": "Вебсайт"
|
||||
}
|
||||
}
|
||||
|
||||
28
docs/README-ko.md
vendored
@@ -263,23 +263,19 @@ docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/De
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 텍스트 노트의 시각적 편집기입니다. 프리미엄
|
||||
기능을 제공해주셔서 감사합니다.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 수많은 언어를 지원하는 코드 편집기.
|
||||
* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite
|
||||
whiteboard used in Canvas notes.
|
||||
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the
|
||||
mind map functionality.
|
||||
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical
|
||||
maps.
|
||||
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive
|
||||
table used in collections.
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library
|
||||
without real competition.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library.
|
||||
Used in [relation
|
||||
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
|
||||
[link
|
||||
maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)
|
||||
* [Excalidraw](https://github.com/excalidraw/excalidraw) - Canvas 노트에서 사용되는 무한
|
||||
화이트보드입니다.
|
||||
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - 마인드맵 기능을 제공합니다.
|
||||
* [Leaflet](https://github.com/Leaflet/Leaflet) - 지리 지도를 렌더링 합니다.
|
||||
* [Tabulator](https://github.com/olifolkerd/tabulator) - 컬렉션에서 사용되는 인터랙티브
|
||||
테이블입니다.
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - 독보적으로 기능이 풍부한 트리 라이브러리입니다.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - 시각적 연결 라이브러리입니다. [관계
|
||||
맵](https://docs.triliumnotes.org/user-guide/note-types/relation-map) 과 [링크
|
||||
맵](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)에
|
||||
사용됩니다
|
||||
|
||||
## 🤝 Support
|
||||
## 🤝 후원
|
||||
|
||||
Trilium is built and maintained with [hundreds of hours of
|
||||
work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your
|
||||
|
||||
46
docs/README-uk.md
vendored
@@ -95,8 +95,8 @@ Trilium Notes — це безкоштовний кросплатформний
|
||||
безпечнішого входу
|
||||
* [Синхронізація](https://docs.triliumnotes.org/user-guide/setup/synchronization)
|
||||
із власним сервером синхронізації
|
||||
* there are [3rd party services for hosting synchronisation
|
||||
server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
|
||||
* існують [сторонні сервіси для розміщення сервера
|
||||
синхронізації](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
|
||||
* [Спільне
|
||||
використання](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing)
|
||||
(публікація) нотаток у загальнодоступному інтернеті
|
||||
@@ -105,10 +105,11 @@ Trilium Notes — це безкоштовний кросплатформний
|
||||
з деталізацією для кожної нотатки
|
||||
* Створення ескізних схем на основі [Excalidraw](https://excalidraw.com/) (тип
|
||||
нотатки "полотно")
|
||||
* [Relation
|
||||
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
|
||||
[note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map)
|
||||
for visualizing notes and their relations
|
||||
* [Карти
|
||||
зв'язків](https://docs.triliumnotes.org/user-guide/note-types/relation-map) та
|
||||
[карти
|
||||
нотаток/посилань](https://docs.triliumnotes.org/user-guide/note-types/note-map)
|
||||
для візуалізації нотаток та їх зв'язків
|
||||
* Інтелект-карти, засновані на [Mind Elixir](https://docs.mind-elixir.com/)
|
||||
* [Геокарти](https://docs.triliumnotes.org/user-guide/collections/geomap) з
|
||||
географічними позначками та GPX-треками
|
||||
@@ -148,19 +149,18 @@ TriliumNext:
|
||||
надав репозиторій Trilium спільнотному проекту, який знаходиться за адресою
|
||||
https://github.com/TriliumNext
|
||||
|
||||
### ⬆️Migrating from Zadam/Trilium?
|
||||
### ⬆️Переходите із Zadam/Trilium?
|
||||
|
||||
There are no special migration steps to migrate from a zadam/Trilium instance to
|
||||
a TriliumNext/Trilium instance. Simply [install
|
||||
TriliumNext/Trilium](#-installation) as usual and it will use your existing
|
||||
database.
|
||||
Немає жодних спеціальних кроків для міграції з екземпляра zadam/Trilium до
|
||||
екземпляра TriliumNext/Trilium. Просто [встановіть
|
||||
TriliumNext/Trilium](#-installation) як завжди, і він використовуватиме вашу
|
||||
існуючу базу даних.
|
||||
|
||||
Versions up to and including
|
||||
[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are
|
||||
compatible with the latest zadam/trilium version of
|
||||
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later
|
||||
versions of TriliumNext/Trilium have their sync versions incremented which
|
||||
prevents direct migration.
|
||||
Версії до [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4)
|
||||
включно сумісні з останньою версією zadam/trilium
|
||||
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Будь-які
|
||||
пізніші версії TriliumNext/Trilium мають збільшені версії синхронізації, що
|
||||
запобігає прямій міграції.
|
||||
|
||||
## Обговоріть це з нами
|
||||
|
||||
@@ -189,8 +189,8 @@ prevents direct migration.
|
||||
Якщо ваш дистрибутив зазначено в таблиці нижче, використовуйте пакет вашого
|
||||
дистрибутива.
|
||||
|
||||
[](https://repology.org/project/triliumnext/versions)
|
||||
[](https://repology.org/project/triliumnext/versions)
|
||||
|
||||
Ви також можете завантажити бінарний реліз для вашої платформи зі сторінки
|
||||
[останнього релізу](https://github.com/TriliumNext/Trilium/releases/latest),
|
||||
@@ -281,10 +281,10 @@ pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
|
||||
|
||||
### Документація розробника
|
||||
|
||||
Please view the [documentation
|
||||
guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
|
||||
for details. If you have more questions, feel free to reach out via the links
|
||||
described in the "Discuss with us" section above.
|
||||
Будь ласка, перегляньте
|
||||
[документацію](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
|
||||
для отримання детальної інформації. Якщо у вас виникнуть додаткові запитання,
|
||||
звертайтеся до нас за посиланнями, описаними в розділі «Обговоріть з нами» вище.
|
||||
|
||||
## 👏 Привітання
|
||||
|
||||
|
||||
165
docs/User Guide/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.101.3",
|
||||
"appVersion": "0.102.1",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
@@ -10394,65 +10394,58 @@
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "wX4HbRucYSDD",
|
||||
"value": "AjqEeiDUOzj4",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "ODY7qQn5m2FT",
|
||||
"value": "mHbBMPDPkVV5",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "mHbBMPDPkVV5",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"isInheritable": false,
|
||||
"position": 80
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"value": "BlN9DFI679QC",
|
||||
"isInheritable": false,
|
||||
"position": 90
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "BlN9DFI679QC",
|
||||
"value": "0vhv7lsOLy82",
|
||||
"isInheritable": false,
|
||||
"position": 100
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "0vhv7lsOLy82",
|
||||
"value": "8YBEPzcpUgxw",
|
||||
"isInheritable": false,
|
||||
"position": 110
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "8YBEPzcpUgxw",
|
||||
"value": "0ESUbbAxVnoK",
|
||||
"isInheritable": false,
|
||||
"position": 120
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "0ESUbbAxVnoK",
|
||||
"isInheritable": false,
|
||||
"position": 130
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "nBAXQFj20hS1",
|
||||
"isInheritable": false,
|
||||
"position": 140
|
||||
"position": 130
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
@@ -10473,7 +10466,7 @@
|
||||
"dataFileName": "File.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "FoEnowwOhzLT",
|
||||
"attachmentId": "fZ7VMfQJWuLQ",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
@@ -10481,7 +10474,7 @@
|
||||
"dataFileName": "File_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "fZ7VMfQJWuLQ",
|
||||
"attachmentId": "hddkgf7kr9g4",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
@@ -10489,7 +10482,7 @@
|
||||
"dataFileName": "1_File_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "hddkgf7kr9g4",
|
||||
"attachmentId": "hIg9g5pgsjS3",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
@@ -10497,28 +10490,20 @@
|
||||
"dataFileName": "2_File_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "hIg9g5pgsjS3",
|
||||
"attachmentId": "IC0j8LFCOKka",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_File_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "IC0j8LFCOKka",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_File_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "wNHX24feZRAl",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_File_image.png"
|
||||
"dataFileName": "4_File_image.png"
|
||||
}
|
||||
],
|
||||
"dirFileName": "File",
|
||||
@@ -10609,6 +10594,114 @@
|
||||
"dataFileName": "1_PDFs_image.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "AjqEeiDUOzj4",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"KSZ04uQ2D1St",
|
||||
"W8vYD3Q1zjCR",
|
||||
"AjqEeiDUOzj4"
|
||||
],
|
||||
"title": "Videos",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "wX4HbRucYSDD",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "ODY7qQn5m2FT",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-video",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Videos.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "649jtu5ELGa8",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Videos_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "GWHEkY4I4OE3",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"KSZ04uQ2D1St",
|
||||
"GWHEkY4I4OE3"
|
||||
],
|
||||
"title": "Spreadsheets",
|
||||
"notePosition": 220,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-table",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "2FvYrpmOXm29",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "iPIMuisry3hd",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "wy8So3yZZlH9",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Spreadsheets.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "Eedn7QHJQbiV",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Spreadsheets_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15810,6 +15903,13 @@
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
@@ -15823,13 +15923,6 @@
|
||||
"value": "bx bx-bot",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "6f9hih2hXXZk",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
|
||||
2
docs/User Guide/User Guide.md
vendored
@@ -15,7 +15,7 @@ Trilium is an open-source solution for note-taking and organizing a personal kno
|
||||
|
||||
* <a class="reference-link" href="User%20Guide/Installation%20%26%20Setup/Desktop%20Installation.md">Desktop Installation</a>
|
||||
* <a class="reference-link" href="User%20Guide/Installation%20%26%20Setup/Server%20Installation.md">Server Installation</a>
|
||||
* <a class="reference-link" href="User%20Guide/Scripting/Script%20API/Frontend%20API">Frontend API</a> or <a class="reference-link" href="User%20Guide/Scripting/Script%20API/Backend%20API.dat">[missing note]</a>
|
||||
* <a class="reference-link" href="User%20Guide/Scripting/Script%20API/Frontend%20API">Frontend API</a> or <a class="reference-link" href="User%20Guide/Scripting/Script%20API/Backend%20API.dat">Backend API</a>
|
||||
* [ETAPI reference](User%20Guide/Advanced%20Usage/ETAPI%20\(REST%20API\)/API%20Reference.dat)
|
||||
|
||||
## External links
|
||||
|
||||
@@ -45,7 +45,7 @@ When a new promoted attribute definition is created, it creates a corresponding
|
||||
|
||||
The only purpose of the attribute definition is to set up a template. If the attribute was marked as promoted, then it's also displayed to the user for easy editing.
|
||||
|
||||
| | |
|
||||
| | |
|
||||
| --- | --- |
|
||||
| <figure class="image"><img style="aspect-ratio:495/157;" src="2_Promoted Attributes_image.png" width="495" height="157"></figure> | Notice how the promoted attribute definition only creates a “Due date” box above the text content. |
|
||||
| <figure class="image"><img style="aspect-ratio:663/160;" src="3_Promoted Attributes_image.png" width="663" height="160"></figure> | Once a value is set by the user, a new label (or relation, depending on the type) is created. The name of the attribute matches one set when creating the promoted attribute. |
|
||||
|
||||
@@ -30,7 +30,7 @@ Additionally, shorter aliases are available for common configurations (see Alter
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `TRILIUM_GENERAL_INSTANCENAME` | string | "" | Instance name for API identification |
|
||||
| `TRILIUM_GENERAL_INSTANCENAME` | string | "" | Instance name for API identification |
|
||||
| `TRILIUM_GENERAL_NOAUTHENTICATION` | boolean | false | Disable authentication (server only) |
|
||||
| `TRILIUM_GENERAL_NOBACKUP` | boolean | false | Disable automatic backups |
|
||||
| `TRILIUM_GENERAL_NODESKTOPICON` | boolean | false | Disable desktop icon creation |
|
||||
@@ -43,12 +43,12 @@ Additionally, shorter aliases are available for common configurations (see Alter
|
||||
| `TRILIUM_NETWORK_HOST` | string | "0.0.0.0" | Server host binding |
|
||||
| `TRILIUM_NETWORK_PORT` | string | "3000" | Server port |
|
||||
| `TRILIUM_NETWORK_HTTPS` | boolean | false | Enable HTTPS |
|
||||
| `TRILIUM_NETWORK_CERTPATH` | string | "" | SSL certificate path |
|
||||
| `TRILIUM_NETWORK_KEYPATH` | string | "" | SSL key path |
|
||||
| `TRILIUM_NETWORK_CERTPATH` | string | "" | SSL certificate path |
|
||||
| `TRILIUM_NETWORK_KEYPATH` | string | "" | SSL key path |
|
||||
| `TRILIUM_NETWORK_TRUSTEDREVERSEPROXY` | boolean/string | false | Reverse proxy trust settings |
|
||||
| `TRILIUM_NETWORK_CORSALLOWORIGIN` | string | "" | CORS allowed origins |
|
||||
| `TRILIUM_NETWORK_CORSALLOWMETHODS` | string | "" | CORS allowed methods |
|
||||
| `TRILIUM_NETWORK_CORSALLOWHEADERS` | string | "" | CORS allowed headers |
|
||||
| `TRILIUM_NETWORK_CORSALLOWORIGIN` | string | "" | CORS allowed origins |
|
||||
| `TRILIUM_NETWORK_CORSALLOWMETHODS` | string | "" | CORS allowed methods |
|
||||
| `TRILIUM_NETWORK_CORSALLOWHEADERS` | string | "" | CORS allowed headers |
|
||||
| `TRILIUM_NETWORK_CORSRESOURCEPOLICY` | string | same-origin | CORS Resource Policy allows same-origin/same-site/cross-origin as values, will error if not |
|
||||
|
||||
### Session Section
|
||||
@@ -61,26 +61,26 @@ Additionally, shorter aliases are available for common configurations (see Alter
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `TRILIUM_SYNC_SYNCSERVERHOST` | string | "" | Sync server host URL |
|
||||
| `TRILIUM_SYNC_SYNCSERVERHOST` | string | "" | Sync server host URL |
|
||||
| `TRILIUM_SYNC_SYNCSERVERTIMEOUT` | string | "120000" | Sync server timeout in milliseconds |
|
||||
| `TRILIUM_SYNC_SYNCPROXY` | string | "" | Sync proxy URL |
|
||||
| `TRILIUM_SYNC_SYNCPROXY` | string | "" | Sync proxy URL |
|
||||
|
||||
### MultiFactorAuthentication Section
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL` | string | "" | OAuth/OpenID base URL |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID` | string | "" | OAuth client ID |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET` | string | "" | OAuth client secret |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHBASEURL` | string | "" | OAuth/OpenID base URL |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTID` | string | "" | OAuth client ID |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHCLIENTSECRET` | string | "" | OAuth client secret |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERBASEURL` | string | "[https://accounts.google.com](https://accounts.google.com)" | OAuth issuer base URL |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERNAME` | string | "Google" | OAuth issuer display name |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON` | string | "" | OAuth issuer icon URL |
|
||||
| `TRILIUM_MULTIFACTORAUTHENTICATION_OAUTHISSUERICON` | string | "" | OAuth issuer icon URL |
|
||||
|
||||
### Logging Section
|
||||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `TRILIUM_LOGGING_RETENTIONDAYS` | integer | 90 | Number of days to retain log files |
|
||||
| `TRILIUM_LOGGING_RETENTIONDAYS` | integer | 90 | Number of days to retain log files |
|
||||
|
||||
## Alternative Environment Variables
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ These are the types of active content in Trilium, along with a few examples of w
|
||||
| <a class="reference-link" href="../Scripting/Backend%20scripts.md">Backend scripts</a> | Yes | Can run custom code on the server of Trilium (Node.js environment), with full access to the notes and the database. | Has access to all the unencrypted notes, but with full access to the database it can completely destroy the data. It also has access to execute other applications or alter the files and folders on the server). |
|
||||
| <a class="reference-link" href="../Note%20Types/Web%20View.md">Web View</a> | Yes | Displays a website inside a note. | Can point to a phishing website which can collect the data (for example on a log in page). |
|
||||
| <a class="reference-link" href="../Note%20Types/Render%20Note.md">Render Note</a> | Yes | Renders custom content inside a note, such as a dashboard or a new editor that is not officially supported by Trilium. | Can affect the UI similar to front-end scripts or custom widgets since the scripts are not completely encapsulated, or they can act similar to a web view where they can collect data entered by the user. |
|
||||
| <a class="reference-link" href="../Theme%20development/Custom%20app-wide%20CSS.md">Custom app-wide CSS</a> | No | Can alter the layout and style of the UI using CSS, applied regardless of theme. | Generally less problematic than the rest of active content, but a badly written CSS can affect the layout of the application, requiring the use of <a class="reference-link" href="../Advanced%20Usage/Safe%20mode.md">Safe mode</a> to be able to use the application. |
|
||||
| [Custom themes](../Theme%20development) | No | Can change the style of the entire UI. | Similar to custom app-wide CSS. |
|
||||
| <a class="reference-link" href="Themes/Icon%20Packs.md">Icon Packs</a> | No | Introduces new icons that can be used for notes. | Generally are more contained and less prone to cause issues, but they can cause performance issues (for example if the icon pack has millions of icons in it). |
|
||||
| <a class="reference-link" href="../Theme%20development/Custom%20app-wide%20CSS.md">Custom app-wide CSS</a> | No | Can alter the layout and style of the UI using CSS, applied regardless of theme. | Generally less problematic than the rest of active content, but a badly written CSS can affect the layout of the application, requiring the use of <a class="reference-link" href="../Advanced%20Usage/Safe%20mode.md">Safe mode</a> to be able to use the application. |
|
||||
| [Custom themes](../Theme%20development) | No | Can change the style of the entire UI. | Similar to custom app-wide CSS. |
|
||||
| <a class="reference-link" href="Themes/Icon%20Packs.md">Icon Packs</a> | No | Introduces new icons that can be used for notes. | Generally are more contained and less prone to cause issues, but they can cause performance issues (for example if the icon pack has millions of icons in it). |
|
||||
|
||||
## Active content badge
|
||||
|
||||
|
||||
@@ -229,9 +229,9 @@ You can open Trilium and automatically trigger a search by including the search
|
||||
|
||||
| Parameter | Value | Description |
|
||||
| --- | --- | --- |
|
||||
| MIN\_FUZZY\_TOKEN\_LENGTH | 3 | Minimum characters for fuzzy matching |
|
||||
| MAX\_EDIT\_DISTANCE | 2 | Maximum character changes allowed |
|
||||
| RESULT\_SUFFICIENCY\_THRESHOLD | 5 | Minimum exact results before fuzzy fallback |
|
||||
| MIN\_FUZZY\_TOKEN\_LENGTH | 3 | Minimum characters for fuzzy matching |
|
||||
| MAX\_EDIT\_DISTANCE | 2 | Maximum character changes allowed |
|
||||
| RESULT\_SUFFICIENCY\_THRESHOLD | 5 | Minimum exact results before fuzzy fallback |
|
||||
| MAX\_CONTENT\_SIZE | 10MB | Maximum note content size for search processing |
|
||||
|
||||
### Limits
|
||||
|
||||
2
docs/User Guide/User Guide/Collections.md
vendored
@@ -3,7 +3,7 @@ Collections are a unique type of note that don't have content, but instead displ
|
||||
|
||||
## Main collections
|
||||
|
||||
| | |
|
||||
| | |
|
||||
| --- | --- |
|
||||
| <figure class="image"><img style="aspect-ratio:1651/810;" src="Collections_collection_ca.webp" width="1651" height="810"></figure> | <a class="reference-link" href="Collections/Calendar.md">Calendar</a> <br>which displays a week, month or year calendar with the notes being shown as events. New events can be added easily by dragging across the calendar. |
|
||||
| <figure class="image"><img style="aspect-ratio:1643/647;" src="Collections_collection_ta.webp" width="1643" height="647"></figure> | <a class="reference-link" href="Collections/Table.md">Table</a> <br>displays each note as a row in a table, with <a class="reference-link" href="Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a> being shown as well. This makes it easy to visualize attributes of notes, as well as making them easily editable. |
|
||||
|
||||
@@ -23,12 +23,12 @@ The position on the map and the zoom are saved inside the map note and restored
|
||||
|
||||
### Adding a new note using the plus button
|
||||
|
||||
| | | |
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| 1 | To create a marker, first navigate to the desired point on the map. Then press the  button in the [Floating buttons](../Basic%20Concepts%20and%20Features/UI%20Elements/Floating%20buttons.md) (top-right) area. <br> <br>If the button is not visible, make sure the button section is visible by pressing the chevron button () in the top-right of the map. | |
|
||||
| 2 | <img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png" width="1730" height="416"> | Once pressed, the map will enter in the insert mode, as illustrated by the notification. <br> <br>Simply click the point on the map where to place the marker, or the Escape key to cancel. |
|
||||
| 3 | <img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map_image.png" width="1586" height="404"> | Enter the name of the marker/note to be created. |
|
||||
| 4 | <img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="14_Geo Map_image.png" width="1696" height="608"> | Once confirmed, the marker will show up on the map and it will also be displayed as a child note of the map. |
|
||||
| 1 | To create a marker, first navigate to the desired point on the map. Then press the  button in the [Floating buttons](../Basic%20Concepts%20and%20Features/UI%20Elements/Floating%20buttons.md) (top-right) area. <br> <br>If the button is not visible, make sure the button section is visible by pressing the chevron button () in the top-right of the map. | |
|
||||
| 2 | <img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png" width="1730" height="416"> | Once pressed, the map will enter in the insert mode, as illustrated by the notification. <br> <br>Simply click the point on the map where to place the marker, or the Escape key to cancel. |
|
||||
| 3 | <img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map_image.png" width="1586" height="404"> | Enter the name of the marker/note to be created. |
|
||||
| 4 | <img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="14_Geo Map_image.png" width="1696" height="608"> | Once confirmed, the marker will show up on the map and it will also be displayed as a child note of the map. |
|
||||
|
||||
### Adding a new note using the contextual menu
|
||||
|
||||
@@ -106,31 +106,31 @@ The value of the attribute is made up of the latitude and longitude separated by
|
||||
|
||||
### Adding from Google Maps
|
||||
|
||||
| | | |
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| 1 | <figure class="image image-style-align-center image_resized" style="width:56.84%;"><img style="aspect-ratio:732/918;" src="11_Geo Map_image.png" width="732" height="918"></figure> | Go to Google Maps on the web and look for a desired location, right click on it and a context menu will show up. <br> <br>Simply click on the first item displaying the coordinates and they will be copied to clipboard. <br> <br>Then paste the value inside the text box into the `#geolocation` attribute of a child note of the map (don't forget to surround the value with a `"` character). |
|
||||
| 2 | <figure class="image image-style-align-center image_resized" style="width:100%;"><img style="aspect-ratio:518/84;" src="4_Geo Map_image.png" width="518" height="84"></figure> | In Trilium, create a child note under the map. |
|
||||
| 3 | <figure class="image image-style-align-center image_resized" style="width:100%;"><img style="aspect-ratio:1074/276;" src="10_Geo Map_image.png" width="1074" height="276"></figure> | And then go to Owned Attributes and type `#geolocation="`, then paste from the clipboard as-is and then add the ending `"` character. Press Enter to confirm and the map should now be updated to contain the new note. |
|
||||
| 1 | <figure class="image image-style-align-center image_resized" style="width:56.84%;"><img style="aspect-ratio:732/918;" src="11_Geo Map_image.png" width="732" height="918"></figure> | Go to Google Maps on the web and look for a desired location, right click on it and a context menu will show up. <br> <br>Simply click on the first item displaying the coordinates and they will be copied to clipboard. <br> <br>Then paste the value inside the text box into the `#geolocation` attribute of a child note of the map (don't forget to surround the value with a `"` character). |
|
||||
| 2 | <figure class="image image-style-align-center image_resized" style="width:100%;"><img style="aspect-ratio:518/84;" src="4_Geo Map_image.png" width="518" height="84"></figure> | In Trilium, create a child note under the map. |
|
||||
| 3 | <figure class="image image-style-align-center image_resized" style="width:100%;"><img style="aspect-ratio:1074/276;" src="10_Geo Map_image.png" width="1074" height="276"></figure> | And then go to Owned Attributes and type `#geolocation="`, then paste from the clipboard as-is and then add the ending `"` character. Press Enter to confirm and the map should now be updated to contain the new note. |
|
||||
|
||||
### Adding from OpenStreetMap
|
||||
|
||||
Similarly to the Google Maps approach:
|
||||
|
||||
| | | |
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| 1 | <img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png" width="562" height="454"> | Go to any location on openstreetmap.org and right click to bring up the context menu. Select the “Show address” item. |
|
||||
| 2 | <img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png" width="696" height="480"> | The address will be visible in the top-left of the screen, in the place of the search bar. <br> <br>Select the coordinates and copy them into the clipboard. |
|
||||
| 3 | <img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png" width="640" height="276"> | Simply paste the value inside the text box into the `#geolocation` attribute of a child note of the map and then it should be displayed on the map. |
|
||||
| 1 | <img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png" width="562" height="454"> | Go to any location on openstreetmap.org and right click to bring up the context menu. Select the “Show address” item. |
|
||||
| 2 | <img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png" width="696" height="480"> | The address will be visible in the top-left of the screen, in the place of the search bar. <br> <br>Select the coordinates and copy them into the clipboard. |
|
||||
| 3 | <img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png" width="640" height="276"> | Simply paste the value inside the text box into the `#geolocation` attribute of a child note of the map and then it should be displayed on the map. |
|
||||
|
||||
## Adding GPS tracks (.gpx)
|
||||
|
||||
Trilium has basic support for displaying GPS tracks on the geo map.
|
||||
|
||||
| | | |
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| 1 | <figure class="image image-style-align-center"><img style="aspect-ratio:226/74;" src="3_Geo Map_image.png" width="226" height="74"></figure> | To add a track, simply drag & drop a .gpx file inside the geo map in the note tree. |
|
||||
| 2 | <figure class="image image-style-align-center"><img style="aspect-ratio:322/222;" src="13_Geo Map_image.png" width="322" height="222"></figure> | In order for the file to be recognized as a GPS track, it needs to show up as `application/gpx+xml` in the _File type_ field. |
|
||||
| 3 | <figure class="image image-style-align-center"><img style="aspect-ratio:620/530;" src="6_Geo Map_image.png" width="620" height="530"></figure> | When going back to the map, the track should now be visible. <br> <br>The start and end points of the track are indicated by the two blue markers. |
|
||||
| 1 | <figure class="image image-style-align-center"><img style="aspect-ratio:226/74;" src="3_Geo Map_image.png" width="226" height="74"></figure> | To add a track, simply drag & drop a .gpx file inside the geo map in the note tree. |
|
||||
| 2 | <figure class="image image-style-align-center"><img style="aspect-ratio:322/222;" src="13_Geo Map_image.png" width="322" height="222"></figure> | In order for the file to be recognized as a GPS track, it needs to show up as `application/gpx+xml` in the _File type_ field. |
|
||||
| 3 | <figure class="image image-style-align-center"><img style="aspect-ratio:620/530;" src="6_Geo Map_image.png" width="620" height="530"></figure> | When going back to the map, the track should now be visible. <br> <br>The start and end points of the track are indicated by the two blue markers. |
|
||||
|
||||
> [!NOTE]
|
||||
> The starting point of the track will be displayed as a marker, with the name of the note underneath. The start marker will also respect the icon and the `color` of the note. The end marker is displayed with a distinct icon.
|
||||
|
||||
4
docs/User Guide/User Guide/Note Types.md
vendored
@@ -33,10 +33,10 @@ The following note types are supported by Trilium:
|
||||
| <a class="reference-link" href="Note%20Types/Relation%20Map.md">Relation Map</a> | Allows easy creation of notes and relations between them. Can be used for mainly relational data such as a family tree. |
|
||||
| <a class="reference-link" href="Note%20Types/Note%20Map.md">Note Map</a> | Displays the relationships between the notes, whether via relations or their hierarchical structure. |
|
||||
| <a class="reference-link" href="Note%20Types/Render%20Note.md">Render Note</a> | Used in <a class="reference-link" href="Scripting.md">Scripting</a>, it displays the HTML content of another note. This allows displaying any kind of content, provided there is a script behind it to generate it. |
|
||||
| <a class="reference-link" href="Collections.md">Collections</a> | Displays the children of the note either as a grid, a list, or for a more specialized case: a calendar. <br> <br>Generally useful for easy reading of short notes. |
|
||||
| <a class="reference-link" href="Collections.md">Collections</a> | Displays the children of the note either as a grid, a list, or for a more specialized case: a calendar. <br> <br>Generally useful for easy reading of short notes. |
|
||||
| <a class="reference-link" href="Note%20Types/Mermaid%20Diagrams.md">Mermaid Diagrams</a> | Displays diagrams such as bar charts, flow charts, state diagrams, etc. Requires a bit of technical knowledge since the diagrams are written in a specialized format. |
|
||||
| <a class="reference-link" href="Note%20Types/Canvas.md">Canvas</a> | Allows easy drawing of sketches, diagrams, handwritten content. Uses the same technology behind [excalidraw.com](https://excalidraw.com). |
|
||||
| <a class="reference-link" href="Note%20Types/Web%20View.md">Web View</a> | Displays the content of an external web page, similar to a browser. |
|
||||
| <a class="reference-link" href="Note%20Types/Mind%20Map.md">Mind Map</a> | Easy for brainstorming ideas, by placing them in a hierarchical layout. |
|
||||
| <a class="reference-link" href="Collections/Geo%20Map.md">Geo Map View</a> | Displays the children of the note as a geographical map, one use-case would be to plan vacations. It even has basic support for tracks. Notes can also be created from it. |
|
||||
| <a class="reference-link" href="Collections/Geo%20Map.md">Geo Map</a> | Displays the children of the note as a geographical map, one use-case would be to plan vacations. It even has basic support for tracks. Notes can also be created from it. |
|
||||
| <a class="reference-link" href="Note%20Types/File.md">File</a> | Represents an uploaded file such as PDFs, images, video or audio files. |
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 612 KiB |
|
Before Width: | Height: | Size: 612 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
17
docs/User Guide/User Guide/Note Types/File.md
vendored
@@ -16,7 +16,7 @@ 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="3_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="2_File_image.png" width="879" height="766"></figure>
|
||||
|
||||
Interaction:
|
||||
|
||||
@@ -26,16 +26,11 @@ Interaction:
|
||||
|
||||
### Videos
|
||||
|
||||
<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.
|
||||
|
||||
> [!CAUTION]
|
||||
> Although Trilium offers support for videos, it is generally not meant to be used with very large files. Uploading large videos will cause the <a class="reference-link" href="../Advanced%20Usage/Database.md">Database</a> to balloon as well as the any <a class="reference-link" href="../Installation%20%26%20Setup/Backup.md">Backup</a> of it. In addition to that, there might be slowdowns when first uploading the files. Otherwise, a large database should not impact the general performance of Trilium significantly.
|
||||
See <a class="reference-link" href="File/Videos.md">Videos</a>.
|
||||
|
||||
### Audio
|
||||
|
||||
<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>
|
||||
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:850/243;" src="1_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.
|
||||
|
||||
@@ -48,7 +43,7 @@ Interactions:
|
||||
|
||||
### Text files
|
||||
|
||||
<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>
|
||||
<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:926/347;" src="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.
|
||||
|
||||
@@ -58,7 +53,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="4_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="3_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.
|
||||
|
||||
@@ -75,6 +70,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="5_File_image.png" width="853" height="315">
|
||||
<img class="image_resized" style="aspect-ratio:853/315;width:50%;" src="4_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>.
|
||||
63
docs/User Guide/User Guide/Note Types/File/Videos.md
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# Videos
|
||||
<figure class="image image-style-align-right image_resized" style="width:61.8%;"><img style="aspect-ratio:953/587;" src="Videos_image.png" width="953" height="587"></figure>
|
||||
|
||||
Starting with v0.103.0, Trilium has a custom video player which offers more features than the built-in video player.
|
||||
|
||||
Versions prior to v0.103.0 also support videos, but using the built-in player.
|
||||
|
||||
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.
|
||||
|
||||
## Note on large video files
|
||||
|
||||
Although Trilium offers support for videos, it is generally not meant to be used with very large files. Uploading large videos will cause the <a class="reference-link" href="../../Advanced%20Usage/Database.md">Database</a> to balloon as well as the any <a class="reference-link" href="../../Installation%20%26%20Setup/Backup.md">Backup</a> of it. In addition to that, there might be slowdowns when first uploading the files. Otherwise, a large database should not impact the general performance of Trilium significantly.
|
||||
|
||||
## Supported formats
|
||||
|
||||
Trilium uses the built-in video decoding mechanism of the browser (or Electron/Chromium when running on the desktop). Starting with v0.103.0, a message will be displayed instead when a video format is not supported.
|
||||
|
||||
## Interactions
|
||||
|
||||
To play/pause the video, simply click anywhere on the video.
|
||||
|
||||
The controls at the bottom will hide automatically after playing, simply move the mouse to show them again.
|
||||
|
||||
The bottom bar has the following features:
|
||||
|
||||
* A track bar to seek across the video.
|
||||
* On the left of the track bar, the current time is indicated.
|
||||
* On the right of the track bar, the remaining time is indicated.
|
||||
* On the left side there are buttons to:
|
||||
|
||||
* Adjust the playback speed (e.g. 0.5x, 1x).
|
||||
* Rotate the video by 90 degrees.
|
||||
* In the center:
|
||||
|
||||
* Go back by 10s
|
||||
* Play/pause
|
||||
* Go forward by 30s
|
||||
* Loop, which when enabled will restart the video once it reaches the end.
|
||||
* On the right side:
|
||||
|
||||
* Mute button
|
||||
* Volume adjustment
|
||||
* Full screen
|
||||
* Zoom to fill, which will crop the video so that it fills the entire window.
|
||||
* Picture-in-picture (if the browser supports it).
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
The following keyboard shortcuts are supported by the video player:
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| <kbd>Space</kbd> | Play/pause |
|
||||
| <kbd>Left arrow key</kbd> | Go back by 10s |
|
||||
| <kbd>Right arrow key</kbd> | Go forward by 10s |
|
||||
| <kbd>Ctrl</kbd> + <kbd>Left arrow key</kbd> | Go back by 1 min |
|
||||
| <kbd>Ctrl</kbd> + <kbd>Right arrow key</kbd> | Go right by 1 min |
|
||||
| <kbd>F</kbd> | Toggle full-screen |
|
||||
| <kbd>M</kbd> | Mute/unmute |
|
||||
| <kbd>Home</kbd> | Go to the beginning of the video |
|
||||
| <kbd>End</kbd> | Go to the end of the video |
|
||||
| <kbd>Up</kbd> | Increase volume by 5% |
|
||||
| <kbd>Down</kbd> | Decrease volume by 5% |
|
||||
BIN
docs/User Guide/User Guide/Note Types/File/Videos_image.png
vendored
Normal file
|
After Width: | Height: | Size: 842 KiB |
BIN
docs/User Guide/User Guide/Note Types/File_image.png
vendored
|
Before Width: | Height: | Size: 652 KiB After Width: | Height: | Size: 15 KiB |
66
docs/User Guide/User Guide/Note Types/Spreadsheets.md
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
# Spreadsheets
|
||||
<figure class="image"><img style="aspect-ratio:1102/573;" src="Spreadsheets_image.png" width="1102" height="573"></figure>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Spreadsheets are a new type of note introduced in v0.103.0 and are currently considered experimental/beta. As such, expect major changes to occur to this note type.
|
||||
|
||||
Spreadsheets provide a familiar experience to Microsoft Excel or LibreOffice Calc, with support for formulas, data validation and text formatting.
|
||||
|
||||
## Spreadsheets vs. collections
|
||||
|
||||
There is a slight overlap between spreadsheets and the <a class="reference-link" href="../Collections/Table.md">Table</a> collection. In general the table collection is useful to track meta-information about notes (for example a collection of people and their birthdays), whereas spreadsheets are quite useful for calculations since they support formulas.
|
||||
|
||||
Spreadsheets also benefit from a wider range of features such as data validation, formatting and can work on a relatively large dataset.
|
||||
|
||||
## Important statement regarding data format
|
||||
|
||||
For Trilium as a knowledge database, it is important that data is stored in a format that is easy to convert to something else. For example, <a class="reference-link" href="Text.md">Text</a> notes can be exported to either HTML or Markdown, making it relatively easy to migrate to another software or simply to stand the test of time.
|
||||
|
||||
For spreadsheets, Trilium uses a technology called [Univer Sheets](https://docs.univer.ai/), developed by DreamNum Co., Ltd. Although this software library is quite powerful and has a good track record (starting with Luckysheet from 2020, becoming Univer somewhere in 2023), it uses its own JSON format to store the sheets.
|
||||
|
||||
As such, if Univer were to become unmaintained or incompatible for some reason, your data might become vendor locked-in.
|
||||
|
||||
With that in mind, spreadsheets can be really useful for quick calculations, but it's important not to have critical information on it that you might not want to need in a few years time.
|
||||
|
||||
## Regarding data export
|
||||
|
||||
Currently, in Trilium there is no way to export the spreadsheets to CSV or Excel formats. We might manage to add support for it at some point, but currently this is not the case.
|
||||
|
||||
## Supported features
|
||||
|
||||
The spreadsheet has support for the following features:
|
||||
|
||||
* Filtering
|
||||
* Sorting
|
||||
* Data validation
|
||||
* Conditional formatting
|
||||
* Notes / annotations
|
||||
* Find / replace
|
||||
|
||||
We might consider adding [other features](https://docs.univer.ai/guides/sheets/features/filter) from Univer at some point. If there is a particular feature that can be added easily, it can be discussed over [GitHub Issues](../Troubleshooting/Reporting%20issues.md).
|
||||
|
||||
## Features not supported yet
|
||||
|
||||
### Regarding Pro features
|
||||
|
||||
Univer spreadsheets also feature a [Pro plan](https://univer.ai/pro) which adds quite a lot of functionality such as charts, printing, pivot tables, export, etc.
|
||||
|
||||
As the Pro plan needs a license, Trilium does not support any of the premium features. Theoretically, pro features can be used in trial mode with some limitations, we might explore this direction at some point.
|
||||
|
||||
### Planned features
|
||||
|
||||
There are a few features that are already planned but are not supported yet:
|
||||
|
||||
* Trilium-specific formulas (e.g. to obtain the title of a note).
|
||||
* User-defined formulas
|
||||
* Cross-workbook calculation
|
||||
|
||||
If you would like us to work on these features, consider [supporting us](https://triliumnotes.org/en/support-us).
|
||||
|
||||
## Known limitations
|
||||
|
||||
* It is possible to share a spreadsheet, case in which a best-effort HTML rendering of the spreadsheet is done.
|
||||
|
||||
* For more advanced use cases, this will most likely not work as intended. Feel free to [report issues](../Troubleshooting/Reporting%20issues.md), but keep in mind that we might not be able to have a complete feature parity with all the features of Univer.
|
||||
* There is currently no export functionality, as stated previously.
|
||||
* There is no dedicated mobile support. Mobile support is currently experimental in Univer and when it becomes stable, we could potentially integrate it into Trilium as well.
|
||||
BIN
docs/User Guide/User Guide/Note Types/Spreadsheets_image.png
vendored
Normal file
|
After Width: | Height: | Size: 117 KiB |
@@ -1,7 +1,7 @@
|
||||
# Keyboard shortcuts
|
||||
## Trilium-specific shortcuts
|
||||
|
||||
| Action | PC | Mac |
|
||||
| Action | PC | Mac |
|
||||
| --- | --- | --- |
|
||||
| Bring up inline formatting toolbar (arrow keys <kbd><span>←</span></kbd>,<kbd><span>→</span></kbd> to navigate, <kbd>Enter</kbd> to apply) | <kbd>Alt</kbd>+<kbd>F10</kbd> | <kbd>⌥</kbd>+<kbd>F10</kbd> |
|
||||
| Bring up block formatting toolbar | <kbd>Alt</kbd>+<kbd>F10</kbd> | <kbd>⌥</kbd>+<kbd>F10</kbd> |
|
||||
@@ -13,9 +13,9 @@
|
||||
| Mark selected text as [keyboard shortcut](Developer-specific%20formatting.md) | <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>K</kbd> | <kbd>⌘</kbd>\+ <kbd>⌥</kbd>\+ <kbd>K</kbd> |
|
||||
| Insert <a class="reference-link" href="Math%20Equations.md">Math Equations</a> | <kbd>Ctrl</kbd> + <kbd>M</kbd> | <kbd>⌘</kbd>\+ <kbd>M</kbd> |
|
||||
| Move blocks (lists, paragraphs, etc.) up | <kbd>Ctrl</kbd>+<kbd>↑</kbd> | <kbd>⌘</kbd>+<kbd>↑</kbd> |
|
||||
| <kbd>Alt</kbd>+<kbd>↑</kbd> | <kbd>⌥</kbd>+<kbd>↑</kbd> | |
|
||||
| <kbd>Alt</kbd>+<kbd>↑</kbd> | <kbd>⌥</kbd>+<kbd>↑</kbd> | |
|
||||
| Move blocks (lists, paragraphs, etc.) down | <kbd>Ctrl</kbd>+<kbd>↑</kbd> | <kbd>⌘</kbd>+<kbd>↑</kbd> |
|
||||
| <kbd>Alt</kbd>+<kbd>↓</kbd> | <kbd>⌥</kbd>+<kbd>↓</kbd> | |
|
||||
| <kbd>Alt</kbd>+<kbd>↓</kbd> | <kbd>⌥</kbd>+<kbd>↓</kbd> | |
|
||||
|
||||
## Common shortcuts
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
### Content editing
|
||||
|
||||
| Action | PC | Mac |
|
||||
| Action | PC | Mac |
|
||||
| --- | --- | --- |
|
||||
| Insert a hard break (a new paragraph) | <kbd>Enter</kbd> | |
|
||||
| Insert a hard break (a new paragraph) | <kbd>Enter</kbd> | |
|
||||
| Insert a soft break (a `<br>` element) | <kbd>Shift</kbd>+<kbd>Enter</kbd> | <kbd>⇧Enter</kbd> |
|
||||
| Copy selected content | <kbd>Ctrl</kbd>+<kbd>C</kbd> | <kbd>⌘C</kbd> |
|
||||
| Paste content | <kbd>Ctrl</kbd>+<kbd>V</kbd> | <kbd>⌘V</kbd> |
|
||||
@@ -36,8 +36,8 @@
|
||||
| Bold text | <kbd>Ctrl</kbd>+<kbd>B</kbd> | <kbd>⌘B</kbd> |
|
||||
| Change text case | <kbd>Shift</kbd>+<kbd>F3</kbd> | <kbd>⇧F3</kbd> (may require <kbd>Fn</kbd>) |
|
||||
| Create link | <kbd>Ctrl</kbd>+<kbd>K</kbd> | <kbd>⌘K</kbd> |
|
||||
| Move out of a link | <kbd>←←</kbd>, <kbd>→→</kbd> | |
|
||||
| Move out of an inline code style | <kbd>←←</kbd>, <kbd>→→</kbd> | |
|
||||
| Move out of a link | <kbd>←←</kbd>, <kbd>→→</kbd> | |
|
||||
| Move out of an inline code style | <kbd>←←</kbd>, <kbd>→→</kbd> | |
|
||||
| Select all | <kbd>Ctrl</kbd>+<kbd>A</kbd> | <kbd>⌘A</kbd> |
|
||||
| Find in the document | <kbd>Ctrl</kbd>+<kbd>F</kbd> | <kbd>⌘F</kbd> |
|
||||
| Copy text formatting | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>C</kbd> | <kbd>⌘⇧C</kbd> |
|
||||
@@ -45,45 +45,45 @@
|
||||
| Italic text | <kbd>Ctrl</kbd>+<kbd>I</kbd> | <kbd>⌘I</kbd> |
|
||||
| Strikethrough text | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>X</kbd> | <kbd>⌘⇧X</kbd> |
|
||||
| Underline text | <kbd>Ctrl</kbd>+<kbd>U</kbd> | <kbd>⌘U</kbd> |
|
||||
| Revert autoformatting action | <kbd>Backspace</kbd> | |
|
||||
| Revert autoformatting action | <kbd>Backspace</kbd> | |
|
||||
|
||||
### Interacting with blocks
|
||||
|
||||
Blocks are images, tables, blockquotes, annotations.
|
||||
|
||||
| Action | PC | Mac |
|
||||
| Action | PC | Mac |
|
||||
| --- | --- | --- |
|
||||
| Insert a new paragraph directly after a widget | <kbd>Enter</kbd> | |
|
||||
| Insert a new paragraph directly after a widget | <kbd>Enter</kbd> | |
|
||||
| Insert a new paragraph directly before a widget | <kbd>Shift</kbd>+<kbd>Enter</kbd> | <kbd>⇧Enter</kbd> |
|
||||
| Move the caret to allow typing directly before a widget | <kbd>↑</kbd>, <kbd>←</kbd> | |
|
||||
| Move the caret to allow typing directly after a widget | <kbd>↓</kbd>, <kbd>→</kbd> | |
|
||||
| After entering a nested editable, move the selection to the closest ancestor widget. For example: move from an image caption to the whole image widget. | <kbd>Tab</kbd> then <kbd>Esc</kbd> | |
|
||||
| Move the caret to allow typing directly before a widget | <kbd>↑</kbd>, <kbd>←</kbd> | |
|
||||
| Move the caret to allow typing directly after a widget | <kbd>↓</kbd>, <kbd>→</kbd> | |
|
||||
| After entering a nested editable, move the selection to the closest ancestor widget. For example: move from an image caption to the whole image widget. | <kbd>Tab</kbd> then <kbd>Esc</kbd> | |
|
||||
|
||||
Specifically for lists:
|
||||
|
||||
| Action | PC | Mac |
|
||||
| Action | PC | Mac |
|
||||
| --- | --- | --- |
|
||||
| Increase list item indent | <kbd>⇥</kbd> | |
|
||||
| Increase list item indent | <kbd>⇥</kbd> | |
|
||||
| Decrease list item indent | <kbd>Shift</kbd>+<kbd>⇥</kbd> | <kbd>⇧⇥</kbd> |
|
||||
|
||||
In tables:
|
||||
|
||||
| Action | PC | Mac |
|
||||
| Action | PC | Mac |
|
||||
| --- | --- | --- |
|
||||
| Move the selection to the next cell | <kbd>⇥</kbd> | |
|
||||
| Move the selection to the next cell | <kbd>⇥</kbd> | |
|
||||
| Move the selection to the previous cell | <kbd>Shift</kbd>+<kbd>⇥</kbd> | <kbd>⇧⇥</kbd> |
|
||||
| Insert a new table row (when in the last cell of a table) | <kbd>⇥</kbd> | |
|
||||
| Navigate through the table | <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd>, <kbd>←</kbd> | |
|
||||
| Insert a new table row (when in the last cell of a table) | <kbd>⇥</kbd> | |
|
||||
| Navigate through the table | <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd>, <kbd>←</kbd> | |
|
||||
|
||||
### General UI shortcuts
|
||||
|
||||
| Action | PC | Mac |
|
||||
| Action | PC | Mac |
|
||||
| --- | --- | --- |
|
||||
| Close contextual balloons, dropdowns, and dialogs | <kbd>Esc</kbd> | |
|
||||
| Close contextual balloons, dropdowns, and dialogs | <kbd>Esc</kbd> | |
|
||||
| Open the accessibility help dialog | <kbd>Alt</kbd>+<kbd>0</kbd> | <kbd>⌥0</kbd> |
|
||||
| Move focus between form fields (inputs, buttons, etc.) | <kbd>⇥</kbd>, <kbd>Shift</kbd>+<kbd>⇥</kbd> | <kbd>⇥</kbd>, <kbd>⇧⇥</kbd> |
|
||||
| Move focus to the toolbar, navigate between toolbars | <kbd>Alt</kbd>+<kbd>F10</kbd> | <kbd>⌥F10</kbd> (may require <kbd>Fn</kbd>) |
|
||||
| Navigate through the toolbar or menu bar | <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd>, <kbd>←</kbd> | |
|
||||
| Navigate to the next focusable field or an element outside the editor | <kbd>Tab</kbd>, <kbd>Shift</kbd>+<kbd>Tab</kbd> | |
|
||||
| Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content. | <kbd>Enter</kbd>, <kbd>Space</kbd> | |
|
||||
| Navigate through the toolbar or menu bar | <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd>, <kbd>←</kbd> | |
|
||||
| Navigate to the next focusable field or an element outside the editor | <kbd>Tab</kbd>, <kbd>Shift</kbd>+<kbd>Tab</kbd> | |
|
||||
| Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content. | <kbd>Enter</kbd>, <kbd>Space</kbd> | |
|
||||
| Move focus in and out of an active dialog window | <kbd>Ctrl</kbd>+<kbd>F6</kbd> | <kbd>⌘F6</kbd> (may require <kbd>Fn</kbd>) |
|
||||
@@ -23,14 +23,14 @@ For bulleted and numbered lists, it's possible to configure an alternative marke
|
||||
|
||||
It possible to add content-level blocks such as headings, code blocks, tables within lists, as follows:
|
||||
|
||||
| | | |
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
| 1 |  | First, create a list. |
|
||||
| 2 |  | Press Enter to create a new list item. |
|
||||
| 3 |  | Press Backspace to get rid of the bullet point. Notice the cursor position. |
|
||||
| 4 | <img class="image_resized" style="aspect-ratio:676/112;width:98.29%;" src="10_Lists_image.png" width="676" height="112"> | At this point, insert any desired block-level item such as a code block. |
|
||||
| 5 | <img class="image_resized" style="aspect-ratio:675/129;width:94.22%;" src="8_Lists_image.png" width="675" height="129"> | To continue with a new bullet point, press Enter until the cursor moves to a new blank position. |
|
||||
| 6 | <img class="image_resized" style="aspect-ratio:675/129;width:100%;" src="3_Lists_image.png" width="675" height="129"> | Press Enter once more to create the new bullet. |
|
||||
| 1 |  | First, create a list. |
|
||||
| 2 |  | Press Enter to create a new list item. |
|
||||
| 3 |  | Press Backspace to get rid of the bullet point. Notice the cursor position. |
|
||||
| 4 | <img class="image_resized" style="aspect-ratio:676/112;width:98.29%;" src="10_Lists_image.png" width="676" height="112"> | At this point, insert any desired block-level item such as a code block. |
|
||||
| 5 | <img class="image_resized" style="aspect-ratio:675/129;width:94.22%;" src="8_Lists_image.png" width="675" height="129"> | To continue with a new bullet point, press Enter until the cursor moves to a new blank position. |
|
||||
| 6 | <img class="image_resized" style="aspect-ratio:675/129;width:100%;" src="3_Lists_image.png" width="675" height="129"> | Press Enter once more to create the new bullet. |
|
||||
|
||||
The same principle applies to all three list types (bullet, numbered and to-do).
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Front-end scripts are custom JavaScript notes that are run on the client (browse
|
||||
|
||||
There are four flavors of front-end scripts:
|
||||
|
||||
| | |
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Regular scripts | These are run with the current app and note context. These can be run either manually or automatically on start-up. |
|
||||
| <a class="reference-link" href="Frontend%20Basics/Custom%20Widgets.md">Custom Widgets</a> | These can introduce new UI elements in various positions, such as near the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Note%20Tree.md">Note Tree</a>, content area or even the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Right%20Sidebar.md">Right Sidebar</a>. |
|
||||
|
||||
@@ -9,7 +9,7 @@ As such, the first step is to create a new note to gather all the themes.
|
||||
|
||||
## Step 2. Create the theme
|
||||
|
||||
| | |
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  | Themes are code notes with a special attribute. Start by creating a new code note. |
|
||||
|  | Then change the note type to a CSS code. |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Custom app-wide CSS
|
||||
It is possible to provide a CSS file to be used regardless of the theme set by the user.
|
||||
|
||||
| | |
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  | Start by creating a new note and changing the note type to CSS |
|
||||
|  | In the ribbon, press the “Owned Attributes” section and type `#appCss`. |
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"tslib": "2.8.1",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.56.1",
|
||||
"typescript-eslint": "8.57.0",
|
||||
"upath": "2.0.1",
|
||||
"vite": "7.3.1",
|
||||
"vite-plugin-dts": "4.5.4",
|
||||
@@ -93,7 +93,7 @@
|
||||
"url": "https://github.com/TriliumNext/Trilium/issues"
|
||||
},
|
||||
"homepage": "https://triliumnotes.org",
|
||||
"packageManager": "pnpm@10.31.0",
|
||||
"packageManager": "pnpm@10.32.0",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",
|
||||
@@ -101,7 +101,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"mermaid": "11.12.3",
|
||||
"preact": "10.28.4",
|
||||
"preact": "10.29.0",
|
||||
"roughjs": "4.6.6",
|
||||
"@types/express-serve-static-core": "5.1.1",
|
||||
"flat@<5.0.1": ">=5.0.1",
|
||||
|
||||
@@ -24,22 +24,22 @@
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/coverage-istanbul": "4.0.18",
|
||||
"ckeditor5": "47.4.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.3.2",
|
||||
"lint-staged": "16.3.3",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.0.18",
|
||||
"webdriverio": "9.24.0"
|
||||
"webdriverio": "9.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ckeditor5": "47.4.0"
|
||||
|
||||
@@ -25,22 +25,22 @@
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/coverage-istanbul": "4.0.18",
|
||||
"ckeditor5": "47.4.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.3.2",
|
||||
"lint-staged": "16.3.3",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.0.18",
|
||||
"webdriverio": "9.24.0"
|
||||
"webdriverio": "9.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ckeditor5": "47.4.0"
|
||||
|
||||
@@ -27,22 +27,22 @@
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/coverage-istanbul": "4.0.18",
|
||||
"ckeditor5": "47.4.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.3.2",
|
||||
"lint-staged": "16.3.3",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.0.18",
|
||||
"webdriverio": "9.24.0"
|
||||
"webdriverio": "9.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ckeditor5": "47.4.0"
|
||||
|
||||
@@ -27,22 +27,22 @@
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/coverage-istanbul": "4.0.18",
|
||||
"ckeditor5": "47.4.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.3.2",
|
||||
"lint-staged": "16.3.3",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.0.18",
|
||||
"webdriverio": "9.24.0"
|
||||
"webdriverio": "9.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ckeditor5": "47.4.0"
|
||||
|
||||
@@ -27,22 +27,22 @@
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "54.3.3",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"@vitest/browser": "4.0.18",
|
||||
"@vitest/coverage-istanbul": "4.0.18",
|
||||
"ckeditor5": "47.4.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.3.2",
|
||||
"lint-staged": "16.3.3",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.0.18",
|
||||
"webdriverio": "9.24.0"
|
||||
"webdriverio": "9.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ckeditor5": "47.4.0"
|
||||
|
||||
@@ -11,6 +11,7 @@ export type { EditorConfig, MentionFeed, MentionFeedObjectItem, ModelNode, Model
|
||||
export type { TemplateDefinition } from "ckeditor5-premium-features";
|
||||
export { default as buildExtraCommands } from "./extra_slash_commands.js";
|
||||
export { default as getCkLocale } from "./i18n.js";
|
||||
export * from "./utils.js";
|
||||
|
||||
// Import with sideffects to ensure that type augmentations are present.
|
||||
import "@triliumnext/ckeditor5-math";
|
||||
|
||||
28
packages/ckeditor5/src/utils.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { DifferItemAttribute, Editor, ModelDocumentFragment, ModelElement, ModelNode } from "ckeditor5";
|
||||
|
||||
function hasHeadingAncestor(node: ModelElement | ModelNode | ModelDocumentFragment | null): boolean {
|
||||
let current: ModelElement | ModelNode | ModelDocumentFragment | null = node;
|
||||
while (current) {
|
||||
if (!!current && current.is('element') && (current as ModelElement).name.startsWith("heading")) return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function attributeChangeAffectsHeading(change: DifferItemAttribute, editor: Editor): boolean {
|
||||
if (change.type !== "attribute") return false;
|
||||
|
||||
// Fast checks on range boundaries
|
||||
if (hasHeadingAncestor(change.range.start.parent) || hasHeadingAncestor(change.range.end.parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Robust check across the whole changed range
|
||||
const range = editor.model.createRange(change.range.start, change.range.end);
|
||||
for (const item of range.getItems()) {
|
||||
const baseNode = item.is("$textProxy") ? item.parent : item;
|
||||
if (hasHeadingAncestor(baseNode)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"@codemirror/lang-xml": "6.1.0",
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/view": "6.39.16",
|
||||
"@codemirror/view": "6.39.17",
|
||||
"@fsegurai/codemirror-theme-abcdef": "6.2.3",
|
||||
"@fsegurai/codemirror-theme-abyss": "6.2.3",
|
||||
"@fsegurai/codemirror-theme-android-studio": "6.2.3",
|
||||
@@ -50,6 +50,6 @@
|
||||
"codemirror-lang-elixir": "4.0.1",
|
||||
"codemirror-lang-hcl": "0.1.0",
|
||||
"codemirror-lang-mermaid": "0.5.0",
|
||||
"eslint-linter-browserify": "10.0.2"
|
||||
"eslint-linter-browserify": "10.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"devDependencies": {
|
||||
"@digitak/esrun": "3.2.26",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@typescript-eslint/eslint-plugin": "8.56.1",
|
||||
"@typescript-eslint/parser": "8.56.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.0",
|
||||
"@typescript-eslint/parser": "8.57.0",
|
||||
"dotenv": "17.3.1",
|
||||
"esbuild": "0.27.3",
|
||||
"eslint": "10.0.3",
|
||||
|
||||