mirror of
https://github.com/zadam/trilium.git
synced 2025-11-13 16:55:50 +01:00
Merge branch 'main' into fix/fix-equals-operator-in-search
This commit is contained in:
2
.github/actions/build-server/action.yml
vendored
2
.github/actions/build-server/action.yml
vendored
@@ -12,7 +12,7 @@ runs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
2
.github/workflows/deploy-docs.yml
vendored
2
.github/workflows/deploy-docs.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '24'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
# Install Node.js dependencies for the TypeScript script
|
# Install Node.js dependencies for the TypeScript script
|
||||||
|
|||||||
2
.github/workflows/dev.yml
vendored
2
.github/workflows/dev.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
|||||||
12
.github/workflows/main-docker.yml
vendored
12
.github/workflows/main-docker.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install npm dependencies
|
- name: Install npm dependencies
|
||||||
@@ -86,12 +86,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Playwright trace
|
- name: Upload Playwright trace
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: Playwright trace (${{ matrix.dockerfile }})
|
name: Playwright trace (${{ matrix.dockerfile }})
|
||||||
path: test-output/playwright/output
|
path: test-output/playwright/output
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v5
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: Playwright report (${{ matrix.dockerfile }})
|
name: Playwright report (${{ matrix.dockerfile }})
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
touch "/tmp/digests/${digest#sha256:}"
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
|
||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
|
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
|
||||||
path: /tmp/digests/*
|
path: /tmp/digests/*
|
||||||
@@ -223,7 +223,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
|
|||||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
name: Nightly Build
|
name: Nightly Build
|
||||||
|
|
||||||
- name: Publish artifacts
|
- name: Publish artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||||
|
|||||||
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload test report
|
- name: Upload test report
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: e2e report
|
name: e2e report
|
||||||
path: apps/server-e2e/test-output
|
path: apps/server-e2e/test-output
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
||||||
|
|
||||||
- name: Upload the artifact
|
- name: Upload the artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
|
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
|
||||||
path: apps/desktop/upload/*.*
|
path: apps/desktop/upload/*.*
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
|
||||||
- name: Upload the artifact
|
- name: Upload the artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: release-server-linux-${{ matrix.arch }}
|
name: release-server-linux-${{ matrix.arch }}
|
||||||
path: upload/*.*
|
path: upload/*.*
|
||||||
@@ -120,7 +120,7 @@ jobs:
|
|||||||
docs/Release Notes
|
docs/Release Notes
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
pattern: release-*
|
pattern: release-*
|
||||||
|
|||||||
2
.github/workflows/website.yml
vendored
2
.github/workflows/website.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 24
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.56.1",
|
"@playwright/test": "1.56.1",
|
||||||
"@stylistic/eslint-plugin": "5.5.0",
|
"@stylistic/eslint-plugin": "5.5.0",
|
||||||
"@types/express": "5.0.3",
|
"@types/express": "5.0.5",
|
||||||
"@types/node": "22.18.12",
|
"@types/node": "24.9.1",
|
||||||
"@types/yargs": "17.0.33",
|
"@types/yargs": "17.0.34",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"eslint": "9.38.0",
|
"eslint": "9.38.0",
|
||||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/client",
|
"name": "@triliumnext/client",
|
||||||
"version": "0.99.2",
|
"version": "0.99.3",
|
||||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
@@ -54,12 +54,12 @@
|
|||||||
"leaflet-gpx": "2.2.0",
|
"leaflet-gpx": "2.2.0",
|
||||||
"mark.js": "8.11.1",
|
"mark.js": "8.11.1",
|
||||||
"marked": "16.4.1",
|
"marked": "16.4.1",
|
||||||
"mermaid": "11.12.0",
|
"mermaid": "11.12.1",
|
||||||
"mind-elixir": "5.3.3",
|
"mind-elixir": "5.3.4",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
"preact": "10.27.2",
|
"preact": "10.27.2",
|
||||||
"react-i18next": "16.1.2",
|
"react-i18next": "16.2.1",
|
||||||
"reveal.js": "5.2.1",
|
"reveal.js": "5.2.1",
|
||||||
"svg-pan-zoom": "3.6.2",
|
"svg-pan-zoom": "3.6.2",
|
||||||
"tabulator-tables": "6.3.1",
|
"tabulator-tables": "6.3.1",
|
||||||
@@ -74,9 +74,9 @@
|
|||||||
"@types/leaflet-gpx": "1.3.8",
|
"@types/leaflet-gpx": "1.3.8",
|
||||||
"@types/mark.js": "8.11.12",
|
"@types/mark.js": "8.11.12",
|
||||||
"@types/reveal.js": "5.2.1",
|
"@types/reveal.js": "5.2.1",
|
||||||
"@types/tabulator-tables": "6.2.11",
|
"@types/tabulator-tables": "6.3.0",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "13.0.1",
|
||||||
"happy-dom": "20.0.7",
|
"happy-dom": "20.0.8",
|
||||||
"script-loader": "0.7.2",
|
"script-loader": "0.7.2",
|
||||||
"vite-plugin-static-copy": "3.1.4"
|
"vite-plugin-static-copy": "3.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,12 +218,12 @@ export type CommandMappings = {
|
|||||||
/** Works only in the electron context menu. */
|
/** Works only in the electron context menu. */
|
||||||
replaceMisspelling: CommandData;
|
replaceMisspelling: CommandData;
|
||||||
|
|
||||||
importMarkdownInline: CommandData;
|
|
||||||
showPasswordNotSet: CommandData;
|
showPasswordNotSet: CommandData;
|
||||||
showProtectedSessionPasswordDialog: CommandData;
|
showProtectedSessionPasswordDialog: CommandData;
|
||||||
showUploadAttachmentsDialog: CommandData & { noteId: string };
|
showUploadAttachmentsDialog: CommandData & { noteId: string };
|
||||||
showIncludeNoteDialog: CommandData & { textTypeWidget: EditableTextTypeWidget };
|
showIncludeNoteDialog: CommandData & { textTypeWidget: EditableTextTypeWidget };
|
||||||
showAddLinkDialog: CommandData & { textTypeWidget: EditableTextTypeWidget, text: string };
|
showAddLinkDialog: CommandData & { textTypeWidget: EditableTextTypeWidget, text: string };
|
||||||
|
showPasteMarkdownDialog: CommandData & { textTypeWidget: EditableTextTypeWidget };
|
||||||
closeProtectedSessionPasswordDialog: CommandData;
|
closeProtectedSessionPasswordDialog: CommandData;
|
||||||
copyImageReferenceToClipboard: CommandData;
|
copyImageReferenceToClipboard: CommandData;
|
||||||
copyImageToClipboard: CommandData;
|
copyImageToClipboard: CommandData;
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
|
|||||||
import NoteDetailWidget from "../widgets/note_detail.js";
|
import NoteDetailWidget from "../widgets/note_detail.js";
|
||||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
import NoteTitleWidget from "../widgets/note_title.jsx";
|
||||||
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
|
import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.js";
|
||||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||||
|
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
|
||||||
|
|
||||||
export function applyModals(rootContainer: RootContainer) {
|
export function applyModals(rootContainer: RootContainer) {
|
||||||
rootContainer
|
rootContainer
|
||||||
@@ -63,7 +64,7 @@ export function applyModals(rootContainer: RootContainer) {
|
|||||||
.cssBlock(".title-row > * { margin: 5px; }")
|
.cssBlock(".title-row > * { margin: 5px; }")
|
||||||
.child(<NoteIconWidget />)
|
.child(<NoteIconWidget />)
|
||||||
.child(<NoteTitleWidget />))
|
.child(<NoteTitleWidget />))
|
||||||
.child(<PopupEditorFormattingToolbar />)
|
.child(<StandaloneRibbonAdapter component={FormattingToolbar} />)
|
||||||
.child(new PromotedAttributesWidget())
|
.child(new PromotedAttributesWidget())
|
||||||
.child(new NoteDetailWidget())
|
.child(new NoteDetailWidget())
|
||||||
.child(<NoteList media="screen" displayOnlyCollections />))
|
.child(<NoteList media="screen" displayOnlyCollections />))
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import CloseZenModeButton from "../widgets/close_zen_button.js";
|
|||||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||||
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||||
|
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
|
||||||
|
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
|
||||||
|
import SearchResult from "../widgets/search_result.jsx";
|
||||||
|
|
||||||
const MOBILE_CSS = `
|
const MOBILE_CSS = `
|
||||||
<style>
|
<style>
|
||||||
@@ -155,6 +158,8 @@ export default class MobileLayout {
|
|||||||
.contentSized()
|
.contentSized()
|
||||||
.child(new NoteDetailWidget())
|
.child(new NoteDetailWidget())
|
||||||
.child(<NoteList media="screen" />)
|
.child(<NoteList media="screen" />)
|
||||||
|
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />)
|
||||||
|
.child(<SearchResult />)
|
||||||
.child(<FilePropertiesWrapper />)
|
.child(<FilePropertiesWrapper />)
|
||||||
)
|
)
|
||||||
.child(<MobileEditorToolbar />)
|
.child(<MobileEditorToolbar />)
|
||||||
|
|||||||
@@ -56,7 +56,20 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) {
|
|||||||
await import("@triliumnext/ckeditor5/src/theme/ck-content.css");
|
await import("@triliumnext/ckeditor5/src/theme/ck-content.css");
|
||||||
}
|
}
|
||||||
const { $renderedContent } = await content_renderer.getRenderedContent(note, { noChildrenList: true });
|
const { $renderedContent } = await content_renderer.getRenderedContent(note, { noChildrenList: true });
|
||||||
containerRef.current?.replaceChildren(...$renderedContent);
|
const container = containerRef.current!;
|
||||||
|
container.replaceChildren(...$renderedContent);
|
||||||
|
|
||||||
|
// Wait for all images to load.
|
||||||
|
const images = Array.from(container.querySelectorAll("img"));
|
||||||
|
await Promise.all(
|
||||||
|
images.map(img => {
|
||||||
|
if (img.complete) return Promise.resolve();
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
img.addEventListener("load", () => resolve(), { once: true });
|
||||||
|
img.addEventListener("error", () => resolve(), { once: true });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
load().then(() => requestAnimationFrame(onReady))
|
load().then(() => requestAnimationFrame(onReady))
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ function setupGlobs() {
|
|||||||
window.glob.froca = froca;
|
window.glob.froca = froca;
|
||||||
window.glob.treeCache = froca; // compatibility for CKEditor builds for a while
|
window.glob.treeCache = froca; // compatibility for CKEditor builds for a while
|
||||||
|
|
||||||
// for CKEditor integration (button on block toolbar)
|
|
||||||
window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline");
|
|
||||||
|
|
||||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||||
const string = String(msg).toLowerCase();
|
const string = String(msg).toLowerCase();
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,6 @@ async function ensureJQuery() {
|
|||||||
(window as any).$ = $;
|
(window as any).$ = $;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyMath() {
|
|
||||||
const anyMathBlock = document.querySelector("#content .math-tex");
|
|
||||||
if (!anyMathBlock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderMathInElement = (await import("./services/math.js")).renderMathInElement;
|
|
||||||
renderMathInElement(document.getElementById("content"));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function formatCodeBlocks() {
|
async function formatCodeBlocks() {
|
||||||
const anyCodeBlock = document.querySelector("#content pre");
|
const anyCodeBlock = document.querySelector("#content pre");
|
||||||
if (!anyCodeBlock) {
|
if (!anyCodeBlock) {
|
||||||
@@ -31,54 +21,4 @@ async function formatCodeBlocks() {
|
|||||||
|
|
||||||
async function setupTextNote() {
|
async function setupTextNote() {
|
||||||
formatCodeBlocks();
|
formatCodeBlocks();
|
||||||
applyMath();
|
|
||||||
|
|
||||||
const setupMermaid = (await import("./share/mermaid.js")).default;
|
|
||||||
setupMermaid();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch note with given ID from backend
|
|
||||||
*
|
|
||||||
* @param noteId of the given note to be fetched. If false, fetches current note.
|
|
||||||
*/
|
|
||||||
async function fetchNote(noteId: string | null = null) {
|
|
||||||
if (!noteId) {
|
|
||||||
noteId = document.body.getAttribute("data-note-id");
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = await fetch(`api/notes/${noteId}`);
|
|
||||||
|
|
||||||
return await resp.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener(
|
|
||||||
"DOMContentLoaded",
|
|
||||||
() => {
|
|
||||||
const noteType = determineNoteType();
|
|
||||||
|
|
||||||
if (noteType === "text") {
|
|
||||||
setupTextNote();
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleMenuButton = document.getElementById("toggleMenuButton");
|
|
||||||
const layout = document.getElementById("layout");
|
|
||||||
|
|
||||||
if (toggleMenuButton && layout) {
|
|
||||||
toggleMenuButton.addEventListener("click", () => layout.classList.toggle("showMenu"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
function determineNoteType() {
|
|
||||||
const bodyClass = document.body.className;
|
|
||||||
const match = bodyClass.match(/type-([^\s]+)/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// workaround to prevent webpack from removing "fetchNote" as dead code:
|
|
||||||
// add fetchNote as property to the window object
|
|
||||||
Object.defineProperty(window, "fetchNote", {
|
|
||||||
value: fetchNote
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -2034,9 +2034,9 @@ body.zen #right-pane,
|
|||||||
body.zen #mobile-sidebar-wrapper,
|
body.zen #mobile-sidebar-wrapper,
|
||||||
body.zen .tab-row-container,
|
body.zen .tab-row-container,
|
||||||
body.zen .tab-row-widget,
|
body.zen .tab-row-widget,
|
||||||
body.zen .ribbon-container:not(:has(.classic-toolbar-widget.visible)),
|
body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
|
||||||
body.zen .ribbon-container:has(.classic-toolbar-widget.visible) .ribbon-top-row,
|
body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
|
||||||
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget.visible)),
|
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
|
||||||
body.zen .note-icon-widget,
|
body.zen .note-icon-widget,
|
||||||
body.zen .title-row .icon-action,
|
body.zen .title-row .icon-action,
|
||||||
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
|
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
|
||||||
@@ -2433,3 +2433,7 @@ iframe.print-iframe {
|
|||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.excalidraw.theme--dark canvas {
|
||||||
|
--theme-filter: invert(100%) hue-rotate(180deg);
|
||||||
|
}
|
||||||
@@ -86,6 +86,13 @@ body ::-webkit-calendar-picker-indicator {
|
|||||||
--custom-color: var(--dark-theme-custom-color);
|
--custom-color: var(--dark-theme-custom-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root .reference-link,
|
||||||
|
:root .reference-link:hover,
|
||||||
|
.ck-content a.reference-link > span,
|
||||||
|
.board-note {
|
||||||
|
color: var(--dark-theme-custom-color, inherit);
|
||||||
|
}
|
||||||
|
|
||||||
.excalidraw.theme--dark {
|
.excalidraw.theme--dark {
|
||||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||||
}
|
}
|
||||||
@@ -101,3 +108,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
|
|||||||
.ck-content pre {
|
.ck-content pre {
|
||||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important;
|
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,3 +85,10 @@ html {
|
|||||||
#left-pane .fancytree-node.tinted {
|
#left-pane .fancytree-node.tinted {
|
||||||
--custom-color: var(--light-theme-custom-color);
|
--custom-color: var(--light-theme-custom-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root .reference-link,
|
||||||
|
:root .reference-link:hover,
|
||||||
|
.ck-content a.reference-link > span,
|
||||||
|
.board-note {
|
||||||
|
color: var(--light-theme-custom-color, inherit);
|
||||||
|
}
|
||||||
@@ -277,6 +277,13 @@
|
|||||||
--custom-bg-color: hsl(var(--custom-color-hue), 20%, 33%, 0.4);
|
--custom-bg-color: hsl(var(--custom-color-hue), 20%, 33%, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root .reference-link,
|
||||||
|
:root .reference-link:hover,
|
||||||
|
.ck-content a.reference-link > span,
|
||||||
|
.board-note {
|
||||||
|
color: var(--dark-theme-custom-color, inherit);
|
||||||
|
}
|
||||||
|
|
||||||
body ::-webkit-calendar-picker-indicator {
|
body ::-webkit-calendar-picker-indicator {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,7 +392,8 @@ div.tn-tool-dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.delete-notes-list .note-path {
|
.delete-notes-list .note-path {
|
||||||
padding-inline-end: 8px;
|
padding-inline-start: 8px;
|
||||||
|
color: var(--muted-text-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -667,3 +667,16 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
|||||||
background: var(--accented-background-color);
|
background: var(--accented-background-color);
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reference link */
|
||||||
|
|
||||||
|
.ck-content a.reference-link,
|
||||||
|
.ck-content a.reference-link:hover {
|
||||||
|
/* Apply underline only to the span inside the link so it can follow the
|
||||||
|
* target note's user defined color */
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck-content a.reference-link > span {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
/* Button bar */
|
/* Button bar */
|
||||||
.search-definition-widget .search-setting-table tbody:last-child div {
|
.search-definition-widget .search-setting-table tbody:last-child div {
|
||||||
justify-content: flex-end !important;
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
"toast": {
|
"toast": {
|
||||||
"critical-error": {
|
"critical-error": {
|
||||||
"title": "خطأ فادح"
|
"title": "خطأ فادح"
|
||||||
|
},
|
||||||
|
"widget-error": {
|
||||||
|
"title": "فشل في البدء بعنصر الواجهة"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"add_link": {
|
"add_link": {
|
||||||
@@ -26,7 +29,8 @@
|
|||||||
"edit_branch_prefix": "تعديل بادئة الفرع",
|
"edit_branch_prefix": "تعديل بادئة الفرع",
|
||||||
"prefix": "البادئة: ",
|
"prefix": "البادئة: ",
|
||||||
"save": "حفظ",
|
"save": "حفظ",
|
||||||
"help_on_tree_prefix": "مساعدة حول بادئة الشجرة"
|
"help_on_tree_prefix": "مساعدة حول بادئة الشجرة",
|
||||||
|
"branch_prefix_saved": "تم حفظ بادئة الفرع."
|
||||||
},
|
},
|
||||||
"bulk_actions": {
|
"bulk_actions": {
|
||||||
"bulk_actions": "اجراءات جماعية",
|
"bulk_actions": "اجراءات جماعية",
|
||||||
@@ -83,7 +87,8 @@
|
|||||||
"workspace_calendar_root": "تحديد جذر التقويم لكل مساحة عمل",
|
"workspace_calendar_root": "تحديد جذر التقويم لكل مساحة عمل",
|
||||||
"hide_highlight_widget": "اخفاء عنصر واجهة قائمة التمييزات",
|
"hide_highlight_widget": "اخفاء عنصر واجهة قائمة التمييزات",
|
||||||
"is_owned_by_note": "تخص الملاحظة",
|
"is_owned_by_note": "تخص الملاحظة",
|
||||||
"and_more": "... و {{count}}مرات اكثر."
|
"and_more": "... و {{count}}مرات اكثر.",
|
||||||
|
"related_notes_title": "ملاحظات اخرى بنفس التسمية"
|
||||||
},
|
},
|
||||||
"rename_label": {
|
"rename_label": {
|
||||||
"to": "الى",
|
"to": "الى",
|
||||||
@@ -127,7 +132,9 @@
|
|||||||
"delete_attachment": "حذف المرفق",
|
"delete_attachment": "حذف المرفق",
|
||||||
"upload_new_revision": "رفع مراجعة جديدة",
|
"upload_new_revision": "رفع مراجعة جديدة",
|
||||||
"copy_link_to_clipboard": "نسخ الرابط الى الحافظة",
|
"copy_link_to_clipboard": "نسخ الرابط الى الحافظة",
|
||||||
"convert_attachment_into_note": "تحويل المرفق الى ملاحظة"
|
"convert_attachment_into_note": "تحويل المرفق الى ملاحظة",
|
||||||
|
"delete_success": "تم حذف المرفق \"{{title}}\" .",
|
||||||
|
"enter_new_name": "ادخل اسم مرفق جديد"
|
||||||
},
|
},
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"week": "أسبوع",
|
"week": "أسبوع",
|
||||||
@@ -259,7 +266,8 @@
|
|||||||
"note_paths": {
|
"note_paths": {
|
||||||
"search": "بحث",
|
"search": "بحث",
|
||||||
"archived": "مؤرشف",
|
"archived": "مؤرشف",
|
||||||
"title": "مسارات الملاحظة"
|
"title": "مسارات الملاحظة",
|
||||||
|
"clone_button": "جار نسخ الملاحظة الى مكان جديد..."
|
||||||
},
|
},
|
||||||
"script_executor": {
|
"script_executor": {
|
||||||
"query": "استعلام",
|
"query": "استعلام",
|
||||||
@@ -372,7 +380,8 @@
|
|||||||
"export_note_title": "تصدير الملاحظة",
|
"export_note_title": "تصدير الملاحظة",
|
||||||
"export_status": "حالة التصدير",
|
"export_status": "حالة التصدير",
|
||||||
"export_finished_successfully": "اكتمل التصدير بنجاح.",
|
"export_finished_successfully": "اكتمل التصدير بنجاح.",
|
||||||
"export_in_progress": "جار التصدير: {{progressCount}}"
|
"export_in_progress": "جار التصدير: {{progressCount}}",
|
||||||
|
"choose_export_type": "اختر نوع التصدير اولا من فضلك"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"troubleshooting": "أستكشاف الاخطاء واصلاحها",
|
"troubleshooting": "أستكشاف الاخطاء واصلاحها",
|
||||||
@@ -402,7 +411,10 @@
|
|||||||
"movingCloningNotes": "نقل/ استنساخ الملاحظات",
|
"movingCloningNotes": "نقل/ استنساخ الملاحظات",
|
||||||
"deleteNotes": "حذف الملاحظة/ الشجرة الفرعية",
|
"deleteNotes": "حذف الملاحظة/ الشجرة الفرعية",
|
||||||
"collapseWholeTree": "طي شجرة الملاحظة باكملها",
|
"collapseWholeTree": "طي شجرة الملاحظة باكملها",
|
||||||
"followLink": "اتبع تلرابط تحت المؤشر"
|
"followLink": "اتبع تلرابط تحت المؤشر",
|
||||||
|
"onlyInDesktop": "في سطح المكتب فقط(Electron build)",
|
||||||
|
"createEditLink": "انشاء/ تحرير رابط خارجي",
|
||||||
|
"quickSearch": "الانتقال الى مربع البحث السريع"
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"options": "خيارات",
|
"options": "خيارات",
|
||||||
@@ -465,7 +477,13 @@
|
|||||||
"delete_all_button": "حذف كل المراجعات",
|
"delete_all_button": "حذف كل المراجعات",
|
||||||
"settings": "اعدادات مراجعة الملاحظة",
|
"settings": "اعدادات مراجعة الملاحظة",
|
||||||
"diff_not_available": "المقارنة غير متوفرة.",
|
"diff_not_available": "المقارنة غير متوفرة.",
|
||||||
"help_title": "مساعدة حول مراجعات الملاحظة"
|
"help_title": "مساعدة حول مراجعات الملاحظة",
|
||||||
|
"diff_off_hint": "انقر لعرض محتويات الملاحظة",
|
||||||
|
"revisions_deleted": "تم حذف جميع نسخ المراجعات للملاحظة.",
|
||||||
|
"revision_restored": "تم استعادة نسخ المراجعة للملاحظة.",
|
||||||
|
"revision_deleted": "تم حذف مراجعة الملاحظة.",
|
||||||
|
"snapshot_interval": "فاصل زمني لحفظ لقطات اصدارات المراجعة: {{seconds}}",
|
||||||
|
"maximum_revisions": "حد عدد لقطات اصدارات الملاحظة: {{number}}"
|
||||||
},
|
},
|
||||||
"sort_child_notes": {
|
"sort_child_notes": {
|
||||||
"title": "عنوان",
|
"title": "عنوان",
|
||||||
@@ -479,13 +497,15 @@
|
|||||||
"sorting_direction": "اتجاه الترتيب",
|
"sorting_direction": "اتجاه الترتيب",
|
||||||
"natural_sort": "الترتيب الطبيعي",
|
"natural_sort": "الترتيب الطبيعي",
|
||||||
"natural_sort_language": "لغات الترتيب الطبيعي",
|
"natural_sort_language": "لغات الترتيب الطبيعي",
|
||||||
"sort_children_by": "ترتيب العناصر الفرعية حسب..."
|
"sort_children_by": "ترتيب العناصر الفرعية حسب...",
|
||||||
|
"sort_folders_at_top": "ترتيب المجلدات في الاعلى"
|
||||||
},
|
},
|
||||||
"recent_changes": {
|
"recent_changes": {
|
||||||
"undelete_link": "الغاء الحذف",
|
"undelete_link": "الغاء الحذف",
|
||||||
"title": "التغيرات الاخيرة",
|
"title": "التغيرات الاخيرة",
|
||||||
"no_changes_message": "لايوجد تغيير لحد الان...",
|
"no_changes_message": "لايوجد تغيير لحد الان...",
|
||||||
"erase_notes_button": "مسح الملاحظات المحذوفة الان"
|
"erase_notes_button": "مسح الملاحظات المحذوفة الان",
|
||||||
|
"deleted_notes_message": "تم حذف الملاحظات نهائيا."
|
||||||
},
|
},
|
||||||
"edited_notes": {
|
"edited_notes": {
|
||||||
"deleted": "(حذف)",
|
"deleted": "(حذف)",
|
||||||
@@ -655,7 +675,11 @@
|
|||||||
"google": "جوجل",
|
"google": "جوجل",
|
||||||
"save_button": "حفظ",
|
"save_button": "حفظ",
|
||||||
"baidu": "Baidu",
|
"baidu": "Baidu",
|
||||||
"title": "محرك البحث"
|
"title": "محرك البحث",
|
||||||
|
"predefined_templates_label": "قوالب محرك البحث المعرفة مسبقا",
|
||||||
|
"custom_name_label": "اسم محرك البحث المخصص",
|
||||||
|
"custom_name_placeholder": "اسم محرك البحث المخصص",
|
||||||
|
"custom_url_placeholder": "تخصيص عنوان URL لمحرك البحث"
|
||||||
},
|
},
|
||||||
"heading_style": {
|
"heading_style": {
|
||||||
"plain": "بسيط",
|
"plain": "بسيط",
|
||||||
@@ -676,7 +700,8 @@
|
|||||||
"wednesday": "الاربعاء",
|
"wednesday": "الاربعاء",
|
||||||
"thursday": "الخميس",
|
"thursday": "الخميس",
|
||||||
"friday": "الجمعة",
|
"friday": "الجمعة",
|
||||||
"saturday": "السبت"
|
"saturday": "السبت",
|
||||||
|
"formatting-locale": "تنسيق التاريخ والارقام"
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
"path": "مسار",
|
"path": "مسار",
|
||||||
@@ -699,7 +724,10 @@
|
|||||||
"token_name": "اسم الرمز",
|
"token_name": "اسم الرمز",
|
||||||
"default_token_name": "رمز جديد",
|
"default_token_name": "رمز جديد",
|
||||||
"rename_token_title": "اعادة تسمية الرمز",
|
"rename_token_title": "اعادة تسمية الرمز",
|
||||||
"rename_token": "اعادة تسمية هذا الرمز"
|
"rename_token": "اعادة تسمية هذا الرمز",
|
||||||
|
"create_token": "انشاء رمز PEAPI جديد",
|
||||||
|
"new_token_title": "رمز ETAPI جديد",
|
||||||
|
"token_created_title": "انشاء رمز ETAPI"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"heading": "كلمة المرور",
|
"heading": "كلمة المرور",
|
||||||
@@ -731,7 +759,8 @@
|
|||||||
"timeout": "انتهاء مهلة المزامنة",
|
"timeout": "انتهاء مهلة المزامنة",
|
||||||
"test_title": "اختبار المزامنة",
|
"test_title": "اختبار المزامنة",
|
||||||
"test_button": "اختبار المزامنة",
|
"test_button": "اختبار المزامنة",
|
||||||
"server_address": "عنوان نسخة الخادم"
|
"server_address": "عنوان نسخة الخادم",
|
||||||
|
"proxy_label": "خادم وكيل المزامنة (اخياري)"
|
||||||
},
|
},
|
||||||
"api_log": {
|
"api_log": {
|
||||||
"close": "أغلاق"
|
"close": "أغلاق"
|
||||||
@@ -751,7 +780,8 @@
|
|||||||
"new_tab": "تبويب جديد",
|
"new_tab": "تبويب جديد",
|
||||||
"close_all_tabs": "اغلاق كل علامات التبويب",
|
"close_all_tabs": "اغلاق كل علامات التبويب",
|
||||||
"add_new_tab": "اضافة علامة تبويب جديدة",
|
"add_new_tab": "اضافة علامة تبويب جديدة",
|
||||||
"close_other_tabs": "اغلاق علامات التبويب الاخرى"
|
"close_other_tabs": "اغلاق علامات التبويب الاخرى",
|
||||||
|
"reopen_last_tab": "اعادة فتح اخر علامة تبويب مغلقة"
|
||||||
},
|
},
|
||||||
"toc": {
|
"toc": {
|
||||||
"options": "خيارات",
|
"options": "خيارات",
|
||||||
@@ -791,7 +821,8 @@
|
|||||||
},
|
},
|
||||||
"call_to_action": {
|
"call_to_action": {
|
||||||
"dismiss": "تجاهل",
|
"dismiss": "تجاهل",
|
||||||
"background_effects_button": "تفعيل مؤثرات الخلفية"
|
"background_effects_button": "تفعيل مؤثرات الخلفية",
|
||||||
|
"next_theme_button": "جرب النسق الجديد"
|
||||||
},
|
},
|
||||||
"units": {
|
"units": {
|
||||||
"percentage": "%"
|
"percentage": "%"
|
||||||
@@ -802,7 +833,8 @@
|
|||||||
"help_on_links": "مساعدة حول الارتباطات التشعبية",
|
"help_on_links": "مساعدة حول الارتباطات التشعبية",
|
||||||
"notes_to_clone": "ملاحظات للنسخ",
|
"notes_to_clone": "ملاحظات للنسخ",
|
||||||
"target_parent_note": "الملاحظة الاصلية الهدف",
|
"target_parent_note": "الملاحظة الاصلية الهدف",
|
||||||
"clone_to_selected_note": "استنساخ الى الملاحظة المحددة"
|
"clone_to_selected_note": "استنساخ الى الملاحظة المحددة",
|
||||||
|
"no_path_to_clone_to": "لايوجد مسار لنسخ المحتوى الية."
|
||||||
},
|
},
|
||||||
"table_of_contents": {
|
"table_of_contents": {
|
||||||
"unit": "عناوين",
|
"unit": "عناوين",
|
||||||
@@ -835,7 +867,8 @@
|
|||||||
"search-in-subtree": "البحث في الشجرة الفرعية",
|
"search-in-subtree": "البحث في الشجرة الفرعية",
|
||||||
"edit-branch-prefix": "تعديل بادئة الفرع",
|
"edit-branch-prefix": "تعديل بادئة الفرع",
|
||||||
"convert-to-attachment": "التحويل الى مرفق",
|
"convert-to-attachment": "التحويل الى مرفق",
|
||||||
"apply-bulk-actions": "تطبيق الاجراءات الجماعية"
|
"apply-bulk-actions": "تطبيق الاجراءات الجماعية",
|
||||||
|
"recent-changes-in-subtree": "التغييرات الاخيرة في الشجرة الفرعية"
|
||||||
},
|
},
|
||||||
"note_types": {
|
"note_types": {
|
||||||
"text": "نص",
|
"text": "نص",
|
||||||
@@ -884,7 +917,8 @@
|
|||||||
"quick-search": {
|
"quick-search": {
|
||||||
"searching": "جار البحث...",
|
"searching": "جار البحث...",
|
||||||
"placeholder": "البحث السريع",
|
"placeholder": "البحث السريع",
|
||||||
"no-results": "لم يتم العثور على نتائج"
|
"no-results": "لم يتم العثور على نتائج",
|
||||||
|
"show-in-full-search": "عرض في البحث الكامل"
|
||||||
},
|
},
|
||||||
"note_tree": {
|
"note_tree": {
|
||||||
"unhoist": "ارجاع الى الترتيب الطبيعي",
|
"unhoist": "ارجاع الى الترتيب الطبيعي",
|
||||||
@@ -893,7 +927,12 @@
|
|||||||
"collapse-title": "طي شجرة الملاحظة",
|
"collapse-title": "طي شجرة الملاحظة",
|
||||||
"hide-archived-notes": "اخفاء الملاحظات المؤرشفة",
|
"hide-archived-notes": "اخفاء الملاحظات المؤرشفة",
|
||||||
"automatically-collapse-notes": "طي الملاحظات تلقائيا",
|
"automatically-collapse-notes": "طي الملاحظات تلقائيا",
|
||||||
"create-child-note": "انشاء ملاحظة فرعية"
|
"create-child-note": "انشاء ملاحظة فرعية",
|
||||||
|
"scroll-active-title": "تمرير الى الملاحظة النشطة",
|
||||||
|
"save-changes": "حفظ وتطبيق التغييرات",
|
||||||
|
"saved-search-note-refreshed": "تم تحديث ملاحظة البحث المحفوظة.",
|
||||||
|
"hoist-this-note-workspace": "تثبيت هذه الملاحظة (مساحة العمل)",
|
||||||
|
"refresh-saved-search-results": "تحديث نتائج البحث المحفوظة"
|
||||||
},
|
},
|
||||||
"sql_table_schemas": {
|
"sql_table_schemas": {
|
||||||
"tables": "جداول"
|
"tables": "جداول"
|
||||||
@@ -901,7 +940,13 @@
|
|||||||
"launcher_context_menu": {
|
"launcher_context_menu": {
|
||||||
"reset": "اعادة ضبط",
|
"reset": "اعادة ضبط",
|
||||||
"add-spacer": "اضافة فاصل",
|
"add-spacer": "اضافة فاصل",
|
||||||
"delete": "حذف\n<kbd data-command=\"deleteNotes\">"
|
"delete": "حذف\n<kbd data-command=\"deleteNotes\">",
|
||||||
|
"add-note-launcher": "اضافة مشغل الملاحظة",
|
||||||
|
"add-script-launcher": "اضافة مشغل السكريبت",
|
||||||
|
"add-custom-widget": "اضافة عنصر واجهة مخصص",
|
||||||
|
"move-to-visible-launchers": "نقل الى المشغلات المرئية",
|
||||||
|
"move-to-available-launchers": "نقل الى المشغلات المتوفرة",
|
||||||
|
"duplicate-launcher": "تكرار المشغل <kbd data-command=\"duplicateSubtree\">"
|
||||||
},
|
},
|
||||||
"editable-text": {
|
"editable-text": {
|
||||||
"auto-detect-language": "تم اكتشافه تلقائيا"
|
"auto-detect-language": "تم اكتشافه تلقائيا"
|
||||||
@@ -927,7 +972,9 @@
|
|||||||
"cut": "قص",
|
"cut": "قص",
|
||||||
"copy": "نسخ",
|
"copy": "نسخ",
|
||||||
"paste": "لصق",
|
"paste": "لصق",
|
||||||
"copy-link": "نسخ الرابط"
|
"copy-link": "نسخ الرابط",
|
||||||
|
"add-term-to-dictionary": "اضافة \"{{term}}\" الى القاموس",
|
||||||
|
"paste-as-plain-text": "لصق كنص عادي"
|
||||||
},
|
},
|
||||||
"promoted_attributes": {
|
"promoted_attributes": {
|
||||||
"url_placeholder": "http://website...",
|
"url_placeholder": "http://website...",
|
||||||
@@ -977,7 +1024,11 @@
|
|||||||
"totp_secret_regenerate": "اعادة توليد TOTP السري",
|
"totp_secret_regenerate": "اعادة توليد TOTP السري",
|
||||||
"totp_secret_generated": "تم انشاء TOTP السري",
|
"totp_secret_generated": "تم انشاء TOTP السري",
|
||||||
"oauth_missing_vars": "اعدادات مفقودة: {{-variables}}",
|
"oauth_missing_vars": "اعدادات مفقودة: {{-variables}}",
|
||||||
"totp_secret_title": "توليد TOTP سري"
|
"totp_secret_title": "توليد TOTP سري",
|
||||||
|
"totp_title": "كلمة مرور لمرة واحدة معتمدة على الوقت (TOTP)",
|
||||||
|
"recovery_keys_title": "مفاتيح استرداد تسجيل الدخول الاحادي",
|
||||||
|
"recovery_keys_error": "حدث خطأ اثناء توليد رموز الاسترجاع",
|
||||||
|
"recovery_keys_no_key_set": "لاتوجد رموز استرجاع معينة"
|
||||||
},
|
},
|
||||||
"execute_script": {
|
"execute_script": {
|
||||||
"execute_script": "تنفيذ السكريبت"
|
"execute_script": "تنفيذ السكريبت"
|
||||||
@@ -1001,7 +1052,8 @@
|
|||||||
},
|
},
|
||||||
"delete_note": {
|
"delete_note": {
|
||||||
"delete_note": "حذف الملاحظة",
|
"delete_note": "حذف الملاحظة",
|
||||||
"delete_matched_notes": "حف الملاحظات المطابقة"
|
"delete_matched_notes": "حف الملاحظات المطابقة",
|
||||||
|
"delete_matched_notes_description": "سوف يؤدي هذا الى حذف الملاحظات المطابقة."
|
||||||
},
|
},
|
||||||
"rename_note": {
|
"rename_note": {
|
||||||
"rename_note": "اعادة تسمية الملاحظة",
|
"rename_note": "اعادة تسمية الملاحظة",
|
||||||
@@ -1119,7 +1171,12 @@
|
|||||||
"title": "اخفاء هوية البيانات",
|
"title": "اخفاء هوية البيانات",
|
||||||
"full_anonymization": "الاخفاء الكامل للهوية",
|
"full_anonymization": "الاخفاء الكامل للهوية",
|
||||||
"light_anonymization": "الاخفاء الجزئي للهوية",
|
"light_anonymization": "الاخفاء الجزئي للهوية",
|
||||||
"existing_anonymized_databases": "قواعد البيانات المجهولة الحالية"
|
"existing_anonymized_databases": "قواعد البيانات المجهولة الحالية",
|
||||||
|
"save_fully_anonymized_database": "حفظ قاعدة البيانات بعد اخفاء كل الهويات",
|
||||||
|
"save_lightly_anonymized_database": "حفظ قاعدةةبيانات مخفية جزئيا",
|
||||||
|
"creating_fully_anonymized_database": "انشاء قاعدة بيانات مجهولة بالكامل",
|
||||||
|
"creating_lightly_anonymized_database": "انشاء قاعدةة بيانات مجهولة جزئيا...",
|
||||||
|
"no_anonymized_database_yet": "لاتوجد قاعدة بيانات مجهولة بعد."
|
||||||
},
|
},
|
||||||
"vacuum_database": {
|
"vacuum_database": {
|
||||||
"title": "تحرير مساحة قاعدة البيانات",
|
"title": "تحرير مساحة قاعدة البيانات",
|
||||||
@@ -1146,7 +1203,8 @@
|
|||||||
"italic": "نص مائل",
|
"italic": "نص مائل",
|
||||||
"underline": "خط تحت النص",
|
"underline": "خط تحت النص",
|
||||||
"color": "نص ملون",
|
"color": "نص ملون",
|
||||||
"visibility_title": "اظهار قائمة التضليلات"
|
"visibility_title": "اظهار قائمة التضليلات",
|
||||||
|
"bg_color": "نص مع لون خلفية"
|
||||||
},
|
},
|
||||||
"revisions_button": {
|
"revisions_button": {
|
||||||
"note_revisions": "مراجعات الملاحظة"
|
"note_revisions": "مراجعات الملاحظة"
|
||||||
@@ -1163,7 +1221,8 @@
|
|||||||
"title": "التدقيق الاملائي",
|
"title": "التدقيق الاملائي",
|
||||||
"enable": "تفعيل التدقيق الاملائي",
|
"enable": "تفعيل التدقيق الاملائي",
|
||||||
"language_code_label": "رمز اللغة او رموز اللغات",
|
"language_code_label": "رمز اللغة او رموز اللغات",
|
||||||
"available_language_codes_label": "رموز اللغات المتاحة:"
|
"available_language_codes_label": "رموز اللغات المتاحة:",
|
||||||
|
"language_code_placeholder": "على سبيل المثال \"en-US\", \"de-AI\""
|
||||||
},
|
},
|
||||||
"note-map": {
|
"note-map": {
|
||||||
"button-link-map": "خريطة الروابط",
|
"button-link-map": "خريطة الروابط",
|
||||||
@@ -1177,7 +1236,9 @@
|
|||||||
},
|
},
|
||||||
"branches": {
|
"branches": {
|
||||||
"delete-status": "حالة الحذف",
|
"delete-status": "حالة الحذف",
|
||||||
"delete-finished-successfully": "تم الحذف بنجاح."
|
"delete-finished-successfully": "تم الحذف بنجاح.",
|
||||||
|
"cannot-move-notes-here": "لايمكن نقل الملاحظات الى هنا.",
|
||||||
|
"undeleting-notes-finished-successfully": "تم استرجاع الملاحظات بنجاح."
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "كتل الكود",
|
"title": "كتل الكود",
|
||||||
@@ -1199,14 +1260,16 @@
|
|||||||
"native-title-bar": "شريط العنوان الاصلي"
|
"native-title-bar": "شريط العنوان الاصلي"
|
||||||
},
|
},
|
||||||
"note_tooltip": {
|
"note_tooltip": {
|
||||||
"quick-edit": "التحرير السريع"
|
"quick-edit": "التحرير السريع",
|
||||||
|
"note-has-been-deleted": "تم حذف الملاحظة."
|
||||||
},
|
},
|
||||||
"geo-map-context": {
|
"geo-map-context": {
|
||||||
"open-location": "فتح الموقع",
|
"open-location": "فتح الموقع",
|
||||||
"remove-from-map": "ازالة من الخريطة"
|
"remove-from-map": "ازالة من الخريطة"
|
||||||
},
|
},
|
||||||
"share": {
|
"share": {
|
||||||
"title": "اعدادات المشاركة"
|
"title": "اعدادات المشاركة",
|
||||||
|
"check_share_root": "التحقق من حالة جذر المشاركة"
|
||||||
},
|
},
|
||||||
"note_language": {
|
"note_language": {
|
||||||
"not_set": "غير محدد",
|
"not_set": "غير محدد",
|
||||||
@@ -1251,7 +1314,8 @@
|
|||||||
"search_subtree_title": "بحث في الشجرة الفرعية",
|
"search_subtree_title": "بحث في الشجرة الفرعية",
|
||||||
"search_history_title": "عرص سجل البحث",
|
"search_history_title": "عرص سجل البحث",
|
||||||
"search_history_description": "عرض البحث السابق",
|
"search_history_description": "عرض البحث السابق",
|
||||||
"configure_launch_bar_title": "تكوين شريط الاطلاق"
|
"configure_launch_bar_title": "تكوين شريط الاطلاق",
|
||||||
|
"search_subtree_description": "البحث ضمن الشجرة الفرعية الحالية"
|
||||||
},
|
},
|
||||||
"content_renderer": {
|
"content_renderer": {
|
||||||
"open_externally": "فتح خارجيا"
|
"open_externally": "فتح خارجيا"
|
||||||
@@ -1272,7 +1336,8 @@
|
|||||||
"notes_to_move": "الملاحظات المراد نقلها",
|
"notes_to_move": "الملاحظات المراد نقلها",
|
||||||
"target_parent_note": "ملاحظة الاصل الهدف",
|
"target_parent_note": "ملاحظة الاصل الهدف",
|
||||||
"dialog_title": "انقل الملاحظات الى...",
|
"dialog_title": "انقل الملاحظات الى...",
|
||||||
"move_button": "نقل الىالملاحظة المحددة"
|
"move_button": "نقل الىالملاحظة المحددة",
|
||||||
|
"error_no_path": "لايوجد مسار لنقل العنصر الية."
|
||||||
},
|
},
|
||||||
"delete_revisions": {
|
"delete_revisions": {
|
||||||
"delete_note_revisions": "حذف مراجعات الملاحظة"
|
"delete_note_revisions": "حذف مراجعات الملاحظة"
|
||||||
@@ -1295,7 +1360,8 @@
|
|||||||
"database_integrity_check": {
|
"database_integrity_check": {
|
||||||
"title": "فحص سلامة قاعدة البيانات",
|
"title": "فحص سلامة قاعدة البيانات",
|
||||||
"check_button": "التحقق من سلامة قاعدة البيانات",
|
"check_button": "التحقق من سلامة قاعدة البيانات",
|
||||||
"checking_integrity": "جار التحقق من سلامة قاعدة البيانات..."
|
"checking_integrity": "جار التحقق من سلامة قاعدة البيانات...",
|
||||||
|
"integrity_check_failed": "فشل التحقق من السلامة: {{results}}"
|
||||||
},
|
},
|
||||||
"watched_file_update_status": {
|
"watched_file_update_status": {
|
||||||
"upload_modified_file": "رفع الملف المعدل",
|
"upload_modified_file": "رفع الملف المعدل",
|
||||||
@@ -1322,13 +1388,15 @@
|
|||||||
"save_attributes": "حفظ السمات <enter>",
|
"save_attributes": "حفظ السمات <enter>",
|
||||||
"add_a_new_attribute": "اضافة سمة جديدة",
|
"add_a_new_attribute": "اضافة سمة جديدة",
|
||||||
"add_new_label_definition": "اضافة تعريف لتسمية جديدة",
|
"add_new_label_definition": "اضافة تعريف لتسمية جديدة",
|
||||||
"add_new_relation_definition": "اضافة تعريف لعلاقة جديدة"
|
"add_new_relation_definition": "اضافة تعريف لعلاقة جديدة",
|
||||||
|
"add_new_relation": "اضافة علاقة جديدة <kbd data-command=\"addNewRelation\">"
|
||||||
},
|
},
|
||||||
"zen_mode": {
|
"zen_mode": {
|
||||||
"button_exit": "الخروج من وضع Zen"
|
"button_exit": "الخروج من وضع Zen"
|
||||||
},
|
},
|
||||||
"attachment_erasure_timeout": {
|
"attachment_erasure_timeout": {
|
||||||
"attachment_erasure_timeout": "مهلة مسح المرفقات"
|
"attachment_erasure_timeout": "مهلة مسح المرفقات",
|
||||||
|
"erase_attachments_after": "حذف المرفقات الغير مستخدمة بعد:"
|
||||||
},
|
},
|
||||||
"note_erasure_timeout": {
|
"note_erasure_timeout": {
|
||||||
"note_erasure_timeout_title": "مهلة مسح الملاحظة",
|
"note_erasure_timeout_title": "مهلة مسح الملاحظة",
|
||||||
@@ -1366,5 +1434,34 @@
|
|||||||
},
|
},
|
||||||
"revisions_snapshot_interval": {
|
"revisions_snapshot_interval": {
|
||||||
"note_revisions_snapshot_interval_title": "الفاصل الزمني لنسخ الملاحظات الاحتياطية"
|
"note_revisions_snapshot_interval_title": "الفاصل الزمني لنسخ الملاحظات الاحتياطية"
|
||||||
|
},
|
||||||
|
"note_detail": {
|
||||||
|
"printing": "جار الطباعة ..."
|
||||||
|
},
|
||||||
|
"attachment_detail_2": {
|
||||||
|
"role_and_size": "الدور: {{role}}، الحجم: {{size}}",
|
||||||
|
"unrecognized_role": "دور المرفق '{{role}}'الغير معروف."
|
||||||
|
},
|
||||||
|
"title_bar_buttons": {
|
||||||
|
"window-on-top": "ابقاء النافذة في الاعلى"
|
||||||
|
},
|
||||||
|
"note_title": {
|
||||||
|
"placeholder": "اكتب عنوان الملاحظة هنا..."
|
||||||
|
},
|
||||||
|
"image_context_menu": {
|
||||||
|
"copy_reference_to_clipboard": "نسخ المرجع الى الحافظة",
|
||||||
|
"copy_image_to_clipboard": "نسخ الصورة الى الحافظة"
|
||||||
|
},
|
||||||
|
"geo-map": {
|
||||||
|
"unable-to-load-map": "تعذر تحميل الخريطة."
|
||||||
|
},
|
||||||
|
"content_widget": {
|
||||||
|
"unknown_widget": "عنصر واجهة غير معروف للمعرف \"{{id}}\"."
|
||||||
|
},
|
||||||
|
"png_export_button": {
|
||||||
|
"button_title": "تصدير المخطط كملف PNG"
|
||||||
|
},
|
||||||
|
"protected_session_status": {
|
||||||
|
"inactive": "انقر للدخول الى جلسة محمية"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"homepage": "Startseite:",
|
"homepage": "Startseite:",
|
||||||
"app_version": "App-Version:",
|
"app_version": "App-Version:",
|
||||||
"db_version": "DB-Version:",
|
"db_version": "DB-Version:",
|
||||||
"sync_version": "Synch-version:",
|
"sync_version": "Sync-Version:",
|
||||||
"build_date": "Build-Datum:",
|
"build_date": "Build-Datum:",
|
||||||
"build_revision": "Build-Revision:",
|
"build_revision": "Build-Revision:",
|
||||||
"data_directory": "Datenverzeichnis:"
|
"data_directory": "Datenverzeichnis:"
|
||||||
@@ -184,7 +184,8 @@
|
|||||||
},
|
},
|
||||||
"import-status": "Importstatus",
|
"import-status": "Importstatus",
|
||||||
"in-progress": "Import läuft: {{progress}}",
|
"in-progress": "Import läuft: {{progress}}",
|
||||||
"successful": "Import erfolgreich abgeschlossen."
|
"successful": "Import erfolgreich abgeschlossen.",
|
||||||
|
"importZipRecommendation": "Beim Import einer ZIP-Datei wird die Notizhierarchie aus der Ordnerstruktur im Archiv übernommen."
|
||||||
},
|
},
|
||||||
"include_note": {
|
"include_note": {
|
||||||
"dialog_title": "Notiz beifügen",
|
"dialog_title": "Notiz beifügen",
|
||||||
@@ -647,7 +648,8 @@
|
|||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"show-cheatsheet": "Cheatsheet anzeigen",
|
"show-cheatsheet": "Cheatsheet anzeigen",
|
||||||
"toggle-zen-mode": "Zen Modus",
|
"toggle-zen-mode": "Zen Modus",
|
||||||
"new-version-available": "Neues Update verfügbar"
|
"new-version-available": "Neues Update verfügbar",
|
||||||
|
"download-update": "Version {{latestVersion}} herunterladen"
|
||||||
},
|
},
|
||||||
"sync_status": {
|
"sync_status": {
|
||||||
"unknown": "<p>Der Synchronisations-Status wird bekannt, sobald der nächste Synchronisierungsversuch gestartet wird.</p><p>Klicke, um eine Synchronisierung jetzt auszulösen.</p>",
|
"unknown": "<p>Der Synchronisations-Status wird bekannt, sobald der nächste Synchronisierungsversuch gestartet wird.</p><p>Klicke, um eine Synchronisierung jetzt auszulösen.</p>",
|
||||||
@@ -989,7 +991,7 @@
|
|||||||
"enter_password_instruction": "Um die geschützte Notiz anzuzeigen, musst du dein Passwort eingeben:",
|
"enter_password_instruction": "Um die geschützte Notiz anzuzeigen, musst du dein Passwort eingeben:",
|
||||||
"start_session_button": "Starte eine geschützte Sitzung <kbd>Eingabetaste</kbd>",
|
"start_session_button": "Starte eine geschützte Sitzung <kbd>Eingabetaste</kbd>",
|
||||||
"started": "Geschützte Sitzung gestartet.",
|
"started": "Geschützte Sitzung gestartet.",
|
||||||
"wrong_password": "Passwort flasch.",
|
"wrong_password": "Passwort falsch.",
|
||||||
"protecting-finished-successfully": "Geschützt erfolgreich beendet.",
|
"protecting-finished-successfully": "Geschützt erfolgreich beendet.",
|
||||||
"unprotecting-finished-successfully": "Ungeschützt erfolgreich beendet.",
|
"unprotecting-finished-successfully": "Ungeschützt erfolgreich beendet.",
|
||||||
"protecting-in-progress": "Schützen läuft: {{count}}",
|
"protecting-in-progress": "Schützen läuft: {{count}}",
|
||||||
@@ -1521,7 +1523,9 @@
|
|||||||
"window-on-top": "Dieses Fenster immer oben halten"
|
"window-on-top": "Dieses Fenster immer oben halten"
|
||||||
},
|
},
|
||||||
"note_detail": {
|
"note_detail": {
|
||||||
"could_not_find_typewidget": "Konnte typeWidget für Typ ‚{{type}}‘ nicht finden"
|
"could_not_find_typewidget": "Konnte typeWidget für Typ ‚{{type}}‘ nicht finden",
|
||||||
|
"printing": "Druckvorgang läuft…",
|
||||||
|
"printing_pdf": "PDF-Export läuft…"
|
||||||
},
|
},
|
||||||
"note_title": {
|
"note_title": {
|
||||||
"placeholder": "Titel der Notiz hier eingeben…"
|
"placeholder": "Titel der Notiz hier eingeben…"
|
||||||
@@ -1654,7 +1658,7 @@
|
|||||||
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
|
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
|
||||||
"cut": "Ausschneiden",
|
"cut": "Ausschneiden",
|
||||||
"copy": "Kopieren",
|
"copy": "Kopieren",
|
||||||
"copy-link": "Link opieren",
|
"copy-link": "Link kopieren",
|
||||||
"paste": "Einfügen",
|
"paste": "Einfügen",
|
||||||
"paste-as-plain-text": "Als unformatierten Text einfügen",
|
"paste-as-plain-text": "Als unformatierten Text einfügen",
|
||||||
"search_online": "Suche nach \"{{term}}\" mit {{searchEngine}} starten"
|
"search_online": "Suche nach \"{{term}}\" mit {{searchEngine}} starten"
|
||||||
@@ -2079,6 +2083,7 @@
|
|||||||
},
|
},
|
||||||
"presentation_view": {
|
"presentation_view": {
|
||||||
"edit-slide": "Folie bearbeiten",
|
"edit-slide": "Folie bearbeiten",
|
||||||
"start-presentation": "Präsentation starten"
|
"start-presentation": "Präsentation starten",
|
||||||
|
"slide-overview": "Übersicht der Folien ein-/ausblenden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,8 @@
|
|||||||
"export_status": "Export status",
|
"export_status": "Export status",
|
||||||
"export_in_progress": "Export in progress: {{progressCount}}",
|
"export_in_progress": "Export in progress: {{progressCount}}",
|
||||||
"export_finished_successfully": "Export finished successfully.",
|
"export_finished_successfully": "Export finished successfully.",
|
||||||
"format_pdf": "PDF - for printing or sharing purposes."
|
"format_pdf": "PDF - for printing or sharing purposes.",
|
||||||
|
"share-format": "HTML for web publishing - uses the same theme that is used shared notes, but can be published as a static website."
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"title": "Cheatsheet",
|
"title": "Cheatsheet",
|
||||||
|
|||||||
@@ -184,7 +184,8 @@
|
|||||||
},
|
},
|
||||||
"import-status": "Statut de l'importation",
|
"import-status": "Statut de l'importation",
|
||||||
"in-progress": "Importation en cours : {{progress}}",
|
"in-progress": "Importation en cours : {{progress}}",
|
||||||
"successful": "Importation terminée avec succès."
|
"successful": "Importation terminée avec succès.",
|
||||||
|
"importZipRecommendation": "Lors de l'importation d'un fichier ZIP, la hiérarchie des notes reflétera la structure des sous-répertoires au sein de l'archive."
|
||||||
},
|
},
|
||||||
"include_note": {
|
"include_note": {
|
||||||
"dialog_title": "Inclure une note",
|
"dialog_title": "Inclure une note",
|
||||||
@@ -279,8 +280,8 @@
|
|||||||
"delete_button": "Supprimer",
|
"delete_button": "Supprimer",
|
||||||
"diff_on": "Afficher les différences",
|
"diff_on": "Afficher les différences",
|
||||||
"diff_off": "Afficher le contenu",
|
"diff_off": "Afficher le contenu",
|
||||||
"diff_on_hint": "Cliquez pour afficher les différences de la note d'origine",
|
"diff_on_hint": "Cliquer pour afficher les différences avec la note d'origine",
|
||||||
"diff_off_hint": "Cliquez pour afficher le contenu de la note",
|
"diff_off_hint": "Cliquer pour afficher le contenu de la note",
|
||||||
"diff_not_available": "La comparaison n'est pas disponible."
|
"diff_not_available": "La comparaison n'est pas disponible."
|
||||||
},
|
},
|
||||||
"sort_child_notes": {
|
"sort_child_notes": {
|
||||||
@@ -646,7 +647,9 @@
|
|||||||
"about": "À propos de Trilium Notes",
|
"about": "À propos de Trilium Notes",
|
||||||
"logout": "Déconnexion",
|
"logout": "Déconnexion",
|
||||||
"show-cheatsheet": "Afficher l'aide rapide",
|
"show-cheatsheet": "Afficher l'aide rapide",
|
||||||
"toggle-zen-mode": "Zen Mode"
|
"toggle-zen-mode": "Zen Mode",
|
||||||
|
"new-version-available": "Nouvelle mise à jour disponible",
|
||||||
|
"download-update": "Obtenir la version {{latestVersion}}"
|
||||||
},
|
},
|
||||||
"zen_mode": {
|
"zen_mode": {
|
||||||
"button_exit": "Sortir du Zen mode"
|
"button_exit": "Sortir du Zen mode"
|
||||||
@@ -673,7 +676,7 @@
|
|||||||
"search_in_note": "Rechercher dans la note",
|
"search_in_note": "Rechercher dans la note",
|
||||||
"note_source": "Code source",
|
"note_source": "Code source",
|
||||||
"note_attachments": "Pièces jointes",
|
"note_attachments": "Pièces jointes",
|
||||||
"open_note_externally": "Ouverture externe",
|
"open_note_externally": "Ouvrir la note en externe",
|
||||||
"open_note_externally_title": "Le fichier sera ouvert dans une application externe et les modifications apportées seront surveillées. Vous pourrez ensuite téléverser la version modifiée dans Trilium.",
|
"open_note_externally_title": "Le fichier sera ouvert dans une application externe et les modifications apportées seront surveillées. Vous pourrez ensuite téléverser la version modifiée dans Trilium.",
|
||||||
"open_note_custom": "Ouvrir la note avec",
|
"open_note_custom": "Ouvrir la note avec",
|
||||||
"import_files": "Importer des fichiers",
|
"import_files": "Importer des fichiers",
|
||||||
@@ -766,7 +769,8 @@
|
|||||||
"table": "Tableau",
|
"table": "Tableau",
|
||||||
"geo-map": "Carte géographique",
|
"geo-map": "Carte géographique",
|
||||||
"board": "Tableau de bord",
|
"board": "Tableau de bord",
|
||||||
"include_archived_notes": "Afficher les notes archivées"
|
"include_archived_notes": "Afficher les notes archivées",
|
||||||
|
"presentation": "Présentation"
|
||||||
},
|
},
|
||||||
"edited_notes": {
|
"edited_notes": {
|
||||||
"no_edited_notes_found": "Aucune note modifiée ce jour-là...",
|
"no_edited_notes_found": "Aucune note modifiée ce jour-là...",
|
||||||
@@ -1141,7 +1145,8 @@
|
|||||||
"code_auto_read_only_size": {
|
"code_auto_read_only_size": {
|
||||||
"title": "Taille pour la lecture seule automatique",
|
"title": "Taille pour la lecture seule automatique",
|
||||||
"description": "La taille pour la lecture seule automatique est le seuil au-delà de laquelle les notes seront affichées en mode lecture seule (pour optimiser les performances).",
|
"description": "La taille pour la lecture seule automatique est le seuil au-delà de laquelle les notes seront affichées en mode lecture seule (pour optimiser les performances).",
|
||||||
"label": "Taille pour la lecture seule automatique (notes de code)"
|
"label": "Taille pour la lecture seule automatique (notes de code)",
|
||||||
|
"unit": "caractères"
|
||||||
},
|
},
|
||||||
"code_mime_types": {
|
"code_mime_types": {
|
||||||
"title": "Types MIME disponibles dans la liste déroulante"
|
"title": "Types MIME disponibles dans la liste déroulante"
|
||||||
@@ -1160,7 +1165,8 @@
|
|||||||
"download_images_description": "Le HTML collé peut contenir des références à des images en ligne, Trilium trouvera ces références et téléchargera les images afin qu'elles soient disponibles hors ligne.",
|
"download_images_description": "Le HTML collé peut contenir des références à des images en ligne, Trilium trouvera ces références et téléchargera les images afin qu'elles soient disponibles hors ligne.",
|
||||||
"enable_image_compression": "Activer la compression des images",
|
"enable_image_compression": "Activer la compression des images",
|
||||||
"max_image_dimensions": "Largeur/hauteur maximale d'une image en pixels (l'image sera redimensionnée si elle dépasse ce paramètre).",
|
"max_image_dimensions": "Largeur/hauteur maximale d'une image en pixels (l'image sera redimensionnée si elle dépasse ce paramètre).",
|
||||||
"jpeg_quality_description": "Qualité JPEG (10 - pire qualité, 100 - meilleure qualité, 50 - 85 est recommandé)"
|
"jpeg_quality_description": "Qualité JPEG (10 - pire qualité, 100 - meilleure qualité, 50 - 85 est recommandé)",
|
||||||
|
"max_image_dimensions_unit": "pixels"
|
||||||
},
|
},
|
||||||
"attachment_erasure_timeout": {
|
"attachment_erasure_timeout": {
|
||||||
"attachment_erasure_timeout": "Délai d'effacement des pièces jointes",
|
"attachment_erasure_timeout": "Délai d'effacement des pièces jointes",
|
||||||
@@ -1192,7 +1198,8 @@
|
|||||||
"note_revisions_snapshot_limit_description": "La limite du nombre de versions de note désigne le nombre maximum de versions pouvant être enregistrées pour chaque note. -1 signifie aucune limite, 0 signifie supprimer toutes les versions. Vous pouvez définir le nombre maximal de versions pour une seule note avec le label #versioningLimit.",
|
"note_revisions_snapshot_limit_description": "La limite du nombre de versions de note désigne le nombre maximum de versions pouvant être enregistrées pour chaque note. -1 signifie aucune limite, 0 signifie supprimer toutes les versions. Vous pouvez définir le nombre maximal de versions pour une seule note avec le label #versioningLimit.",
|
||||||
"snapshot_number_limit_label": "Nombre limite de versions de note :",
|
"snapshot_number_limit_label": "Nombre limite de versions de note :",
|
||||||
"erase_excess_revision_snapshots": "Effacer maintenant les versions en excès",
|
"erase_excess_revision_snapshots": "Effacer maintenant les versions en excès",
|
||||||
"erase_excess_revision_snapshots_prompt": "Les versions en excès ont été effacées."
|
"erase_excess_revision_snapshots_prompt": "Les versions en excès ont été effacées.",
|
||||||
|
"snapshot_number_limit_unit": "instantanés"
|
||||||
},
|
},
|
||||||
"search_engine": {
|
"search_engine": {
|
||||||
"title": "Moteur de recherche",
|
"title": "Moteur de recherche",
|
||||||
@@ -1234,19 +1241,35 @@
|
|||||||
"title": "Table des matières",
|
"title": "Table des matières",
|
||||||
"description": "La table des matières apparaîtra dans les notes textuelles lorsque la note comporte plus d'un nombre défini de titres. Vous pouvez personnaliser ce nombre :",
|
"description": "La table des matières apparaîtra dans les notes textuelles lorsque la note comporte plus d'un nombre défini de titres. Vous pouvez personnaliser ce nombre :",
|
||||||
"disable_info": "Vous pouvez également utiliser cette option pour désactiver la table des matières en définissant un nombre très élevé.",
|
"disable_info": "Vous pouvez également utiliser cette option pour désactiver la table des matières en définissant un nombre très élevé.",
|
||||||
"shortcut_info": "Vous pouvez configurer un raccourci clavier pour afficher/masquer le volet de droite (y compris la table des matières) dans Options -> Raccourcis (nom « toggleRightPane »)."
|
"shortcut_info": "Vous pouvez configurer un raccourci clavier pour afficher/masquer le volet de droite (y compris la table des matières) dans Options -> Raccourcis (nom « toggleRightPane »).",
|
||||||
|
"unit": "titres"
|
||||||
},
|
},
|
||||||
"text_auto_read_only_size": {
|
"text_auto_read_only_size": {
|
||||||
"title": "Taille automatique en lecture seule",
|
"title": "Taille automatique en lecture seule",
|
||||||
"description": "La taille automatique des notes en lecture seule est la taille au-delà de laquelle les notes seront affichées en mode lecture seule (pour des raisons de performances).",
|
"description": "La taille automatique des notes en lecture seule est la taille au-delà de laquelle les notes seront affichées en mode lecture seule (pour des raisons de performances).",
|
||||||
"label": "Taille automatique en lecture seule (notes de texte)"
|
"label": "Taille automatique en lecture seule (notes de texte)",
|
||||||
|
"unit": "caractères"
|
||||||
},
|
},
|
||||||
"i18n": {
|
"i18n": {
|
||||||
"title": "Paramètres régionaux",
|
"title": "Paramètres régionaux",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"first-day-of-the-week": "Premier jour de la semaine",
|
"first-day-of-the-week": "Premier jour de la semaine",
|
||||||
"sunday": "Dimanche",
|
"sunday": "Dimanche",
|
||||||
"monday": "Lundi"
|
"monday": "Lundi",
|
||||||
|
"tuesday": "Mardi",
|
||||||
|
"wednesday": "Mercredi",
|
||||||
|
"thursday": "Jeudi",
|
||||||
|
"friday": "Vendredi",
|
||||||
|
"saturday": "Samedi",
|
||||||
|
"first-week-of-the-year": "Première semaine de l'année",
|
||||||
|
"first-week-contains-first-day": "La première semaine contient le premier jour de l'année",
|
||||||
|
"first-week-contains-first-thursday": "La première semaine contient le premier jeudi de l'année",
|
||||||
|
"first-week-has-minimum-days": "La première semaine a un nombre minimum de jours",
|
||||||
|
"min-days-in-first-week": "Nombre minimum de jours dans la première semaine",
|
||||||
|
"first-week-info": "La première semaine contient le premier jeudi de l'année et est basée sur la norme <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a> .",
|
||||||
|
"first-week-warning": "La modification des options de la première semaine peut entraîner des doublons avec les notes de semaine existantes et les notes de semaine existantes ne seront pas mises à jour en conséquence.",
|
||||||
|
"formatting-locale": "Format de date et de nombre",
|
||||||
|
"formatting-locale-auto": "En fonction de la langue de l'application"
|
||||||
},
|
},
|
||||||
"backup": {
|
"backup": {
|
||||||
"automatic_backup": "Sauvegarde automatique",
|
"automatic_backup": "Sauvegarde automatique",
|
||||||
@@ -1284,7 +1307,9 @@
|
|||||||
"delete_token": "Supprimer/désactiver ce token",
|
"delete_token": "Supprimer/désactiver ce token",
|
||||||
"rename_token_title": "Renommer le jeton",
|
"rename_token_title": "Renommer le jeton",
|
||||||
"rename_token_message": "Veuillez saisir le nom du nouveau jeton",
|
"rename_token_message": "Veuillez saisir le nom du nouveau jeton",
|
||||||
"delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?"
|
"delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?",
|
||||||
|
"see_more": "Voir plus de détails dans le {{- link_to_wiki}} et le {{- link_to_openapi_spec}} ou le {{- link_to_swagger_ui }}.",
|
||||||
|
"swagger_ui": "Interface utilisateur ETAPI Swagger"
|
||||||
},
|
},
|
||||||
"options_widget": {
|
"options_widget": {
|
||||||
"options_status": "Statut des options",
|
"options_status": "Statut des options",
|
||||||
@@ -1347,7 +1372,8 @@
|
|||||||
"test_title": "Test de synchronisation",
|
"test_title": "Test de synchronisation",
|
||||||
"test_description": "Testera la connexion et la prise de contact avec le serveur de synchronisation. Si le serveur de synchronisation n'est pas initialisé, cela le configurera pour qu'il se synchronise avec le document local.",
|
"test_description": "Testera la connexion et la prise de contact avec le serveur de synchronisation. Si le serveur de synchronisation n'est pas initialisé, cela le configurera pour qu'il se synchronise avec le document local.",
|
||||||
"test_button": "Tester la synchronisation",
|
"test_button": "Tester la synchronisation",
|
||||||
"handshake_failed": "Échec de la négociation avec le serveur de synchronisation, erreur : {{message}}"
|
"handshake_failed": "Échec de la négociation avec le serveur de synchronisation, erreur : {{message}}",
|
||||||
|
"timeout_unit": "millisecondes"
|
||||||
},
|
},
|
||||||
"api_log": {
|
"api_log": {
|
||||||
"close": "Fermer"
|
"close": "Fermer"
|
||||||
@@ -1407,11 +1433,14 @@
|
|||||||
"import-into-note": "Importer dans la note",
|
"import-into-note": "Importer dans la note",
|
||||||
"apply-bulk-actions": "Appliquer des Actions groupées",
|
"apply-bulk-actions": "Appliquer des Actions groupées",
|
||||||
"converted-to-attachments": "Les notes {{count}} ont été converties en pièces jointes.",
|
"converted-to-attachments": "Les notes {{count}} ont été converties en pièces jointes.",
|
||||||
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentes ?"
|
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentes ?",
|
||||||
|
"archive": "Archive",
|
||||||
|
"unarchive": "Désarchiver",
|
||||||
|
"open-in-popup": "Modification rapide"
|
||||||
},
|
},
|
||||||
"shared_info": {
|
"shared_info": {
|
||||||
"shared_publicly": "Cette note est partagée publiquement sur {{- link}}",
|
"shared_publicly": "Cette note est partagée publiquement sur {{- link}}.",
|
||||||
"shared_locally": "Cette note est partagée localement sur {{- link}}",
|
"shared_locally": "Cette note est partagée localement sur {{- link}}.",
|
||||||
"help_link": "Pour obtenir de l'aide, visitez le <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
"help_link": "Pour obtenir de l'aide, visitez le <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||||
},
|
},
|
||||||
"note_types": {
|
"note_types": {
|
||||||
@@ -1433,7 +1462,11 @@
|
|||||||
"confirm-change": "Il n'est pas recommandé de modifier le type de note lorsque son contenu n'est pas vide. Voulez-vous continuer ?",
|
"confirm-change": "Il n'est pas recommandé de modifier le type de note lorsque son contenu n'est pas vide. Voulez-vous continuer ?",
|
||||||
"geo-map": "Carte géo",
|
"geo-map": "Carte géo",
|
||||||
"beta-feature": "Beta",
|
"beta-feature": "Beta",
|
||||||
"task-list": "Liste de tâches"
|
"task-list": "Liste de tâches",
|
||||||
|
"book": "Collection",
|
||||||
|
"ai-chat": "Chat IA",
|
||||||
|
"new-feature": "Nouveau",
|
||||||
|
"collections": "Collections"
|
||||||
},
|
},
|
||||||
"protect_note": {
|
"protect_note": {
|
||||||
"toggle-on": "Protéger la note",
|
"toggle-on": "Protéger la note",
|
||||||
@@ -1486,13 +1519,16 @@
|
|||||||
"hoist-this-note-workspace": "Focus cette note (espace de travail)",
|
"hoist-this-note-workspace": "Focus cette note (espace de travail)",
|
||||||
"refresh-saved-search-results": "Rafraîchir les résultats de recherche enregistrée",
|
"refresh-saved-search-results": "Rafraîchir les résultats de recherche enregistrée",
|
||||||
"create-child-note": "Créer une note enfant",
|
"create-child-note": "Créer une note enfant",
|
||||||
"unhoist": "Désactiver le focus"
|
"unhoist": "Désactiver le focus",
|
||||||
|
"toggle-sidebar": "Basculer la barre latérale"
|
||||||
},
|
},
|
||||||
"title_bar_buttons": {
|
"title_bar_buttons": {
|
||||||
"window-on-top": "Épingler cette fenêtre au premier plan"
|
"window-on-top": "Épingler cette fenêtre au premier plan"
|
||||||
},
|
},
|
||||||
"note_detail": {
|
"note_detail": {
|
||||||
"could_not_find_typewidget": "Impossible de trouver typeWidget pour le type '{{type}}'"
|
"could_not_find_typewidget": "Impossible de trouver typeWidget pour le type '{{type}}'",
|
||||||
|
"printing": "Impression en cours...",
|
||||||
|
"printing_pdf": "Export au format PDF en cours..."
|
||||||
},
|
},
|
||||||
"note_title": {
|
"note_title": {
|
||||||
"placeholder": "saisir le titre de la note ici..."
|
"placeholder": "saisir le titre de la note ici..."
|
||||||
@@ -1543,7 +1579,9 @@
|
|||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
"cut": "Les note(s) ont été coupées dans le presse-papiers.",
|
"cut": "Les note(s) ont été coupées dans le presse-papiers.",
|
||||||
"copied": "Les note(s) ont été coupées dans le presse-papiers."
|
"copied": "Les note(s) ont été coupées dans le presse-papiers.",
|
||||||
|
"copy_failed": "Impossible de copier dans le presse-papiers en raison de problèmes d'autorisation.",
|
||||||
|
"copy_success": "Copié dans le presse-papiers."
|
||||||
},
|
},
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"note-revision-created": "La version de la note a été créée.",
|
"note-revision-created": "La version de la note a été créée.",
|
||||||
@@ -1565,7 +1603,9 @@
|
|||||||
"ws": {
|
"ws": {
|
||||||
"sync-check-failed": "Le test de synchronisation a échoué !",
|
"sync-check-failed": "Le test de synchronisation a échoué !",
|
||||||
"consistency-checks-failed": "Les tests de cohérence ont échoué ! Consultez les journaux pour plus de détails.",
|
"consistency-checks-failed": "Les tests de cohérence ont échoué ! Consultez les journaux pour plus de détails.",
|
||||||
"encountered-error": "Erreur \"{{message}}\", consultez la console."
|
"encountered-error": "Erreur \"{{message}}\", consultez la console.",
|
||||||
|
"lost-websocket-connection-title": "Connexion au serveur perdue",
|
||||||
|
"lost-websocket-connection-message": "Vérifiez la configuration de votre proxy inverse (par exemple nginx ou Apache) pour vous assurer que les connexions WebSocket sont correctement autorisées et ne sont pas bloquées."
|
||||||
},
|
},
|
||||||
"hoisted_note": {
|
"hoisted_note": {
|
||||||
"confirm_unhoisting": "La note demandée «{{requestedNote}}» est en dehors du sous-arbre de la note focus «{{hoistedNote}}». Le focus doit être désactivé pour accéder à la note. Voulez-vous enlever le focus ?"
|
"confirm_unhoisting": "La note demandée «{{requestedNote}}» est en dehors du sous-arbre de la note focus «{{hoistedNote}}». Le focus doit être désactivé pour accéder à la note. Voulez-vous enlever le focus ?"
|
||||||
@@ -1587,13 +1627,15 @@
|
|||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"description": "Contrôle la coloration syntaxique des blocs de code à l'intérieur des notes texte, les notes de code ne seront pas affectées.",
|
"description": "Contrôle la coloration syntaxique des blocs de code à l'intérieur des notes texte, les notes de code ne seront pas affectées.",
|
||||||
"color-scheme": "Jeu de couleurs"
|
"color-scheme": "Jeu de couleurs",
|
||||||
|
"title": "Blocs de code"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Saut à la ligne automatique suivant la largeur",
|
"word_wrapping": "Saut à la ligne automatique suivant la largeur",
|
||||||
"theme_none": "Pas de coloration syntaxique",
|
"theme_none": "Pas de coloration syntaxique",
|
||||||
"theme_group_light": "Thèmes clairs",
|
"theme_group_light": "Thèmes clairs",
|
||||||
"theme_group_dark": "Thèmes sombres"
|
"theme_group_dark": "Thèmes sombres",
|
||||||
|
"copy_title": "Copier dans le presse-papiers"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Mise en forme"
|
"title": "Mise en forme"
|
||||||
@@ -1652,7 +1694,8 @@
|
|||||||
"full-text-search": "Recherche dans le texte"
|
"full-text-search": "Recherche dans le texte"
|
||||||
},
|
},
|
||||||
"note_tooltip": {
|
"note_tooltip": {
|
||||||
"note-has-been-deleted": "La note a été supprimée."
|
"note-has-been-deleted": "La note a été supprimée.",
|
||||||
|
"quick-edit": "Edition rapide"
|
||||||
},
|
},
|
||||||
"geo-map": {
|
"geo-map": {
|
||||||
"create-child-note-title": "Créer une nouvelle note enfant et l'ajouter à la carte",
|
"create-child-note-title": "Créer une nouvelle note enfant et l'ajouter à la carte",
|
||||||
@@ -1661,7 +1704,8 @@
|
|||||||
},
|
},
|
||||||
"geo-map-context": {
|
"geo-map-context": {
|
||||||
"open-location": "Ouvrir la position",
|
"open-location": "Ouvrir la position",
|
||||||
"remove-from-map": "Retirer de la carte"
|
"remove-from-map": "Retirer de la carte",
|
||||||
|
"add-note": "Ajouter un marqueur à cet endroit"
|
||||||
},
|
},
|
||||||
"help-button": {
|
"help-button": {
|
||||||
"title": "Ouvrir la page d'aide correspondante"
|
"title": "Ouvrir la page d'aide correspondante"
|
||||||
@@ -1688,10 +1732,41 @@
|
|||||||
"minimum_input": "La valeur de temps saisie doit être d'au moins {{minimumSeconds}} secondes."
|
"minimum_input": "La valeur de temps saisie doit être d'au moins {{minimumSeconds}} secondes."
|
||||||
},
|
},
|
||||||
"multi_factor_authentication": {
|
"multi_factor_authentication": {
|
||||||
"oauth_user_email": "Courriel de l'utilisateur : "
|
"oauth_user_email": "Courriel de l'utilisateur : ",
|
||||||
|
"title": "Authentification multifacteur",
|
||||||
|
"description": "L'authentification multifacteur (MFA) renforce la sécurité de votre compte. Au lieu de simplement saisir un mot de passe pour vous connecter, le MFA vous demande de fournir une ou plusieurs preuves supplémentaires pour vérifier votre identité. Ainsi, même si quelqu'un obtient votre mot de passe, il ne peut accéder à votre compte sans cette deuxième information. C'est comme ajouter une serrure supplémentaire à votre porte, rendant l'effraction beaucoup plus difficile.<br><br>Veuillez suivre les instructions ci-dessous pour activer le MFA. Si vous ne configurez pas correctement, la connexion se fera uniquement par mot de passe.",
|
||||||
|
"mfa_enabled": "Activer l'authentification multifacteur",
|
||||||
|
"mfa_method": "Méthode MFA",
|
||||||
|
"electron_disabled": "L'authentification multifacteur n'est actuellement pas prise en charge dans la version de bureau.",
|
||||||
|
"totp_title": "Mot de passe à usage unique basé sur le temps (TOTP)",
|
||||||
|
"totp_description": "Le TOTP (Time-Based One-Time Password) est une fonctionnalité de sécurité qui génère un code unique et temporaire, modifié toutes les 30 secondes. Vous utilisez ce code, associé à votre mot de passe, pour vous connecter à votre compte, ce qui rend l'accès à celui-ci beaucoup plus difficile.",
|
||||||
|
"totp_secret_title": "Générer un secret TOTP",
|
||||||
|
"totp_secret_generate": "Générer un secret TOTP",
|
||||||
|
"totp_secret_regenerate": "Re-générer un secret TOTP",
|
||||||
|
"no_totp_secret_warning": "Pour activer TOTP, vous devez d’abord générer un secret TOTP.",
|
||||||
|
"totp_secret_description_warning": "Après avoir généré un nouveau secret TOTP, vous devrez vous reconnecter avec le nouveau secret TOTP.",
|
||||||
|
"totp_secret_generated": "Secret TOTP généré",
|
||||||
|
"totp_secret_warning": "Veuillez conserver le secret généré dans un endroit sûr. Il ne sera plus affiché.",
|
||||||
|
"totp_secret_regenerate_confirm": "Voulez-vous vraiment régénérer le secret TOTP ? Cela invalidera le secret TOTP précédent et tous les codes de récupération existants.",
|
||||||
|
"recovery_keys_title": "Clés de récupération d'authentification unique",
|
||||||
|
"recovery_keys_description": "Les clés de récupération d'authentification unique sont utilisées pour vous connecter même si vous ne pouvez pas accéder à vos codes d'authentification.",
|
||||||
|
"recovery_keys_description_warning": "Les clés de récupération ne seront plus affichées après avoir quitté la page, conservez-les dans un endroit sûr et sécurisé.<br>Une fois qu'une clé de récupération a été utilisée, elle devient inutilisable.",
|
||||||
|
"recovery_keys_error": "Erreur lors de la génération des codes de récupération",
|
||||||
|
"recovery_keys_no_key_set": "Aucun code de récupération défini",
|
||||||
|
"recovery_keys_generate": "Générer des codes de récupération",
|
||||||
|
"recovery_keys_regenerate": "Re-générer des codes de récupération",
|
||||||
|
"recovery_keys_used": "Utilisé : {{date}}",
|
||||||
|
"recovery_keys_unused": "Le code de récupération {{index}} n'est pas utilisé",
|
||||||
|
"oauth_title": "OAuth/OpenID",
|
||||||
|
"oauth_description": "OpenID est un moyen standardisé de vous connecter à des sites web avec un compte d'un autre service, comme Google, afin de vérifier votre identité. L'émetteur par défaut est Google, mais vous pouvez le modifier pour n'importe quel autre fournisseur OpenID. Consultez <a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">ici</a> pour plus d'informations. Suivez ces <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">instructions</a> pour configurer un service OpenID via Google.",
|
||||||
|
"oauth_description_warning": "Pour activer OAuth/OpenID, vous devez définir l'URL de base, l'ID client et le secret client OAuth/OpenID dans le fichier config.ini, puis redémarrer l'application. Pour les définir à partir des variables d'environnement, définissez TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID et TRILIUM_OAUTH_CLIENT_SECRET.",
|
||||||
|
"oauth_missing_vars": "Paramètres manquants : {{-variables}}",
|
||||||
|
"oauth_user_account": "Compte utilisateur: ",
|
||||||
|
"oauth_user_not_logged_in": "Pas connecté !"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close": "Fermer"
|
"close": "Fermer",
|
||||||
|
"help_title": "Afficher plus d'informations sur cet écran"
|
||||||
},
|
},
|
||||||
"ai_llm": {
|
"ai_llm": {
|
||||||
"not_started": "Non démarré",
|
"not_started": "Non démarré",
|
||||||
@@ -1771,13 +1846,76 @@
|
|||||||
"reprocessing_index": "Mise à jour...",
|
"reprocessing_index": "Mise à jour...",
|
||||||
"reprocess_index_started": "L'optimisation de l'indice de recherche à commencer en arrière-plan",
|
"reprocess_index_started": "L'optimisation de l'indice de recherche à commencer en arrière-plan",
|
||||||
"reprocess_index_error": "Erreur dans le rafraichissement de l'indice de recherche",
|
"reprocess_index_error": "Erreur dans le rafraichissement de l'indice de recherche",
|
||||||
"failed_notes": "Notes échouées",
|
"failed_notes": "Notes en erreur",
|
||||||
"last_processed": "Dernier traitement",
|
"last_processed": "Dernier traitement",
|
||||||
"restore_provider": "Restaurer le fournisseur de la recherche",
|
"restore_provider": "Restaurer le fournisseur de recherche",
|
||||||
"index_rebuild_progress": "Progression de la reconstruction de l'index",
|
"index_rebuild_progress": "Progression de la reconstruction de l'index",
|
||||||
"index_rebuilding": "Optimisation de l'index ({{percentage}}%)",
|
"index_rebuilding": "Optimisation de l'index ({{percentage}}%)",
|
||||||
"index_rebuild_complete": "Optimisation de l'index terminée",
|
"index_rebuild_complete": "Optimisation de l'index terminée",
|
||||||
"index_rebuild_status_error": "Erreur lors de la vérification de l'état de reconstruction de l'index"
|
"index_rebuild_status_error": "Erreur lors de la vérification de l'état de reconstruction de l'index",
|
||||||
|
"provider_precedence": "Priorité du fournisseur",
|
||||||
|
"never": "Jamais",
|
||||||
|
"processing": "Traitement en cours ({{percentage}}%)",
|
||||||
|
"incomplete": "Incomplet ({{percentage}}%)",
|
||||||
|
"complete": "Terminé (100%)",
|
||||||
|
"refreshing": "Mise à jour...",
|
||||||
|
"auto_refresh_notice": "Actualisation automatique toutes les {{seconds}} secondes",
|
||||||
|
"note_queued_for_retry": "Note mise en file d'attente pour une nouvelle tentative",
|
||||||
|
"failed_to_retry_note": "Échec de la nouvelle tentative de note",
|
||||||
|
"all_notes_queued_for_retry": "Toutes les notes ayant échoué sont mises en file d'attente pour une nouvelle tentative",
|
||||||
|
"failed_to_retry_all": "Échec du ré essai des notes",
|
||||||
|
"ai_settings": "Paramètres IA",
|
||||||
|
"api_key_tooltip": "Clé API pour accéder au service",
|
||||||
|
"empty_key_warning": {
|
||||||
|
"anthropic": "La clé API Anthropic est vide. Veuillez saisir une clé API valide.",
|
||||||
|
"openai": "La clé API OpenAI est vide. Veuillez saisir une clé API valide.",
|
||||||
|
"voyage": "La clé API Voyage est vide. Veuillez saisir une clé API valide.",
|
||||||
|
"ollama": "La clé API Ollama est vide. Veuillez saisir une clé API valide."
|
||||||
|
},
|
||||||
|
"agent": {
|
||||||
|
"processing": "Traitement...",
|
||||||
|
"thinking": "Réflexion...",
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"generating": "Génération..."
|
||||||
|
},
|
||||||
|
"name": "IA",
|
||||||
|
"openai": "OpenAI",
|
||||||
|
"use_enhanced_context": "Utiliser un contexte amélioré",
|
||||||
|
"enhanced_context_description": "Fournit à l'IA plus de contexte à partir de la note et de ses notes associées pour de meilleures réponses",
|
||||||
|
"show_thinking": "Montrer la réflexion",
|
||||||
|
"show_thinking_description": "Montrer la chaîne de pensée de l'IA",
|
||||||
|
"enter_message": "Entrez votre message...",
|
||||||
|
"error_contacting_provider": "Erreur lors de la connexion au fournisseur d'IA. Veuillez vérifier vos paramètres et votre connexion Internet.",
|
||||||
|
"error_generating_response": "Erreur lors de la génération de la réponse de l'IA",
|
||||||
|
"index_all_notes": "Indexer toutes les notes",
|
||||||
|
"index_status": "Statut de l'index",
|
||||||
|
"indexed_notes": "Notes indexées",
|
||||||
|
"indexing_stopped": "Arrêt de l'indexation",
|
||||||
|
"indexing_in_progress": "Indexation en cours...",
|
||||||
|
"last_indexed": "Dernière indexée",
|
||||||
|
"note_chat": "Note discussion",
|
||||||
|
"sources": "Sources",
|
||||||
|
"start_indexing": "Démarrage de l'indexation",
|
||||||
|
"use_advanced_context": "Utiliser le contexte avancé",
|
||||||
|
"ollama_no_url": "Ollama n'est pas configuré. Veuillez saisir une URL valide.",
|
||||||
|
"chat": {
|
||||||
|
"root_note_title": "Discussions IA",
|
||||||
|
"root_note_content": "Cette note contient vos conversations de chat IA enregistrées.",
|
||||||
|
"new_chat_title": "Nouvelle discussion",
|
||||||
|
"create_new_ai_chat": "Créer une nouvelle discussion IA"
|
||||||
|
},
|
||||||
|
"create_new_ai_chat": "Créer une nouvelle discussion IA",
|
||||||
|
"configuration_warnings": "Il y a quelques problèmes avec la configuration de votre IA. Veuillez vérifier vos paramètres.",
|
||||||
|
"experimental_warning": "La fonctionnalité LLM est actuellement expérimentale – vous êtes prévenu.",
|
||||||
|
"selected_provider": "Fournisseur sélectionné",
|
||||||
|
"selected_provider_description": "Choisissez le fournisseur d’IA pour les fonctionnalités de discussion et de complétion",
|
||||||
|
"select_model": "Sélectionner le modèle...",
|
||||||
|
"select_provider": "Sélectionnez un fournisseur...",
|
||||||
|
"ai_enabled": "Fonctionnalités d'IA activées",
|
||||||
|
"ai_disabled": "Fonctionnalités d'IA désactivées",
|
||||||
|
"no_models_found_online": "Aucun modèle trouvé. Veuillez vérifier votre clé API et vos paramètres.",
|
||||||
|
"no_models_found_ollama": "Aucun modèle Ollama trouvé. Veuillez vérifier si Ollama est en cours d'exécution.",
|
||||||
|
"error_fetching": "Erreur lors de la récupération des modèles : {{error}}"
|
||||||
},
|
},
|
||||||
"ui-performance": {
|
"ui-performance": {
|
||||||
"title": "Performance",
|
"title": "Performance",
|
||||||
@@ -1786,5 +1924,168 @@
|
|||||||
"enable-backdrop-effects": "Activer les effets d'arrière plan pour les menus, popups et panneaux",
|
"enable-backdrop-effects": "Activer les effets d'arrière plan pour les menus, popups et panneaux",
|
||||||
"enable-smooth-scroll": "Active le défilement fluide",
|
"enable-smooth-scroll": "Active le défilement fluide",
|
||||||
"app-restart-required": "(redémarrer l'application pour appliquer les changements)"
|
"app-restart-required": "(redémarrer l'application pour appliquer les changements)"
|
||||||
|
},
|
||||||
|
"custom_date_time_format": {
|
||||||
|
"title": "Format de date/heure personnalisé",
|
||||||
|
"description": "Personnalisez le format de la date et de l'heure insérées via <shortcut /> ou la barre d'outils. Consultez la <doc>Day.js docs</doc> pour connaître les formats disponibles.",
|
||||||
|
"format_string": "Chaîne de format :",
|
||||||
|
"formatted_time": "Date/heure formatée :"
|
||||||
|
},
|
||||||
|
"table_view": {
|
||||||
|
"delete_column_confirmation": "Êtes-vous sûr de vouloir supprimer cette colonne ? L'attribut correspondant sera supprimé de toutes les notes.",
|
||||||
|
"delete-column": "Supprimer la colonne",
|
||||||
|
"new-column-label": "Étiquette",
|
||||||
|
"new-column-relation": "Relation",
|
||||||
|
"edit-column": "Editer la colonne",
|
||||||
|
"add-column-to-the-right": "Ajouter une colonne à droite",
|
||||||
|
"new-row": "Nouvelle ligne",
|
||||||
|
"new-column": "Nouvelle colonne",
|
||||||
|
"sort-column-by": "Trier par « {{title}} »",
|
||||||
|
"sort-column-ascending": "Ascendant",
|
||||||
|
"sort-column-descending": "Descendant",
|
||||||
|
"sort-column-clear": "Annuler le tri",
|
||||||
|
"hide-column": "Masquer la colonne \"{{title}}\"",
|
||||||
|
"show-hide-columns": "Afficher/masquer les colonnes",
|
||||||
|
"row-insert-above": "Insérer une ligne au-dessus",
|
||||||
|
"row-insert-below": "Insérer une ligne au-dessous",
|
||||||
|
"row-insert-child": "Insérer une note enfant",
|
||||||
|
"add-column-to-the-left": "Ajouter une colonne à gauche"
|
||||||
|
},
|
||||||
|
"book_properties_config": {
|
||||||
|
"hide-weekends": "Masquer les week-ends",
|
||||||
|
"display-week-numbers": "Afficher les numéros de semaine",
|
||||||
|
"map-style": "Style de carte :",
|
||||||
|
"max-nesting-depth": "Profondeur d'imbrication maximale :",
|
||||||
|
"raster": "Trame",
|
||||||
|
"vector_light": "Vecteur (clair)",
|
||||||
|
"vector_dark": "Vecteur (foncé)",
|
||||||
|
"show-scale": "Afficher l'échelle"
|
||||||
|
},
|
||||||
|
"table_context_menu": {
|
||||||
|
"delete_row": "Supprimer la ligne"
|
||||||
|
},
|
||||||
|
"board_view": {
|
||||||
|
"delete-note": "Supprimer la note...",
|
||||||
|
"remove-from-board": "Retirer du tableau",
|
||||||
|
"archive-note": "Note archivée",
|
||||||
|
"unarchive-note": "Note désarchivée",
|
||||||
|
"move-to": "Déplacer vers",
|
||||||
|
"insert-above": "Insérer au-dessus",
|
||||||
|
"insert-below": "Insérer au-dessous",
|
||||||
|
"delete-column": "Supprimer la colonne",
|
||||||
|
"delete-column-confirmation": "Êtes-vous sûr de vouloir supprimer cette colonne ? L'attribut correspondant sera également supprimé dans les notes sous cette colonne.",
|
||||||
|
"new-item": "Nouvel article",
|
||||||
|
"new-item-placeholder": "Entrez le titre de note...",
|
||||||
|
"add-column": "Ajouter une colonne",
|
||||||
|
"add-column-placeholder": "Entrez le nom de la colonne...",
|
||||||
|
"edit-note-title": "Cliquez pour modifier le titre de la note",
|
||||||
|
"edit-column-title": "Cliquez pour modifier le titre de la colonne"
|
||||||
|
},
|
||||||
|
"presentation_view": {
|
||||||
|
"edit-slide": "Modifier cette diapositive",
|
||||||
|
"start-presentation": "Démarrer la présentation",
|
||||||
|
"slide-overview": "Afficher un aperçu des diapositives"
|
||||||
|
},
|
||||||
|
"command_palette": {
|
||||||
|
"tree-action-name": "Arborescence : {{name}}",
|
||||||
|
"export_note_title": "Exporter la note",
|
||||||
|
"export_note_description": "Exporter la note actuelle",
|
||||||
|
"show_attachments_title": "Afficher les pièces jointes",
|
||||||
|
"show_attachments_description": "Afficher les pièces jointes des notes",
|
||||||
|
"search_notes_title": "Rechercher des notes",
|
||||||
|
"search_notes_description": "Ouvrir la recherche avancée",
|
||||||
|
"search_subtree_title": "Rechercher dans la sous-arborescence",
|
||||||
|
"search_subtree_description": "Rechercher dans la sous-arborescence actuelle",
|
||||||
|
"search_history_title": "Afficher l'historique de recherche",
|
||||||
|
"search_history_description": "Afficher les recherches précédentes",
|
||||||
|
"configure_launch_bar_title": "Configurer la barre de lancement",
|
||||||
|
"configure_launch_bar_description": "Ouvrir la configuration de la barre de lancement pour ajouter ou supprimer des éléments."
|
||||||
|
},
|
||||||
|
"content_renderer": {
|
||||||
|
"open_externally": "Ouverture externe"
|
||||||
|
},
|
||||||
|
"call_to_action": {
|
||||||
|
"next_theme_title": "Essayez le nouveau thème Trilium",
|
||||||
|
"next_theme_message": "Vous utilisez actuellement le thème hérité de l'ancienne version, souhaitez-vous essayer le nouveau thème ?",
|
||||||
|
"next_theme_button": "Essayez le nouveau thème",
|
||||||
|
"background_effects_title": "Les effets d'arrière-plan sont désormais stables",
|
||||||
|
"background_effects_message": "Sur les appareils Windows, les effets d'arrière-plan sont désormais parfaitement stables. Ils ajoutent une touche de couleur à l'interface utilisateur en floutant l'arrière-plan. Cette technique est également utilisée dans d'autres applications comme l'Explorateur Windows.",
|
||||||
|
"background_effects_button": "Activer les effets d'arrière-plan",
|
||||||
|
"dismiss": "Rejeter"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"related_settings": "Paramètres associés"
|
||||||
|
},
|
||||||
|
"settings_appearance": {
|
||||||
|
"related_code_blocks": "Schéma de coloration syntaxique pour les blocs de code dans les notes de texte",
|
||||||
|
"related_code_notes": "Schéma de couleurs pour les notes de code"
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"percentage": "%"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"page_title": "Page de {{startIndex}} - {{endIndex}}",
|
||||||
|
"total_notes": "{{count}} notes"
|
||||||
|
},
|
||||||
|
"collections": {
|
||||||
|
"rendering_error": "Impossible d'afficher le contenu en raison d'une erreur."
|
||||||
|
},
|
||||||
|
"code-editor-options": {
|
||||||
|
"title": "Éditeur"
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"due": {
|
||||||
|
"today": "Aujourd'hui",
|
||||||
|
"tomorrow": "Demain",
|
||||||
|
"yesterday": "Hier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content_widget": {
|
||||||
|
"unknown_widget": "Widget inconnu pour « {{id}} »."
|
||||||
|
},
|
||||||
|
"note_language": {
|
||||||
|
"not_set": "Non défini",
|
||||||
|
"configure-languages": "Configurer les langues..."
|
||||||
|
},
|
||||||
|
"content_language": {
|
||||||
|
"title": "Contenu des langues",
|
||||||
|
"description": "Sélectionnez une ou plusieurs langues à afficher dans la section « Propriétés de base » d'une note textuelle en lecture seule ou modifiable. Cela permettra d'utiliser des fonctionnalités telles que la vérification orthographique ou la prise en charge de l'écriture de droite à gauche."
|
||||||
|
},
|
||||||
|
"switch_layout_button": {
|
||||||
|
"title_vertical": "Déplacer le volet d'édition vers le bas",
|
||||||
|
"title_horizontal": "Déplacer le panneau d'édition vers la gauche"
|
||||||
|
},
|
||||||
|
"toggle_read_only_button": {
|
||||||
|
"unlock-editing": "Déverrouiller l'édition",
|
||||||
|
"lock-editing": "Verrouiller l'édition"
|
||||||
|
},
|
||||||
|
"png_export_button": {
|
||||||
|
"button_title": "Exporter le diagramme au format PNG"
|
||||||
|
},
|
||||||
|
"svg": {
|
||||||
|
"export_to_png": "Le diagramme n'a pas pu être exporté au format PNG."
|
||||||
|
},
|
||||||
|
"code_theme": {
|
||||||
|
"title": "Apparence",
|
||||||
|
"word_wrapping": "retour à la ligne automatique",
|
||||||
|
"color-scheme": "Jeu de couleurs"
|
||||||
|
},
|
||||||
|
"cpu_arch_warning": {
|
||||||
|
"title": "Veuillez télécharger la version ARM64",
|
||||||
|
"message_macos": "TriliumNext fonctionne actuellement sous Rosetta 2, ce qui signifie que vous utilisez la version Intel (x64) sur un Mac Apple Silicon. Cela aura un impact significatif sur les performances et l'autonomie de la batterie.",
|
||||||
|
"message_windows": "TriliumNext fonctionne actuellement en mode émulation, ce qui signifie que vous utilisez la version Intel (x64) sur un appareil Windows sur ARM. Cela aura un impact significatif sur les performances et l'autonomie de la batterie.",
|
||||||
|
"recommendation": "Pour une expérience optimale, veuillez télécharger la version ARM64 native de TriliumNext depuis notre page de versions.",
|
||||||
|
"download_link": "Télécharger la version native",
|
||||||
|
"continue_anyway": "Continuer quand même",
|
||||||
|
"dont_show_again": "Ne plus afficher cet avertissement"
|
||||||
|
},
|
||||||
|
"editorfeatures": {
|
||||||
|
"title": "Caractéristiques",
|
||||||
|
"emoji_completion_enabled": "Activer la saisie semi-automatique des emojis",
|
||||||
|
"emoji_completion_description": "Si cette option est activée, les emojis peuvent être facilement insérés dans le texte en tapant `:` , suivi du nom d'un emoji.",
|
||||||
|
"note_completion_enabled": "Activer la saisie semi-automatique des notes",
|
||||||
|
"note_completion_description": "Si cette option est activée, des liens vers des notes peuvent être créés en tapant `@` suivi du titre d'une note.",
|
||||||
|
"slash_commands_enabled": "Activer les commandes slash",
|
||||||
|
"slash_commands_description": "Si cette option est activée, les commandes d'édition telles que l'insertion de sauts de ligne ou d'en-têtes peuvent être activées en tapant `/`."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
apps/client/src/translations/hi/translation.json
Normal file
5
apps/client/src/translations/hi/translation.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"about": {
|
||||||
|
"title": "ट्रिलियम नोट्स के बारें में"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,50 @@
|
|||||||
{}
|
{
|
||||||
|
"about": {
|
||||||
|
"title": "A Trilium Notes-ról",
|
||||||
|
"homepage": "Kezdőlap:",
|
||||||
|
"app_version": "Alkalmazás verziója:",
|
||||||
|
"db_version": "Adatbázis verzió:",
|
||||||
|
"sync_version": "Verzió szinkronizálás :",
|
||||||
|
"build_revision": "Build revízió:",
|
||||||
|
"data_directory": "Adatkönyvtár:",
|
||||||
|
"build_date": "Build dátum:"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"critical-error": {
|
||||||
|
"title": "Kritikus hiba",
|
||||||
|
"message": "Kritikus hiba történt, amely megakadályozza a kliensalkalmazás indítását:\n\n{{message}}\n\nEzt valószínűleg egy váratlan szkripthiba okozza. Próbálja meg biztonságos módban elindítani az alkalmazást, és hárítsa el a problémát."
|
||||||
|
},
|
||||||
|
"widget-error": {
|
||||||
|
"title": "Nem sikerült inicializálni egy widgetet",
|
||||||
|
"message-custom": "A(z) \"{{id}}\" azonosítójú, \"{{title}}\" című jegyzetből származó egyéni widget inicializálása sikertelen volt a következő ok miatt:\n\n{{message}}",
|
||||||
|
"message-unknown": "Ismeretlen widget inicializálása sikertelen volt a következő ok miatt:\n\n{{message}}"
|
||||||
|
},
|
||||||
|
"bundle-error": {
|
||||||
|
"title": "Nem sikerült betölteni az egyéni szkriptet",
|
||||||
|
"message": "A(z) \"{{id}}\" azonosítójú, \"{{title}}\" című jegyzetből származó szkript nem hajtható végre a következő ok miatt:\n\n{{message}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"add_link": {
|
||||||
|
"add_link": "Link hozzáadása",
|
||||||
|
"help_on_links": "Segítség a linkekhez",
|
||||||
|
"note": "Jegyzet",
|
||||||
|
"search_note": "név szerinti jegyzetkeresés",
|
||||||
|
"link_title_mirrors": "A link cím tükrözi a jegyzet aktuális címét",
|
||||||
|
"link_title_arbitrary": "link cím önkényesen módosítható",
|
||||||
|
"link_title": "Link cím",
|
||||||
|
"button_add_link": "Link hozzáadása"
|
||||||
|
},
|
||||||
|
"branch_prefix": {
|
||||||
|
"edit_branch_prefix": "Az elágazás előtagjának szerkesztése",
|
||||||
|
"help_on_tree_prefix": "Segítség a fa előtagján",
|
||||||
|
"prefix": "Az előtag: ",
|
||||||
|
"save": "Mentés"
|
||||||
|
},
|
||||||
|
"bulk_actions": {
|
||||||
|
"bulk_actions": "Tömeges akciók",
|
||||||
|
"affected_notes": "Érintett jegyzetek",
|
||||||
|
"labels": "Címkék",
|
||||||
|
"relations": "Kapcsolatok",
|
||||||
|
"notes": "Jegyzetek"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@
|
|||||||
"emoji_completion_description": "Se abilitata, è possibile inserire facilmente gli emoji nel testo digitando `:`, seguito dal nome dell'emoji.",
|
"emoji_completion_description": "Se abilitata, è possibile inserire facilmente gli emoji nel testo digitando `:`, seguito dal nome dell'emoji.",
|
||||||
"note_completion_description": "Se abilitato, è possibile creare collegamenti alle note digitando `@` seguito dal titolo di una nota.",
|
"note_completion_description": "Se abilitato, è possibile creare collegamenti alle note digitando `@` seguito dal titolo di una nota.",
|
||||||
"slash_commands_enabled": "Abilita i comandi slash",
|
"slash_commands_enabled": "Abilita i comandi slash",
|
||||||
"slash_commands_description": "Se abilitato, i comandi di modifica come l'inserimento di interruzioni di riga o intestazioni possono essere attivati digitando `/`."
|
"slash_commands_description": "Se abilitato, i comandi di modifica come l'inserimento di interruzioni di riga o intestazioni possono essere attivati digitando `/`."
|
||||||
},
|
},
|
||||||
"table_view": {
|
"table_view": {
|
||||||
"new-row": "Nuova riga",
|
"new-row": "Nuova riga",
|
||||||
@@ -703,7 +703,7 @@
|
|||||||
"last_attempt": "Ultimo tentativo",
|
"last_attempt": "Ultimo tentativo",
|
||||||
"actions": "Azioni",
|
"actions": "Azioni",
|
||||||
"retry": "Riprova",
|
"retry": "Riprova",
|
||||||
"partial": "{{ percentuale }}% completato",
|
"partial": "{{ percentage }}% completato",
|
||||||
"retry_queued": "Nota in coda per un nuovo tentativo",
|
"retry_queued": "Nota in coda per un nuovo tentativo",
|
||||||
"retry_failed": "Impossibile mettere in coda la nota per un nuovo tentativo",
|
"retry_failed": "Impossibile mettere in coda la nota per un nuovo tentativo",
|
||||||
"max_notes_per_llm_query": "Numero massimo di note per query",
|
"max_notes_per_llm_query": "Numero massimo di note per query",
|
||||||
@@ -719,12 +719,12 @@
|
|||||||
"reprocess_index_started": "Ottimizzazione dell'indice di ricerca avviata in background",
|
"reprocess_index_started": "Ottimizzazione dell'indice di ricerca avviata in background",
|
||||||
"reprocess_index_error": "Errore durante la ricostruzione dell'indice di ricerca",
|
"reprocess_index_error": "Errore durante la ricostruzione dell'indice di ricerca",
|
||||||
"index_rebuild_progress": "Progresso nella ricostruzione dell'indice",
|
"index_rebuild_progress": "Progresso nella ricostruzione dell'indice",
|
||||||
"index_rebuilding": "Indice di ottimizzazione ({{percentuale}}%)",
|
"index_rebuilding": "Indice di ottimizzazione ({{percentage}}%)",
|
||||||
"index_rebuild_complete": "Ottimizzazione dell'indice completata",
|
"index_rebuild_complete": "Ottimizzazione dell'indice completata",
|
||||||
"index_rebuild_status_error": "Errore durante il controllo dello stato di ricostruzione dell'indice",
|
"index_rebuild_status_error": "Errore durante il controllo dello stato di ricostruzione dell'indice",
|
||||||
"never": "Mai",
|
"never": "Mai",
|
||||||
"processing": "Elaborazione ({{percentuale}}%)",
|
"processing": "Elaborazione ({{percentage}}%)",
|
||||||
"incomplete": "Incompleto ({{percentuale}}%)",
|
"incomplete": "Incompleto ({{percentage}}%)",
|
||||||
"complete": "Completato (100%)",
|
"complete": "Completato (100%)",
|
||||||
"refreshing": "Rinfrescante...",
|
"refreshing": "Rinfrescante...",
|
||||||
"auto_refresh_notice": "Si aggiorna automaticamente ogni {{seconds}} secondi",
|
"auto_refresh_notice": "Si aggiorna automaticamente ogni {{seconds}} secondi",
|
||||||
@@ -761,9 +761,7 @@
|
|||||||
"indexing_stopped": "Indicizzazione interrotta",
|
"indexing_stopped": "Indicizzazione interrotta",
|
||||||
"indexing_in_progress": "Indicizzazione in corso...",
|
"indexing_in_progress": "Indicizzazione in corso...",
|
||||||
"last_indexed": "Ultimo indicizzato",
|
"last_indexed": "Ultimo indicizzato",
|
||||||
"n_notes_queued": "{{ count }} nota in coda per l'indicizzazione",
|
|
||||||
"note_chat": "Nota Chat",
|
"note_chat": "Nota Chat",
|
||||||
"notes_indexed": "{{ count }} nota indicizzata",
|
|
||||||
"sources": "Fonti",
|
"sources": "Fonti",
|
||||||
"start_indexing": "Avvia l'indicizzazione",
|
"start_indexing": "Avvia l'indicizzazione",
|
||||||
"use_advanced_context": "Usa contesto avanzato",
|
"use_advanced_context": "Usa contesto avanzato",
|
||||||
@@ -811,7 +809,8 @@
|
|||||||
"codeImportedAsCode": "Importa i file di codice riconosciuti (ad esempio <code>.json</code>) come note di codice se non è chiaro dai metadati",
|
"codeImportedAsCode": "Importa i file di codice riconosciuti (ad esempio <code>.json</code>) come note di codice se non è chiaro dai metadati",
|
||||||
"replaceUnderscoresWithSpaces": "Sostituisci i trattini bassi con spazi nei nomi delle note importate",
|
"replaceUnderscoresWithSpaces": "Sostituisci i trattini bassi con spazi nei nomi delle note importate",
|
||||||
"import": "Importa",
|
"import": "Importa",
|
||||||
"failed": "Importazione fallita: {{message}}."
|
"failed": "Importazione fallita: {{message}}.",
|
||||||
|
"importZipRecommendation": "Quando si importa un file ZIP, la gerarchia delle note rifletterà la struttura delle sottodirectory all'interno dell'archivio."
|
||||||
},
|
},
|
||||||
"include_note": {
|
"include_note": {
|
||||||
"dialog_title": "Includi nota",
|
"dialog_title": "Includi nota",
|
||||||
@@ -1710,7 +1709,7 @@
|
|||||||
"for_more_info": "per maggiori informazioni.",
|
"for_more_info": "per maggiori informazioni.",
|
||||||
"protected_session_timeout_label": "Timeout della sessione protetta:",
|
"protected_session_timeout_label": "Timeout della sessione protetta:",
|
||||||
"reset_confirmation": "Reimpostando la password perderai per sempre l'accesso a tutte le tue note protette. Vuoi davvero reimpostare la password?",
|
"reset_confirmation": "Reimpostando la password perderai per sempre l'accesso a tutte le tue note protette. Vuoi davvero reimpostare la password?",
|
||||||
"reset_success_message": "La password è stata reimpostata. Imposta una nuova password.",
|
"reset_success_message": "La password è stata resettata. Imposta una nuova password",
|
||||||
"change_password_heading": "Cambiare la password",
|
"change_password_heading": "Cambiare la password",
|
||||||
"set_password_heading": "Imposta password",
|
"set_password_heading": "Imposta password",
|
||||||
"set_password": "Imposta password",
|
"set_password": "Imposta password",
|
||||||
@@ -1740,12 +1739,12 @@
|
|||||||
"recovery_keys_no_key_set": "Nessun codice di ripristino impostato",
|
"recovery_keys_no_key_set": "Nessun codice di ripristino impostato",
|
||||||
"recovery_keys_generate": "Genera codici di recupero",
|
"recovery_keys_generate": "Genera codici di recupero",
|
||||||
"recovery_keys_regenerate": "Rigenera i codici di recupero",
|
"recovery_keys_regenerate": "Rigenera i codici di recupero",
|
||||||
"recovery_keys_used": "Utilizzato: {{data}}",
|
"recovery_keys_used": "Utilizzato: {{date}}",
|
||||||
"recovery_keys_unused": "Il codice di ripristino {{index}} non è utilizzato",
|
"recovery_keys_unused": "Il codice di ripristino {{index}} non è utilizzato",
|
||||||
"oauth_title": "OAuth/OpenID",
|
"oauth_title": "OAuth/OpenID",
|
||||||
"oauth_description": "OpenID è un metodo standardizzato che ti consente di accedere ai siti web utilizzando un account di un altro servizio, come Google, per verificare la tua identità. L'emittente predefinito è Google, ma puoi cambiarlo con qualsiasi altro provider OpenID. Per ulteriori informazioni, consulta <a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">qui</a>. Segui queste <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">istruzioni</a> per configurare un servizio OpenID tramite Google.",
|
"oauth_description": "OpenID è un metodo standardizzato che ti consente di accedere ai siti web utilizzando un account di un altro servizio, come Google, per verificare la tua identità. L'emittente predefinito è Google, ma puoi cambiarlo con qualsiasi altro provider OpenID. Per ulteriori informazioni, consulta <a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">qui</a>. Segui queste <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">istruzioni</a> per configurare un servizio OpenID tramite Google.",
|
||||||
"oauth_description_warning": "Per abilitare OAuth/OpenID, è necessario impostare l'URL di base di OAuth/OpenID, l'ID client e il segreto client nel file config.ini e riavviare l'applicazione. Per impostare le variabili d'ambiente, impostare TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID e TRILIUM_OAUTH_CLIENT_SECRET.",
|
"oauth_description_warning": "Per abilitare OAuth/OpenID, è necessario impostare l'URL di base di OAuth/OpenID, l'ID client e il segreto client nel file config.ini e riavviare l'applicazione. Per impostare le variabili d'ambiente, impostare TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID e TRILIUM_OAUTH_CLIENT_SECRET.",
|
||||||
"oauth_missing_vars": "Impostazioni mancanti: {{-variabili}}",
|
"oauth_missing_vars": "Impostazioni mancanti: {{-variables}}",
|
||||||
"oauth_user_account": "Account utente: ",
|
"oauth_user_account": "Account utente: ",
|
||||||
"oauth_user_email": "Email utente: ",
|
"oauth_user_email": "Email utente: ",
|
||||||
"oauth_user_not_logged_in": "Non hai effettuato l'accesso!"
|
"oauth_user_not_logged_in": "Non hai effettuato l'accesso!"
|
||||||
@@ -1858,7 +1857,9 @@
|
|||||||
"window-on-top": "Mantieni la finestra in primo piano"
|
"window-on-top": "Mantieni la finestra in primo piano"
|
||||||
},
|
},
|
||||||
"note_detail": {
|
"note_detail": {
|
||||||
"could_not_find_typewidget": "Impossibile trovare typeWidget per il tipo '{{type}}'"
|
"could_not_find_typewidget": "Impossibile trovare typeWidget per il tipo '{{type}}'",
|
||||||
|
"printing": "Stampa in corso...",
|
||||||
|
"printing_pdf": "Esportazione in PDF in corso..."
|
||||||
},
|
},
|
||||||
"note_title": {
|
"note_title": {
|
||||||
"placeholder": "scrivi qui il titolo della nota..."
|
"placeholder": "scrivi qui il titolo della nota..."
|
||||||
@@ -1909,7 +1910,7 @@
|
|||||||
},
|
},
|
||||||
"frontend_script_api": {
|
"frontend_script_api": {
|
||||||
"async_warning": "Stai passando una funzione asincrona a `api.runOnBackend()` che probabilmente non funzionerà come previsto.\\nRendi la funzione sincrona (rimuovendo la parola chiave `async`) oppure usa `api.runAsyncOnBackendWithManualTransactionHandling()`.",
|
"async_warning": "Stai passando una funzione asincrona a `api.runOnBackend()` che probabilmente non funzionerà come previsto.\\nRendi la funzione sincrona (rimuovendo la parola chiave `async`) oppure usa `api.runAsyncOnBackendWithManualTransactionHandling()`.",
|
||||||
"sync_warning": "Stai passando una funzione sincrona a `api.runAsyncOnBackendWithManualTransactionHandling()`, mentre probabilmente dovresti usare `api.runOnBackend()`."
|
"sync_warning": "Stai passando una funzione sincrona a `api.runAsyncOnBackendWithManualTransactionHandling()`, \\nmentre probabilmente dovresti usare `api.runOnBackend()`."
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"sync-check-failed": "Controllo di sincronizzazione fallito!",
|
"sync-check-failed": "Controllo di sincronizzazione fallito!",
|
||||||
@@ -2044,7 +2045,7 @@
|
|||||||
"slide-overview": "Attiva/disattiva una panoramica delle diapositive"
|
"slide-overview": "Attiva/disattiva una panoramica delle diapositive"
|
||||||
},
|
},
|
||||||
"command_palette": {
|
"command_palette": {
|
||||||
"tree-action-name": "Albero: {{nome}}",
|
"tree-action-name": "Albero: {{name}}",
|
||||||
"export_note_title": "Nota di esportazione",
|
"export_note_title": "Nota di esportazione",
|
||||||
"export_note_description": "Esporta la nota corrente",
|
"export_note_description": "Esporta la nota corrente",
|
||||||
"show_attachments_title": "Mostra allegati",
|
"show_attachments_title": "Mostra allegati",
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
},
|
},
|
||||||
"left_pane_toggle": {
|
"left_pane_toggle": {
|
||||||
"show_panel": "パネルを表示",
|
"show_panel": "パネルを表示",
|
||||||
"hide_panel": "パネルを隠す"
|
"hide_panel": "パネルを非表示"
|
||||||
},
|
},
|
||||||
"move_pane_button": {
|
"move_pane_button": {
|
||||||
"move_left": "左に移動",
|
"move_left": "左に移動",
|
||||||
@@ -741,7 +741,7 @@
|
|||||||
"new-column": "新しい列",
|
"new-column": "新しい列",
|
||||||
"sort-column-by": "\"{{title}}\" で並べ替え",
|
"sort-column-by": "\"{{title}}\" で並べ替え",
|
||||||
"sort-column-clear": "並べ替えをクリア",
|
"sort-column-clear": "並べ替えをクリア",
|
||||||
"hide-column": "列 \"{{title}}\" を隠す",
|
"hide-column": "列 \"{{title}}\" を非表示",
|
||||||
"show-hide-columns": "列を表示/非表示",
|
"show-hide-columns": "列を表示/非表示",
|
||||||
"row-insert-above": "上に行を挿入",
|
"row-insert-above": "上に行を挿入",
|
||||||
"row-insert-below": "下に行を挿入",
|
"row-insert-below": "下に行を挿入",
|
||||||
@@ -1200,7 +1200,7 @@
|
|||||||
"collapse-title": "ノートツリーを折りたたむ",
|
"collapse-title": "ノートツリーを折りたたむ",
|
||||||
"scroll-active-title": "アクティブノートまでスクロール",
|
"scroll-active-title": "アクティブノートまでスクロール",
|
||||||
"tree-settings-title": "ツリーの設定",
|
"tree-settings-title": "ツリーの設定",
|
||||||
"hide-archived-notes": "アーカイブノートを隠す",
|
"hide-archived-notes": "アーカイブノートを非表示",
|
||||||
"automatically-collapse-notes": "ノートを自動的に折りたたむ",
|
"automatically-collapse-notes": "ノートを自動的に折りたたむ",
|
||||||
"automatically-collapse-notes-title": "一定期間使用されないと、ツリーを整理するためにノートは折りたたまれます。",
|
"automatically-collapse-notes-title": "一定期間使用されないと、ツリーを整理するためにノートは折りたたまれます。",
|
||||||
"save-changes": "変更を保存して適用",
|
"save-changes": "変更を保存して適用",
|
||||||
|
|||||||
@@ -13,6 +13,13 @@
|
|||||||
"critical-error": {
|
"critical-error": {
|
||||||
"title": "Kritische Error",
|
"title": "Kritische Error",
|
||||||
"message": "Een kritieke fout heeft plaatsgevonden waardoor de cliënt zich aanmeldt vanaf het begin:\n\n84X\n\nDit is waarschijnlijk veroorzaakt door een script dat op een onverwachte manier faalt. Probeer de sollicitatie in veilige modus te starten en de kwestie aan te spreken."
|
"message": "Een kritieke fout heeft plaatsgevonden waardoor de cliënt zich aanmeldt vanaf het begin:\n\n84X\n\nDit is waarschijnlijk veroorzaakt door een script dat op een onverwachte manier faalt. Probeer de sollicitatie in veilige modus te starten en de kwestie aan te spreken."
|
||||||
|
},
|
||||||
|
"widget-error": {
|
||||||
|
"title": "Starten widget mislukt",
|
||||||
|
"message-unknown": "Onbekende widget kan niet gestart worden omdat:\n\n{{message}}"
|
||||||
|
},
|
||||||
|
"bundle-error": {
|
||||||
|
"title": "Custom script laden mislukt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"add_link": {
|
"add_link": {
|
||||||
|
|||||||
@@ -320,7 +320,8 @@
|
|||||||
"explodeArchivesTooltip": "Если этот флажок установлен, Trilium будет читать файлы <code>.zip</code>, <code>.enex</code> и <code>.opml</code> и создавать заметки из файлов внутри этих архивов. Если флажок не установлен, Trilium будет прикреплять сами архивы к заметке.",
|
"explodeArchivesTooltip": "Если этот флажок установлен, Trilium будет читать файлы <code>.zip</code>, <code>.enex</code> и <code>.opml</code> и создавать заметки из файлов внутри этих архивов. Если флажок не установлен, Trilium будет прикреплять сами архивы к заметке.",
|
||||||
"explodeArchives": "Прочитать содержимое архивов <code>.zip</code>, <code>.enex</code> и <code>.opml</code>.",
|
"explodeArchives": "Прочитать содержимое архивов <code>.zip</code>, <code>.enex</code> и <code>.opml</code>.",
|
||||||
"shrinkImagesTooltip": "<p>Если этот параметр включен, Trilium попытается уменьшить размер импортируемых изображений путём масштабирования и оптимизации, что может повлиять на воспринимаемое качество изображения. Если этот параметр не установлен, изображения будут импортированы без изменений.</p><p>Это не относится к импорту файлов <code>.zip</code> с метаданными, поскольку предполагается, что эти файлы уже оптимизированы.</p>",
|
"shrinkImagesTooltip": "<p>Если этот параметр включен, Trilium попытается уменьшить размер импортируемых изображений путём масштабирования и оптимизации, что может повлиять на воспринимаемое качество изображения. Если этот параметр не установлен, изображения будут импортированы без изменений.</p><p>Это не относится к импорту файлов <code>.zip</code> с метаданными, поскольку предполагается, что эти файлы уже оптимизированы.</p>",
|
||||||
"codeImportedAsCode": "Импортировать распознанные файлы кода (например, <code>.json</code>) в виде заметок типа \"код\", если это неясно из метаданных"
|
"codeImportedAsCode": "Импортировать распознанные файлы кода (например, <code>.json</code>) в виде заметок типа \"код\", если это неясно из метаданных",
|
||||||
|
"importZipRecommendation": "При импорте ZIP файла иерархия заметок будет отражена в структуре папок внутри архива."
|
||||||
},
|
},
|
||||||
"markdown_import": {
|
"markdown_import": {
|
||||||
"dialog_title": "Импорт Markdown",
|
"dialog_title": "Импорт Markdown",
|
||||||
@@ -980,7 +981,8 @@
|
|||||||
"open_sql_console_history": "Открыть историю консоли SQL",
|
"open_sql_console_history": "Открыть историю консоли SQL",
|
||||||
"show_shared_notes_subtree": "Поддерево общедоступных заметок",
|
"show_shared_notes_subtree": "Поддерево общедоступных заметок",
|
||||||
"switch_to_mobile_version": "Перейти на мобильную версию",
|
"switch_to_mobile_version": "Перейти на мобильную версию",
|
||||||
"switch_to_desktop_version": "Переключиться на версию для ПК"
|
"switch_to_desktop_version": "Переключиться на версию для ПК",
|
||||||
|
"new-version-available": "Доступно обновление"
|
||||||
},
|
},
|
||||||
"zpetne_odkazy": {
|
"zpetne_odkazy": {
|
||||||
"backlink": "{{count}} ссылки",
|
"backlink": "{{count}} ссылки",
|
||||||
|
|||||||
@@ -184,7 +184,8 @@
|
|||||||
},
|
},
|
||||||
"import-status": "匯入狀態",
|
"import-status": "匯入狀態",
|
||||||
"in-progress": "正在匯入:{{progress}}",
|
"in-progress": "正在匯入:{{progress}}",
|
||||||
"successful": "匯入成功。"
|
"successful": "匯入成功。",
|
||||||
|
"importZipRecommendation": "匯入 ZIP 檔案時,筆記層級將反映壓縮檔內的子目錄結構。"
|
||||||
},
|
},
|
||||||
"include_note": {
|
"include_note": {
|
||||||
"dialog_title": "內嵌筆記",
|
"dialog_title": "內嵌筆記",
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
},
|
},
|
||||||
"add_link": {
|
"add_link": {
|
||||||
"add_link": "Thêm liên kết",
|
"add_link": "Thêm liên kết",
|
||||||
"button_add_link": "Thêm liên kết"
|
"button_add_link": "Thêm liên kết",
|
||||||
|
"help_on_links": "Trợ giúp về các liên kết"
|
||||||
},
|
},
|
||||||
"bulk_actions": {
|
"bulk_actions": {
|
||||||
"other": "Khác"
|
"other": "Khác"
|
||||||
@@ -41,7 +42,13 @@
|
|||||||
"message": "Đã xảy ra lỗi nghiêm trọng ngăn ứng dụng client khởi động\n\n{{message}}\n\nĐiều này có khả năng bị gây ra bởi một script hoạt động không như mong đợi. Hãy thử khởi động ứng dụng ở chế độ an toàn và giải quyết vấn đề."
|
"message": "Đã xảy ra lỗi nghiêm trọng ngăn ứng dụng client khởi động\n\n{{message}}\n\nĐiều này có khả năng bị gây ra bởi một script hoạt động không như mong đợi. Hãy thử khởi động ứng dụng ở chế độ an toàn và giải quyết vấn đề."
|
||||||
},
|
},
|
||||||
"widget-error": {
|
"widget-error": {
|
||||||
"title": "Khởi tạo widget thất bại"
|
"title": "Khởi tạo widget thất bại",
|
||||||
|
"message-custom": "Tiện ích tùy chỉnh từ ghi chú với ID \"{{id}}\", tiêu đề \"{{title}}\" không thể khởi tạo vì:\n\n{{message}}",
|
||||||
|
"message-unknown": "Tiện ích chưa biết không thể được khởi tạo vì:\n\n{{message}}"
|
||||||
|
},
|
||||||
|
"bundle-error": {
|
||||||
|
"title": "Tải script tùy chọn thất bại",
|
||||||
|
"message": "Script từ ghi chú ID \"{{id}}\", tiêu đề \"{{title}}\" không thể chạy được vì:\n\n{{message}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
|
|||||||
1
apps/client/src/types.d.ts
vendored
1
apps/client/src/types.d.ts
vendored
@@ -26,7 +26,6 @@ interface CustomGlobals {
|
|||||||
appContext: AppContext;
|
appContext: AppContext;
|
||||||
froca: Froca;
|
froca: Froca;
|
||||||
treeCache: Froca;
|
treeCache: Froca;
|
||||||
importMarkdownInline: () => Promise<unknown>;
|
|
||||||
SEARCH_HELP_TEXT: string;
|
SEARCH_HELP_TEXT: string;
|
||||||
activeDialog: JQuery<HTMLElement> | null;
|
activeDialog: JQuery<HTMLElement> | null;
|
||||||
componentId: string;
|
componentId: string;
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
|
this.$widget.attr("title", this.title);
|
||||||
this.tooltip = new Tooltip(this.$tooltip[0], {
|
this.tooltip = Tooltip.getOrCreateInstance(this.$widget[0], {
|
||||||
|
trigger: "hover",
|
||||||
placement: handleRightToLeftPlacement(this.settings.titlePlacement),
|
placement: handleRightToLeftPlacement(this.settings.titlePlacement),
|
||||||
fallbackPlacements: [ handleRightToLeftPlacement(this.settings.titlePlacement) ]
|
fallbackPlacements: [ handleRightToLeftPlacement(this.settings.titlePlacement) ]
|
||||||
});
|
});
|
||||||
@@ -56,9 +57,7 @@ export default class RightDropdownButtonWidget extends BasicWidget {
|
|||||||
this.$widget
|
this.$widget
|
||||||
.find(".right-dropdown-button")
|
.find(".right-dropdown-button")
|
||||||
.addClass(this.iconClass)
|
.addClass(this.iconClass)
|
||||||
.on("click", () => this.tooltip.hide())
|
.on("click", () => this.tooltip.hide());
|
||||||
.on("mouseenter", () => this.tooltip.show())
|
|
||||||
.on("mouseleave", () => this.tooltip.hide());
|
|
||||||
|
|
||||||
this.$widget.on("show.bs.dropdown", async () => {
|
this.$widget.on("show.bs.dropdown", async () => {
|
||||||
await this.dropdownShown();
|
await this.dropdownShown();
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, (() => Promise<{ de
|
|||||||
de: () => import("@fullcalendar/core/locales/de"),
|
de: () => import("@fullcalendar/core/locales/de"),
|
||||||
es: () => import("@fullcalendar/core/locales/es"),
|
es: () => import("@fullcalendar/core/locales/es"),
|
||||||
fr: () => import("@fullcalendar/core/locales/fr"),
|
fr: () => import("@fullcalendar/core/locales/fr"),
|
||||||
|
it: () => import("@fullcalendar/core/locales/it"),
|
||||||
cn: () => import("@fullcalendar/core/locales/zh-cn"),
|
cn: () => import("@fullcalendar/core/locales/zh-cn"),
|
||||||
tw: () => import("@fullcalendar/core/locales/zh-tw"),
|
tw: () => import("@fullcalendar/core/locales/zh-tw"),
|
||||||
ro: () => import("@fullcalendar/core/locales/ro"),
|
ro: () => import("@fullcalendar/core/locales/ro"),
|
||||||
|
|||||||
@@ -141,7 +141,11 @@ function NoteContent({ note, trim, noChildrenList, highlightedTokens }: { note:
|
|||||||
})
|
})
|
||||||
.then(({ $renderedContent, type }) => {
|
.then(({ $renderedContent, type }) => {
|
||||||
if (!contentRef.current) return;
|
if (!contentRef.current) return;
|
||||||
|
if ($renderedContent[0].innerHTML) {
|
||||||
contentRef.current.replaceChildren(...$renderedContent);
|
contentRef.current.replaceChildren(...$renderedContent);
|
||||||
|
} else {
|
||||||
|
contentRef.current.replaceChildren();
|
||||||
|
}
|
||||||
contentRef.current.classList.add(`type-${type}`);
|
contentRef.current.classList.add(`type-${type}`);
|
||||||
highlightSearch(contentRef.current);
|
highlightSearch(contentRef.current);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ export default function ExportDialog() {
|
|||||||
values={[
|
values={[
|
||||||
{ value: "html", label: t("export.format_html_zip") },
|
{ value: "html", label: t("export.format_html_zip") },
|
||||||
{ value: "markdown", label: t("export.format_markdown") },
|
{ value: "markdown", label: t("export.format_markdown") },
|
||||||
{ value: "opml", label: t("export.format_opml") }
|
{ value: "opml", label: t("export.format_opml") },
|
||||||
|
{ value: "share", label: t("export.share-format") }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import utils from "../../services/utils";
|
|||||||
import Modal from "../react/Modal";
|
import Modal from "../react/Modal";
|
||||||
import Button from "../react/Button";
|
import Button from "../react/Button";
|
||||||
import { useTriliumEvent } from "../react/hooks";
|
import { useTriliumEvent } from "../react/hooks";
|
||||||
|
import EditableTextTypeWidget from "../type_widgets/editable_text";
|
||||||
|
|
||||||
interface RenderMarkdownResponse {
|
interface RenderMarkdownResponse {
|
||||||
htmlContent: string;
|
htmlContent: string;
|
||||||
@@ -14,39 +15,34 @@ interface RenderMarkdownResponse {
|
|||||||
|
|
||||||
export default function MarkdownImportDialog() {
|
export default function MarkdownImportDialog() {
|
||||||
const markdownImportTextArea = useRef<HTMLTextAreaElement>(null);
|
const markdownImportTextArea = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const [textTypeWidget, setTextTypeWidget] = useState<EditableTextTypeWidget>();
|
||||||
const [ text, setText ] = useState("");
|
const [ text, setText ] = useState("");
|
||||||
const [ shown, setShown ] = useState(false);
|
const [ shown, setShown ] = useState(false);
|
||||||
|
|
||||||
const triggerImport = useCallback(() => {
|
useTriliumEvent("showPasteMarkdownDialog", ({ textTypeWidget }) => {
|
||||||
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
|
setTextTypeWidget(textTypeWidget);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const { clipboard } = utils.dynamicRequire("electron");
|
const { clipboard } = utils.dynamicRequire("electron");
|
||||||
const text = clipboard.readText();
|
const text = clipboard.readText();
|
||||||
|
|
||||||
convertMarkdownToHtml(text);
|
convertMarkdownToHtml(text, textTypeWidget);
|
||||||
} else {
|
} else {
|
||||||
setShown(true);
|
setShown(true);
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
useTriliumEvent("importMarkdownInline", triggerImport);
|
|
||||||
useTriliumEvent("pasteMarkdownIntoText", triggerImport);
|
|
||||||
|
|
||||||
async function sendForm() {
|
|
||||||
await convertMarkdownToHtml(text);
|
|
||||||
setText("");
|
|
||||||
setShown(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="markdown-import-dialog" title={t("markdown_import.dialog_title")} size="lg"
|
className="markdown-import-dialog" title={t("markdown_import.dialog_title")} size="lg"
|
||||||
footer={<Button className="markdown-import-button" text={t("markdown_import.import_button")} onClick={sendForm} keyboardShortcut="Ctrl+Space" />}
|
footer={<Button className="markdown-import-button" text={t("markdown_import.import_button")} onClick={() => setShown(false)} keyboardShortcut="Ctrl+Enter" />}
|
||||||
onShown={() => markdownImportTextArea.current?.focus()}
|
onShown={() => markdownImportTextArea.current?.focus()}
|
||||||
onHidden={() => setShown(false) }
|
onHidden={async () => {
|
||||||
|
if (textTypeWidget) {
|
||||||
|
await convertMarkdownToHtml(text, textTypeWidget);
|
||||||
|
}
|
||||||
|
setShown(false);
|
||||||
|
setText("");
|
||||||
|
}}
|
||||||
show={shown}
|
show={shown}
|
||||||
>
|
>
|
||||||
<p>{t("markdown_import.modal_body_text")}</p>
|
<p>{t("markdown_import.modal_body_text")}</p>
|
||||||
@@ -56,26 +52,17 @@ export default function MarkdownImportDialog() {
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter" && e.ctrlKey) {
|
if (e.key === "Enter" && e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendForm();
|
setShown(false);
|
||||||
}
|
}
|
||||||
}}></textarea>
|
}}></textarea>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function convertMarkdownToHtml(markdownContent: string) {
|
async function convertMarkdownToHtml(markdownContent: string, textTypeWidget: EditableTextTypeWidget) {
|
||||||
const { htmlContent } = await server.post<RenderMarkdownResponse>("other/render-markdown", { markdownContent });
|
const { htmlContent } = await server.post<RenderMarkdownResponse>("other/render-markdown", { markdownContent });
|
||||||
|
|
||||||
const textEditor = await appContext.tabManager.getActiveContext()?.getTextEditor();
|
await textTypeWidget.addHtmlToEditor(htmlContent);
|
||||||
if (!textEditor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewFragment = textEditor.data.processor.toView(htmlContent);
|
|
||||||
const modelFragment = textEditor.data.toModel(viewFragment);
|
|
||||||
|
|
||||||
textEditor.model.insertContent(modelFragment, textEditor.model.document.selection);
|
|
||||||
textEditor.editing.view.focus();
|
|
||||||
|
|
||||||
toast.showMessage(t("markdown_import.import_success"));
|
toast.showMessage(t("markdown_import.import_success"));
|
||||||
}
|
}
|
||||||
@@ -155,6 +155,11 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid not showing recent notes when creating a new empty tab.
|
||||||
|
if ("noteContext" in data && data.noteContext.ntxId !== "_popup-editor") {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
return super.handleEventInChildren(name, data);
|
return super.handleEventInChildren(name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,12 @@ const categories: Category[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const icons: Icon[] = [
|
const icons: Icon[] = [
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
slug: "empty",
|
||||||
|
category_id: 113,
|
||||||
|
type_of_icon: "REGULAR"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "child",
|
name: "child",
|
||||||
slug: "child-regular",
|
slug: "child-regular",
|
||||||
|
|||||||
@@ -57,3 +57,15 @@
|
|||||||
.note-icon-widget .icon-list span:hover {
|
.note-icon-widget .icon-list span:hover {
|
||||||
border: 1px solid var(--main-border-color);
|
border: 1px solid var(--main-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-icon-widget .icon-list span.bx-empty {
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-widget .icon-list span.bx-empty::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: "";
|
||||||
|
border: 1px dashed var(--muted-text-color);
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useNoteContext, useTriliumOption } from "../react/hooks";
|
import { useTriliumOption } from "../react/hooks";
|
||||||
|
import { TabContext } from "./ribbon-interface";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the editing toolbar when the CKEditor is in decoupled mode.
|
* Handles the editing toolbar when the CKEditor is in decoupled mode.
|
||||||
@@ -9,16 +10,10 @@ import { useNoteContext, useTriliumOption } from "../react/hooks";
|
|||||||
*
|
*
|
||||||
* ! The toolbar is not only used in the ribbon, but also in the quick edit feature.
|
* ! The toolbar is not only used in the ribbon, but also in the quick edit feature.
|
||||||
*/
|
*/
|
||||||
export default function FormattingToolbar({ hidden }: { hidden?: boolean }) {
|
export default function FormattingToolbar({ hidden }: TabContext) {
|
||||||
const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType");
|
const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType");
|
||||||
|
|
||||||
return (textNoteEditorType === "ckeditor-classic" &&
|
return (textNoteEditorType === "ckeditor-classic" &&
|
||||||
<div className={`classic-toolbar-widget ${hidden ? "hidden-ext" : ""}`} />
|
<div className={`classic-toolbar-widget ${hidden ? "hidden-ext" : ""}`} />
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PopupEditorFormattingToolbar() {
|
|
||||||
// TODO: Integrate this directly once we migrate away from class components.
|
|
||||||
const { note } = useNoteContext();
|
|
||||||
return <FormattingToolbar hidden={note?.type !== "text"} />;
|
|
||||||
}
|
|
||||||
@@ -46,7 +46,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment();
|
||||||
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type);
|
||||||
const isInOptions = note.noteId.startsWith("_options");
|
const isInOptionsOrHelp = note?.noteId.startsWith("_options") || note?.noteId.startsWith("_help");
|
||||||
const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && note.getLabelValue("viewType") === "presentation");
|
const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && note.getLabelValue("viewType") === "presentation");
|
||||||
const isElectron = getIsElectron();
|
const isElectron = getIsElectron();
|
||||||
const isMac = getIsMac();
|
const isMac = getIsMac();
|
||||||
@@ -69,10 +69,10 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
||||||
disabled={isInOptions || note.type === "search"}
|
disabled={isInOptionsOrHelp || note.type === "search"}
|
||||||
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
||||||
<CommandItem icon="bx bx-export" text={t("note_actions.export_note")}
|
<CommandItem icon="bx bx-export" text={t("note_actions.export_note")}
|
||||||
disabled={isInOptions || note.noteId === "_backendLog"}
|
disabled={isInOptionsOrHelp || note.noteId === "_backendLog"}
|
||||||
command={() => noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", {
|
command={() => noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", {
|
||||||
notePath: noteContext.notePath,
|
notePath: noteContext.notePath,
|
||||||
defaultType: "single"
|
defaultType: "single"
|
||||||
@@ -84,14 +84,14 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
|||||||
<CommandItem command="showNoteSource" icon="bx bx-code" disabled={!hasSource} text={t("note_actions.note_source")} />
|
<CommandItem command="showNoteSource" icon="bx bx-code" disabled={!hasSource} text={t("note_actions.note_source")} />
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem command="forceSaveRevision" icon="bx bx-save" disabled={isInOptions} text={t("note_actions.save_revision")} />
|
<CommandItem command="forceSaveRevision" icon="bx bx-save" disabled={isInOptionsOrHelp} text={t("note_actions.save_revision")} />
|
||||||
<CommandItem icon="bx bx-trash destructive-action-icon" text={t("note_actions.delete_note")} destructive
|
<CommandItem icon="bx bx-trash destructive-action-icon" text={t("note_actions.delete_note")} destructive
|
||||||
disabled={isInOptions}
|
disabled={isInOptionsOrHelp}
|
||||||
command={() => branches.deleteNotes([note.getParentBranches()[0].branchId])}
|
command={() => branches.deleteNotes([note.getParentBranches()[0].branchId])}
|
||||||
/>
|
/>
|
||||||
<FormDropdownDivider />
|
<FormDropdownDivider />
|
||||||
|
|
||||||
<CommandItem command="showAttachments" icon="bx bx-paperclip" disabled={isInOptions} text={t("note_actions.note_attachments")} />
|
<CommandItem command="showAttachments" icon="bx bx-paperclip" disabled={isInOptionsOrHelp} text={t("note_actions.note_attachments")} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,163 +1,15 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
import { t } from "../../services/i18n";
|
|
||||||
import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks";
|
import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks";
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
import { VNode } from "preact";
|
|
||||||
import BasicPropertiesTab from "./BasicPropertiesTab";
|
|
||||||
import FormattingToolbar from "./FormattingToolbar";
|
|
||||||
import { numberObjectsInPlace } from "../../services/utils";
|
import { numberObjectsInPlace } from "../../services/utils";
|
||||||
import { TabContext } from "./ribbon-interface";
|
|
||||||
import options from "../../services/options";
|
|
||||||
import { EventNames } from "../../components/app_context";
|
import { EventNames } from "../../components/app_context";
|
||||||
import FNote from "../../entities/fnote";
|
|
||||||
import ScriptTab from "./ScriptTab";
|
|
||||||
import EditedNotesTab from "./EditedNotesTab";
|
|
||||||
import NotePropertiesTab from "./NotePropertiesTab";
|
|
||||||
import NoteInfoTab from "./NoteInfoTab";
|
|
||||||
import SimilarNotesTab from "./SimilarNotesTab";
|
|
||||||
import FilePropertiesTab from "./FilePropertiesTab";
|
|
||||||
import ImagePropertiesTab from "./ImagePropertiesTab";
|
|
||||||
import NotePathsTab from "./NotePathsTab";
|
|
||||||
import NoteMapTab from "./NoteMapTab";
|
|
||||||
import OwnedAttributesTab from "./OwnedAttributesTab";
|
|
||||||
import InheritedAttributesTab from "./InheritedAttributesTab";
|
|
||||||
import CollectionPropertiesTab from "./CollectionPropertiesTab";
|
|
||||||
import SearchDefinitionTab from "./SearchDefinitionTab";
|
|
||||||
import NoteActions from "./NoteActions";
|
import NoteActions from "./NoteActions";
|
||||||
import { KeyboardActionNames } from "@triliumnext/commons";
|
import { KeyboardActionNames } from "@triliumnext/commons";
|
||||||
|
import { RIBBON_TAB_DEFINITIONS } from "./RibbonDefinition";
|
||||||
|
import { TabConfiguration, TitleContext } from "./ribbon-interface";
|
||||||
|
|
||||||
interface TitleContext {
|
const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>(RIBBON_TAB_DEFINITIONS);
|
||||||
note: FNote | null | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TabConfiguration {
|
|
||||||
title: string | ((context: TitleContext) => string);
|
|
||||||
icon: string;
|
|
||||||
content: (context: TabContext) => VNode | false;
|
|
||||||
show: boolean | ((context: TitleContext) => boolean | null | undefined);
|
|
||||||
toggleCommand?: KeyboardActionNames;
|
|
||||||
activate?: boolean | ((context: TitleContext) => boolean);
|
|
||||||
/**
|
|
||||||
* By default the tab content will not be rendered unless the tab is active (i.e. selected by the user). Setting to `true` will ensure that the tab is rendered even when inactive, for cases where the tab needs to be accessible at all times (e.g. for the detached editor toolbar) or if event handling is needed.
|
|
||||||
*/
|
|
||||||
stayInDom?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>([
|
|
||||||
{
|
|
||||||
title: t("classic_editor_toolbar.title"),
|
|
||||||
icon: "bx bx-text",
|
|
||||||
show: ({ note }) => note?.type === "text" && options.get("textNoteEditorType") === "ckeditor-classic",
|
|
||||||
toggleCommand: "toggleRibbonTabClassicEditor",
|
|
||||||
content: FormattingToolbar,
|
|
||||||
activate: true,
|
|
||||||
stayInDom: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: ({ note }) => note?.isTriliumSqlite() ? t("script_executor.query") : t("script_executor.script"),
|
|
||||||
icon: "bx bx-play",
|
|
||||||
content: ScriptTab,
|
|
||||||
activate: true,
|
|
||||||
show: ({ note }) => note &&
|
|
||||||
(note.isTriliumScript() || note.isTriliumSqlite()) &&
|
|
||||||
(note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("search_definition.search_parameters"),
|
|
||||||
icon: "bx bx-search",
|
|
||||||
content: SearchDefinitionTab,
|
|
||||||
activate: true,
|
|
||||||
show: ({ note }) => note?.type === "search"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("edited_notes.title"),
|
|
||||||
icon: "bx bx-calendar-edit",
|
|
||||||
content: EditedNotesTab,
|
|
||||||
show: ({ note }) => note?.hasOwnedLabel("dateNote"),
|
|
||||||
activate: ({ note }) => (note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("book_properties.book_properties"),
|
|
||||||
icon: "bx bx-book",
|
|
||||||
content: CollectionPropertiesTab,
|
|
||||||
show: ({ note }) => note?.type === "book" || note?.type === "search",
|
|
||||||
toggleCommand: "toggleRibbonTabBookProperties"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("note_properties.info"),
|
|
||||||
icon: "bx bx-info-square",
|
|
||||||
content: NotePropertiesTab,
|
|
||||||
show: ({ note }) => !!note?.getLabelValue("pageUrl"),
|
|
||||||
activate: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("file_properties.title"),
|
|
||||||
icon: "bx bx-file",
|
|
||||||
content: FilePropertiesTab,
|
|
||||||
show: ({ note }) => note?.type === "file",
|
|
||||||
toggleCommand: "toggleRibbonTabFileProperties",
|
|
||||||
activate: ({ note }) => note?.mime !== "application/pdf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("image_properties.title"),
|
|
||||||
icon: "bx bx-image",
|
|
||||||
content: ImagePropertiesTab,
|
|
||||||
show: ({ note }) => note?.type === "image",
|
|
||||||
toggleCommand: "toggleRibbonTabImageProperties",
|
|
||||||
activate: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// BasicProperties
|
|
||||||
title: t("basic_properties.basic_properties"),
|
|
||||||
icon: "bx bx-slider",
|
|
||||||
content: BasicPropertiesTab,
|
|
||||||
show: ({note}) => !note?.isLaunchBarConfig(),
|
|
||||||
toggleCommand: "toggleRibbonTabBasicProperties"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("owned_attribute_list.owned_attributes"),
|
|
||||||
icon: "bx bx-list-check",
|
|
||||||
content: OwnedAttributesTab,
|
|
||||||
show: ({note}) => !note?.isLaunchBarConfig(),
|
|
||||||
toggleCommand: "toggleRibbonTabOwnedAttributes",
|
|
||||||
stayInDom: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("inherited_attribute_list.title"),
|
|
||||||
icon: "bx bx-list-plus",
|
|
||||||
content: InheritedAttributesTab,
|
|
||||||
show: ({note}) => !note?.isLaunchBarConfig(),
|
|
||||||
toggleCommand: "toggleRibbonTabInheritedAttributes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("note_paths.title"),
|
|
||||||
icon: "bx bx-collection",
|
|
||||||
content: NotePathsTab,
|
|
||||||
show: true,
|
|
||||||
toggleCommand: "toggleRibbonTabNotePaths"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("note_map.title"),
|
|
||||||
icon: "bx bxs-network-chart",
|
|
||||||
content: NoteMapTab,
|
|
||||||
show: true,
|
|
||||||
toggleCommand: "toggleRibbonTabNoteMap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("similar_notes.title"),
|
|
||||||
icon: "bx bx-bar-chart",
|
|
||||||
show: ({ note }) => note?.type !== "search" && !note?.isLabelTruthy("similarNotesWidgetDisabled"),
|
|
||||||
content: SimilarNotesTab,
|
|
||||||
toggleCommand: "toggleRibbonTabSimilarNotes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("note_info_widget.title"),
|
|
||||||
icon: "bx bx-info-circle",
|
|
||||||
show: ({ note }) => !!note,
|
|
||||||
content: NoteInfoTab,
|
|
||||||
toggleCommand: "toggleRibbonTabNoteInfo"
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default function Ribbon() {
|
export default function Ribbon() {
|
||||||
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
|
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
|
||||||
|
|||||||
134
apps/client/src/widgets/ribbon/RibbonDefinition.ts
Normal file
134
apps/client/src/widgets/ribbon/RibbonDefinition.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import ScriptTab from "./ScriptTab";
|
||||||
|
import EditedNotesTab from "./EditedNotesTab";
|
||||||
|
import NotePropertiesTab from "./NotePropertiesTab";
|
||||||
|
import NoteInfoTab from "./NoteInfoTab";
|
||||||
|
import SimilarNotesTab from "./SimilarNotesTab";
|
||||||
|
import FilePropertiesTab from "./FilePropertiesTab";
|
||||||
|
import ImagePropertiesTab from "./ImagePropertiesTab";
|
||||||
|
import NotePathsTab from "./NotePathsTab";
|
||||||
|
import NoteMapTab from "./NoteMapTab";
|
||||||
|
import OwnedAttributesTab from "./OwnedAttributesTab";
|
||||||
|
import InheritedAttributesTab from "./InheritedAttributesTab";
|
||||||
|
import CollectionPropertiesTab from "./CollectionPropertiesTab";
|
||||||
|
import SearchDefinitionTab from "./SearchDefinitionTab";
|
||||||
|
import BasicPropertiesTab from "./BasicPropertiesTab";
|
||||||
|
import FormattingToolbar from "./FormattingToolbar";
|
||||||
|
import options from "../../services/options";
|
||||||
|
import { t } from "../../services/i18n";
|
||||||
|
import { TabConfiguration } from "./ribbon-interface";
|
||||||
|
|
||||||
|
export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
|
||||||
|
{
|
||||||
|
title: t("classic_editor_toolbar.title"),
|
||||||
|
icon: "bx bx-text",
|
||||||
|
show: ({ note }) => note?.type === "text" && options.get("textNoteEditorType") === "ckeditor-classic",
|
||||||
|
toggleCommand: "toggleRibbonTabClassicEditor",
|
||||||
|
content: FormattingToolbar,
|
||||||
|
activate: true,
|
||||||
|
stayInDom: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: ({ note }) => note?.isTriliumSqlite() ? t("script_executor.query") : t("script_executor.script"),
|
||||||
|
icon: "bx bx-play",
|
||||||
|
content: ScriptTab,
|
||||||
|
activate: true,
|
||||||
|
show: ({ note }) => note &&
|
||||||
|
(note.isTriliumScript() || note.isTriliumSqlite()) &&
|
||||||
|
(note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("search_definition.search_parameters"),
|
||||||
|
icon: "bx bx-search",
|
||||||
|
content: SearchDefinitionTab,
|
||||||
|
activate: true,
|
||||||
|
show: ({ note }) => note?.type === "search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("edited_notes.title"),
|
||||||
|
icon: "bx bx-calendar-edit",
|
||||||
|
content: EditedNotesTab,
|
||||||
|
show: ({ note }) => note?.hasOwnedLabel("dateNote"),
|
||||||
|
activate: ({ note }) => (note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("book_properties.book_properties"),
|
||||||
|
icon: "bx bx-book",
|
||||||
|
content: CollectionPropertiesTab,
|
||||||
|
show: ({ note }) => note?.type === "book" || note?.type === "search",
|
||||||
|
toggleCommand: "toggleRibbonTabBookProperties"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("note_properties.info"),
|
||||||
|
icon: "bx bx-info-square",
|
||||||
|
content: NotePropertiesTab,
|
||||||
|
show: ({ note }) => !!note?.getLabelValue("pageUrl"),
|
||||||
|
activate: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("file_properties.title"),
|
||||||
|
icon: "bx bx-file",
|
||||||
|
content: FilePropertiesTab,
|
||||||
|
show: ({ note }) => note?.type === "file",
|
||||||
|
toggleCommand: "toggleRibbonTabFileProperties",
|
||||||
|
activate: ({ note }) => note?.mime !== "application/pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("image_properties.title"),
|
||||||
|
icon: "bx bx-image",
|
||||||
|
content: ImagePropertiesTab,
|
||||||
|
show: ({ note }) => note?.type === "image",
|
||||||
|
toggleCommand: "toggleRibbonTabImageProperties",
|
||||||
|
activate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// BasicProperties
|
||||||
|
title: t("basic_properties.basic_properties"),
|
||||||
|
icon: "bx bx-slider",
|
||||||
|
content: BasicPropertiesTab,
|
||||||
|
show: ({note}) => !note?.isLaunchBarConfig(),
|
||||||
|
toggleCommand: "toggleRibbonTabBasicProperties"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("owned_attribute_list.owned_attributes"),
|
||||||
|
icon: "bx bx-list-check",
|
||||||
|
content: OwnedAttributesTab,
|
||||||
|
show: ({note}) => !note?.isLaunchBarConfig(),
|
||||||
|
toggleCommand: "toggleRibbonTabOwnedAttributes",
|
||||||
|
stayInDom: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("inherited_attribute_list.title"),
|
||||||
|
icon: "bx bx-list-plus",
|
||||||
|
content: InheritedAttributesTab,
|
||||||
|
show: ({note}) => !note?.isLaunchBarConfig(),
|
||||||
|
toggleCommand: "toggleRibbonTabInheritedAttributes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("note_paths.title"),
|
||||||
|
icon: "bx bx-collection",
|
||||||
|
content: NotePathsTab,
|
||||||
|
show: true,
|
||||||
|
toggleCommand: "toggleRibbonTabNotePaths"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("note_map.title"),
|
||||||
|
icon: "bx bxs-network-chart",
|
||||||
|
content: NoteMapTab,
|
||||||
|
show: true,
|
||||||
|
toggleCommand: "toggleRibbonTabNoteMap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("similar_notes.title"),
|
||||||
|
icon: "bx bx-bar-chart",
|
||||||
|
show: ({ note }) => note?.type !== "search" && !note?.isLabelTruthy("similarNotesWidgetDisabled"),
|
||||||
|
content: SimilarNotesTab,
|
||||||
|
toggleCommand: "toggleRibbonTabSimilarNotes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("note_info_widget.title"),
|
||||||
|
icon: "bx bx-info-circle",
|
||||||
|
show: ({ note }) => !!note,
|
||||||
|
content: NoteInfoTab,
|
||||||
|
toggleCommand: "toggleRibbonTabNoteInfo"
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -115,7 +115,7 @@ function SearchOption({ note, title, titleIcon, children, help, attributeName, a
|
|||||||
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[]
|
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[]
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr className={attributeName}>
|
||||||
<td className="title-column">
|
<td className="title-column">
|
||||||
{titleIcon && <><Icon icon={titleIcon} />{" "}</>}
|
{titleIcon && <><Icon icon={titleIcon} />{" "}</>}
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
174
apps/client/src/widgets/ribbon/SearchDefinitionTab.css
Normal file
174
apps/client/src/widgets/ribbon/SearchDefinitionTab.css
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
.search-setting-table {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table div {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .title-column {
|
||||||
|
/* minimal width so that table remains static sized and most space remains for middle column with settings */
|
||||||
|
width: 50px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .button-column {
|
||||||
|
/* minimal width so that table remains static sized and most space remains for middle column with settings */
|
||||||
|
width: 50px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: end;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .button-column .dropdown {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .button-column .dropdown-menu {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .button-column > * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-list hr {
|
||||||
|
height: 1px;
|
||||||
|
border-color: var(--main-border-color);
|
||||||
|
position: relative;
|
||||||
|
top: 4px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-definition-widget input:invalid {
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-search-option button {
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-header {
|
||||||
|
background-color: var(--accented-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-actions-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile .search-definition-widget {
|
||||||
|
contain: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
|
||||||
|
.search-setting-table {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tr {
|
||||||
|
padding: 0.5em 0;
|
||||||
|
border-bottom: 1px solid var(--main-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tr,
|
||||||
|
.search-setting-table td {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tbody {
|
||||||
|
display: block;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tbody:first-of-type {
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .add-search-option {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .add-search-option button {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-options tr,
|
||||||
|
.action-options tr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-options tr > td > div {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-options input {
|
||||||
|
max-width: 75vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .title-column {
|
||||||
|
width: unset;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
min-width: 30%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .button-column {
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: end;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table .button-column .bx-help-circle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tr.orderBy td:nth-of-type(2) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tr.searchString td:nth-of-type(2) {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tr.searchString .button-column {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-setting-table tr.ancestor > td > div {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-actions tr {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-actions-container {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-widget,
|
||||||
|
.note-list.list-view,
|
||||||
|
.note-list-wrapper {
|
||||||
|
overflow: unset;
|
||||||
|
height: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,8 +20,9 @@ import bulk_action, { ACTION_GROUPS } from "../../services/bulk_action";
|
|||||||
import { FormListHeader, FormListItem } from "../react/FormList";
|
import { FormListHeader, FormListItem } from "../react/FormList";
|
||||||
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
||||||
import { getErrorMessage } from "../../services/utils";
|
import { getErrorMessage } from "../../services/utils";
|
||||||
|
import "./SearchDefinitionTab.css";
|
||||||
|
|
||||||
export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
export default function SearchDefinitionTab({ note, ntxId, hidden }: TabContext) {
|
||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
|
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
|
||||||
const [ error, setError ] = useState<{ message: string }>();
|
const [ error, setError ] = useState<{ message: string }>();
|
||||||
@@ -75,7 +76,7 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
|||||||
return (
|
return (
|
||||||
<div className="search-definition-widget">
|
<div className="search-definition-widget">
|
||||||
<div className="search-settings">
|
<div className="search-settings">
|
||||||
{note &&
|
{note && !hidden &&
|
||||||
<table className="search-setting-table">
|
<table className="search-setting-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -110,10 +111,10 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
<BulkActionsList note={note} />
|
<BulkActionsList note={note} />
|
||||||
<tbody>
|
<tbody className="search-actions">
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={3}>
|
<td colSpan={3}>
|
||||||
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
|
<div className="search-actions-container">
|
||||||
<Button
|
<Button
|
||||||
icon="bx bx-search"
|
icon="bx bx-search"
|
||||||
text={t("search_definition.search_button")}
|
text={t("search_definition.search_button")}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { ComponentChildren } from "preact";
|
||||||
|
import { useNoteContext } from "../../react/hooks";
|
||||||
|
import { TabContext, TitleContext } from "../ribbon-interface";
|
||||||
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||||
|
import { RIBBON_TAB_DEFINITIONS } from "../RibbonDefinition";
|
||||||
|
|
||||||
|
interface StandaloneRibbonAdapterProps {
|
||||||
|
component: (props: TabContext) => ComponentChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in any ribbon tab component and renders it in standalone mod using the note context, thus requiring no inputs.
|
||||||
|
* Especially useful on mobile to detach components that would normally fit in the ribbon.
|
||||||
|
*/
|
||||||
|
export default function StandaloneRibbonAdapter({ component }: StandaloneRibbonAdapterProps) {
|
||||||
|
const Component = component;
|
||||||
|
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
|
||||||
|
const definition = useMemo(() => RIBBON_TAB_DEFINITIONS.find(def => def.content === component), [ component ]);
|
||||||
|
const [ shown, setShown ] = useState(unwrapShown(definition?.show, { note }));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShown(unwrapShown(definition?.show, { note }));
|
||||||
|
}, [ note ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
note={note}
|
||||||
|
hidden={!shown}
|
||||||
|
ntxId={ntxId}
|
||||||
|
hoistedNoteId={hoistedNoteId}
|
||||||
|
notePath={notePath}
|
||||||
|
noteContext={noteContext}
|
||||||
|
componentId={componentId}
|
||||||
|
activate={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapShown(value: boolean | ((context: TitleContext) => boolean | null | undefined) | undefined, context: TitleContext) {
|
||||||
|
if (!value) return true;
|
||||||
|
if (typeof value === "boolean") return value;
|
||||||
|
return !!value(context);
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { KeyboardActionNames } from "@triliumnext/commons";
|
||||||
import NoteContext from "../../components/note_context";
|
import NoteContext from "../../components/note_context";
|
||||||
import FNote from "../../entities/fnote";
|
import FNote from "../../entities/fnote";
|
||||||
|
import { VNode } from "preact";
|
||||||
|
|
||||||
export interface TabContext {
|
export interface TabContext {
|
||||||
note: FNote | null | undefined;
|
note: FNote | null | undefined;
|
||||||
@@ -11,3 +13,20 @@ export interface TabContext {
|
|||||||
componentId: string;
|
componentId: string;
|
||||||
activate(): void;
|
activate(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TitleContext {
|
||||||
|
note: FNote | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabConfiguration {
|
||||||
|
title: string | ((context: TitleContext) => string);
|
||||||
|
icon: string;
|
||||||
|
content: (context: TabContext) => VNode | false;
|
||||||
|
show: boolean | ((context: TitleContext) => boolean | null | undefined);
|
||||||
|
toggleCommand?: KeyboardActionNames;
|
||||||
|
activate?: boolean | ((context: TitleContext) => boolean);
|
||||||
|
/**
|
||||||
|
* By default the tab content will not be rendered unless the tab is active (i.e. selected by the user). Setting to `true` will ensure that the tab is rendered even when inactive, for cases where the tab needs to be accessible at all times (e.g. for the detached editor toolbar) or if event handling is needed.
|
||||||
|
*/
|
||||||
|
stayInDom?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -264,7 +264,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset-inline-end: 5px;
|
inset-inline-end: 5px;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
z-index: 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.style-resolver {
|
.style-resolver {
|
||||||
@@ -376,67 +375,6 @@ body[dir=rtl] .attribute-list-editor {
|
|||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
/* #region Search definition */
|
|
||||||
.search-setting-table {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 7px;
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: separate;
|
|
||||||
border-spacing: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-setting-table div {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-setting-table .title-column {
|
|
||||||
/* minimal width so that table remains static sized and most space remains for middle column with settings */
|
|
||||||
width: 50px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-setting-table .button-column {
|
|
||||||
/* minimal width so that table remains static sized and most space remains for middle column with settings */
|
|
||||||
width: 50px;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: end;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-setting-table .button-column .dropdown {
|
|
||||||
display: inline-block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-setting-table .button-column .dropdown-menu {
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-setting-table .button-column > * {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-list hr {
|
|
||||||
height: 1px;
|
|
||||||
border-color: var(--main-border-color);
|
|
||||||
position: relative;
|
|
||||||
top: 4px;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-definition-widget input:invalid {
|
|
||||||
border: 3px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-search-option button {
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-header {
|
|
||||||
background-color: var(--accented-background-color);
|
|
||||||
}
|
|
||||||
/* #endregion */
|
|
||||||
|
|
||||||
/* #region Note actions */
|
/* #region Note actions */
|
||||||
.note-actions {
|
.note-actions {
|
||||||
width: 35px;
|
width: 35px;
|
||||||
|
|||||||
@@ -329,6 +329,30 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addHtmlToEditor(html: string) {
|
||||||
|
await this.initialized;
|
||||||
|
|
||||||
|
const editor = this.watchdog.editor;
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
editor.model.change((writer) => {
|
||||||
|
const viewFragment = editor.data.processor.toView(html);
|
||||||
|
const modelFragment = editor.data.toModel(viewFragment);
|
||||||
|
const insertPosition = editor.model.document.selection.getLastPosition();
|
||||||
|
|
||||||
|
if (insertPosition) {
|
||||||
|
const range = editor.model.insertContent(modelFragment, insertPosition);
|
||||||
|
|
||||||
|
if (range) {
|
||||||
|
writer.setSelection(range.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.editing.view.focus();
|
||||||
|
}
|
||||||
|
|
||||||
addTextToActiveEditorEvent({ text }: EventData<"addTextToActiveEditor">) {
|
addTextToActiveEditorEvent({ text }: EventData<"addTextToActiveEditor">) {
|
||||||
if (!this.isActive()) {
|
if (!this.isActive()) {
|
||||||
return;
|
return;
|
||||||
@@ -385,6 +409,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
this.triggerCommand("showAddLinkDialog", { textTypeWidget: this, text: selectedText });
|
this.triggerCommand("showAddLinkDialog", { textTypeWidget: this, text: selectedText });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pasteMarkdownIntoTextCommand() {
|
||||||
|
this.triggerCommand("showPasteMarkdownDialog", { textTypeWidget: this });
|
||||||
|
}
|
||||||
|
|
||||||
getSelectedText() {
|
getSelectedText() {
|
||||||
const range = this.watchdog.editor?.model.document.selection.getFirstRange();
|
const range = this.watchdog.editor?.model.document.selection.getFirstRange();
|
||||||
let text = "";
|
let text = "";
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ const config: ForgeConfig = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
rebuildConfig: {
|
rebuildConfig: {
|
||||||
force: true,
|
|
||||||
extraModules: [ "better-sqlite3" ]
|
extraModules: [ "better-sqlite3" ]
|
||||||
},
|
},
|
||||||
makers: [
|
makers: [
|
||||||
@@ -85,14 +84,30 @@ const config: ForgeConfig = {
|
|||||||
config: {
|
config: {
|
||||||
options: {
|
options: {
|
||||||
...baseLinuxMakerConfigOptions,
|
...baseLinuxMakerConfigOptions,
|
||||||
|
desktopTemplate: undefined, // otherwise it would put in the wrong exec
|
||||||
|
icon: {
|
||||||
|
"128x128": path.join(APP_ICON_PATH, "png/128x128.png"),
|
||||||
|
},
|
||||||
id: "com.triliumnext.notes",
|
id: "com.triliumnext.notes",
|
||||||
runtimeVersion: "24.08",
|
runtimeVersion: "24.08",
|
||||||
base: "org.electronjs.Electron2.BaseApp",
|
base: "org.electronjs.Electron2.BaseApp",
|
||||||
baseVersion: "24.08",
|
baseVersion: "24.08",
|
||||||
baseFlatpakref: "https://flathub.org/repo/flathub.flatpakrepo",
|
baseFlatpakref: "https://flathub.org/repo/flathub.flatpakrepo",
|
||||||
finishArgs: [
|
finishArgs: [
|
||||||
|
// Wayland/X11 Rendering
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland"
|
"--socket=wayland",
|
||||||
|
"--share=ipc",
|
||||||
|
// Open GL
|
||||||
|
"--device=dri",
|
||||||
|
// Audio output
|
||||||
|
"--socket=pulseaudio",
|
||||||
|
// Read/write home directory access
|
||||||
|
"--filesystem=home",
|
||||||
|
// Allow communication with network
|
||||||
|
"--share=network",
|
||||||
|
// System notifications with libnotify
|
||||||
|
"--talk-name=org.freedesktop.Notifications",
|
||||||
],
|
],
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
|
|||||||
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
|
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
|
||||||
|
|
||||||
:POWERSHELL
|
:POWERSHELL
|
||||||
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo "Set-Item -Path Env:NODE_TLS_REJECT_UNAUTHORIZED -Value 0; ./trilium.exe"
|
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:NODE_TLS_REJECT_UNAUTHORIZED -Value 0; ./trilium.exe"
|
||||||
GOTO END
|
GOTO END
|
||||||
|
|
||||||
:BATCH
|
:BATCH
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
|
|||||||
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
|
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
|
||||||
|
|
||||||
:POWERSHELL
|
:POWERSHELL
|
||||||
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo "Set-Item -Path Env:TRILIUM_DATA_DIR -Value './trilium-data'; ./trilium.exe"
|
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:TRILIUM_DATA_DIR -Value './trilium-data'; ./trilium.exe"
|
||||||
GOTO END
|
GOTO END
|
||||||
|
|
||||||
:BATCH
|
:BATCH
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
|
|||||||
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
|
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
|
||||||
|
|
||||||
:POWERSHELL
|
:POWERSHELL
|
||||||
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo "Set-Item -Path Env:TRILIUM_SAFE_MODE -Value 1; ./trilium.exe --disable-gpu"
|
powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:TRILIUM_SAFE_MODE -Value 1; ./trilium.exe --disable-gpu"
|
||||||
GOTO END
|
GOTO END
|
||||||
|
|
||||||
:BATCH
|
:BATCH
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/desktop",
|
"name": "@triliumnext/desktop",
|
||||||
"version": "0.99.2",
|
"version": "0.99.3",
|
||||||
"description": "Build your personal knowledge base with Trilium Notes",
|
"description": "Build your personal knowledge base with Trilium Notes",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "src/main.ts",
|
"main": "src/main.ts",
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
"start-no-dir": "cross-env TRILIUM_PORT=37743 tsx ../../scripts/electron-start.mts src/main.ts",
|
"start-no-dir": "cross-env TRILIUM_PORT=37743 tsx ../../scripts/electron-start.mts src/main.ts",
|
||||||
"build": "tsx scripts/build.ts",
|
"build": "tsx scripts/build.ts",
|
||||||
"start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_PORT=37841 ELECTRON_IS_DEV=0 electron dist",
|
"start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_PORT=37841 ELECTRON_IS_DEV=0 electron dist",
|
||||||
"electron-forge:make": "pnpm build && cross-env electron-forge make dist",
|
"electron-forge:make": "pnpm build && electron-forge make dist",
|
||||||
|
"electron-forge:make-flatpak": "pnpm build && electron-forge make dist --targets=@electron-forge/maker-flatpak",
|
||||||
"electron-forge:package": "pnpm build && electron-forge package dist",
|
"electron-forge:package": "pnpm build && electron-forge package dist",
|
||||||
"electron-forge:start": "pnpm build && electron-forge start dist",
|
"electron-forge:start": "pnpm build && electron-forge start dist",
|
||||||
"e2e": "pnpm build && cross-env TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=8082 TRILIUM_DATA_DIR=data-e2e ELECTRON_IS_DEV=0 playwright test"
|
"e2e": "pnpm build && cross-env TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=8082 TRILIUM_DATA_DIR=data-e2e ELECTRON_IS_DEV=0 playwright test"
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
"@triliumnext/commons": "workspace:*",
|
"@triliumnext/commons": "workspace:*",
|
||||||
"@triliumnext/server": "workspace:*",
|
"@triliumnext/server": "workspace:*",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "13.0.1",
|
||||||
"electron": "38.3.0",
|
"electron": "38.4.0",
|
||||||
"@electron-forge/cli": "7.10.2",
|
"@electron-forge/cli": "7.10.2",
|
||||||
"@electron-forge/maker-deb": "7.10.2",
|
"@electron-forge/maker-deb": "7.10.2",
|
||||||
"@electron-forge/maker-dmg": "7.10.2",
|
"@electron-forge/maker-dmg": "7.10.2",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ async function main() {
|
|||||||
// Copy assets.
|
// Copy assets.
|
||||||
build.copy("src/assets", "assets/");
|
build.copy("src/assets", "assets/");
|
||||||
build.copy("/apps/server/src/assets", "assets/");
|
build.copy("/apps/server/src/assets", "assets/");
|
||||||
|
build.triggerBuildAndCopyTo("packages/share-theme", "share-theme/assets/");
|
||||||
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
|
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
|
||||||
|
|
||||||
// Copy node modules dependencies
|
// Copy node modules dependencies
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
"@types/mime-types": "3.0.1",
|
"@types/mime-types": "3.0.1",
|
||||||
"@types/yargs": "17.0.33"
|
"@types/yargs": "17.0.34"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx src/main.ts",
|
"dev": "tsx src/main.ts",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"@triliumnext/desktop": "workspace:*",
|
"@triliumnext/desktop": "workspace:*",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/fs-extra": "11.0.4",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "13.0.1",
|
||||||
"electron": "38.3.0",
|
"electron": "38.4.0",
|
||||||
"fs-extra": "11.3.2"
|
"fs-extra": "11.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js
|
|||||||
import debounce from "@triliumnext/client/src/services/debounce.js";
|
import debounce from "@triliumnext/client/src/services/debounce.js";
|
||||||
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
|
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
|
||||||
import cls from "@triliumnext/server/src/services/cls.js";
|
import cls from "@triliumnext/server/src/services/cls.js";
|
||||||
import type { AdvancedExportOptions } from "@triliumnext/server/src/services/export/zip.js";
|
import type { AdvancedExportOptions, ExportFormat } from "@triliumnext/server/src/services/export/zip/abstract_provider.js";
|
||||||
import { parseNoteMetaFile } from "@triliumnext/server/src/services/in_app_help.js";
|
import { parseNoteMetaFile } from "@triliumnext/server/src/services/in_app_help.js";
|
||||||
import type NoteMeta from "@triliumnext/server/src/services/meta/note_meta.js";
|
import type NoteMeta from "@triliumnext/server/src/services/meta/note_meta.js";
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ async function setOptions() {
|
|||||||
optionsService.setOption("compressImages", "false");
|
optionsService.setOption("compressImages", "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportData(noteId: string, format: "html" | "markdown", outputPath: string, ignoredFiles?: Set<string>) {
|
async function exportData(noteId: string, format: ExportFormat, outputPath: string, ignoredFiles?: Set<string>) {
|
||||||
const zipFilePath = "output.zip";
|
const zipFilePath = "output.zip";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.20.0-bullseye-slim AS builder
|
FROM node:24.10.0-bullseye-slim AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.20.0-bullseye-slim
|
FROM node:24.10.0-bullseye-slim
|
||||||
# Install only runtime dependencies
|
# Install only runtime dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.20.0-alpine AS builder
|
FROM node:24.10.0-alpine AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.20.0-alpine
|
FROM node:24.10.0-alpine
|
||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
RUN apk add --no-cache su-exec shadow
|
RUN apk add --no-cache su-exec shadow
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.20.0-alpine AS builder
|
FROM node:24.10.0-alpine AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.20.0-alpine
|
FROM node:24.10.0-alpine
|
||||||
# Create a non-root user with configurable UID/GID
|
# Create a non-root user with configurable UID/GID
|
||||||
ARG USER=trilium
|
ARG USER=trilium
|
||||||
ARG UID=1001
|
ARG UID=1001
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.20.0-bullseye-slim AS builder
|
FROM node:24.10.0-bullseye-slim AS builder
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
# Install native dependencies since we might be building cross-platform.
|
# Install native dependencies since we might be building cross-platform.
|
||||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
|||||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||||
|
|
||||||
FROM node:22.20.0-bullseye-slim
|
FROM node:24.10.0-bullseye-slim
|
||||||
# Create a non-root user with configurable UID/GID
|
# Create a non-root user with configurable UID/GID
|
||||||
ARG USER=trilium
|
ARG USER=trilium
|
||||||
ARG UID=1001
|
ARG UID=1001
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/server",
|
"name": "@triliumnext/server",
|
||||||
"version": "0.99.2",
|
"version": "0.99.3",
|
||||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./src/main.ts",
|
"main": "./src/main.ts",
|
||||||
@@ -36,11 +36,12 @@
|
|||||||
"@triliumnext/commons": "workspace:*",
|
"@triliumnext/commons": "workspace:*",
|
||||||
"@triliumnext/express-partial-content": "workspace:*",
|
"@triliumnext/express-partial-content": "workspace:*",
|
||||||
"@triliumnext/turndown-plugin-gfm": "workspace:*",
|
"@triliumnext/turndown-plugin-gfm": "workspace:*",
|
||||||
"@types/archiver": "6.0.3",
|
"@triliumnext/highlightjs": "workspace:*",
|
||||||
|
"@types/archiver": "7.0.0",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
"@types/cls-hooked": "4.3.9",
|
"@types/cls-hooked": "4.3.9",
|
||||||
"@types/compression": "1.8.1",
|
"@types/compression": "1.8.1",
|
||||||
"@types/cookie-parser": "1.4.9",
|
"@types/cookie-parser": "1.4.10",
|
||||||
"@types/debounce": "1.2.4",
|
"@types/debounce": "1.2.4",
|
||||||
"@types/ejs": "3.1.5",
|
"@types/ejs": "3.1.5",
|
||||||
"@types/escape-html": "1.0.4",
|
"@types/escape-html": "1.0.4",
|
||||||
@@ -56,18 +57,17 @@
|
|||||||
"@types/sanitize-html": "2.16.0",
|
"@types/sanitize-html": "2.16.0",
|
||||||
"@types/sax": "1.2.7",
|
"@types/sax": "1.2.7",
|
||||||
"@types/serve-favicon": "2.5.7",
|
"@types/serve-favicon": "2.5.7",
|
||||||
"@types/serve-static": "1.15.9",
|
"@types/serve-static": "2.2.0",
|
||||||
"@types/session-file-store": "1.2.5",
|
|
||||||
"@types/stream-throttle": "0.1.4",
|
"@types/stream-throttle": "0.1.4",
|
||||||
"@types/supertest": "6.0.3",
|
"@types/supertest": "6.0.3",
|
||||||
"@types/swagger-ui-express": "4.1.8",
|
"@types/swagger-ui-express": "4.1.8",
|
||||||
"@types/tmp": "0.2.6",
|
"@types/tmp": "0.2.6",
|
||||||
"@types/turndown": "5.0.5",
|
"@types/turndown": "5.0.6",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@types/xml2js": "0.4.14",
|
"@types/xml2js": "0.4.14",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"async-mutex": "0.5.0",
|
"async-mutex": "0.5.0",
|
||||||
"axios": "1.12.2",
|
"axios": "1.13.0",
|
||||||
"bindings": "1.5.0",
|
"bindings": "1.5.0",
|
||||||
"bootstrap": "5.3.8",
|
"bootstrap": "5.3.8",
|
||||||
"chardet": "2.1.0",
|
"chardet": "2.1.0",
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"debounce": "2.2.0",
|
"debounce": "2.2.0",
|
||||||
"debug": "4.4.3",
|
"debug": "4.4.3",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"electron": "38.3.0",
|
"electron": "38.4.0",
|
||||||
"electron-debug": "4.1.0",
|
"electron-debug": "4.1.0",
|
||||||
"electron-window-state": "5.0.3",
|
"electron-window-state": "5.0.3",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"i18next": "25.6.0",
|
"i18next": "25.6.0",
|
||||||
"i18next-fs-backend": "2.6.0",
|
"i18next-fs-backend": "2.6.0",
|
||||||
"image-type": "6.0.0",
|
"image-type": "6.0.0",
|
||||||
"ini": "5.0.0",
|
"ini": "6.0.0",
|
||||||
"is-animated": "2.0.2",
|
"is-animated": "2.0.2",
|
||||||
"is-svg": "6.1.0",
|
"is-svg": "6.1.0",
|
||||||
"jimp": "1.6.0",
|
"jimp": "1.6.0",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"multer": "2.0.2",
|
"multer": "2.0.2",
|
||||||
"normalize-strings": "1.1.1",
|
"normalize-strings": "1.1.1",
|
||||||
"ollama": "0.6.0",
|
"ollama": "0.6.0",
|
||||||
"openai": "6.6.0",
|
"openai": "6.7.0",
|
||||||
"rand-token": "1.0.1",
|
"rand-token": "1.0.1",
|
||||||
"safe-compare": "1.1.4",
|
"safe-compare": "1.1.4",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
@@ -125,9 +125,9 @@
|
|||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"time2fa": "1.4.2",
|
"time2fa": "1.4.2",
|
||||||
"tmp": "0.2.5",
|
"tmp": "0.2.5",
|
||||||
"turndown": "7.2.1",
|
"turndown": "7.2.2",
|
||||||
"unescape": "1.0.1",
|
"unescape": "1.0.1",
|
||||||
"vite": "7.1.11",
|
"vite": "7.1.12",
|
||||||
"ws": "8.18.3",
|
"ws": "8.18.3",
|
||||||
"xml2js": "0.6.2",
|
"xml2js": "0.6.2",
|
||||||
"yauzl": "3.2.0"
|
"yauzl": "3.2.0"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ async function main() {
|
|||||||
|
|
||||||
// Copy assets
|
// Copy assets
|
||||||
build.copy("src/assets", "assets/");
|
build.copy("src/assets", "assets/");
|
||||||
|
build.triggerBuildAndCopyTo("packages/share-theme", "share-theme/assets/");
|
||||||
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
|
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
|
||||||
|
|
||||||
// Copy node modules dependencies
|
// Copy node modules dependencies
|
||||||
|
|||||||
@@ -84,7 +84,9 @@
|
|||||||
"show-backend-log": "فتح صفحة \"سجل الخلفية\"",
|
"show-backend-log": "فتح صفحة \"سجل الخلفية\"",
|
||||||
"edit-readonly-note": "تعديل ملاحظة القراءة فقط",
|
"edit-readonly-note": "تعديل ملاحظة القراءة فقط",
|
||||||
"attributes-labels-and-relations": "سمات ( تسميات و علاقات)",
|
"attributes-labels-and-relations": "سمات ( تسميات و علاقات)",
|
||||||
"render-active-note": "عرض ( اعادة عرض) الملاحظة المؤرشفة"
|
"render-active-note": "عرض ( اعادة عرض) الملاحظة المؤرشفة",
|
||||||
|
"show-help": "فتح دليل التعليمات",
|
||||||
|
"copy-without-formatting": "نسخ النص المحدد بدون تنسيق"
|
||||||
},
|
},
|
||||||
"setup_sync-from-server": {
|
"setup_sync-from-server": {
|
||||||
"note": "ملاحظة:",
|
"note": "ملاحظة:",
|
||||||
@@ -196,7 +198,8 @@
|
|||||||
"expand": "توسيع",
|
"expand": "توسيع",
|
||||||
"site-theme": "المظهر العام للموقع",
|
"site-theme": "المظهر العام للموقع",
|
||||||
"image_alt": "صورة المقال",
|
"image_alt": "صورة المقال",
|
||||||
"on-this-page": "في هذه السفحة"
|
"on-this-page": "في هذه السفحة",
|
||||||
|
"last-updated": "اخر تحديث {{- date}}"
|
||||||
},
|
},
|
||||||
"hidden_subtree_templates": {
|
"hidden_subtree_templates": {
|
||||||
"description": "الوصف",
|
"description": "الوصف",
|
||||||
@@ -258,7 +261,8 @@
|
|||||||
},
|
},
|
||||||
"share_page": {
|
"share_page": {
|
||||||
"parent": "الأصل:",
|
"parent": "الأصل:",
|
||||||
"child-notes": "الملاحظات الفرعية:"
|
"child-notes": "الملاحظات الفرعية:",
|
||||||
|
"no-content": "لاتحتوي هذة الملاحظة على محتوى."
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"duplicate-note-suffix": "(مكرر)",
|
"duplicate-note-suffix": "(مكرر)",
|
||||||
@@ -339,7 +343,24 @@
|
|||||||
"toggle-system-tray-icon": "تبديل ايقونة علبة النظام",
|
"toggle-system-tray-icon": "تبديل ايقونة علبة النظام",
|
||||||
"switch-to-first-tab": "التبديل الى التبويب الاول",
|
"switch-to-first-tab": "التبديل الى التبويب الاول",
|
||||||
"follow-link-under-cursor": "اتبع الرابط اسفل المؤشر",
|
"follow-link-under-cursor": "اتبع الرابط اسفل المؤشر",
|
||||||
"paste-markdown-into-text": "لصق نص بتنسبق Markdown"
|
"paste-markdown-into-text": "لصق نص بتنسبق Markdown",
|
||||||
|
"move-note-up-in-hierarchy": "نقل الملاحظة للاعلى في الهيكل",
|
||||||
|
"move-note-down-in-hierarchy": "نقل الملاحظة للاسفل في الهيكل",
|
||||||
|
"select-all-notes-in-parent": "تحديد جميع الملاحظات التابعة للملاحظة الاصل",
|
||||||
|
"add-note-above-to-selection": "اضافة ملاحظة فوق الملاحظة المحددة",
|
||||||
|
"add-note-below-to-selection": "اصافة ملاحظة اسفل الملاحظة المحددة",
|
||||||
|
"add-include-note-to-text": "اضافة الملاحظة الى النص",
|
||||||
|
"toggle-ribbon-tab-image-properties": "اظهار/ اخفاء صورة علامة التبويب في الشريط.",
|
||||||
|
"toggle-ribbon-tab-classic-editor": "عرض/اخفاء تبويب المحور الكلاسيكي",
|
||||||
|
"toggle-ribbon-tab-basic-properties": "عرض/اخفاء تبويب الخصائص الاساسية",
|
||||||
|
"toggle-ribbon-tab-book-properties": "عرض/اخفاء تبويب خصائص الدفتر",
|
||||||
|
"toggle-ribbon-tab-file-properties": "عرض/ادخفاء تبويب خصائص الملف",
|
||||||
|
"toggle-ribbon-tab-owned-attributes": "عرض/اخفاء تبويب المميزات المملوكة",
|
||||||
|
"toggle-ribbon-tab-inherited-attributes": "عرض/اخفاء تبويب السمات الموروثة",
|
||||||
|
"toggle-ribbon-tab-promoted-attributes": "عرض/ اخفاء تبويب السمات المعززة",
|
||||||
|
"toggle-ribbon-tab-note-map": "عرض/اخفاء تبويب خريطة الملاحظات",
|
||||||
|
"toggle-ribbon-tab-similar-notes": "عرض/اخفاء شريط الملاحظات المشابهة",
|
||||||
|
"export-active-note-as-pdf": "تصدير الملاحظة النشطة كملفPDF"
|
||||||
},
|
},
|
||||||
"share_404": {
|
"share_404": {
|
||||||
"title": "غير موجود",
|
"title": "غير موجود",
|
||||||
@@ -348,6 +369,7 @@
|
|||||||
"weekdayNumber": "الاسبوع{رقم الاسيوع}",
|
"weekdayNumber": "الاسبوع{رقم الاسيوع}",
|
||||||
"quarterNumber": "الربع {رقم الربع}",
|
"quarterNumber": "الربع {رقم الربع}",
|
||||||
"pdf": {
|
"pdf": {
|
||||||
"export_filter": "مستند PDF (.pdf)"
|
"export_filter": "مستند PDF (.pdf)",
|
||||||
|
"unable-to-export-title": "تعذر التصدير كملف PDF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,7 +274,8 @@
|
|||||||
"export_filter": "PDF Dokument (*.pdf)",
|
"export_filter": "PDF Dokument (*.pdf)",
|
||||||
"unable-to-export-message": "Die aktuelle Notiz konnte nicht als PDF exportiert werden.",
|
"unable-to-export-message": "Die aktuelle Notiz konnte nicht als PDF exportiert werden.",
|
||||||
"unable-to-export-title": "Export als PDF fehlgeschlagen",
|
"unable-to-export-title": "Export als PDF fehlgeschlagen",
|
||||||
"unable-to-save-message": "Die ausgewählte Datei konnte nicht beschrieben werden. Erneut versuchen oder ein anderes Ziel auswählen."
|
"unable-to-save-message": "Die ausgewählte Datei konnte nicht beschrieben werden. Erneut versuchen oder ein anderes Ziel auswählen.",
|
||||||
|
"unable-to-print": "Notiz kann nicht gedruckt werden"
|
||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"tooltip": "Trilium Notes",
|
"tooltip": "Trilium Notes",
|
||||||
|
|||||||
@@ -250,7 +250,13 @@
|
|||||||
"other": "Autre",
|
"other": "Autre",
|
||||||
"advanced-title": "Avancé",
|
"advanced-title": "Avancé",
|
||||||
"visible-launchers-title": "Raccourcis visibles",
|
"visible-launchers-title": "Raccourcis visibles",
|
||||||
"user-guide": "Guide de l'utilisateur"
|
"user-guide": "Guide de l'utilisateur",
|
||||||
|
"jump-to-note-title": "Aller à...",
|
||||||
|
"llm-chat-title": "Discuter avec Notes",
|
||||||
|
"multi-factor-authentication-title": "MFA",
|
||||||
|
"ai-llm-title": "AI/LLM",
|
||||||
|
"localization": "Langue et région",
|
||||||
|
"inbox-title": "Boîte de réception"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"new-note": "Nouvelle note",
|
"new-note": "Nouvelle note",
|
||||||
@@ -268,7 +274,8 @@
|
|||||||
"export_filter": "Document PDF (*.pdf)",
|
"export_filter": "Document PDF (*.pdf)",
|
||||||
"unable-to-export-message": "La note actuelle n'a pas pu être exportée en format PDF.",
|
"unable-to-export-message": "La note actuelle n'a pas pu être exportée en format PDF.",
|
||||||
"unable-to-export-title": "Impossible d'exporter au format PDF",
|
"unable-to-export-title": "Impossible d'exporter au format PDF",
|
||||||
"unable-to-save-message": "Le fichier sélectionné n'a pas pu être écrit. Réessayez ou sélectionnez une autre destination."
|
"unable-to-save-message": "Le fichier sélectionné n'a pas pu être écrit. Réessayez ou sélectionnez une autre destination.",
|
||||||
|
"unable-to-print": "Impossible d'imprimer la note"
|
||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"tooltip": "Trilium Notes",
|
"tooltip": "Trilium Notes",
|
||||||
@@ -277,7 +284,8 @@
|
|||||||
"bookmarks": "Signets",
|
"bookmarks": "Signets",
|
||||||
"today": "Ouvrir la note du journal du jour",
|
"today": "Ouvrir la note du journal du jour",
|
||||||
"new-note": "Nouvelle note",
|
"new-note": "Nouvelle note",
|
||||||
"show-windows": "Afficher les fenêtres"
|
"show-windows": "Afficher les fenêtres",
|
||||||
|
"open_new_window": "Ouvrir une nouvelle fenêtre"
|
||||||
},
|
},
|
||||||
"migration": {
|
"migration": {
|
||||||
"old_version": "La migration directe à partir de votre version actuelle n'est pas prise en charge. Veuillez d'abord mettre à jour vers la version v0.60.4, puis vers cette nouvelle version.",
|
"old_version": "La migration directe à partir de votre version actuelle n'est pas prise en charge. Veuillez d'abord mettre à jour vers la version v0.60.4, puis vers cette nouvelle version.",
|
||||||
@@ -375,7 +383,14 @@
|
|||||||
"zoom-in": "Zoomer",
|
"zoom-in": "Zoomer",
|
||||||
"reset-zoom-level": "Réinitilaliser le zoom",
|
"reset-zoom-level": "Réinitilaliser le zoom",
|
||||||
"copy-without-formatting": "Copier sans mise en forme",
|
"copy-without-formatting": "Copier sans mise en forme",
|
||||||
"force-save-revision": "Forcer la sauvegarde de la révision"
|
"force-save-revision": "Forcer la sauvegarde de la révision",
|
||||||
|
"toggle-ribbon-tab-promoted-attributes": "Basculer les attributs promus de l'onglet du ruban",
|
||||||
|
"toggle-ribbon-tab-note-map": "Basculer l'onglet du ruban Note Map",
|
||||||
|
"toggle-ribbon-tab-note-info": "Basculer l'onglet du ruban Note Info",
|
||||||
|
"toggle-ribbon-tab-note-paths": "Basculer les chemins de notes de l'onglet du ruban",
|
||||||
|
"toggle-ribbon-tab-similar-notes": "Basculer l'onglet du ruban Notes similaires",
|
||||||
|
"toggle-note-hoisting": "Activer la focalisation sur la note",
|
||||||
|
"unhoist-note": "Désactiver la focalisation sur la note"
|
||||||
},
|
},
|
||||||
"sql_init": {
|
"sql_init": {
|
||||||
"db_not_initialized_desktop": "Base de données non initialisée, merci de suivre les instructions à l'écran.",
|
"db_not_initialized_desktop": "Base de données non initialisée, merci de suivre les instructions à l'écran.",
|
||||||
@@ -383,5 +398,44 @@
|
|||||||
},
|
},
|
||||||
"desktop": {
|
"desktop": {
|
||||||
"instance_already_running": "Une instance est déjà en cours d'execution, ouverture de cette instance à la place."
|
"instance_already_running": "Une instance est déjà en cours d'execution, ouverture de cette instance à la place."
|
||||||
|
},
|
||||||
|
"weekdayNumber": "Semaine {weekNumber}",
|
||||||
|
"quarterNumber": "Trimestre {quarterNumber}",
|
||||||
|
"share_theme": {
|
||||||
|
"site-theme": "Thème du site",
|
||||||
|
"search_placeholder": "Recherche...",
|
||||||
|
"image_alt": "Image de l'article",
|
||||||
|
"last-updated": "Dernière mise à jour le {{- date}}",
|
||||||
|
"subpages": "Sous-pages:",
|
||||||
|
"on-this-page": "Sur cette page",
|
||||||
|
"expand": "Développer"
|
||||||
|
},
|
||||||
|
"hidden_subtree_templates": {
|
||||||
|
"text-snippet": "Extrait de texte",
|
||||||
|
"description": "Description",
|
||||||
|
"list-view": "Vue en liste",
|
||||||
|
"grid-view": "Vue en grille",
|
||||||
|
"calendar": "Calendrier",
|
||||||
|
"table": "Tableau",
|
||||||
|
"geo-map": "Carte géographique",
|
||||||
|
"start-date": "Date de début",
|
||||||
|
"end-date": "Date de fin",
|
||||||
|
"start-time": "Heure de début",
|
||||||
|
"end-time": "Heure de fin",
|
||||||
|
"geolocation": "Géolocalisation",
|
||||||
|
"built-in-templates": "Modèles intégrés",
|
||||||
|
"board": "Tableau de bord",
|
||||||
|
"status": "État",
|
||||||
|
"board_note_first": "Première note",
|
||||||
|
"board_note_second": "Deuxième note",
|
||||||
|
"board_note_third": "Troisième note",
|
||||||
|
"board_status_todo": "A faire",
|
||||||
|
"board_status_progress": "En cours",
|
||||||
|
"board_status_done": "Terminé",
|
||||||
|
"presentation": "Présentation",
|
||||||
|
"presentation_slide": "Diapositive de présentation",
|
||||||
|
"presentation_slide_first": "Première diapositive",
|
||||||
|
"presentation_slide_second": "Deuxième diapositive",
|
||||||
|
"background": "Arrière-plan"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,14 @@
|
|||||||
"edit-note-title": "Ugrás fáról a jegyzet részleteihez és a cím szerkesztése",
|
"edit-note-title": "Ugrás fáról a jegyzet részleteihez és a cím szerkesztése",
|
||||||
"edit-branch-prefix": "\"Ág címjelzésének szerkesztése\" ablak mutatása",
|
"edit-branch-prefix": "\"Ág címjelzésének szerkesztése\" ablak mutatása",
|
||||||
"clone-notes-to": "Kijelölt jegyzetek másolása",
|
"clone-notes-to": "Kijelölt jegyzetek másolása",
|
||||||
"move-notes-to": "Kijelölt jegyzetek elhelyzése"
|
"move-notes-to": "Kijelölt jegyzetek elhelyzése",
|
||||||
|
"note-clipboard": "Megjegyzés vágólap",
|
||||||
|
"copy-notes-to-clipboard": "Másolja a kiválasztott jegyzeteket a vágólapra",
|
||||||
|
"paste-notes-from-clipboard": "A vágólapról szóló jegyzetek beillesztése aktív jegyzetbe",
|
||||||
|
"cut-notes-to-clipboard": "A kiválasztott jegyzetek kivágása a vágólapra",
|
||||||
|
"select-all-notes-in-parent": "Válassza ki az összes jegyzetet az aktuális jegyzetszintről",
|
||||||
|
"activate-next-tab": "Aktiválja a jobb oldali fület",
|
||||||
|
"activate-previous-tab": "Aktiválja a lapot a bal oldalon",
|
||||||
|
"open-new-window": "Nyiss új üres ablakot"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,8 @@
|
|||||||
"export_filter": "Documento PDF (*.pdf)",
|
"export_filter": "Documento PDF (*.pdf)",
|
||||||
"unable-to-export-message": "La nota corrente non può essere esportata come PDF.",
|
"unable-to-export-message": "La nota corrente non può essere esportata come PDF.",
|
||||||
"unable-to-export-title": "Impossibile esportare come PDF",
|
"unable-to-export-title": "Impossibile esportare come PDF",
|
||||||
"unable-to-save-message": "Il file selezionato non può essere salvato. Prova di nuovo o seleziona un'altra destinazione."
|
"unable-to-save-message": "Il file selezionato non può essere salvato. Prova di nuovo o seleziona un'altra destinazione.",
|
||||||
|
"unable-to-print": "Impossibile stampare la nota"
|
||||||
},
|
},
|
||||||
"tray": {
|
"tray": {
|
||||||
"tooltip": "Trilium Notes",
|
"tooltip": "Trilium Notes",
|
||||||
@@ -430,7 +431,8 @@
|
|||||||
"presentation": "Presentazione",
|
"presentation": "Presentazione",
|
||||||
"presentation_slide": "Diapositiva di presentazione",
|
"presentation_slide": "Diapositiva di presentazione",
|
||||||
"presentation_slide_first": "Prima diapositiva",
|
"presentation_slide_first": "Prima diapositiva",
|
||||||
"presentation_slide_second": "Seconda diapositiva"
|
"presentation_slide_second": "Seconda diapositiva",
|
||||||
|
"background": "Contesto"
|
||||||
},
|
},
|
||||||
"sql_init": {
|
"sql_init": {
|
||||||
"db_not_initialized_desktop": "Database non inizializzato, seguire le istruzioni a schermo.",
|
"db_not_initialized_desktop": "Database non inizializzato, seguire le istruzioni a schermo.",
|
||||||
|
|||||||
@@ -278,6 +278,11 @@ class BBranch extends AbstractBeccaEntity<BBranch> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getParentNote() {
|
||||||
|
return this.parentNote;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BBranch;
|
export default BBranch;
|
||||||
|
|||||||
@@ -1758,6 +1758,26 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
|||||||
return childBranches;
|
return childBranches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get encodedTitle() {
|
||||||
|
return encodeURIComponent(this.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
getVisibleChildBranches() {
|
||||||
|
return this.getChildBranches().filter((branch) => !branch.getNote().isLabelTruthy("shareHiddenFromTree"));
|
||||||
|
}
|
||||||
|
|
||||||
|
getVisibleChildNotes() {
|
||||||
|
return this.getVisibleChildBranches().map((branch) => branch.getNote());
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVisibleChildren() {
|
||||||
|
return this.getVisibleChildNotes().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shareId() {
|
||||||
|
return this.noteId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an attribute by it's attributeId. Requires the attribute cache to be available.
|
* Return an attribute by it's attributeId. Requires the attribute cache to be available.
|
||||||
* @param attributeId - the id of the attribute owned by this note
|
* @param attributeId - the id of the attribute owned by this note
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type { ParsedQs } from "qs";
|
|||||||
import type { NoteParams } from "../services/note-interface.js";
|
import type { NoteParams } from "../services/note-interface.js";
|
||||||
import type { SearchParams } from "../services/search/services/types.js";
|
import type { SearchParams } from "../services/search/services/types.js";
|
||||||
import type { ValidatorMap } from "./etapi-interface.js";
|
import type { ValidatorMap } from "./etapi-interface.js";
|
||||||
|
import type { ExportFormat } from "../services/export/zip/abstract_provider.js";
|
||||||
|
|
||||||
function register(router: Router) {
|
function register(router: Router) {
|
||||||
eu.route(router, "get", "/etapi/notes", (req, res, next) => {
|
eu.route(router, "get", "/etapi/notes", (req, res, next) => {
|
||||||
@@ -149,8 +150,8 @@ function register(router: Router) {
|
|||||||
const note = eu.getAndCheckNote(req.params.noteId);
|
const note = eu.getAndCheckNote(req.params.noteId);
|
||||||
const format = req.query.format || "html";
|
const format = req.query.format || "html";
|
||||||
|
|
||||||
if (typeof format !== "string" || !["html", "markdown"].includes(format)) {
|
if (typeof format !== "string" || !["html", "markdown", "share"].includes(format)) {
|
||||||
throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`);
|
throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default), 'markdown' or 'share'.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskContext = new TaskContext("no-progress-reporting", "export", null);
|
const taskContext = new TaskContext("no-progress-reporting", "export", null);
|
||||||
@@ -159,7 +160,7 @@ function register(router: Router) {
|
|||||||
// (e.g. branchIds are not seen in UI), that we export "note export" instead.
|
// (e.g. branchIds are not seen in UI), that we export "note export" instead.
|
||||||
const branch = note.getParentBranches()[0];
|
const branch = note.getParentBranches()[0];
|
||||||
|
|
||||||
zipExportService.exportToZip(taskContext, branch, format as "html" | "markdown", res);
|
zipExportService.exportToZip(taskContext, branch, format as ExportFormat, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, "post", "/etapi/notes/:noteId/import", (req, res, next) => {
|
eu.route(router, "post", "/etapi/notes/:noteId/import", (req, res, next) => {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function exportBranch(req: Request, res: Response) {
|
|||||||
const taskContext = new TaskContext(taskId, "export", null);
|
const taskContext = new TaskContext(taskId, "export", null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (type === "subtree" && (format === "html" || format === "markdown")) {
|
if (type === "subtree" && (format === "html" || format === "markdown" || format === "share")) {
|
||||||
zipExportService.exportToZip(taskContext, branch, format, res);
|
zipExportService.exportToZip(taskContext, branch, format, res);
|
||||||
} else if (type === "single") {
|
} else if (type === "single") {
|
||||||
if (format !== "html" && format !== "markdown") {
|
if (format !== "html" && format !== "markdown") {
|
||||||
|
|||||||
@@ -152,14 +152,14 @@ function restoreRevision(req: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEditedNotesOnDate(req: Request) {
|
function getEditedNotesOnDate(req: Request) {
|
||||||
const noteIds = sql.getColumn<string>(
|
const noteIds = sql.getColumn<string>(/*sql*/`\
|
||||||
`
|
|
||||||
SELECT notes.*
|
SELECT notes.*
|
||||||
FROM notes
|
FROM notes
|
||||||
WHERE noteId IN (
|
WHERE noteId IN (
|
||||||
SELECT noteId FROM notes
|
SELECT noteId FROM notes
|
||||||
WHERE notes.dateCreated LIKE :date
|
WHERE
|
||||||
OR notes.dateModified LIKE :date
|
(notes.dateCreated LIKE :date OR notes.dateModified LIKE :date)
|
||||||
|
AND (noteId NOT LIKE '_%')
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT noteId FROM revisions
|
SELECT noteId FROM revisions
|
||||||
WHERE revisions.dateLastEdited LIKE :date
|
WHERE revisions.dateLastEdited LIKE :date
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ async function register(app: express.Application) {
|
|||||||
app.use(`/${assetUrlFragment}/translations/`, persistentCacheStatic(path.join(publicDir, "translations")));
|
app.use(`/${assetUrlFragment}/translations/`, persistentCacheStatic(path.join(publicDir, "translations")));
|
||||||
app.use(`/node_modules/`, persistentCacheStatic(path.join(publicDir, "node_modules")));
|
app.use(`/node_modules/`, persistentCacheStatic(path.join(publicDir, "node_modules")));
|
||||||
}
|
}
|
||||||
|
app.use(`/share/assets/`, express.static(getShareThemeAssetDir()));
|
||||||
app.use(`/${assetUrlFragment}/images`, persistentCacheStatic(path.join(resourceDir, "assets", "images")));
|
app.use(`/${assetUrlFragment}/images`, persistentCacheStatic(path.join(resourceDir, "assets", "images")));
|
||||||
app.use(`/${assetUrlFragment}/doc_notes`, persistentCacheStatic(path.join(resourceDir, "assets", "doc_notes")));
|
app.use(`/${assetUrlFragment}/doc_notes`, persistentCacheStatic(path.join(resourceDir, "assets", "doc_notes")));
|
||||||
app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts")));
|
app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts")));
|
||||||
@@ -51,6 +52,16 @@ async function register(app: express.Application) {
|
|||||||
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets")));
|
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getShareThemeAssetDir() {
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
const srcRoot = path.join(__dirname, "..", "..");
|
||||||
|
return path.join(srcRoot, "../../packages/share-theme/dist");
|
||||||
|
} else {
|
||||||
|
const resourceDir = getResourceDir();
|
||||||
|
return path.join(resourceDir, "share-theme/assets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
register
|
register
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import type TaskContext from "../task_context.js";
|
|||||||
import type BBranch from "../../becca/entities/bbranch.js";
|
import type BBranch from "../../becca/entities/bbranch.js";
|
||||||
import type { Response } from "express";
|
import type { Response } from "express";
|
||||||
import type BNote from "../../becca/entities/bnote.js";
|
import type BNote from "../../becca/entities/bnote.js";
|
||||||
|
import type { ExportFormat } from "./zip/abstract_provider.js";
|
||||||
|
|
||||||
function exportSingleNote(taskContext: TaskContext<"export">, branch: BBranch, format: "html" | "markdown", res: Response) {
|
function exportSingleNote(taskContext: TaskContext<"export">, branch: BBranch, format: ExportFormat, res: Response) {
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
|
|
||||||
if (note.type === "image" || note.type === "file") {
|
if (note.type === "image" || note.type === "file") {
|
||||||
@@ -33,7 +34,7 @@ function exportSingleNote(taskContext: TaskContext<"export">, branch: BBranch, f
|
|||||||
taskContext.taskSucceeded(null);
|
taskContext.taskSucceeded(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapByNoteType(note: BNote, content: string | Buffer<ArrayBufferLike>, format: "html" | "markdown") {
|
export function mapByNoteType(note: BNote, content: string | Buffer<ArrayBufferLike>, format: ExportFormat) {
|
||||||
let payload, extension, mime;
|
let payload, extension, mime;
|
||||||
|
|
||||||
if (typeof content !== "string") {
|
if (typeof content !== "string") {
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import html from "html";
|
|
||||||
import dateUtils from "../date_utils.js";
|
import dateUtils from "../date_utils.js";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import mimeTypes from "mime-types";
|
|
||||||
import mdService from "./markdown.js";
|
|
||||||
import packageInfo from "../../../package.json" with { type: "json" };
|
import packageInfo from "../../../package.json" with { type: "json" };
|
||||||
import { getContentDisposition, escapeHtml, getResourceDir, isDev } from "../utils.js";
|
import { getContentDisposition } from "../utils.js";
|
||||||
import protectedSessionService from "../protected_session.js";
|
import protectedSessionService from "../protected_session.js";
|
||||||
import sanitize from "sanitize-filename";
|
import sanitize from "sanitize-filename";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -18,39 +15,48 @@ import ValidationError from "../../errors/validation_error.js";
|
|||||||
import type NoteMeta from "../meta/note_meta.js";
|
import type NoteMeta from "../meta/note_meta.js";
|
||||||
import type AttachmentMeta from "../meta/attachment_meta.js";
|
import type AttachmentMeta from "../meta/attachment_meta.js";
|
||||||
import type AttributeMeta from "../meta/attribute_meta.js";
|
import type AttributeMeta from "../meta/attribute_meta.js";
|
||||||
import type BBranch from "../../becca/entities/bbranch.js";
|
import BBranch from "../../becca/entities/bbranch.js";
|
||||||
import type { Response } from "express";
|
import type { Response } from "express";
|
||||||
import type { NoteMetaFile } from "../meta/note_meta.js";
|
import type { NoteMetaFile } from "../meta/note_meta.js";
|
||||||
|
import HtmlExportProvider from "./zip/html.js";
|
||||||
|
import { AdvancedExportOptions, type ExportFormat, ZipExportProviderData } from "./zip/abstract_provider.js";
|
||||||
|
import MarkdownExportProvider from "./zip/markdown.js";
|
||||||
|
import ShareThemeExportProvider from "./zip/share_theme.js";
|
||||||
|
import type BNote from "../../becca/entities/bnote.js";
|
||||||
|
import { NoteType } from "@triliumnext/commons";
|
||||||
|
|
||||||
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
|
async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch, format: ExportFormat, res: Response | fs.WriteStream, setHeaders = true, zipExportOptions?: AdvancedExportOptions) {
|
||||||
|
if (!["html", "markdown", "share"].includes(format)) {
|
||||||
export interface AdvancedExportOptions {
|
throw new ValidationError(`Only 'html', 'markdown' and 'share' allowed as export format, '${format}' given`);
|
||||||
/**
|
|
||||||
* If `true`, then only the note's content will be kept. If `false` (default), then each page will have its own <html> template.
|
|
||||||
*/
|
|
||||||
skipHtmlTemplate?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a custom function to rewrite the links found in HTML or Markdown notes. This method is called for every note imported, if it's of the right type.
|
|
||||||
*
|
|
||||||
* @param originalRewriteLinks the original rewrite links function. Can be used to access the default behaviour without having to reimplement it.
|
|
||||||
* @param getNoteTargetUrl the method to obtain a note's target URL, used internally by `originalRewriteLinks` but can be used here as well.
|
|
||||||
* @returns a function to rewrite the links in HTML or Markdown notes.
|
|
||||||
*/
|
|
||||||
customRewriteLinks?: (originalRewriteLinks: RewriteLinksFn, getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null) => RewriteLinksFn;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true, zipExportOptions?: AdvancedExportOptions) {
|
|
||||||
if (!["html", "markdown"].includes(format)) {
|
|
||||||
throw new ValidationError(`Only 'html' and 'markdown' allowed as export format, '${format}' given`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const archive = archiver("zip", {
|
const archive = archiver("zip", {
|
||||||
zlib: { level: 9 } // Sets the compression level.
|
zlib: { level: 9 } // Sets the compression level.
|
||||||
});
|
});
|
||||||
|
const rewriteFn = (zipExportOptions?.customRewriteLinks ? zipExportOptions?.customRewriteLinks(rewriteLinks, getNoteTargetUrl) : rewriteLinks);
|
||||||
|
const provider = buildProvider();
|
||||||
|
|
||||||
const noteIdToMeta: Record<string, NoteMeta> = {};
|
const noteIdToMeta: Record<string, NoteMeta> = {};
|
||||||
|
|
||||||
|
function buildProvider() {
|
||||||
|
const providerData: ZipExportProviderData = {
|
||||||
|
getNoteTargetUrl,
|
||||||
|
archive,
|
||||||
|
branch,
|
||||||
|
rewriteFn
|
||||||
|
};
|
||||||
|
switch (format) {
|
||||||
|
case "html":
|
||||||
|
return new HtmlExportProvider(providerData);
|
||||||
|
case "markdown":
|
||||||
|
return new MarkdownExportProvider(providerData);
|
||||||
|
case "share":
|
||||||
|
return new ShareThemeExportProvider(providerData);
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getUniqueFilename(existingFileNames: Record<string, number>, fileName: string) {
|
function getUniqueFilename(existingFileNames: Record<string, number>, fileName: string) {
|
||||||
const lcFileName = fileName.toLowerCase();
|
const lcFileName = fileName.toLowerCase();
|
||||||
|
|
||||||
@@ -72,7 +78,7 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
|
function getDataFileName(type: NoteType | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
|
||||||
let fileName = baseFileName.trim();
|
let fileName = baseFileName.trim();
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
fileName = "note";
|
fileName = "note";
|
||||||
@@ -90,36 +96,14 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
let existingExtension = path.extname(fileName).toLowerCase();
|
let existingExtension = path.extname(fileName).toLowerCase();
|
||||||
let newExtension;
|
const newExtension = provider.mapExtension(type, mime, existingExtension, format);
|
||||||
|
|
||||||
// the following two are handled specifically since we always want to have these extensions no matter the automatic detection
|
|
||||||
// and/or existing detected extensions in the note name
|
|
||||||
if (type === "text" && format === "markdown") {
|
|
||||||
newExtension = "md";
|
|
||||||
} else if (type === "text" && format === "html") {
|
|
||||||
newExtension = "html";
|
|
||||||
} else if (mime === "application/x-javascript" || mime === "text/javascript") {
|
|
||||||
newExtension = "js";
|
|
||||||
} else if (type === "canvas" || mime === "application/json") {
|
|
||||||
newExtension = "json";
|
|
||||||
} else if (existingExtension.length > 0) {
|
|
||||||
// if the page already has an extension, then we'll just keep it
|
|
||||||
newExtension = null;
|
|
||||||
} else {
|
|
||||||
if (mime?.toLowerCase()?.trim() === "image/jpg") {
|
|
||||||
newExtension = "jpg";
|
|
||||||
} else if (mime?.toLowerCase()?.trim() === "text/mermaid") {
|
|
||||||
newExtension = "txt";
|
|
||||||
} else {
|
|
||||||
newExtension = mimeTypes.extension(mime) || "dat";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the note is already named with the extension (e.g. "image.jpg"), then it's silly to append the exact same extension again
|
// if the note is already named with the extension (e.g. "image.jpg"), then it's silly to append the exact same extension again
|
||||||
if (newExtension && existingExtension !== `.${newExtension.toLowerCase()}`) {
|
if (newExtension && existingExtension !== `.${newExtension.toLowerCase()}`) {
|
||||||
fileName += `.${newExtension}`;
|
fileName += `.${newExtension}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return getUniqueFilename(existingFileNames, fileName);
|
return getUniqueFilename(existingFileNames, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +129,8 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
const notePath = parentMeta.notePath.concat([note.noteId]);
|
const notePath = parentMeta.notePath.concat([note.noteId]);
|
||||||
|
|
||||||
if (note.noteId in noteIdToMeta) {
|
if (note.noteId in noteIdToMeta) {
|
||||||
const fileName = getUniqueFilename(existingFileNames, `${baseFileName}.clone.${format === "html" ? "html" : "md"}`);
|
const extension = provider.mapExtension("text", "text/html", "", format);
|
||||||
|
const fileName = getUniqueFilename(existingFileNames, `${baseFileName}.clone.${extension}`);
|
||||||
|
|
||||||
const meta: NoteMeta = {
|
const meta: NoteMeta = {
|
||||||
isClone: true,
|
isClone: true,
|
||||||
@@ -155,7 +140,7 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
prefix: branch.prefix,
|
prefix: branch.prefix,
|
||||||
dataFileName: fileName,
|
dataFileName: fileName,
|
||||||
type: "text", // export will have text description
|
type: "text", // export will have text description
|
||||||
format: format
|
format: (format === "markdown" ? "markdown" : "html")
|
||||||
};
|
};
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
@@ -185,7 +170,7 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
taskContext.increaseProgressCount();
|
taskContext.increaseProgressCount();
|
||||||
|
|
||||||
if (note.type === "text") {
|
if (note.type === "text") {
|
||||||
meta.format = format;
|
meta.format = (format === "markdown" ? "markdown" : "html");
|
||||||
}
|
}
|
||||||
|
|
||||||
noteIdToMeta[note.noteId] = meta as NoteMeta;
|
noteIdToMeta[note.noteId] = meta as NoteMeta;
|
||||||
@@ -194,10 +179,13 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
note.sortChildren();
|
note.sortChildren();
|
||||||
const childBranches = note.getChildBranches().filter((branch) => branch?.noteId !== "_hidden");
|
const childBranches = note.getChildBranches().filter((branch) => branch?.noteId !== "_hidden");
|
||||||
|
|
||||||
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
|
let shouldIncludeFile = (!note.isProtected || protectedSessionService.isProtectedSessionAvailable());
|
||||||
|
if (format !== "share") {
|
||||||
|
shouldIncludeFile = shouldIncludeFile && (note.getContent().length > 0 || childBranches.length === 0);
|
||||||
|
}
|
||||||
|
|
||||||
// if it's a leaf, then we'll export it even if it's empty
|
// if it's a leaf, then we'll export it even if it's empty
|
||||||
if (available && (note.getContent().length > 0 || childBranches.length === 0)) {
|
if (shouldIncludeFile) {
|
||||||
meta.dataFileName = getDataFileName(note.type, note.mime, baseFileName, existingFileNames);
|
meta.dataFileName = getDataFileName(note.type, note.mime, baseFileName, existingFileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,8 +261,6 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rewriteFn = (zipExportOptions?.customRewriteLinks ? zipExportOptions?.customRewriteLinks(rewriteLinks, getNoteTargetUrl) : rewriteLinks);
|
|
||||||
|
|
||||||
function rewriteLinks(content: string, noteMeta: NoteMeta): string {
|
function rewriteLinks(content: string, noteMeta: NoteMeta): string {
|
||||||
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/[^"]*"/g, (match, targetNoteId) => {
|
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/[^"]*"/g, (match, targetNoteId) => {
|
||||||
const url = getNoteTargetUrl(targetNoteId, noteMeta);
|
const url = getNoteTargetUrl(targetNoteId, noteMeta);
|
||||||
@@ -316,54 +302,16 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer {
|
function prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note?: BNote): string | Buffer {
|
||||||
if (["html", "markdown"].includes(noteMeta?.format || "")) {
|
const isText = ["html", "markdown"].includes(noteMeta?.format || "");
|
||||||
|
if (isText) {
|
||||||
content = content.toString();
|
content = content.toString();
|
||||||
content = rewriteFn(content, noteMeta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteMeta.format === "html" && typeof content === "string") {
|
content = provider.prepareContent(title, content, noteMeta, note, branch);
|
||||||
if (!content.substr(0, 100).toLowerCase().includes("<html") && !zipExportOptions?.skipHtmlTemplate) {
|
|
||||||
if (!noteMeta?.notePath?.length) {
|
|
||||||
throw new Error("Missing note path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const cssUrl = `${"../".repeat(noteMeta.notePath.length - 1)}style.css`;
|
|
||||||
const htmlTitle = escapeHtml(title);
|
|
||||||
|
|
||||||
// <base> element will make sure external links are openable - https://github.com/zadam/trilium/issues/1289#issuecomment-704066809
|
|
||||||
content = `<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="${cssUrl}">
|
|
||||||
<base target="_parent">
|
|
||||||
<title data-trilium-title>${htmlTitle}</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
<h1 data-trilium-h1>${htmlTitle}</h1>
|
|
||||||
|
|
||||||
<div class="ck-content">${content}</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content.length < 100_000 ? html.prettyPrint(content, { indent_size: 2 }) : content;
|
|
||||||
} else if (noteMeta.format === "markdown" && typeof content === "string") {
|
|
||||||
let markdownContent = mdService.toMarkdown(content);
|
|
||||||
|
|
||||||
if (markdownContent.trim().length > 0 && !markdownContent.startsWith("# ")) {
|
|
||||||
markdownContent = `# ${title}\r
|
|
||||||
${markdownContent}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return markdownContent;
|
|
||||||
} else {
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function saveNote(noteMeta: NoteMeta, filePathPrefix: string) {
|
function saveNote(noteMeta: NoteMeta, filePathPrefix: string) {
|
||||||
log.info(`Exporting note '${noteMeta.noteId}'`);
|
log.info(`Exporting note '${noteMeta.noteId}'`);
|
||||||
@@ -377,7 +325,7 @@ ${markdownContent}`;
|
|||||||
|
|
||||||
let content: string | Buffer = `<p>This is a clone of a note. Go to its <a href="${targetUrl}">primary location</a>.</p>`;
|
let content: string | Buffer = `<p>This is a clone of a note. Go to its <a href="${targetUrl}">primary location</a>.</p>`;
|
||||||
|
|
||||||
content = prepareContent(noteMeta.title, content, noteMeta);
|
content = prepareContent(noteMeta.title, content, noteMeta, undefined);
|
||||||
|
|
||||||
archive.append(content, { name: filePathPrefix + noteMeta.dataFileName });
|
archive.append(content, { name: filePathPrefix + noteMeta.dataFileName });
|
||||||
|
|
||||||
@@ -393,7 +341,7 @@ ${markdownContent}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (noteMeta.dataFileName) {
|
if (noteMeta.dataFileName) {
|
||||||
const content = prepareContent(noteMeta.title, note.getContent(), noteMeta);
|
const content = prepareContent(noteMeta.title, note.getContent(), noteMeta, note);
|
||||||
|
|
||||||
archive.append(content, {
|
archive.append(content, {
|
||||||
name: filePathPrefix + noteMeta.dataFileName,
|
name: filePathPrefix + noteMeta.dataFileName,
|
||||||
@@ -429,99 +377,6 @@ ${markdownContent}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveNavigation(rootMeta: NoteMeta, navigationMeta: NoteMeta) {
|
|
||||||
if (!navigationMeta.dataFileName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveNavigationInner(meta: NoteMeta) {
|
|
||||||
let html = "<li>";
|
|
||||||
|
|
||||||
const escapedTitle = escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ""}${meta.title}`);
|
|
||||||
|
|
||||||
if (meta.dataFileName && meta.noteId) {
|
|
||||||
const targetUrl = getNoteTargetUrl(meta.noteId, rootMeta);
|
|
||||||
|
|
||||||
html += `<a href="${targetUrl}" target="detail">${escapedTitle}</a>`;
|
|
||||||
} else {
|
|
||||||
html += escapedTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta.children && meta.children.length > 0) {
|
|
||||||
html += "<ul>";
|
|
||||||
|
|
||||||
for (const child of meta.children) {
|
|
||||||
html += saveNavigationInner(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "</ul>";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${html}</li>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullHtml = `<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ul>${saveNavigationInner(rootMeta)}</ul>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
const prettyHtml = fullHtml.length < 100_000 ? html.prettyPrint(fullHtml, { indent_size: 2 }) : fullHtml;
|
|
||||||
|
|
||||||
archive.append(prettyHtml, { name: navigationMeta.dataFileName });
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveIndex(rootMeta: NoteMeta, indexMeta: NoteMeta) {
|
|
||||||
let firstNonEmptyNote;
|
|
||||||
let curMeta = rootMeta;
|
|
||||||
|
|
||||||
if (!indexMeta.dataFileName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!firstNonEmptyNote) {
|
|
||||||
if (curMeta.dataFileName && curMeta.noteId) {
|
|
||||||
firstNonEmptyNote = getNoteTargetUrl(curMeta.noteId, rootMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curMeta.children && curMeta.children.length > 0) {
|
|
||||||
curMeta = curMeta.children[0];
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullHtml = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
<frameset cols="25%,75%">
|
|
||||||
<frame name="navigation" src="navigation.html">
|
|
||||||
<frame name="detail" src="${firstNonEmptyNote}">
|
|
||||||
</frameset>
|
|
||||||
</html>`;
|
|
||||||
|
|
||||||
archive.append(fullHtml, { name: indexMeta.dataFileName });
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveCss(rootMeta: NoteMeta, cssMeta: NoteMeta) {
|
|
||||||
if (!cssMeta.dataFileName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cssFile = isDev
|
|
||||||
? path.join(__dirname, "../../../../../node_modules/ckeditor5/dist/ckeditor5-content.css")
|
|
||||||
: path.join(getResourceDir(), "ckeditor5-content.css");
|
|
||||||
|
|
||||||
archive.append(fs.readFileSync(cssFile, "utf-8"), { name: cssMeta.dataFileName });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
|
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
|
||||||
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
||||||
if (!rootMeta) {
|
if (!rootMeta) {
|
||||||
@@ -534,33 +389,9 @@ ${markdownContent}`;
|
|||||||
files: [rootMeta]
|
files: [rootMeta]
|
||||||
};
|
};
|
||||||
|
|
||||||
let navigationMeta: NoteMeta | null = null;
|
provider.prepareMeta(metaFile);
|
||||||
let indexMeta: NoteMeta | null = null;
|
|
||||||
let cssMeta: NoteMeta | null = null;
|
|
||||||
|
|
||||||
if (format === "html") {
|
|
||||||
navigationMeta = {
|
|
||||||
noImport: true,
|
|
||||||
dataFileName: "navigation.html"
|
|
||||||
};
|
|
||||||
|
|
||||||
metaFile.files.push(navigationMeta);
|
|
||||||
|
|
||||||
indexMeta = {
|
|
||||||
noImport: true,
|
|
||||||
dataFileName: "index.html"
|
|
||||||
};
|
|
||||||
|
|
||||||
metaFile.files.push(indexMeta);
|
|
||||||
|
|
||||||
cssMeta = {
|
|
||||||
noImport: true,
|
|
||||||
dataFileName: "style.css"
|
|
||||||
};
|
|
||||||
|
|
||||||
metaFile.files.push(cssMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
for (const noteMeta of Object.values(noteIdToMeta)) {
|
for (const noteMeta of Object.values(noteIdToMeta)) {
|
||||||
// filter out relations which are not inside this export
|
// filter out relations which are not inside this export
|
||||||
noteMeta.attributes = (noteMeta.attributes || []).filter((attr) => {
|
noteMeta.attributes = (noteMeta.attributes || []).filter((attr) => {
|
||||||
@@ -584,34 +415,6 @@ ${markdownContent}`;
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metaFileJson = JSON.stringify(metaFile, null, "\t");
|
|
||||||
|
|
||||||
archive.append(metaFileJson, { name: "!!!meta.json" });
|
|
||||||
|
|
||||||
saveNote(rootMeta, "");
|
|
||||||
|
|
||||||
if (format === "html") {
|
|
||||||
if (!navigationMeta || !indexMeta || !cssMeta) {
|
|
||||||
throw new Error("Missing meta.");
|
|
||||||
}
|
|
||||||
|
|
||||||
saveNavigation(rootMeta, navigationMeta);
|
|
||||||
saveIndex(rootMeta, indexMeta);
|
|
||||||
saveCss(rootMeta, cssMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
const note = branch.getNote();
|
|
||||||
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected() || "note"}.zip`;
|
|
||||||
|
|
||||||
if (setHeaders && "setHeader" in res) {
|
|
||||||
res.setHeader("Content-Disposition", getContentDisposition(zipFileName));
|
|
||||||
res.setHeader("Content-Type", "application/zip");
|
|
||||||
}
|
|
||||||
|
|
||||||
archive.pipe(res);
|
|
||||||
await archive.finalize();
|
|
||||||
taskContext.taskSucceeded(null);
|
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const message = `Export failed with error: ${e instanceof Error ? e.message : String(e)}`;
|
const message = `Export failed with error: ${e instanceof Error ? e.message : String(e)}`;
|
||||||
log.error(message);
|
log.error(message);
|
||||||
@@ -623,9 +426,30 @@ ${markdownContent}`;
|
|||||||
res.status(500).send(message);
|
res.status(500).send(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metaFileJson = JSON.stringify(metaFile, null, "\t");
|
||||||
|
|
||||||
|
archive.append(metaFileJson, { name: "!!!meta.json" });
|
||||||
|
|
||||||
|
saveNote(rootMeta, "");
|
||||||
|
|
||||||
|
provider.afterDone(rootMeta);
|
||||||
|
|
||||||
|
const note = branch.getNote();
|
||||||
|
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
|
||||||
|
|
||||||
|
if (setHeaders && "setHeader" in res) {
|
||||||
|
res.setHeader("Content-Disposition", getContentDisposition(zipFileName));
|
||||||
|
res.setHeader("Content-Type", "application/zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string, zipExportOptions?: AdvancedExportOptions) {
|
archive.pipe(res);
|
||||||
|
await archive.finalize();
|
||||||
|
|
||||||
|
taskContext.taskSucceeded(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exportToZipFile(noteId: string, format: ExportFormat, zipFilePath: string, zipExportOptions?: AdvancedExportOptions) {
|
||||||
const fileOutputStream = fs.createWriteStream(zipFilePath);
|
const fileOutputStream = fs.createWriteStream(zipFilePath);
|
||||||
const taskContext = new TaskContext("no-progress-reporting", "export", null);
|
const taskContext = new TaskContext("no-progress-reporting", "export", null);
|
||||||
|
|
||||||
|
|||||||
89
apps/server/src/services/export/zip/abstract_provider.ts
Normal file
89
apps/server/src/services/export/zip/abstract_provider.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Archiver } from "archiver";
|
||||||
|
import type { default as NoteMeta, NoteMetaFile } from "../../meta/note_meta.js";
|
||||||
|
import type BNote from "../../../becca/entities/bnote.js";
|
||||||
|
import type BBranch from "../../../becca/entities/bbranch.js";
|
||||||
|
import mimeTypes from "mime-types";
|
||||||
|
import { NoteType } from "@triliumnext/commons";
|
||||||
|
|
||||||
|
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
|
||||||
|
|
||||||
|
export type ExportFormat = "html" | "markdown" | "share";
|
||||||
|
|
||||||
|
export interface AdvancedExportOptions {
|
||||||
|
/**
|
||||||
|
* If `true`, then only the note's content will be kept. If `false` (default), then each page will have its own <html> template.
|
||||||
|
*/
|
||||||
|
skipHtmlTemplate?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a custom function to rewrite the links found in HTML or Markdown notes. This method is called for every note imported, if it's of the right type.
|
||||||
|
*
|
||||||
|
* @param originalRewriteLinks the original rewrite links function. Can be used to access the default behaviour without having to reimplement it.
|
||||||
|
* @param getNoteTargetUrl the method to obtain a note's target URL, used internally by `originalRewriteLinks` but can be used here as well.
|
||||||
|
* @returns a function to rewrite the links in HTML or Markdown notes.
|
||||||
|
*/
|
||||||
|
customRewriteLinks?: (originalRewriteLinks: RewriteLinksFn, getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null) => RewriteLinksFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZipExportProviderData {
|
||||||
|
branch: BBranch;
|
||||||
|
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
|
||||||
|
archive: Archiver;
|
||||||
|
zipExportOptions?: AdvancedExportOptions;
|
||||||
|
rewriteFn: RewriteLinksFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ZipExportProvider {
|
||||||
|
branch: BBranch;
|
||||||
|
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
|
||||||
|
archive: Archiver;
|
||||||
|
zipExportOptions?: AdvancedExportOptions;
|
||||||
|
rewriteFn: RewriteLinksFn;
|
||||||
|
|
||||||
|
constructor(data: ZipExportProviderData) {
|
||||||
|
this.branch = data.branch;
|
||||||
|
this.getNoteTargetUrl = data.getNoteTargetUrl;
|
||||||
|
this.archive = data.archive;
|
||||||
|
this.zipExportOptions = data.zipExportOptions;
|
||||||
|
this.rewriteFn = data.rewriteFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract prepareMeta(metaFile: NoteMetaFile): void;
|
||||||
|
abstract prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer;
|
||||||
|
abstract afterDone(rootMeta: NoteMeta): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the extension of the resulting file for a specific note type.
|
||||||
|
*
|
||||||
|
* @param type the type of the note.
|
||||||
|
* @param mime the mime type of the note.
|
||||||
|
* @param existingExtension the existing extension, including the leading period character.
|
||||||
|
* @param format the format requested for export (e.g. HTML, Markdown).
|
||||||
|
* @returns an extension *without* the leading period character, or `null` to preserve the existing extension instead.
|
||||||
|
*/
|
||||||
|
mapExtension(type: NoteType | null, mime: string, existingExtension: string, format: ExportFormat) {
|
||||||
|
// the following two are handled specifically since we always want to have these extensions no matter the automatic detection
|
||||||
|
// and/or existing detected extensions in the note name
|
||||||
|
if (type === "text" && format === "markdown") {
|
||||||
|
return "md";
|
||||||
|
} else if (type === "text" && format === "html") {
|
||||||
|
return "html";
|
||||||
|
} else if (mime === "application/x-javascript" || mime === "text/javascript") {
|
||||||
|
return "js";
|
||||||
|
} else if (type === "canvas" || mime === "application/json") {
|
||||||
|
return "json";
|
||||||
|
} else if (existingExtension.length > 0) {
|
||||||
|
// if the page already has an extension, then we'll just keep it
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (mime?.toLowerCase()?.trim() === "image/jpg") {
|
||||||
|
return "jpg";
|
||||||
|
} else if (mime?.toLowerCase()?.trim() === "text/mermaid") {
|
||||||
|
return "txt";
|
||||||
|
} else {
|
||||||
|
return mimeTypes.extension(mime) || "dat";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
176
apps/server/src/services/export/zip/html.ts
Normal file
176
apps/server/src/services/export/zip/html.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import type NoteMeta from "../../meta/note_meta.js";
|
||||||
|
import { escapeHtml, getResourceDir, isDev } from "../../utils";
|
||||||
|
import html from "html";
|
||||||
|
import { ZipExportProvider } from "./abstract_provider.js";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
export default class HtmlExportProvider extends ZipExportProvider {
|
||||||
|
|
||||||
|
private navigationMeta: NoteMeta | null = null;
|
||||||
|
private indexMeta: NoteMeta | null = null;
|
||||||
|
private cssMeta: NoteMeta | null = null;
|
||||||
|
|
||||||
|
prepareMeta(metaFile) {
|
||||||
|
this.navigationMeta = {
|
||||||
|
noImport: true,
|
||||||
|
dataFileName: "navigation.html"
|
||||||
|
};
|
||||||
|
metaFile.files.push(this.navigationMeta);
|
||||||
|
|
||||||
|
this.indexMeta = {
|
||||||
|
noImport: true,
|
||||||
|
dataFileName: "index.html"
|
||||||
|
};
|
||||||
|
metaFile.files.push(this.indexMeta);
|
||||||
|
|
||||||
|
this.cssMeta = {
|
||||||
|
noImport: true,
|
||||||
|
dataFileName: "style.css"
|
||||||
|
};
|
||||||
|
metaFile.files.push(this.cssMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer {
|
||||||
|
if (noteMeta.format === "html" && typeof content === "string") {
|
||||||
|
if (!content.substr(0, 100).toLowerCase().includes("<html") && !this.zipExportOptions?.skipHtmlTemplate) {
|
||||||
|
if (!noteMeta?.notePath?.length) {
|
||||||
|
throw new Error("Missing note path.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssUrl = `${"../".repeat(noteMeta.notePath.length - 1)}style.css`;
|
||||||
|
const htmlTitle = escapeHtml(title);
|
||||||
|
|
||||||
|
// <base> element will make sure external links are openable - https://github.com/zadam/trilium/issues/1289#issuecomment-704066809
|
||||||
|
content = `<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="${cssUrl}">
|
||||||
|
<base target="_parent">
|
||||||
|
<title data-trilium-title>${htmlTitle}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h1 data-trilium-h1>${htmlTitle}</h1>
|
||||||
|
|
||||||
|
<div class="ck-content">${content}</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.length < 100_000) {
|
||||||
|
content = html.prettyPrint(content, { indent_size: 2 })
|
||||||
|
}
|
||||||
|
content = this.rewriteFn(content as string, noteMeta);
|
||||||
|
return content;
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterDone(rootMeta: NoteMeta) {
|
||||||
|
if (!this.navigationMeta || !this.indexMeta || !this.cssMeta) {
|
||||||
|
throw new Error("Missing meta.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#saveNavigation(rootMeta, this.navigationMeta);
|
||||||
|
this.#saveIndex(rootMeta, this.indexMeta);
|
||||||
|
this.#saveCss(rootMeta, this.cssMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveNavigationInner(rootMeta: NoteMeta, meta: NoteMeta) {
|
||||||
|
let html = "<li>";
|
||||||
|
|
||||||
|
const escapedTitle = escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ""}${meta.title}`);
|
||||||
|
|
||||||
|
if (meta.dataFileName && meta.noteId) {
|
||||||
|
const targetUrl = this.getNoteTargetUrl(meta.noteId, rootMeta);
|
||||||
|
|
||||||
|
html += `<a href="${targetUrl}" target="detail">${escapedTitle}</a>`;
|
||||||
|
} else {
|
||||||
|
html += escapedTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta.children && meta.children.length > 0) {
|
||||||
|
html += "<ul>";
|
||||||
|
|
||||||
|
for (const child of meta.children) {
|
||||||
|
html += this.#saveNavigationInner(rootMeta, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${html}</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveNavigation(rootMeta: NoteMeta, navigationMeta: NoteMeta) {
|
||||||
|
if (!navigationMeta.dataFileName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullHtml = `<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ul>${this.#saveNavigationInner(rootMeta, rootMeta)}</ul>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
const prettyHtml = fullHtml.length < 100_000 ? html.prettyPrint(fullHtml, { indent_size: 2 }) : fullHtml;
|
||||||
|
|
||||||
|
this.archive.append(prettyHtml, { name: navigationMeta.dataFileName });
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveIndex(rootMeta: NoteMeta, indexMeta: NoteMeta) {
|
||||||
|
let firstNonEmptyNote;
|
||||||
|
let curMeta = rootMeta;
|
||||||
|
|
||||||
|
if (!indexMeta.dataFileName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!firstNonEmptyNote) {
|
||||||
|
if (curMeta.dataFileName && curMeta.noteId) {
|
||||||
|
firstNonEmptyNote = this.getNoteTargetUrl(curMeta.noteId, rootMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curMeta.children && curMeta.children.length > 0) {
|
||||||
|
curMeta = curMeta.children[0];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullHtml = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<frameset cols="25%,75%">
|
||||||
|
<frame name="navigation" src="navigation.html">
|
||||||
|
<frame name="detail" src="${firstNonEmptyNote}">
|
||||||
|
</frameset>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
this.archive.append(fullHtml, { name: indexMeta.dataFileName });
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveCss(rootMeta: NoteMeta, cssMeta: NoteMeta) {
|
||||||
|
if (!cssMeta.dataFileName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssFile = isDev
|
||||||
|
? path.join(__dirname, "../../../../../../node_modules/ckeditor5/dist/ckeditor5-content.css")
|
||||||
|
: path.join(getResourceDir(), "ckeditor5-content.css");
|
||||||
|
const cssContent = fs.readFileSync(cssFile, "utf-8");
|
||||||
|
this.archive.append(cssContent, { name: cssMeta.dataFileName });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
27
apps/server/src/services/export/zip/markdown.ts
Normal file
27
apps/server/src/services/export/zip/markdown.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import NoteMeta from "../../meta/note_meta"
|
||||||
|
import { ZipExportProvider } from "./abstract_provider.js"
|
||||||
|
import mdService from "../markdown.js";
|
||||||
|
|
||||||
|
export default class MarkdownExportProvider extends ZipExportProvider {
|
||||||
|
|
||||||
|
prepareMeta() { }
|
||||||
|
|
||||||
|
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer {
|
||||||
|
if (noteMeta.format === "markdown" && typeof content === "string") {
|
||||||
|
let markdownContent = mdService.toMarkdown(content);
|
||||||
|
|
||||||
|
if (markdownContent.trim().length > 0 && !markdownContent.startsWith("# ")) {
|
||||||
|
markdownContent = `# ${title}\r
|
||||||
|
${markdownContent}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
markdownContent = this.rewriteFn(markdownContent, noteMeta);
|
||||||
|
return markdownContent;
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterDone() { }
|
||||||
|
|
||||||
|
}
|
||||||
115
apps/server/src/services/export/zip/share_theme.ts
Normal file
115
apps/server/src/services/export/zip/share_theme.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { join } from "path";
|
||||||
|
import NoteMeta, { NoteMetaFile } from "../../meta/note_meta";
|
||||||
|
import { ExportFormat, ZipExportProvider } from "./abstract_provider.js";
|
||||||
|
import { RESOURCE_DIR } from "../../resource_dir";
|
||||||
|
import { getResourceDir, isDev } from "../../utils";
|
||||||
|
import fs, { readdirSync } from "fs";
|
||||||
|
import { renderNoteForExport } from "../../../share/content_renderer";
|
||||||
|
import type BNote from "../../../becca/entities/bnote.js";
|
||||||
|
import type BBranch from "../../../becca/entities/bbranch.js";
|
||||||
|
import { getShareThemeAssetDir } from "../../../routes/assets";
|
||||||
|
|
||||||
|
const shareThemeAssetDir = getShareThemeAssetDir();
|
||||||
|
|
||||||
|
export default class ShareThemeExportProvider extends ZipExportProvider {
|
||||||
|
|
||||||
|
private assetsMeta: NoteMeta[] = [];
|
||||||
|
private indexMeta: NoteMeta | null = null;
|
||||||
|
|
||||||
|
prepareMeta(metaFile: NoteMetaFile): void {
|
||||||
|
|
||||||
|
const assets = [
|
||||||
|
"icon-color.svg"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of readdirSync(shareThemeAssetDir)) {
|
||||||
|
assets.push(`assets/${file}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const asset of assets) {
|
||||||
|
const assetMeta = {
|
||||||
|
noImport: true,
|
||||||
|
dataFileName: asset
|
||||||
|
};
|
||||||
|
this.assetsMeta.push(assetMeta);
|
||||||
|
metaFile.files.push(assetMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.indexMeta = {
|
||||||
|
noImport: true,
|
||||||
|
dataFileName: "index.html"
|
||||||
|
};
|
||||||
|
|
||||||
|
metaFile.files.push(this.indexMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer {
|
||||||
|
if (!noteMeta?.notePath?.length) {
|
||||||
|
throw new Error("Missing note path.");
|
||||||
|
}
|
||||||
|
const basePath = "../".repeat(noteMeta.notePath.length - 1);
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
content = renderNoteForExport(note, branch, basePath, noteMeta.notePath.slice(0, -1));
|
||||||
|
if (typeof content === "string") {
|
||||||
|
content = content.replace(/href="[^"]*\.\/([a-zA-Z0-9_\/]{12})[^"]*"/g, (match, id) => {
|
||||||
|
if (match.includes("/assets/")) return match;
|
||||||
|
return `href="#root/${id}"`;
|
||||||
|
});
|
||||||
|
content = this.rewriteFn(content, noteMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterDone(rootMeta: NoteMeta): void {
|
||||||
|
this.#saveAssets(rootMeta, this.assetsMeta);
|
||||||
|
this.#saveIndex(rootMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapExtension(type: string | null, mime: string, existingExtension: string, format: ExportFormat): string | null {
|
||||||
|
if (mime.startsWith("image/")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "html";
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveIndex(rootMeta: NoteMeta) {
|
||||||
|
if (!this.indexMeta?.dataFileName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = this.branch.getNote();
|
||||||
|
const fullHtml = this.prepareContent(rootMeta.title ?? "", note.getContent(), rootMeta, note, this.branch);
|
||||||
|
this.archive.append(fullHtml, { name: this.indexMeta.dataFileName });
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveAssets(rootMeta: NoteMeta, assetsMeta: NoteMeta[]) {
|
||||||
|
for (const assetMeta of assetsMeta) {
|
||||||
|
if (!assetMeta.dataFileName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cssContent = getShareThemeAssets(assetMeta.dataFileName);
|
||||||
|
this.archive.append(cssContent, { name: assetMeta.dataFileName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getShareThemeAssets(nameWithExtension: string) {
|
||||||
|
let path: string | undefined;
|
||||||
|
if (nameWithExtension === "icon-color.svg") {
|
||||||
|
path = join(RESOURCE_DIR, "images", nameWithExtension);
|
||||||
|
} else if (nameWithExtension.startsWith("assets")) {
|
||||||
|
path = join(shareThemeAssetDir, nameWithExtension.replace(/^assets\//, ""));
|
||||||
|
} else if (isDev) {
|
||||||
|
path = join(getResourceDir(), "..", "..", "client", "dist", "src", nameWithExtension);
|
||||||
|
} else {
|
||||||
|
path = join(getResourceDir(), "public", "src", nameWithExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.readFileSync(path);
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs
|
|||||||
"es": () => import("dayjs/locale/es.js"),
|
"es": () => import("dayjs/locale/es.js"),
|
||||||
"fa": () => import("dayjs/locale/fa.js"),
|
"fa": () => import("dayjs/locale/fa.js"),
|
||||||
"fr": () => import("dayjs/locale/fr.js"),
|
"fr": () => import("dayjs/locale/fr.js"),
|
||||||
|
"it": () => import("dayjs/locale/it.js"),
|
||||||
"he": () => import("dayjs/locale/he.js"),
|
"he": () => import("dayjs/locale/he.js"),
|
||||||
"ja": () => import("dayjs/locale/ja.js"),
|
"ja": () => import("dayjs/locale/ja.js"),
|
||||||
"ku": () => import("dayjs/locale/ku.js"),
|
"ku": () => import("dayjs/locale/ku.js"),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { NoteType } from "@triliumnext/commons";
|
import type { NoteType } from "@triliumnext/commons";
|
||||||
import type AttachmentMeta from "./attachment_meta.js";
|
import type AttachmentMeta from "./attachment_meta.js";
|
||||||
import type AttributeMeta from "./attribute_meta.js";
|
import type AttributeMeta from "./attribute_meta.js";
|
||||||
|
import type { ExportFormat } from "../export/zip/abstract_provider.js";
|
||||||
|
|
||||||
export interface NoteMetaFile {
|
export interface NoteMetaFile {
|
||||||
formatVersion: number;
|
formatVersion: number;
|
||||||
@@ -19,7 +20,7 @@ export default interface NoteMeta {
|
|||||||
type?: NoteType;
|
type?: NoteType;
|
||||||
mime?: string;
|
mime?: string;
|
||||||
/** 'html' or 'markdown', applicable to text notes only */
|
/** 'html' or 'markdown', applicable to text notes only */
|
||||||
format?: "html" | "markdown";
|
format?: ExportFormat;
|
||||||
dataFileName?: string;
|
dataFileName?: string;
|
||||||
dirFileName?: string;
|
dirFileName?: string;
|
||||||
/** this file should not be imported (e.g., HTML navigation) */
|
/** this file should not be imported (e.g., HTML navigation) */
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ sqlInit.dbReady.then(() => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(() => checkProtectedSessionExpiration(), 1);
|
setInterval(() => checkProtectedSessionExpiration(), 30000);
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkProtectedSessionExpiration() {
|
function checkProtectedSessionExpiration() {
|
||||||
|
|||||||
@@ -681,3 +681,34 @@ describe("#normalizeCustomHandlerPattern", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#slugify", () => {
|
||||||
|
it("should return a slugified string", () => {
|
||||||
|
const testString = "This is a Test String! With unicode & Special #Chars.";
|
||||||
|
const expectedSlug = "this-is-a-test-string-with-unicode-special-chars";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports CJK characters without alteration", () => {
|
||||||
|
const testString = "测试中文字符";
|
||||||
|
const expectedSlug = "测试中文字符";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports Cyrillic characters without alteration", () => {
|
||||||
|
const testString = "Тестирование кириллических символов";
|
||||||
|
const expectedSlug = "тестирование-кириллических-символов";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
// preserves diacritic marks
|
||||||
|
it("preserves diacritic marks", () => {
|
||||||
|
const testString = "Café naïve façade jalapeño";
|
||||||
|
const expectedSlug = "café-naïve-façade-jalapeño";
|
||||||
|
const result = utils.slugify(testString);
|
||||||
|
expect(result).toBe(expectedSlug);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -497,6 +497,14 @@ export function formatSize(size: number | null | undefined) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function slugify(text: string) {
|
||||||
|
return text
|
||||||
|
.normalize("NFC") // keep composed form, preserves accents
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\p{Letter}\p{Number}]+/gu, "-") // replace non-letter/number with "-"
|
||||||
|
.replace(/(^-|-$)+/g, ""); // trim dashes
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
compareVersions,
|
compareVersions,
|
||||||
crash,
|
crash,
|
||||||
@@ -532,6 +540,7 @@ export default {
|
|||||||
safeExtractMessageAndStackFromError,
|
safeExtractMessageAndStackFromError,
|
||||||
sanitizeSqlIdentifier,
|
sanitizeSqlIdentifier,
|
||||||
stripTags,
|
stripTags,
|
||||||
|
slugify,
|
||||||
timeLimit,
|
timeLimit,
|
||||||
toBase64,
|
toBase64,
|
||||||
toMap,
|
toMap,
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
import { parse, HTMLElement, TextNode } from "node-html-parser";
|
import { parse, HTMLElement, TextNode, Options } from "node-html-parser";
|
||||||
import shaca from "./shaca/shaca.js";
|
import shaca from "./shaca/shaca.js";
|
||||||
import assetPath from "../services/asset_path.js";
|
import assetPath, { assetUrlFragment } from "../services/asset_path.js";
|
||||||
import shareRoot from "./share_root.js";
|
import shareRoot from "./share_root.js";
|
||||||
import escapeHtml from "escape-html";
|
import escapeHtml from "escape-html";
|
||||||
import type SNote from "./shaca/entities/snote.js";
|
import type SNote from "./shaca/entities/snote.js";
|
||||||
|
import BNote from "../becca/entities/bnote.js";
|
||||||
|
import type BBranch from "../becca/entities/bbranch.js";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
import SBranch from "./shaca/entities/sbranch.js";
|
||||||
|
import options from "../services/options.js";
|
||||||
|
import utils, { getResourceDir, isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
|
||||||
|
import ejs from "ejs";
|
||||||
|
import log from "../services/log.js";
|
||||||
|
import { join } from "path";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { highlightAuto } from "@triliumnext/highlightjs";
|
||||||
|
|
||||||
|
const shareAdjustedAssetPath = isDev ? assetPath : `../${assetPath}`;
|
||||||
|
const templateCache: Map<string, string> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the output of the content renderer.
|
* Represents the output of the content renderer.
|
||||||
@@ -16,7 +29,192 @@ export interface Result {
|
|||||||
isEmpty?: boolean;
|
isEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getContent(note: SNote) {
|
interface Subroot {
|
||||||
|
note?: SNote | BNote;
|
||||||
|
branch?: SBranch | BBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSharedSubTreeRoot(note: SNote | BNote | undefined): Subroot {
|
||||||
|
if (!note || note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
||||||
|
// share root itself is not shared
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// every path leads to share root, but which one to choose?
|
||||||
|
// for the sake of simplicity, URLs are not note paths
|
||||||
|
const parentBranch = note.getParentBranches()[0];
|
||||||
|
|
||||||
|
if (note instanceof BNote) {
|
||||||
|
return {
|
||||||
|
note,
|
||||||
|
branch: parentBranch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentBranch.parentNoteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
||||||
|
return {
|
||||||
|
note,
|
||||||
|
branch: parentBranch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSharedSubTreeRoot(parentBranch.getParentNote());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath: string, ancestors: string[]) {
|
||||||
|
const subRoot: Subroot = {
|
||||||
|
branch: parentBranch,
|
||||||
|
note: parentBranch.getNote()
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderNoteContentInternal(note, {
|
||||||
|
subRoot,
|
||||||
|
rootNoteId: parentBranch.noteId,
|
||||||
|
cssToLoad: [
|
||||||
|
`${basePath}assets/styles.css`,
|
||||||
|
`${basePath}assets/scripts.css`,
|
||||||
|
],
|
||||||
|
jsToLoad: [
|
||||||
|
`${basePath}assets/scripts.js`
|
||||||
|
],
|
||||||
|
logoUrl: `${basePath}icon-color.svg`,
|
||||||
|
ancestors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderNoteContent(note: SNote) {
|
||||||
|
const subRoot = getSharedSubTreeRoot(note);
|
||||||
|
|
||||||
|
const ancestors: string[] = [];
|
||||||
|
let notePointer = note;
|
||||||
|
while (notePointer.parents[0]?.noteId !== subRoot.note?.noteId) {
|
||||||
|
const pointerParent = notePointer.parents[0];
|
||||||
|
if (!pointerParent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ancestors.push(pointerParent.noteId);
|
||||||
|
notePointer = pointerParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine CSS to load.
|
||||||
|
const cssToLoad: string[] = [];
|
||||||
|
if (!note.isLabelTruthy("shareOmitDefaultCss")) {
|
||||||
|
cssToLoad.push(`assets/styles.css`);
|
||||||
|
cssToLoad.push(`assets/scripts.css`);
|
||||||
|
}
|
||||||
|
for (const cssRelation of note.getRelations("shareCss")) {
|
||||||
|
cssToLoad.push(`api/notes/${cssRelation.value}/download`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine JS to load.
|
||||||
|
const jsToLoad: string[] = [
|
||||||
|
"assets/scripts.js"
|
||||||
|
];
|
||||||
|
for (const jsRelation of note.getRelations("shareJs")) {
|
||||||
|
jsToLoad.push(`api/notes/${jsRelation.value}/download`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customLogoId = note.getRelation("shareLogo")?.value;
|
||||||
|
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `../${assetUrlFragment}/images/icon-color.svg`;
|
||||||
|
|
||||||
|
return renderNoteContentInternal(note, {
|
||||||
|
subRoot,
|
||||||
|
rootNoteId: "_share",
|
||||||
|
cssToLoad,
|
||||||
|
jsToLoad,
|
||||||
|
logoUrl,
|
||||||
|
ancestors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RenderArgs {
|
||||||
|
subRoot: Subroot;
|
||||||
|
rootNoteId: string;
|
||||||
|
cssToLoad: string[];
|
||||||
|
jsToLoad: string[];
|
||||||
|
logoUrl: string;
|
||||||
|
ancestors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNoteContentInternal(note: SNote | BNote, renderArgs: RenderArgs) {
|
||||||
|
const { header, content, isEmpty } = getContent(note);
|
||||||
|
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
|
||||||
|
const opts = {
|
||||||
|
note,
|
||||||
|
header,
|
||||||
|
content,
|
||||||
|
isEmpty,
|
||||||
|
assetPath: shareAdjustedAssetPath,
|
||||||
|
assetUrlFragment,
|
||||||
|
showLoginInShareTheme,
|
||||||
|
t,
|
||||||
|
isDev,
|
||||||
|
utils,
|
||||||
|
...renderArgs
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the user has their own template.
|
||||||
|
if (note.hasRelation("shareTemplate")) {
|
||||||
|
// Get the template note and content
|
||||||
|
const templateId = note.getRelation("shareTemplate")?.value;
|
||||||
|
const templateNote = templateId && shaca.getNote(templateId);
|
||||||
|
|
||||||
|
// Make sure the note type is correct
|
||||||
|
if (templateNote && templateNote.type === "code" && templateNote.mime === "application/x-ejs") {
|
||||||
|
// EJS caches the result of this so we don't need to pre-cache
|
||||||
|
const includer = (path: string) => {
|
||||||
|
const childNote = templateNote.children.find((n) => path === n.title);
|
||||||
|
if (!childNote) throw new Error(`Unable to find child note: ${path}.`);
|
||||||
|
if (childNote.type !== "code" || childNote.mime !== "application/x-ejs") throw new Error("Incorrect child note type.");
|
||||||
|
|
||||||
|
const template = childNote.getContent();
|
||||||
|
if (typeof template !== "string") throw new Error("Invalid template content type.");
|
||||||
|
|
||||||
|
return { template };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to render user's template, w/ fallback to default view
|
||||||
|
try {
|
||||||
|
const content = templateNote.getContent();
|
||||||
|
if (typeof content === "string") {
|
||||||
|
return ejs.render(content, opts, { includer });
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
|
||||||
|
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render with the default view otherwise.
|
||||||
|
const templatePath = getDefaultTemplatePath("page");
|
||||||
|
return ejs.render(readTemplate(templatePath), opts, {
|
||||||
|
includer: (path) => {
|
||||||
|
// Path is relative to apps/server/dist/assets/views
|
||||||
|
return { template: readTemplate(getDefaultTemplatePath(path)) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultTemplatePath(template: string) {
|
||||||
|
// Path is relative to apps/server/dist/assets/views
|
||||||
|
return process.env.NODE_ENV === "development"
|
||||||
|
? join(__dirname, `../../../../packages/share-theme/src/templates/${template}.ejs`)
|
||||||
|
: join(getResourceDir(), `share-theme/templates/${template}.ejs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readTemplate(path: string) {
|
||||||
|
const cachedTemplate = templateCache.get(path);
|
||||||
|
if (cachedTemplate) {
|
||||||
|
return cachedTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateString = readFileSync(path, "utf-8");
|
||||||
|
templateCache.set(path, templateString);
|
||||||
|
return templateString;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContent(note: SNote | BNote) {
|
||||||
if (note.isProtected) {
|
if (note.isProtected) {
|
||||||
return {
|
return {
|
||||||
header: "",
|
header: "",
|
||||||
@@ -65,9 +263,12 @@ function renderIndex(result: Result) {
|
|||||||
result.content += "</ul>";
|
result.content += "</ul>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderText(result: Result, note: SNote) {
|
function renderText(result: Result, note: SNote | BNote) {
|
||||||
if (typeof result.content !== "string") return;
|
if (typeof result.content !== "string") return;
|
||||||
const document = parse(result.content || "");
|
const parseOpts: Partial<Options> = {
|
||||||
|
blockTextElements: {}
|
||||||
|
}
|
||||||
|
const document = parse(result.content || "", parseOpts);
|
||||||
|
|
||||||
// Process include notes.
|
// Process include notes.
|
||||||
for (const includeNoteEl of document.querySelectorAll("section.include-note")) {
|
for (const includeNoteEl of document.querySelectorAll("section.include-note")) {
|
||||||
@@ -80,7 +281,7 @@ function renderText(result: Result, note: SNote) {
|
|||||||
const includedResult = getContent(note);
|
const includedResult = getContent(note);
|
||||||
if (typeof includedResult.content !== "string") continue;
|
if (typeof includedResult.content !== "string") continue;
|
||||||
|
|
||||||
const includedDocument = parse(includedResult.content).childNodes;
|
const includedDocument = parse(includedResult.content, parseOpts).childNodes;
|
||||||
if (includedDocument) {
|
if (includedDocument) {
|
||||||
includeNoteEl.replaceWith(...includedDocument);
|
includeNoteEl.replaceWith(...includedDocument);
|
||||||
}
|
}
|
||||||
@@ -89,6 +290,7 @@ function renderText(result: Result, note: SNote) {
|
|||||||
result.isEmpty = document.textContent?.trim().length === 0 && document.querySelectorAll("img").length === 0;
|
result.isEmpty = document.textContent?.trim().length === 0 && document.querySelectorAll("img").length === 0;
|
||||||
|
|
||||||
if (!result.isEmpty) {
|
if (!result.isEmpty) {
|
||||||
|
// Process attachment links.
|
||||||
for (const linkEl of document.querySelectorAll("a")) {
|
for (const linkEl of document.querySelectorAll("a")) {
|
||||||
const href = linkEl.getAttribute("href");
|
const href = linkEl.getAttribute("href");
|
||||||
|
|
||||||
@@ -102,21 +304,15 @@ function renderText(result: Result, note: SNote) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.content = document.innerHTML ?? "";
|
// Apply syntax highlight.
|
||||||
|
for (const codeEl of document.querySelectorAll("pre code")) {
|
||||||
if (result.content.includes(`<span class="math-tex">`)) {
|
const highlightResult = highlightAuto(codeEl.innerText);
|
||||||
result.header += `
|
codeEl.innerHTML = highlightResult.value;
|
||||||
<script src="../${assetPath}/node_modules/katex/dist/katex.min.js"></script>
|
codeEl.classList.add("hljs");
|
||||||
<link rel="stylesheet" href="../${assetPath}/node_modules/katex/dist/katex.min.css">
|
|
||||||
<script src="../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
|
|
||||||
<script src="../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
renderMathInElement(document.getElementById('content'));
|
|
||||||
});
|
|
||||||
</script>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.content = document.innerHTML ?? "";
|
||||||
|
|
||||||
if (note.hasLabel("shareIndex")) {
|
if (note.hasLabel("shareIndex")) {
|
||||||
renderIndex(result);
|
renderIndex(result);
|
||||||
}
|
}
|
||||||
@@ -174,7 +370,7 @@ export function renderCode(result: Result) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMermaid(result: Result, note: SNote) {
|
function renderMermaid(result: Result, note: SNote | BNote) {
|
||||||
if (typeof result.content !== "string") {
|
if (typeof result.content !== "string") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -188,11 +384,11 @@ function renderMermaid(result: Result, note: SNote) {
|
|||||||
</details>`;
|
</details>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderImage(result: Result, note: SNote) {
|
function renderImage(result: Result, note: SNote | BNote) {
|
||||||
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`;
|
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFile(note: SNote, result: Result) {
|
function renderFile(note: SNote | BNote, result: Result) {
|
||||||
if (note.mime === "application/pdf") {
|
if (note.mime === "application/pdf") {
|
||||||
result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`;
|
result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,41 +4,12 @@ import type { Request, Response, Router } from "express";
|
|||||||
|
|
||||||
import shaca from "./shaca/shaca.js";
|
import shaca from "./shaca/shaca.js";
|
||||||
import shacaLoader from "./shaca/shaca_loader.js";
|
import shacaLoader from "./shaca/shaca_loader.js";
|
||||||
import shareRoot from "./share_root.js";
|
|
||||||
import contentRenderer from "./content_renderer.js";
|
|
||||||
import assetPath, { assetUrlFragment } from "../services/asset_path.js";
|
|
||||||
import appPath from "../services/app_path.js";
|
|
||||||
import searchService from "../services/search/services/search.js";
|
import searchService from "../services/search/services/search.js";
|
||||||
import SearchContext from "../services/search/search_context.js";
|
import SearchContext from "../services/search/search_context.js";
|
||||||
import log from "../services/log.js";
|
|
||||||
import type SNote from "./shaca/entities/snote.js";
|
import type SNote from "./shaca/entities/snote.js";
|
||||||
import type SBranch from "./shaca/entities/sbranch.js";
|
|
||||||
import type SAttachment from "./shaca/entities/sattachment.js";
|
import type SAttachment from "./shaca/entities/sattachment.js";
|
||||||
import utils, { isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
|
import { renderNoteContent } from "./content_renderer.js";
|
||||||
import options from "../services/options.js";
|
import utils from "../services/utils.js";
|
||||||
import { t } from "i18next";
|
|
||||||
import ejs from "ejs";
|
|
||||||
import { join } from "path";
|
|
||||||
|
|
||||||
function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } {
|
|
||||||
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
|
||||||
// share root itself is not shared
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// every path leads to share root, but which one to choose?
|
|
||||||
// for the sake of simplicity, URLs are not note paths
|
|
||||||
const parentBranch = note.getParentBranches()[0];
|
|
||||||
|
|
||||||
if (parentBranch.parentNoteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
|
||||||
return {
|
|
||||||
note,
|
|
||||||
branch: parentBranch
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return getSharedSubTreeRoot(parentBranch.getParentNote());
|
|
||||||
}
|
|
||||||
|
|
||||||
function addNoIndexHeader(note: SNote, res: Response) {
|
function addNoIndexHeader(note: SNote, res: Response) {
|
||||||
if (note.isLabelTruthy("shareDisallowRobotIndexing")) {
|
if (note.isLabelTruthy("shareDisallowRobotIndexing")) {
|
||||||
@@ -109,8 +80,7 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
|
|||||||
let svgString = "<svg/>";
|
let svgString = "<svg/>";
|
||||||
const attachment = image.getAttachmentByTitle(attachmentName);
|
const attachment = image.getAttachmentByTitle(attachmentName);
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
res.status(404);
|
|
||||||
renderDefault(res, "404");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const content = attachment.getContent();
|
const content = attachment.getContent();
|
||||||
@@ -138,12 +108,19 @@ function renderImageAttachment(image: SNote, res: Response, attachmentName: stri
|
|||||||
res.send(svg);
|
res.send(svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function render404(res: Response) {
|
||||||
|
res.status(404);
|
||||||
|
const shareThemePath = `../../share-theme/templates/404.ejs`;
|
||||||
|
res.render(shareThemePath);
|
||||||
|
}
|
||||||
|
|
||||||
function register(router: Router) {
|
function register(router: Router) {
|
||||||
|
|
||||||
function renderNote(note: SNote, req: Request, res: Response) {
|
function renderNote(note: SNote, req: Request, res: Response) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
console.log("Unable to find note ", note);
|
console.log("Unable to find note ", note);
|
||||||
res.status(404);
|
res.status(404);
|
||||||
renderDefault(res, "404");
|
render404(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,62 +138,7 @@ function register(router: Router) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { header, content, isEmpty } = contentRenderer.getContent(note);
|
res.send(renderNoteContent(note));
|
||||||
const subRoot = getSharedSubTreeRoot(note);
|
|
||||||
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
|
|
||||||
const opts = {
|
|
||||||
note,
|
|
||||||
header,
|
|
||||||
content,
|
|
||||||
isEmpty,
|
|
||||||
subRoot,
|
|
||||||
assetPath: isDev ? assetPath : `../${assetPath}`,
|
|
||||||
assetUrlFragment,
|
|
||||||
appPath: isDev ? appPath : `../${appPath}`,
|
|
||||||
showLoginInShareTheme,
|
|
||||||
t,
|
|
||||||
isDev
|
|
||||||
};
|
|
||||||
let useDefaultView = true;
|
|
||||||
|
|
||||||
// Check if the user has their own template
|
|
||||||
if (note.hasRelation("shareTemplate")) {
|
|
||||||
// Get the template note and content
|
|
||||||
const templateId = note.getRelation("shareTemplate")?.value;
|
|
||||||
const templateNote = templateId && shaca.getNote(templateId);
|
|
||||||
|
|
||||||
// Make sure the note type is correct
|
|
||||||
if (templateNote && templateNote.type === "code" && templateNote.mime === "application/x-ejs") {
|
|
||||||
// EJS caches the result of this so we don't need to pre-cache
|
|
||||||
const includer = (path: string) => {
|
|
||||||
const childNote = templateNote.children.find((n) => path === n.title);
|
|
||||||
if (!childNote) throw new Error(`Unable to find child note: ${path}.`);
|
|
||||||
if (childNote.type !== "code" || childNote.mime !== "application/x-ejs") throw new Error("Incorrect child note type.");
|
|
||||||
|
|
||||||
const template = childNote.getContent();
|
|
||||||
if (typeof template !== "string") throw new Error("Invalid template content type.");
|
|
||||||
|
|
||||||
return { template };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try to render user's template, w/ fallback to default view
|
|
||||||
try {
|
|
||||||
const content = templateNote.getContent();
|
|
||||||
if (typeof content === "string") {
|
|
||||||
const ejsResult = ejs.render(content, opts, { includer });
|
|
||||||
res.send(ejsResult);
|
|
||||||
useDefaultView = false; // Rendering went okay, don't use default view
|
|
||||||
}
|
|
||||||
} catch (e: unknown) {
|
|
||||||
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
|
|
||||||
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useDefaultView) {
|
|
||||||
renderDefault(res, "page", opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/share/", (req, res) => {
|
router.get("/share/", (req, res) => {
|
||||||
@@ -400,14 +322,6 @@ function register(router: Router) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDefault(res: Response<any, Record<string, any>>, template: "page" | "404", opts: any = {}) {
|
|
||||||
// Path is relative to apps/server/dist/assets/views
|
|
||||||
const shareThemePath = process.env.NODE_ENV === "development"
|
|
||||||
? join(__dirname, `../../../../packages/share-theme/src/templates/${template}.ejs`)
|
|
||||||
: `../../share-theme/templates/${template}.ejs`;
|
|
||||||
res.render(shareThemePath, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
register
|
register
|
||||||
};
|
};
|
||||||
|
|||||||
6
apps/server/src/types.d.ts
vendored
6
apps/server/src/types.d.ts
vendored
@@ -38,3 +38,9 @@ declare module "@triliumnext/share-theme/styles.css" {
|
|||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '*.css' {}
|
||||||
|
declare module '*?raw' {
|
||||||
|
const src: string
|
||||||
|
export default src
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
|
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"test": "vitest",
|
||||||
"preview": "pnpm build && vite preview"
|
"preview": "pnpm build && vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -12,8 +13,8 @@
|
|||||||
"i18next-http-backend": "3.0.2",
|
"i18next-http-backend": "3.0.2",
|
||||||
"preact": "10.27.2",
|
"preact": "10.27.2",
|
||||||
"preact-iso": "2.11.0",
|
"preact-iso": "2.11.0",
|
||||||
"preact-render-to-string": "6.6.2",
|
"preact-render-to-string": "6.6.3",
|
||||||
"react-i18next": "16.1.2"
|
"react-i18next": "16.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@preact/preset-vite": "2.10.2",
|
"@preact/preset-vite": "2.10.2",
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
"eslint-config-preact": "2.0.0",
|
"eslint-config-preact": "2.0.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"user-agent-data-types": "0.4.2",
|
"user-agent-data-types": "0.4.2",
|
||||||
"vite": "7.1.11"
|
"vite": "7.1.12"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "preact"
|
"extends": "preact"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user