Compare commits
39 Commits
web-clippe
...
feat/extra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91237918d8 | ||
|
|
3f207663aa | ||
|
|
21cb896849 | ||
|
|
b9bcb07b6d | ||
|
|
787b180378 | ||
|
|
80404b83b0 | ||
|
|
c612bdbfc1 | ||
|
|
3a9e686533 | ||
|
|
9e8d89a170 | ||
|
|
31c70938d6 | ||
|
|
07f3c48d0b | ||
|
|
2821b6da9d | ||
|
|
daba7c398d | ||
|
|
de1ef5b98b | ||
|
|
1bb206d978 | ||
|
|
2fd5ddab86 | ||
|
|
27dc662636 | ||
|
|
52691b5c8c | ||
|
|
8087ed5688 | ||
|
|
79e2c97882 | ||
|
|
1078107776 | ||
|
|
9c9e123e3d | ||
|
|
a8c2947062 | ||
|
|
366166a561 | ||
|
|
4d2b02eddb | ||
|
|
07871853a5 | ||
|
|
254145f0e5 | ||
|
|
c28f11336e | ||
|
|
2e30683b7b | ||
|
|
0af7b8b145 | ||
|
|
5d39b84886 | ||
|
|
537c4051cc | ||
|
|
d0a22bc517 | ||
|
|
19a75acf3f | ||
|
|
3f0abce874 | ||
|
|
36dd29f919 | ||
|
|
d7838f0b67 | ||
|
|
3353d4f436 | ||
|
|
7740154bdc |
69
.github/workflows/web-clipper.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: Deploy web clipper extension
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/web-clipper/**"
|
||||
tags:
|
||||
- "web-clipper-v*"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web-clipper/**"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
discussions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build web clipper extension
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --filter web-clipper --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Build the web clipper extension
|
||||
run: |
|
||||
pnpm --filter web-clipper zip
|
||||
pnpm --filter web-clipper zip:firefox
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/web-clipper-v') }}
|
||||
with:
|
||||
name: web-clipper-extension
|
||||
path: apps/web-clipper/.output/*.zip
|
||||
include-hidden-files: true
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Release web clipper extension
|
||||
uses: softprops/action-gh-release@v2.5.0
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/web-clipper-v') }}
|
||||
with:
|
||||
draft: false
|
||||
fail_on_unmatched_files: true
|
||||
files: apps/web-clipper/.output/*.zip
|
||||
discussion_category_name: Releases
|
||||
make_latest: false
|
||||
token: ${{ secrets.RELEASE_PAT }}
|
||||
@@ -9,9 +9,9 @@
|
||||
"keywords": [],
|
||||
"author": "Elian Doran <contact@eliandoran.me>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.28.1",
|
||||
"packageManager": "pnpm@10.28.0",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.14.9",
|
||||
"@redocly/cli": "2.14.5",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.3",
|
||||
"react": "19.2.3",
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
"@mind-elixir/node-menu": "5.0.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@preact/signals": "2.6.1",
|
||||
"@preact/signals": "2.5.1",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/codemirror": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/highlightjs": "workspace:*",
|
||||
"@triliumnext/share-theme": "workspace:*",
|
||||
"@triliumnext/split.js": "workspace:*",
|
||||
"@zumer/snapdom": "2.0.2",
|
||||
"@zumer/snapdom": "2.0.1",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
@@ -44,19 +44,19 @@
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"globals": "17.0.0",
|
||||
"i18next": "25.8.0",
|
||||
"i18next": "25.7.4",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "4.0.0",
|
||||
"jquery": "3.7.1",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.28",
|
||||
"katex": "0.16.27",
|
||||
"knockout": "3.5.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"mind-elixir": "5.6.1",
|
||||
"mind-elixir": "5.5.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.28.2",
|
||||
@@ -78,9 +78,9 @@
|
||||
"@types/reveal.js": "5.2.2",
|
||||
"@types/tabulator-tables": "6.3.1",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "20.3.9",
|
||||
"lightningcss": "1.31.1",
|
||||
"happy-dom": "20.1.0",
|
||||
"lightningcss": "1.30.2",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.5"
|
||||
"vite-plugin-static-copy": "3.1.4"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
import type CodeMirror from "@triliumnext/codemirror";
|
||||
import { SqlExecuteResponse } from "@triliumnext/commons";
|
||||
import { SqlExecuteResults } from "@triliumnext/commons";
|
||||
import type { NativeImage, TouchBar } from "electron";
|
||||
import { ColumnComponent } from "tabulator-tables";
|
||||
|
||||
@@ -410,7 +410,7 @@ type EventMappings = {
|
||||
addNewLabel: CommandData;
|
||||
addNewRelation: CommandData;
|
||||
sqlQueryResults: CommandData & {
|
||||
response: SqlExecuteResponse;
|
||||
results: SqlExecuteResults;
|
||||
};
|
||||
readOnlyTemporarilyDisabled: {
|
||||
noteContext: NoteContext;
|
||||
@@ -542,6 +542,7 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
|
||||
|
||||
export class AppContext extends Component {
|
||||
isMainWindow: boolean;
|
||||
windowId: string;
|
||||
components: Component[];
|
||||
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
|
||||
tabManager!: TabManager;
|
||||
@@ -550,10 +551,11 @@ export class AppContext extends Component {
|
||||
|
||||
lastSearchString?: string;
|
||||
|
||||
constructor(isMainWindow: boolean) {
|
||||
constructor(isMainWindow: boolean, windowId: string) {
|
||||
super();
|
||||
|
||||
this.isMainWindow = isMainWindow;
|
||||
this.windowId = windowId;
|
||||
// non-widget/layout components needed for the application
|
||||
this.components = [];
|
||||
this.beforeUnloadListeners = [];
|
||||
@@ -683,8 +685,7 @@ export class AppContext extends Component {
|
||||
this.beforeUnloadListeners = this.beforeUnloadListeners.filter(l => l !== listener);
|
||||
}
|
||||
}
|
||||
|
||||
const appContext = new AppContext(window.glob.isMainWindow);
|
||||
const appContext = new AppContext(window.glob.isMainWindow, window.glob.windowId);
|
||||
|
||||
// we should save all outstanding changes before the page/app is closed
|
||||
$(window).on("beforeunload", () => {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
|
||||
|
||||
import bundleService from "../services/bundle.js";
|
||||
import utils from "../services/utils.js";
|
||||
import dateNoteService from "../services/date_notes.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import linkService from "../services/link.js";
|
||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||
import server from "../services/server.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import utils from "../services/utils.js";
|
||||
import ws from "../services/ws.js";
|
||||
import appContext, { type NoteCommandData } from "./app_context.js";
|
||||
import Component from "./component.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import ws from "../services/ws.js";
|
||||
import bundleService from "../services/bundle.js";
|
||||
import froca from "../services/froca.js";
|
||||
import linkService from "../services/link.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
|
||||
|
||||
export default class Entrypoints extends Component {
|
||||
constructor() {
|
||||
@@ -143,14 +142,15 @@ export default class Entrypoints extends Component {
|
||||
}
|
||||
|
||||
async openInWindowCommand({ notePath, hoistedNoteId, viewScope }: NoteCommandData) {
|
||||
const extraWindowId = utils.randomString(4);
|
||||
const extraWindowHash = linkService.calculateHash({ notePath, hoistedNoteId, viewScope });
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const { ipcRenderer } = utils.dynamicRequire("electron");
|
||||
|
||||
ipcRenderer.send("create-extra-window", { extraWindowHash });
|
||||
ipcRenderer.send("create-extra-window", { extraWindowId, extraWindowHash });
|
||||
} else {
|
||||
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1${extraWindowHash}`;
|
||||
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=${extraWindowId}${extraWindowHash}`;
|
||||
|
||||
window.open(url, "", "width=1000,height=800");
|
||||
}
|
||||
@@ -188,8 +188,13 @@ export default class Entrypoints extends Component {
|
||||
} else if (note.mime.endsWith("env=backend")) {
|
||||
await server.post(`script/run/${note.noteId}`);
|
||||
} else if (note.mime === "text/x-sqlite;schema=trilium") {
|
||||
const response = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
|
||||
await appContext.triggerEvent("sqlQueryResults", { ntxId, response });
|
||||
const resp = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
|
||||
|
||||
if (!resp.success) {
|
||||
toastService.showError(t("entrypoints.sql-error", { message: resp.error }));
|
||||
}
|
||||
|
||||
await appContext.triggerEvent("sqlQueryResults", { ntxId: ntxId, results: resp.results });
|
||||
}
|
||||
|
||||
toastService.showMessage(t("entrypoints.note-executed"));
|
||||
|
||||
@@ -11,6 +11,8 @@ import linkService from "../services/link.js";
|
||||
import type { EventData } from "./app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
const MAX_SAVED_WINDOWS = 10;
|
||||
|
||||
interface TabState {
|
||||
contexts: NoteContext[];
|
||||
position: number;
|
||||
@@ -25,6 +27,13 @@ interface NoteContextState {
|
||||
viewScope: Record<string, any>;
|
||||
}
|
||||
|
||||
interface WindowState {
|
||||
windowId: string;
|
||||
createdAt: number;
|
||||
closedAt: number;
|
||||
contexts: NoteContextState[];
|
||||
}
|
||||
|
||||
export default class TabManager extends Component {
|
||||
public children: NoteContext[];
|
||||
public mutex: Mutex;
|
||||
@@ -41,9 +50,6 @@ export default class TabManager extends Component {
|
||||
this.recentlyClosedTabs = [];
|
||||
|
||||
this.tabsUpdate = new SpacedUpdate(async () => {
|
||||
if (!appContext.isMainWindow) {
|
||||
return;
|
||||
}
|
||||
if (options.is("databaseReadonly")) {
|
||||
return;
|
||||
}
|
||||
@@ -52,9 +58,21 @@ export default class TabManager extends Component {
|
||||
.map((nc) => nc.getPojoState())
|
||||
.filter((t) => !!t);
|
||||
|
||||
await server.put("options", {
|
||||
openNoteContexts: JSON.stringify(openNoteContexts)
|
||||
});
|
||||
// Update the current window’s openNoteContexts in options
|
||||
const savedWindows = options.getJson("openNoteContexts") || [];
|
||||
const win = savedWindows.find((w: WindowState) => w.windowId === appContext.windowId);
|
||||
if (win) {
|
||||
win.contexts = openNoteContexts;
|
||||
} else {
|
||||
savedWindows.push({
|
||||
windowId: appContext.windowId,
|
||||
createdAt: Date.now(),
|
||||
closedAt: 0,
|
||||
contexts: openNoteContexts
|
||||
} as WindowState);
|
||||
}
|
||||
|
||||
await options.save("openNoteContexts", JSON.stringify(savedWindows));
|
||||
});
|
||||
|
||||
appContext.addBeforeUnloadListener(this);
|
||||
@@ -69,8 +87,13 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
async loadTabs() {
|
||||
// Get the current window’s openNoteContexts
|
||||
const savedWindows = options.getJson("openNoteContexts") || [];
|
||||
const currentWin = savedWindows.find(w => w.windowId === appContext.windowId);
|
||||
const openNoteContexts = currentWin ? currentWin.contexts : undefined;
|
||||
|
||||
try {
|
||||
const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || [];
|
||||
const noteContextsToOpen = openNoteContexts || [];
|
||||
|
||||
// preload all notes at once
|
||||
await froca.getNotes([...noteContextsToOpen.flatMap((tab: NoteContextState) =>
|
||||
@@ -119,6 +142,51 @@ export default class TabManager extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
// Save window contents
|
||||
if (currentWin as WindowState) {
|
||||
currentWin.createdAt = Date.now();
|
||||
currentWin.closedAt = 0;
|
||||
currentWin.contexts = filteredNoteContexts;
|
||||
} else {
|
||||
if (savedWindows?.length >= MAX_SAVED_WINDOWS) {
|
||||
// Filter out the oldest entry
|
||||
// 1) Never remove the "main" window
|
||||
// 2) Prefer removing the oldest closed window (closedAt !== 0)
|
||||
// 3) If no closed window exists, remove the window with the oldest created window
|
||||
let oldestClosedIndex = -1;
|
||||
let oldestClosedTime = Infinity;
|
||||
let oldestCreatedIndex = -1;
|
||||
let oldestCreatedTime = Infinity;
|
||||
savedWindows.forEach((w: WindowState, i: number) => {
|
||||
if (w.windowId === "main") return;
|
||||
if (w.closedAt !== 0) {
|
||||
if (w.closedAt < oldestClosedTime) {
|
||||
oldestClosedTime = w.closedAt;
|
||||
oldestClosedIndex = i;
|
||||
}
|
||||
} else {
|
||||
if (w.createdAt < oldestCreatedTime) {
|
||||
oldestCreatedTime = w.createdAt;
|
||||
oldestCreatedIndex = i;
|
||||
}
|
||||
}
|
||||
});
|
||||
const indexToRemove = oldestClosedIndex !== -1 ? oldestClosedIndex : oldestCreatedIndex;
|
||||
if (indexToRemove !== -1) {
|
||||
savedWindows.splice(indexToRemove, 1);
|
||||
}
|
||||
}
|
||||
|
||||
savedWindows.push({
|
||||
windowId: appContext.windowId,
|
||||
createdAt: Date.now(),
|
||||
closedAt: 0,
|
||||
contexts: filteredNoteContexts
|
||||
} as WindowState);
|
||||
}
|
||||
|
||||
await options.save("openNoteContexts", JSON.stringify(savedWindows));
|
||||
|
||||
// if there's a notePath in the URL, make sure it's open and active
|
||||
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
|
||||
if (parsedFromUrl.notePath) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getNoteIcon } from "@triliumnext/commons";
|
||||
import { MIME_TYPES_DICT } from "@triliumnext/commons";
|
||||
|
||||
import cssClassManager from "../services/css_class_manager.js";
|
||||
import type { Froca } from "../services/froca-interface.js";
|
||||
@@ -13,6 +13,25 @@ import type { AttributeType, default as FAttribute } from "./fattribute.js";
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
|
||||
export const NOTE_TYPE_ICONS = {
|
||||
file: "bx bx-file",
|
||||
image: "bx bx-image",
|
||||
code: "bx bx-code",
|
||||
render: "bx bx-extension",
|
||||
search: "bx bx-file-find",
|
||||
relationMap: "bx bxs-network-chart",
|
||||
book: "bx bx-book",
|
||||
noteMap: "bx bxs-network-chart",
|
||||
mermaid: "bx bx-selection",
|
||||
canvas: "bx bx-pen",
|
||||
webView: "bx bx-globe-alt",
|
||||
launcher: "bx bx-link",
|
||||
doc: "bx bxs-file-doc",
|
||||
contentWidget: "bx bxs-widget",
|
||||
mindMap: "bx bx-sitemap",
|
||||
aiChat: "bx bx-bot"
|
||||
};
|
||||
|
||||
/**
|
||||
* There are many different Note types, some of which are entirely opaque to the
|
||||
* end user. Those types should be used only for checking against, they are
|
||||
@@ -563,18 +582,32 @@ export default class FNote {
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return `tn-icon ${this.#getIconInternal()}`;
|
||||
}
|
||||
|
||||
#getIconInternal() {
|
||||
const iconClassLabels = this.getLabels("iconClass");
|
||||
const workspaceIconClass = this.getWorkspaceIconClass();
|
||||
|
||||
const icon = getNoteIcon({
|
||||
noteId: this.noteId,
|
||||
type: this.type,
|
||||
mime: this.mime,
|
||||
iconClass: iconClassLabels.length > 0 ? iconClassLabels[0].value : undefined,
|
||||
workspaceIconClass,
|
||||
isFolder: this.isFolder.bind(this)
|
||||
});
|
||||
return `tn-icon ${icon}`;
|
||||
if (iconClassLabels && iconClassLabels.length > 0) {
|
||||
return iconClassLabels[0].value;
|
||||
} else if (workspaceIconClass) {
|
||||
return workspaceIconClass;
|
||||
} else if (this.noteId === "root") {
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === "_share") {
|
||||
return "bx bx-share-alt";
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
return "bx bx-note";
|
||||
} else if (this.type === "code") {
|
||||
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === this.mime);
|
||||
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
|
||||
}
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
}
|
||||
|
||||
getColorClass() {
|
||||
|
||||
@@ -16,17 +16,6 @@ async function initJQuery() {
|
||||
const $ = (await import("jquery")).default;
|
||||
window.$ = $;
|
||||
window.jQuery = $;
|
||||
|
||||
// Polyfill removed jQuery methods for autocomplete.js compatibility
|
||||
($ as any).isArray = Array.isArray;
|
||||
($ as any).isFunction = function(obj: any) { return typeof obj === 'function'; };
|
||||
($ as any).isPlainObject = function(obj: any) {
|
||||
if (obj == null || typeof obj !== 'object') { return false; }
|
||||
const proto = Object.getPrototypeOf(obj);
|
||||
if (proto === null) { return true; }
|
||||
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
|
||||
return typeof Ctor === 'function' && Ctor === Object;
|
||||
};
|
||||
}
|
||||
|
||||
async function setupGlob() {
|
||||
@@ -50,25 +39,22 @@ async function loadBootstrapCss() {
|
||||
}
|
||||
|
||||
function loadStylesheets() {
|
||||
const { device, assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
|
||||
|
||||
const { assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
|
||||
const cssToLoad: string[] = [];
|
||||
if (device !== "print") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
|
||||
cssToLoad.push(`api/fonts`);
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
|
||||
if (themeCssUrl) {
|
||||
cssToLoad.push(themeCssUrl);
|
||||
}
|
||||
if (themeUseNextAsBase === "next") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
|
||||
} else if (themeUseNextAsBase === "next-dark") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
|
||||
} else if (themeUseNextAsBase === "next-light") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
|
||||
}
|
||||
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
|
||||
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
|
||||
cssToLoad.push(`api/fonts`);
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
|
||||
if (themeCssUrl) {
|
||||
cssToLoad.push(themeCssUrl);
|
||||
}
|
||||
if (themeUseNextAsBase === "next") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
|
||||
} else if (themeUseNextAsBase === "next-dark") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
|
||||
} else if (themeUseNextAsBase === "next-light") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
|
||||
}
|
||||
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
|
||||
|
||||
for (const href of cssToLoad) {
|
||||
const linkEl = document.createElement("link");
|
||||
@@ -85,7 +71,7 @@ function loadIcons() {
|
||||
}
|
||||
|
||||
function setBodyAttributes() {
|
||||
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale } = window.glob;
|
||||
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale, isMainWindow } = window.glob;
|
||||
const classesToSet = [
|
||||
device,
|
||||
`heading-style-${headingStyle}`,
|
||||
@@ -93,7 +79,8 @@ function setBodyAttributes() {
|
||||
`platform-${platform}`,
|
||||
isElectron && "electron",
|
||||
hasNativeTitleBar && "native-titlebar",
|
||||
hasBackgroundEffects && "background-effects"
|
||||
hasBackgroundEffects && "background-effects",
|
||||
!isMainWindow && 'extra-window'
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
for (const classToSet of classesToSet) {
|
||||
@@ -105,17 +92,10 @@ function setBodyAttributes() {
|
||||
}
|
||||
|
||||
async function loadScripts() {
|
||||
switch (glob.device) {
|
||||
case "mobile":
|
||||
await import("./mobile.js");
|
||||
break;
|
||||
case "print":
|
||||
await import("./print.js");
|
||||
break;
|
||||
case "desktop":
|
||||
default:
|
||||
await import("./desktop.js");
|
||||
break;
|
||||
if (glob.device === "mobile") {
|
||||
await import("./mobile.js");
|
||||
} else {
|
||||
await import("./desktop.js");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ import ScrollPadding from "../widgets/scroll_padding.js";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import SharedInfo from "../widgets/shared_info.jsx";
|
||||
import RightPanelContainer from "../widgets/sidebar/RightPanelContainer.jsx";
|
||||
import SqlResults from "../widgets/sql_result.js";
|
||||
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
|
||||
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
|
||||
@@ -161,9 +163,11 @@ export default class DesktopLayout {
|
||||
.child(<SharedInfo />)
|
||||
)
|
||||
.optChild(!isNewLayout, <PromotedAttributes />)
|
||||
.child(<SqlTableSchemas />)
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" />)
|
||||
.child(<SearchResult />)
|
||||
.child(<SqlResults />)
|
||||
.child(<ScrollPadding />)
|
||||
)
|
||||
.child(<ApiLog />)
|
||||
|
||||
@@ -29,9 +29,7 @@ async function main() {
|
||||
const froca = (await import("./services/froca")).default;
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
const bodyWrapper = document.createElement("div");
|
||||
render(<App note={note} noteId={noteId} />, bodyWrapper);
|
||||
document.body.appendChild(bodyWrapper);
|
||||
render(<App note={note} noteId={noteId} />, document.body);
|
||||
}
|
||||
|
||||
function App({ note, noteId }: { note: FNote | null | undefined, noteId: string }) {
|
||||
|
||||
@@ -8,17 +8,6 @@ async function loadBootstrap() {
|
||||
}
|
||||
}
|
||||
|
||||
// Polyfill removed jQuery methods for autocomplete.js compatibility
|
||||
($ as any).isArray = Array.isArray;
|
||||
($ as any).isFunction = function(obj: any) { return typeof obj === 'function'; };
|
||||
($ as any).isPlainObject = function(obj: any) {
|
||||
if (obj == null || typeof obj !== 'object') { return false; }
|
||||
const proto = Object.getPrototypeOf(obj);
|
||||
if (proto === null) { return true; }
|
||||
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
|
||||
return typeof Ctor === 'function' && Ctor === Object;
|
||||
};
|
||||
|
||||
(window as any).$ = $;
|
||||
(window as any).jQuery = $;
|
||||
await loadBootstrap();
|
||||
|
||||
@@ -42,7 +42,7 @@ describe("Set boolean with inheritance", () => {
|
||||
name: "foo",
|
||||
value: "",
|
||||
isInheritable: false
|
||||
}, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("removes boolean normally without inheritance", async () => {
|
||||
@@ -91,7 +91,7 @@ describe("Set boolean with inheritance", () => {
|
||||
name: "foo",
|
||||
value: "false",
|
||||
isInheritable: false
|
||||
}, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("overrides boolean with inherited false", async () => {
|
||||
@@ -112,7 +112,7 @@ describe("Set boolean with inheritance", () => {
|
||||
name: "foo",
|
||||
value: "",
|
||||
isInheritable: false
|
||||
}, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes override boolean with inherited false with already existing value", async () => {
|
||||
@@ -134,6 +134,6 @@ describe("Set boolean with inheritance", () => {
|
||||
name: "foo",
|
||||
value: "",
|
||||
isInheritable: false
|
||||
}, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,13 +14,13 @@ async function addLabel(noteId: string, name: string, value: string = "", isInhe
|
||||
});
|
||||
}
|
||||
|
||||
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false, componentId?: string) {
|
||||
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
|
||||
await server.put(`notes/${noteId}/set-attribute`, {
|
||||
type: "label",
|
||||
name,
|
||||
value,
|
||||
isInheritable,
|
||||
}, componentId);
|
||||
isInheritable
|
||||
});
|
||||
}
|
||||
|
||||
export async function setRelation(noteId: string, name: string, value: string = "", isInheritable = false) {
|
||||
@@ -117,15 +117,15 @@ function removeOwnedRelationByName(note: FNote, relationName: string) {
|
||||
* @param name the name of the attribute to set.
|
||||
* @param value the value of the attribute to set.
|
||||
*/
|
||||
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined, componentId?: string) {
|
||||
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
|
||||
if (value !== null && value !== undefined) {
|
||||
// Create or update the attribute.
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }, componentId);
|
||||
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
|
||||
} else {
|
||||
// Remove the attribute if it exists on the server but we don't define a value for it.
|
||||
const attributeId = note.getAttribute(type, name)?.attributeId;
|
||||
if (attributeId) {
|
||||
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`, componentId);
|
||||
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
|
||||
* @param moveToParent whether to automatically go to the parent note path after a succesful delete. Usually makes sense if deleting the active note(s).
|
||||
* @returns promise that returns false if the operation was cancelled or there was nothing to delete, true if the operation succeeded.
|
||||
*/
|
||||
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true, componentId?: string) {
|
||||
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
|
||||
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
||||
|
||||
if (branchIdsToDelete.length === 0) {
|
||||
@@ -139,9 +139,9 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
|
||||
const branch = froca.getBranch(branchIdToDelete);
|
||||
|
||||
if (deleteAllClones && branch) {
|
||||
await server.remove(`notes/${branch.noteId}${query}`, componentId);
|
||||
await server.remove(`notes/${branch.noteId}${query}`);
|
||||
} else {
|
||||
await server.remove(`branches/${branchIdToDelete}${query}`, componentId);
|
||||
await server.remove(`branches/${branchIdToDelete}${query}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,6 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
|
||||
loadResults.addRevision(ec.entityId, ec.noteId, ec.componentId);
|
||||
} else if (ec.entityName === "options") {
|
||||
const attributeEntity = ec.entity as FAttributeRow;
|
||||
if (attributeEntity.name === "openNoteContexts") {
|
||||
continue; // only noise
|
||||
}
|
||||
|
||||
options.set(attributeEntity.name as OptionNames, attributeEntity.value);
|
||||
loadResults.addOption(attributeEntity.name as OptionNames);
|
||||
} else if (ec.entityName === "attachments") {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import shortcuts, { isIMEComposing, keyMatches, matchesShortcut } from "./shortcuts.js";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
|
||||
|
||||
// Mock utils module
|
||||
vi.mock("./utils.js", () => ({
|
||||
@@ -62,10 +61,9 @@ describe("shortcuts", () => {
|
||||
});
|
||||
|
||||
describe("keyMatches", () => {
|
||||
const createKeyboardEvent = (key: string, code?: string, extraProps: Partial<KeyboardEvent> = {}) => ({
|
||||
const createKeyboardEvent = (key: string, code?: string) => ({
|
||||
key,
|
||||
code: code || `Key${key.toUpperCase()}`,
|
||||
...extraProps
|
||||
code: code || `Key${key.toUpperCase()}`
|
||||
} as KeyboardEvent);
|
||||
|
||||
it("should match regular letter keys using key code", () => {
|
||||
@@ -103,23 +101,17 @@ describe("shortcuts", () => {
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should match azerty keys", () => {
|
||||
const event = createKeyboardEvent("A", "KeyQ");
|
||||
expect(keyMatches(event, "a")).toBe(true);
|
||||
expect(keyMatches(event, "q")).toBe(false);
|
||||
});
|
||||
|
||||
it("should match letter keys using code when key is a special character (macOS Alt behavior)", () => {
|
||||
// On macOS, pressing Option/Alt + A produces 'å' as the key, but code is still 'KeyA'
|
||||
const macOSAltAEvent = createKeyboardEvent("å", "KeyA", { altKey: true });
|
||||
const macOSAltAEvent = createKeyboardEvent("å", "KeyA");
|
||||
expect(keyMatches(macOSAltAEvent, "a")).toBe(true);
|
||||
|
||||
// Option + H produces '˙'
|
||||
const macOSAltHEvent = createKeyboardEvent("˙", "KeyH", { altKey: true });
|
||||
const macOSAltHEvent = createKeyboardEvent("˙", "KeyH");
|
||||
expect(keyMatches(macOSAltHEvent, "h")).toBe(true);
|
||||
|
||||
// Option + S produces 'ß'
|
||||
const macOSAltSEvent = createKeyboardEvent("ß", "KeyS", { altKey: true });
|
||||
const macOSAltSEvent = createKeyboardEvent("ß", "KeyS");
|
||||
expect(keyMatches(macOSAltSEvent, "s")).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -223,15 +215,6 @@ describe("shortcuts", () => {
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("matches azerty", () => {
|
||||
const event = createKeyboardEvent({
|
||||
key: "a",
|
||||
code: "KeyQ",
|
||||
ctrlKey: true
|
||||
});
|
||||
expect(matchesShortcut(event, "Ctrl+A")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match Alt+letter shortcuts on macOS where key is a special character", () => {
|
||||
// On macOS, pressing Option/Alt + A produces 'å' but code remains 'KeyA'
|
||||
const macOSAltAEvent = createKeyboardEvent({
|
||||
|
||||
@@ -215,12 +215,9 @@ export function keyMatches(e: KeyboardEvent, key: string): boolean {
|
||||
// For letter keys, use the physical key code for consistency
|
||||
// On macOS, Option/Alt key produces special characters, so we must use e.code
|
||||
if (key.length === 1 && key >= 'a' && key <= 'z') {
|
||||
if (e.altKey) {
|
||||
// e.code is like "KeyA", "KeyB", etc.
|
||||
const expectedCode = `Key${key.toUpperCase()}`;
|
||||
return e.code === expectedCode || e.key.toLowerCase() === key.toLowerCase();
|
||||
}
|
||||
return e.key.toLowerCase() === key.toLowerCase();
|
||||
// e.code is like "KeyA", "KeyB", etc.
|
||||
const expectedCode = `Key${key.toUpperCase()}`;
|
||||
return e.code === expectedCode || e.key.toLowerCase() === key.toLowerCase();
|
||||
}
|
||||
|
||||
// For regular keys, check both key and code as fallback
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { MimeType } from "@triliumnext/commons";
|
||||
import { type AutoHighlightResult, ensureMimeTypes, highlight, highlightAuto, type HighlightResult, loadTheme, type Theme,Themes } from "@triliumnext/highlightjs";
|
||||
|
||||
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes, type AutoHighlightResult, type HighlightResult, type Theme } from "@triliumnext/highlightjs";
|
||||
import mime_types from "./mime_types.js";
|
||||
import options from "./options.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
|
||||
import { isShare } from "./utils.js";
|
||||
import { MimeType } from "@triliumnext/commons";
|
||||
|
||||
let highlightingLoaded = false;
|
||||
|
||||
@@ -77,15 +76,13 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
|
||||
}
|
||||
|
||||
export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
|
||||
if (!mimeTypeHint && highlightingLoaded) {
|
||||
if (highlightingLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load theme.
|
||||
if (!highlightingLoaded) {
|
||||
const currentThemeName = String(options.get("codeBlockTheme"));
|
||||
await loadHighlightingTheme(currentThemeName);
|
||||
}
|
||||
const currentThemeName = String(options.get("codeBlockTheme"));
|
||||
await loadHighlightingTheme(currentThemeName);
|
||||
|
||||
// Load mime types.
|
||||
let mimeTypes: MimeType[];
|
||||
@@ -97,7 +94,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
|
||||
enabled: true,
|
||||
mime: mimeTypeHint.replace("-", "/")
|
||||
}
|
||||
];
|
||||
]
|
||||
} else {
|
||||
mimeTypes = mime_types.getMimeTypes();
|
||||
}
|
||||
@@ -127,9 +124,9 @@ export function isSyntaxHighlightEnabled() {
|
||||
if (!isShare) {
|
||||
const theme = options.get("codeBlockTheme");
|
||||
return !!theme && theme !== "none";
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
--row-moving-background-color: var(--accented-background-color);
|
||||
--row-text-color: var(--main-text-color);
|
||||
--row-delimiter-color: var(--more-accented-background-color);
|
||||
|
||||
|
||||
--cell-horiz-padding-size: 8px;
|
||||
--cell-vert-padding-size: 8px;
|
||||
|
||||
|
||||
--cell-editable-hover-outline-color: var(--main-border-color);
|
||||
--cell-read-only-text-color: var(--muted-text-color);
|
||||
|
||||
|
||||
--cell-editing-border-color: var(--main-border-color);
|
||||
--cell-editing-border-width: 2px;
|
||||
--cell-editing-background-color: var(--ck-color-selector-focused-cell-background);
|
||||
@@ -40,42 +40,10 @@
|
||||
border-bottom: var(--col-header-bottom-border);
|
||||
background: var(--col-header-background-color);
|
||||
color: var(--col-header-text-color);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.tabulator-col.tabulator-range-highlight {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tabulator-col-content {
|
||||
padding: 0 !important;
|
||||
|
||||
.tabulator-col-title-holder {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
&:has(.tabulator-header-filter) {
|
||||
.tabulator-col-title-holder {
|
||||
padding: 4px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tabulator-header-filter {
|
||||
background: var(--main-background-color);
|
||||
padding: 2px 1px;
|
||||
|
||||
input {
|
||||
background: var(--main-background-color);
|
||||
color: var(--main-text-color);
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabulator .tabulator-col-content {
|
||||
padding: 8px 4px !important;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
@@ -112,6 +80,7 @@
|
||||
|
||||
.tabulator-tableholder {
|
||||
padding-top: 10px;
|
||||
height: unset !important; /* Don't extend on the full height */
|
||||
}
|
||||
|
||||
/* Rows */
|
||||
@@ -130,14 +99,6 @@
|
||||
border-top: none;
|
||||
border-bottom: 1px solid var(--row-delimiter-color);
|
||||
color: var(--row-text-color);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.tabulator-range-highlight > .tabulator-cell.tabulator-frozen {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.tabulator-row.tabulator-row-odd {
|
||||
@@ -159,14 +120,11 @@
|
||||
margin-inline-end: var(--cell-editing-border-width);
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
|
||||
.tabulator-row .tabulator-cell {
|
||||
border-inline-end-color: transparent;
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
|
||||
border-inline-end-color: var(--main-border-color);
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell:not(.tabulator-editable) {
|
||||
color: var(--cell-read-only-text-color);
|
||||
}
|
||||
@@ -216,6 +174,10 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-footer {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
/* Context menus */
|
||||
|
||||
.tabulator-popup-container {
|
||||
@@ -230,27 +192,8 @@
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
:root .tabulator .tabulator-footer {
|
||||
background: transparent;
|
||||
color: var(--main-text-color);
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
border-top: unset;
|
||||
padding: 10px 0;
|
||||
|
||||
.tabulator-page {
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--hover-item-border-color);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
background: var(--button-background-color);
|
||||
color: var(--input-text-color);
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ function injectGlobals() {
|
||||
uncheckedWindow.$ = $;
|
||||
uncheckedWindow.WebSocket = () => {};
|
||||
uncheckedWindow.glob = {
|
||||
isMainWindow: true
|
||||
isMainWindow: true,
|
||||
windowId: "main"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1639,11 +1639,7 @@
|
||||
"configure_launchbar": "配置启动栏"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "此查询没有返回任何数据",
|
||||
"not_executed": "查询尚未执行。",
|
||||
"failed": "SQL 查询执行失败",
|
||||
"execute_now": "立即执行",
|
||||
"statement_result": "执行结果"
|
||||
"no_rows": "此查询没有返回任何数据"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "表"
|
||||
|
||||
@@ -421,7 +421,7 @@
|
||||
"execute_description": "Längere Beschreibung der aktuellen Codenotiz, die zusammen mit der Schaltfläche „Ausführen“ angezeigt wird",
|
||||
"exclude_from_note_map": "Notizen mit dieser Bezeichnung werden in der Notizenkarte ausgeblendet",
|
||||
"new_notes_on_top": "Neue Notizen werden oben in der übergeordneten Notiz erstellt, nicht unten.",
|
||||
"hide_highlight_widget": "Widget „Markierungsliste“ ausblenden",
|
||||
"hide_highlight_widget": "Widget „Hervorhebungsliste“ ausblenden",
|
||||
"run_on_note_creation": "Wird ausgeführt, wenn eine Notiz im Backend erstellt wird. Verwende diese Beziehung, wenn du das Skript für alle Notizen ausführen möchtest, die unter einer bestimmten Unternotiz erstellt wurden. Erstelle es in diesem Fall auf der Unternotiz-Stammnotiz und mache es vererbbar. Eine neue Notiz, die innerhalb der Unternotiz (beliebige Tiefe) erstellt wird, löst das Skript aus.",
|
||||
"run_on_child_note_creation": "Wird ausgeführt, wenn eine neue Notiz unter der Notiz erstellt wird, in der diese Beziehung definiert ist",
|
||||
"run_on_note_title_change": "Wird ausgeführt, wenn der Notiztitel geändert wird (einschließlich der Notizerstellung)",
|
||||
@@ -632,7 +632,7 @@
|
||||
"show_toc": "Inhaltsverzeichnis anzeigen"
|
||||
},
|
||||
"show_highlights_list_widget_button": {
|
||||
"show_highlights_list": "Markierungsliste anzeigen"
|
||||
"show_highlights_list": "Hervorhebungen anzeigen"
|
||||
},
|
||||
"global_menu": {
|
||||
"menu": "Menü",
|
||||
@@ -645,7 +645,7 @@
|
||||
"zoom_out": "Herauszoomen",
|
||||
"reset_zoom_level": "Zoomstufe zurücksetzen",
|
||||
"zoom_in": "Hineinzoomen",
|
||||
"configure_launchbar": "Konfiguriere die Starterleiste",
|
||||
"configure_launchbar": "Konfiguriere die Launchbar",
|
||||
"show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen",
|
||||
"advanced": "Erweitert",
|
||||
"open_dev_tools": "Öffne die Entwicklungstools",
|
||||
@@ -720,7 +720,7 @@
|
||||
"update_available": "Update verfügbar"
|
||||
},
|
||||
"note_launcher": {
|
||||
"this_launcher_doesnt_define_target_note": "Dieser Starter definiert keine Zielnotiz."
|
||||
"this_launcher_doesnt_define_target_note": "Dieser Launcher definiert keine Zielnotiz."
|
||||
},
|
||||
"code_buttons": {
|
||||
"execute_button_title": "Skript ausführen",
|
||||
@@ -763,8 +763,8 @@
|
||||
"change_note_icon": "Notiz-Icon ändern",
|
||||
"search": "Suche:",
|
||||
"reset-default": "Standard wiederherstellen",
|
||||
"search_placeholder_one": "Suche {{number}} Symbole über {{count}} Pakete",
|
||||
"search_placeholder_other": "Suche {{number}} Symbole über {{count}} Pakete",
|
||||
"search_placeholder_one": "Suche {{number}} Icons über {{count}} Pakete",
|
||||
"search_placeholder_other": "Suche {{number}} Icons über {{count}} Pakete",
|
||||
"search_placeholder_filtered": "Suche {{number}} Icons in {{name}}",
|
||||
"filter": "Filter",
|
||||
"filter-none": "Alle Icons",
|
||||
@@ -1169,7 +1169,7 @@
|
||||
"layout": "Layout",
|
||||
"layout-vertical-title": "Vertikal",
|
||||
"layout-horizontal-title": "Horizontal",
|
||||
"layout-vertical-description": "Startleiste ist auf der linken Seite (Standard)",
|
||||
"layout-vertical-description": "Startleiste ist auf der linken Seite (standard)",
|
||||
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert.",
|
||||
"auto_theme": "Alt (Folge dem Farbschema des Systems)",
|
||||
"light_theme": "Alt (Hell)",
|
||||
@@ -1177,7 +1177,7 @@
|
||||
},
|
||||
"zoom_factor": {
|
||||
"title": "Zoomfaktor (nur Desktop-Build)",
|
||||
"description": "Das Zoomen kann auch mit den Tastenkombinationen Strg+- und Strg+= gesteuert werden."
|
||||
"description": "Das Zoomen kann auch mit den Tastenkombinationen STRG+- und STRG+u003d gesteuert werden."
|
||||
},
|
||||
"code_auto_read_only_size": {
|
||||
"title": "Automatische schreibgeschützte Größe",
|
||||
@@ -1266,16 +1266,16 @@
|
||||
"markdown": "Markdown-Stil"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Markierungsliste",
|
||||
"description": "Du kannst die im rechten Bereich angezeigte Markierungsliste anpassen:",
|
||||
"title": "Highlights-Liste",
|
||||
"description": "Du kannst die im rechten Bereich angezeigte Highlights-Liste anpassen:",
|
||||
"bold": "Fettgedruckter Text",
|
||||
"italic": "Kursiver Text",
|
||||
"underline": "Unterstrichener Text",
|
||||
"color": "Farbiger Text",
|
||||
"bg_color": "Text mit Hintergrundfarbe",
|
||||
"visibility_title": "Sichtbarkeit der Markierungsliste",
|
||||
"visibility_description": "Du kannst das Markierungs-Widget pro Notiz ausblenden, indem du die Beschriftung #hideHighlightWidget hinzufügst.",
|
||||
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Markierungen) in den Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
|
||||
"visibility_title": "Sichtbarkeit der Highlights-Liste",
|
||||
"visibility_description": "Du kannst das Hervorhebungs-Widget pro Notiz ausblenden, indem du die Beschriftung #hideHighlightWidget hinzufügst.",
|
||||
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Hervorhebungen) in den Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
|
||||
},
|
||||
"table_of_contents": {
|
||||
"title": "Inhaltsverzeichnis",
|
||||
@@ -1490,12 +1490,12 @@
|
||||
"note-map": "Notizkarte",
|
||||
"render-note": "Render Notiz",
|
||||
"mermaid-diagram": "Mermaid Diagramm",
|
||||
"canvas": "Leinwand",
|
||||
"canvas": "Canvas",
|
||||
"web-view": "Webansicht",
|
||||
"mind-map": "Mind Map",
|
||||
"file": "Datei",
|
||||
"image": "Bild",
|
||||
"launcher": "Starter",
|
||||
"launcher": "Launcher",
|
||||
"doc": "Dokument",
|
||||
"widget": "Widget",
|
||||
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
|
||||
@@ -1535,13 +1535,13 @@
|
||||
"replace_all": "Alle Ersetzen"
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Markierungsliste",
|
||||
"title": "Hervorhebungs-Liste",
|
||||
"options": "Optionen",
|
||||
"title_with_count_one": "{{count}} Markierung",
|
||||
"title_with_count_other": "{{count}} Markierungen",
|
||||
"modal_title": "Markierungsliste konfigurieren",
|
||||
"menu_configure": "Markierungsliste konfigurieren…",
|
||||
"no_highlights": "Keine Markierungen gefunden."
|
||||
"title_with_count_one": "{{count}} Highlight",
|
||||
"title_with_count_other": "{{count}} Highlights",
|
||||
"modal_title": "Highlight Liste konfigurieren",
|
||||
"menu_configure": "Highlight Liste konfigurieren…",
|
||||
"no_highlights": "Keine Highlights gefunden."
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Schnellsuche",
|
||||
@@ -1595,7 +1595,7 @@
|
||||
"last_modified": "Bearbeitet am <Value />",
|
||||
"note_type_switcher_label": "Ändere von {{type}} zu:",
|
||||
"note_type_switcher_others": "Andere Notizart",
|
||||
"note_type_switcher_templates": "Vorlage",
|
||||
"note_type_switcher_templates": "Template",
|
||||
"note_type_switcher_collection": "Sammlung",
|
||||
"edited_notes": "Notizen, bearbeitet an diesem Tag",
|
||||
"promoted_attributes": "Hervorgehobene Attribute"
|
||||
@@ -1605,14 +1605,10 @@
|
||||
"search_not_executed": "Die Suche wurde noch nicht ausgeführt. Klicke oben auf „Suchen“, um die Ergebnisse anzuzeigen."
|
||||
},
|
||||
"spacer": {
|
||||
"configure_launchbar": "Starterleiste konfigurieren"
|
||||
"configure_launchbar": "Startleiste konfigurieren"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben",
|
||||
"not_executed": "Die Abfrage wurde noch nicht ausgeführt.",
|
||||
"failed": "SQL-Abfrage ist fehlgeschlagen",
|
||||
"execute_now": "Jetzt ausführen",
|
||||
"statement_result": "Abfrageergebnis"
|
||||
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabellen"
|
||||
@@ -1683,16 +1679,16 @@
|
||||
"confirm_unhoisting": "Die angeforderte Notiz ‚{{requestedNote}}‘ befindet sich außerhalb des hoisted Bereichs der Notiz ‚{{hoistedNote}}‘. Du musst sie unhoisten, um auf die Notiz zuzugreifen. Möchtest du mit dem Unhoisting fortfahren?"
|
||||
},
|
||||
"launcher_context_menu": {
|
||||
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Starter wird an seinen ursprünglichen Standort zurückgesetzt.",
|
||||
"add-note-launcher": "Notiz-Starter hinzufügen",
|
||||
"add-script-launcher": "Skript-Starter hinzufügen",
|
||||
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Launcher wird an seinen ursprünglichen Standort zurückgesetzt.",
|
||||
"add-note-launcher": "Launcher für Notiz hinzufügen",
|
||||
"add-script-launcher": "Launcher für Skript hinzufügen",
|
||||
"add-custom-widget": "Benutzerdefiniertes Widget hinzufügen",
|
||||
"add-spacer": "Abstandhalter hinzufügen",
|
||||
"add-spacer": "Spacer hinzufügen",
|
||||
"delete": "Löschen <kbd data-command=\"deleteNotes\"></kbd>",
|
||||
"reset": "Zurücksetzen",
|
||||
"move-to-visible-launchers": "Zu sichtbaren Startern verschieben",
|
||||
"move-to-available-launchers": "Zu verfügbaren Startern verschieben",
|
||||
"duplicate-launcher": "Starter duplizieren <kbd data-command=\"duplicateSubtree\">"
|
||||
"move-to-visible-launchers": "Zu sichtbaren Launchern verschieben",
|
||||
"move-to-available-launchers": "Zu verfügbaren Launchern verschieben",
|
||||
"duplicate-launcher": "Launcher duplizieren <kbd data-command=\"duplicateSubtree\">"
|
||||
},
|
||||
"highlighting": {
|
||||
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
|
||||
@@ -1701,7 +1697,7 @@
|
||||
},
|
||||
"code_block": {
|
||||
"word_wrapping": "Wortumbruch",
|
||||
"theme_none": "Keine Syntaxhervorhebung",
|
||||
"theme_none": "Keine Syntax-Hervorhebung",
|
||||
"theme_group_light": "Helle Themen",
|
||||
"theme_group_dark": "Dunkle Themen",
|
||||
"copy_title": "Kopiere in Zwischenablage"
|
||||
@@ -2241,16 +2237,16 @@
|
||||
"attachments_other": "{{count}} Anhänge",
|
||||
"attachments_title_one": "Anhang in einem neuen Tab öffnen",
|
||||
"attachments_title_other": "Anhänge in einem neuen Tab öffnen",
|
||||
"attributes_one": "{{count}} Attribute",
|
||||
"attributes_other": "{{count}} Attribute",
|
||||
"attributes_title": "Eigene und geerbte Attribute",
|
||||
"attributes_one": "{{count}} Eigenschaft",
|
||||
"attributes_other": "{{count}} Eigenschaften",
|
||||
"attributes_title": "Eigene und gererbte Eigenschaften",
|
||||
"note_paths_one": "{{count}} Pfad",
|
||||
"note_paths_other": "{{count}} Pfade",
|
||||
"note_paths_title": "Notizpfade",
|
||||
"code_note_switcher": "Sprachmodus ändern"
|
||||
},
|
||||
"attributes_panel": {
|
||||
"title": "Notizattribute"
|
||||
"title": "Notizeigenschaften"
|
||||
},
|
||||
"right_pane": {
|
||||
"empty_message": "Für diese Notiz gibt es nichts anzuzeigen",
|
||||
|
||||
@@ -1815,11 +1815,7 @@
|
||||
"configure_launchbar": "Configure Launchbar"
|
||||
},
|
||||
"sql_result": {
|
||||
"not_executed": "The query has not been executed yet.",
|
||||
"no_rows": "No rows have been returned for this query",
|
||||
"failed": "SQL query execution has failed",
|
||||
"statement_result": "Statement result",
|
||||
"execute_now": "Execute now"
|
||||
"no_rows": "No rows have been returned for this query"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tables"
|
||||
|
||||
@@ -28,10 +28,7 @@
|
||||
},
|
||||
"widget-render-error": {
|
||||
"title": "Hubo un fallo al renderizar un widget personalizado de React"
|
||||
},
|
||||
"widget-missing-parent": "El widget personalizado no tiene definida la propiedad obligatoria '{{property}}'.\n\nSi este script está pensado para ejecutarse sin un elemento de interfaz de usuario, utilice '#run=frontendStartup' en su lugar.",
|
||||
"open-script-note": "Abrir script",
|
||||
"scripting-error": "Error en script personalizado: {{title}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Agregar enlace",
|
||||
@@ -214,8 +211,7 @@
|
||||
"info": {
|
||||
"modalTitle": "Mensaje informativo",
|
||||
"closeButton": "Cerrar",
|
||||
"okButton": "Aceptar",
|
||||
"copy_to_clipboard": "Copiar al portapapeles"
|
||||
"okButton": "Aceptar"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "Buscar en texto completo",
|
||||
@@ -701,13 +697,7 @@
|
||||
"convert_into_attachment_successful": "La nota '{{title}}' ha sido convertida a un archivo adjunto.",
|
||||
"convert_into_attachment_prompt": "¿Está seguro que desea convertir la nota '{{title}}' en un archivo adjunto de la nota padre?",
|
||||
"print_pdf": "Exportar como PDF...",
|
||||
"open_note_on_server": "Abrir nota en servidor",
|
||||
"view_revisions": "Revisiones de la nota...",
|
||||
"advanced": "Avanzado",
|
||||
"export_as_image": "Exportar como imagen",
|
||||
"export_as_image_png": "PNG (ráster)",
|
||||
"export_as_image_svg": "SVG (vectorial)",
|
||||
"note_map": "Mapa de la nota"
|
||||
"open_note_on_server": "Abrir nota en servidor"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
|
||||
@@ -769,13 +759,7 @@
|
||||
"reset-default": "Restablecer a icono por defecto",
|
||||
"search_placeholder_one": "Buscar {{number}} icono a través de {{count}} paquetes",
|
||||
"search_placeholder_many": "Buscar {{number}} iconos a través de {{count}} paquetes",
|
||||
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes",
|
||||
"search_placeholder_filtered": "Buscar {{number}} iconos en {{name}}",
|
||||
"filter": "Filtro",
|
||||
"filter-none": "Todos los iconos",
|
||||
"filter-default": "Iconos predeterminados",
|
||||
"icon_tooltip": "{{name}}\nPaquete de iconos: {{iconPack}}",
|
||||
"no_results": "No se encontraron iconos."
|
||||
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes"
|
||||
},
|
||||
"basic_properties": {
|
||||
"note_type": "Tipo de nota",
|
||||
@@ -802,8 +786,7 @@
|
||||
"expand_tooltip": "Expande las notas hijas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
|
||||
"expand_first_level": "Expandir hijos inmediatos",
|
||||
"expand_nth_level": "Expandir {{depth}} niveles",
|
||||
"expand_all_levels": "Expandir todos los niveles",
|
||||
"hide_child_notes": "Ocultar notas hijas en el árbol"
|
||||
"expand_all_levels": "Expandir todos los niveles"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "Aún no hay notas editadas en este día...",
|
||||
@@ -836,8 +819,7 @@
|
||||
},
|
||||
"inherited_attribute_list": {
|
||||
"title": "Atributos heredados",
|
||||
"no_inherited_attributes": "Sin atributos heredados.",
|
||||
"none": "ninguno"
|
||||
"no_inherited_attributes": "Sin atributos heredados."
|
||||
},
|
||||
"note_info_widget": {
|
||||
"note_id": "ID de nota",
|
||||
@@ -848,8 +830,7 @@
|
||||
"note_size_info": "El tamaño de la nota proporciona una estimación aproximada de los requisitos de almacenamiento para esta nota. Toma en cuenta el contenido de la nota y el contenido de sus revisiones de nota.",
|
||||
"calculate": "calcular",
|
||||
"subtree_size": "(tamaño del subárbol: {{size}} en {{count}} notas)",
|
||||
"title": "Información de nota",
|
||||
"mime": "Tipo MIME"
|
||||
"title": "Información de nota"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "Ampliar al máximo",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Eror kritikal",
|
||||
"title": "Kesalahan kritis",
|
||||
"message": "Telah terjadi kesalahan kritis yang mencegah aplikasi klien untuk memulai:\n\n{{message}}\n\nHal ini kemungkinan besar disebabkan oleh skrip yang gagal secara tidak terduga. Coba jalankan aplikasi dalam mode aman dan atasi masalahnya."
|
||||
},
|
||||
"widget-error": {
|
||||
@@ -21,57 +21,12 @@
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Gagal memuat skrip kustom",
|
||||
"message": "Skrip tidak dapat dijalankan:\n\n{{message}}"
|
||||
},
|
||||
"widget-list-error": {
|
||||
"title": "Gagal mendapatkan daftar widget dari server"
|
||||
},
|
||||
"open-script-note": "Buka skrip catatan",
|
||||
"widget-render-error": {
|
||||
"title": "Gagal render widget React custom"
|
||||
},
|
||||
"widget-missing-parent": "Widget custom '{{property}}' tidak terdefinisi.\n\nJika skrip ini bermaksud untuk bisa dijalankan tanpa elemen UI, gunakanlah '#run=frontendStartup'.",
|
||||
"scripting-error": "Skrip custom eror : {{title}}"
|
||||
"message": "Skrip dari catatan dengan ID \"{{id}}\", berjudul \"{{title}}\" tidak dapat dijalankan karena:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Tambah tautan",
|
||||
"help_on_links": "Bantuan pada tautan",
|
||||
"note": "Catatan"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix_multiple": "Edit prefiks cabang untuk {{count}} cabang",
|
||||
"help_on_tree_prefix": "Bantuan pada prefiks pohon catatan",
|
||||
"prefix": "Prefiks: ",
|
||||
"save": "Simpan",
|
||||
"branch_prefix_saved": "Prefiks cabang telah disimpan.",
|
||||
"branch_prefix_saved_multiple": "Prefix cabang telah disimpan pada {{count}} cabang.",
|
||||
"affected_branches": "Cabang terdampak ({{count}}):"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Aksi borongan",
|
||||
"affected_notes": "Catatan terdampak",
|
||||
"include_descendants": "Sertakan anakan dari catatan yang dipilih",
|
||||
"available_actions": "Pilihan aksi",
|
||||
"chosen_actions": "Aksi terpilih",
|
||||
"execute_bulk_actions": "Eksekusi aksi borongan",
|
||||
"bulk_actions_executed": "Aksi borongan telah di eksekusi dengan sukses.",
|
||||
"none_yet": "Belum ada... tambahkan aksi dengan memilih salah satu dari aksi di atas.",
|
||||
"labels": "Label-label"
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Batal",
|
||||
"ok": "Oke",
|
||||
"are_you_sure_remove_note": "Apakah anda yakin mau membuang catatan \"{{title}}\" dari peta relasi? ",
|
||||
"if_you_dont_check": "Jika Anda tidak mencentang ini, catatan hanya akan dihapus dari peta relasi.",
|
||||
"also_delete_note": "Hapus juga catatannya"
|
||||
},
|
||||
"delete_notes": {
|
||||
"delete_notes_preview": "Hapus pratinjau catatan",
|
||||
"close": "Tutup",
|
||||
"delete_all_clones_description": "Hapus seluruh duplikat (bisa dikembalikan di menu revisi)",
|
||||
"erase_notes_description": "Penghapusan normal hanya menandai catatan sebagai dihapus dan dapat dipulihkan (melalui dialog versi revisi) dalam jangka waktu tertentu. Mencentang opsi ini akan menghapus catatan secara permanen seketika dan catatan tidak akan bisa dipulihkan kembali.",
|
||||
"erase_notes_warning": "Hapus catatan secara permanen (tidak bisa dikembalikan), termasuk semua duplikat. Aksi akan memaksa aplikasi untuk mengulang kembali.",
|
||||
"notes_to_be_deleted": "Catatan-catatan berikut akan dihapuskan ({{notesCount}})",
|
||||
"no_note_to_delete": "Tidak ada Catatan yang akan dihapus (hanya duplikat)."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1288,11 +1288,7 @@
|
||||
"search_not_executed": "検索はまだ実行されていません。上の「検索」ボタンをクリックすると、検索結果が表示されます。"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "このクエリでは行が返されませんでした",
|
||||
"not_executed": "クエリはまだ実行されていません。",
|
||||
"failed": "SQLクエリの実行に失敗しました",
|
||||
"statement_result": "ステートメント結果",
|
||||
"execute_now": "今すぐ実行"
|
||||
"no_rows": "このクエリでは行が返されませんでした"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "テーブル"
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"attachment_detail_2": {
|
||||
"deletion_reason": ", deoarece nu există o legătură către atașament în conținutul notiței. Pentru a preveni ștergerea, trebuie adăugată înapoi o legătură către atașament în conținut sau atașamentul trebuie convertit în notiță.",
|
||||
"link_copied": "O legătură către atașament a fost copiată în clipboard.",
|
||||
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}, MIME: {{- mimeType}}",
|
||||
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}",
|
||||
"unrecognized_role": "Rol atașament necunoscut: „{{role}}”.",
|
||||
"will_be_deleted_in": "Acest atașament va fi șters automat în {{time}}",
|
||||
"will_be_deleted_soon": "Acest atașament va fi șters automat în curând"
|
||||
@@ -293,8 +293,7 @@
|
||||
"expand_tooltip": "Expandează subnotițele directe ale acestei colecții (un singur nivel de adâncime). Pentru mai multe opțiuni, apăsați săgeata din dreapta.",
|
||||
"expand_first_level": "Expandează subnotițele directe",
|
||||
"expand_nth_level": "Expandează pe {{depth}} nivele",
|
||||
"expand_all_levels": "Expandează pe toate nivelele",
|
||||
"hide_child_notes": "Ascunde subnotițele din arbore"
|
||||
"expand_all_levels": "Expandează pe toate nivelele"
|
||||
},
|
||||
"bookmark_switch": {
|
||||
"bookmark": "Semn de carte",
|
||||
@@ -570,7 +569,7 @@
|
||||
"file_size": "Dimensiunea fișierului",
|
||||
"file_type": "Tipul fișierului",
|
||||
"note_id": "ID-ul notiței",
|
||||
"open": "Deschide în exterior",
|
||||
"open": "Deschide",
|
||||
"original_file_name": "Denumirea originală a fișierului",
|
||||
"title": "Fișier",
|
||||
"upload_failed": "Încărcarea a unei noi revizii ale fișierului a eșuat.",
|
||||
@@ -796,8 +795,7 @@
|
||||
},
|
||||
"inherited_attribute_list": {
|
||||
"no_inherited_attributes": "Niciun atribut moștenit.",
|
||||
"title": "Atribute moștenite",
|
||||
"none": "niciunul"
|
||||
"title": "Atribute moștenite"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_button": "Caută în întregul conținut",
|
||||
@@ -882,11 +880,7 @@
|
||||
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
|
||||
"print_pdf": "Exportare ca PDF...",
|
||||
"open_note_on_server": "Deschide notița pe server",
|
||||
"view_revisions": "Revizii ale notițelor...",
|
||||
"export_as_image": "Exportează ca imagine",
|
||||
"export_as_image_png": "PNG (bitmap)",
|
||||
"export_as_image_svg": "SVG (vectorial)",
|
||||
"note_map": "Harta notițelor"
|
||||
"view_revisions": "Revizii ale notițelor..."
|
||||
},
|
||||
"note_erasure_timeout": {
|
||||
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
|
||||
@@ -905,9 +899,7 @@
|
||||
"note_size_info": "Dimensiunea notiței reprezintă o aproximare a cerințelor de stocare ale acestei notițe. Ia în considerare conținutul notiței dar și ale reviziilor sale.",
|
||||
"subtree_size": "(dimensiunea sub-arborelui: {{size}} în {{count}} notițe)",
|
||||
"title": "Informații despre notiță",
|
||||
"type": "Tip",
|
||||
"mime": "Tip MIME",
|
||||
"show_similar_notes": "Afișează notițe similare"
|
||||
"type": "Tip"
|
||||
},
|
||||
"note_launcher": {
|
||||
"this_launcher_doesnt_define_target_note": "Acesată scurtătură nu definește o notiță-destinație."
|
||||
@@ -1165,8 +1157,7 @@
|
||||
"search_parameters": "Parametrii de căutare",
|
||||
"search_script": "script de căutare",
|
||||
"search_string": "șir de căutat",
|
||||
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”",
|
||||
"view_options": "Opțiuni de afișare:"
|
||||
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”"
|
||||
},
|
||||
"search_engine": {
|
||||
"baidu": "Baidu",
|
||||
@@ -1318,17 +1309,8 @@
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Eroare la încărcarea unui script personalizat",
|
||||
"message": "Scriptul nu a putut fi executat din cauza:\n\n{{message}}"
|
||||
},
|
||||
"widget-list-error": {
|
||||
"title": "Nu s-a putut obține lista de widget-uri de la server"
|
||||
},
|
||||
"widget-render-error": {
|
||||
"title": "Nu s-a putut randa un widget React"
|
||||
},
|
||||
"widget-missing-parent": "Widget-ul personalizat nu are definită proprietatea necesară „{{property}}“.\n\nDacă acest script este menit să ruleze fără interfață grafică, folosiți '#run=frontendStartup'.",
|
||||
"open-script-note": "Deschide notița scriptului",
|
||||
"scripting-error": "Eroare script personalizat: {{title}}"
|
||||
"message": "Scriptul din notița cu ID-ul „{{id}}”, întitulată „{{title}}” nu a putut fi executată din cauza:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
"enable_tray": "Activează system tray-ul (este necesară repornirea aplicației pentru a avea efect)",
|
||||
@@ -1435,10 +1417,7 @@
|
||||
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte? Această operațiune se aplică doar notițelor de tip imagine, celelalte vor fi ignorate.",
|
||||
"open-in-popup": "Editare rapidă",
|
||||
"archive": "Arhivează",
|
||||
"unarchive": "Dezarhivează",
|
||||
"open-in-a-new-window": "Deschide în fereastră nouă",
|
||||
"hide-subtree": "Ascunde subnotițele",
|
||||
"show-subtree": "Afișează subnotițele"
|
||||
"unarchive": "Dezarhivează"
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
|
||||
@@ -1499,27 +1478,12 @@
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"options": "Setări",
|
||||
"title": "Listă de evidențieri",
|
||||
"title_with_count_one": "{{count}} evidențiere",
|
||||
"title_with_count_few": "{{count}} evidențieri",
|
||||
"title_with_count_other": "{{count}} de evidențieri",
|
||||
"modal_title": "Configurează lista de evidențieri",
|
||||
"menu_configure": "Configurează lista de evidențieri...",
|
||||
"no_highlights": "Nu există nicio evidențiere."
|
||||
"title": "Listă de evidențieri"
|
||||
},
|
||||
"note_icon": {
|
||||
"change_note_icon": "Schimbă iconița notiței",
|
||||
"reset-default": "Resetează la iconița implicită",
|
||||
"search": "Căutare:",
|
||||
"search_placeholder_one": "Caută printre {{number}} iconițe dintr-un pachet",
|
||||
"search_placeholder_few": "Caută printre {{number}} iconițe din {{count}} pachete",
|
||||
"search_placeholder_other": "Caută printre {{number}} iconițe din {{count}} de pachete",
|
||||
"search_placeholder_filtered": "Căutați printre {{number}} iconițe în {{name}}",
|
||||
"filter": "Filtrează",
|
||||
"filter-none": "Toate iconițele",
|
||||
"filter-default": "Iconițele implicite",
|
||||
"icon_tooltip": "{{name}}\nPachet iconițe: {{iconPack}}",
|
||||
"no_results": "Nu s-a găsit nicio iconiță."
|
||||
"search": "Căutare:"
|
||||
},
|
||||
"show_highlights_list_widget_button": {
|
||||
"show_highlights_list": "Afișează lista de evidențieri"
|
||||
@@ -1557,17 +1521,7 @@
|
||||
"refresh-saved-search-results": "Reîmprospătează căutarea salvată",
|
||||
"unhoist": "Defocalizează notița",
|
||||
"toggle-sidebar": "Comută bara laterală",
|
||||
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor.",
|
||||
"clone-indicator-tooltip": "Această notiță are {{- count}} părinți: {{- parents}}",
|
||||
"clone-indicator-tooltip-single": "Această notiță este clonată (un singur părinte: {{- parent}})",
|
||||
"shared-indicator-tooltip": "Această notiță este partajată public",
|
||||
"shared-indicator-tooltip-with-url": "Această notiță este partajată public la: {{- url}}",
|
||||
"subtree-hidden-tooltip_one": "{{count}} subnotiță ascunsă din arbore",
|
||||
"subtree-hidden-tooltip_few": "{{count}} subnotițe ascunse din arbore",
|
||||
"subtree-hidden-tooltip_other": "{{count}} de subnotițe ascunse din arbore",
|
||||
"subtree-hidden-moved-title": "Adăugat în {{title}}",
|
||||
"subtree-hidden-moved-description-collection": "Subnotițele din această colecție sunt ascunse din arbore.",
|
||||
"subtree-hidden-moved-description-other": "Subnotițele din această notiță sunt ascunse."
|
||||
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor."
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Menține fereastra mereu vizibilă"
|
||||
@@ -1575,24 +1529,12 @@
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”",
|
||||
"printing": "Imprimare în curs...",
|
||||
"printing_pdf": "Exportare ca PDF în curs...",
|
||||
"print_report_title": "Raport de imprimare",
|
||||
"print_report_collection_content_one": "{{count}} notiță din colecție nu a putut fi imprimată deoarece nu este suportată sau este protejată.",
|
||||
"print_report_collection_content_few": "{{count}} notițe din colecție nu au putut fi imprimate deoarece nu sunt suportate sau sunt protejate.",
|
||||
"print_report_collection_content_other": "{{count}} de notițe din colecție nu au putut fi imprimate deoarece nu sunt suportate sau sunt protejate.",
|
||||
"print_report_collection_details_button": "Afișează detalii",
|
||||
"print_report_collection_details_ignored_notes": "Notițe ignorate"
|
||||
"printing_pdf": "Exportare ca PDF în curs..."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "introduceți titlul notiței aici...",
|
||||
"created_on": "Creată la <Value />",
|
||||
"last_modified": "Modificată la <Value />",
|
||||
"note_type_switcher_label": "Schimbă din {{type}} la:",
|
||||
"note_type_switcher_others": "Mai multe tipuri de notițe",
|
||||
"note_type_switcher_templates": "Șablon",
|
||||
"note_type_switcher_collection": "Colecție",
|
||||
"edited_notes": "Notițe editate în această zi",
|
||||
"promoted_attributes": "Atribute promovate"
|
||||
"last_modified": "Modificată la <Value />"
|
||||
},
|
||||
"revisions_snapshot_limit": {
|
||||
"erase_excess_revision_snapshots": "Șterge acum reviziile excesive",
|
||||
@@ -1613,11 +1555,7 @@
|
||||
"configure_launchbar": "Configurează bara de lansare"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "Nu s-a găsit niciun rând pentru această interogare",
|
||||
"not_executed": "Această interogare nu a fost executată încă.",
|
||||
"failed": "Interogarea SQL a eșuat",
|
||||
"statement_result": "Rezultatul comenzii SQL",
|
||||
"execute_now": "Execută acum"
|
||||
"no_rows": "Nu s-a găsit niciun rând pentru această interogare"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabele"
|
||||
@@ -1639,8 +1577,7 @@
|
||||
},
|
||||
"toc": {
|
||||
"options": "Setări",
|
||||
"table_of_contents": "Cuprins",
|
||||
"no_headings": "Niciun titlu."
|
||||
"table_of_contents": "Cuprins"
|
||||
},
|
||||
"watched_file_update_status": {
|
||||
"file_last_modified": "Fișierul <code class=\"file-path\"></code> a fost ultima oară modificat la data de <span class=\"file-last-modified\"></span>.",
|
||||
@@ -2075,7 +2012,7 @@
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "Ascunde weekend-urile",
|
||||
"display-week-numbers": "Afișează numărul săptămânii",
|
||||
"map-style": "Stil hartă",
|
||||
"map-style": "Stil hartă:",
|
||||
"max-nesting-depth": "Nivel maxim de imbricare:",
|
||||
"raster": "Raster",
|
||||
"vector_light": "Vectorial (culoare deschisă)",
|
||||
@@ -2132,10 +2069,7 @@
|
||||
"next_theme_title": "Încercați noua temă Trilium",
|
||||
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
|
||||
"next_theme_button": "Testează noua temă",
|
||||
"dismiss": "Treci peste",
|
||||
"new_layout_title": "Aspect nou",
|
||||
"new_layout_message": "Am introdus un aspect modernizat pentru Trilium. Panglică a fost integrată în restul interfeței, cu o bară de stare nouă și secțiuni expandabile (precum atributele promovate) ce preiau funcționalitatea de bază.\n\nNoul aspect este activat în mod implicit, și se poate dezactiva momentan din Opțiuni → Aspect.",
|
||||
"new_layout_button": "Mai multe informații"
|
||||
"dismiss": "Treci peste"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Setări de performanță",
|
||||
@@ -2150,10 +2084,7 @@
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Tema de culori pentru blocuri de cod în notițe de tip text",
|
||||
"related_code_notes": "Tema de culori pentru notițele de tip cod",
|
||||
"ui": "Interfață grafică",
|
||||
"ui_old_layout": "Aspect vechi",
|
||||
"ui_new_layout": "Aspect nou"
|
||||
"related_code_notes": "Tema de culori pentru notițele de tip cod"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
@@ -2209,77 +2140,6 @@
|
||||
"read_only_temporarily_disabled": "Editabilă temporar",
|
||||
"read_only_temporarily_disabled_description": "Această notiță se poate modifica, deși în mod normal ea este doar în citire. Notița va reveni la modul doar în citire imediat ce navigați către altă notiță.\n\nClick pentru a re-activa modul doar în citire.",
|
||||
"shared_publicly": "Partajată public",
|
||||
"shared_locally": "Partajată local",
|
||||
"shared_copy_to_clipboard": "Copiază legătură în clipboard",
|
||||
"shared_open_in_browser": "Deschide legătura în browser",
|
||||
"shared_unshare": "Înlătură partajarea",
|
||||
"clipped_note": "Decupare web",
|
||||
"clipped_note_description": "Această notiță a fost preluată de la {{url}}.\n\nClic pentru a naviga la pagina web sursă.",
|
||||
"execute_script": "Rulează script",
|
||||
"execute_script_description": "Această notiță este un script. Clic pentru a executa scriptul.",
|
||||
"execute_sql": "Rulează SQL",
|
||||
"execute_sql_description": "Această notiță este de tip SQL. Clic pentru a executa interogarea SQL.",
|
||||
"save_status_saved": "Salvat",
|
||||
"save_status_saving": "Se salvează...",
|
||||
"save_status_unsaved": "Nesalvat",
|
||||
"save_status_error": "Salvarea a eșuat",
|
||||
"save_status_saving_tooltip": "Modificările sunt în curs de salvare.",
|
||||
"save_status_unsaved_tooltip": "Există schimbări ce nu au fost încă salvate. Acestea vor fi salvate automat într-un moment.",
|
||||
"save_status_error_tooltip": "A intervenit o eroare la salvarea notiței. Dacă este posibil, încercați să copiați conținutul notiței într-un alt loc și să reîmprospătați aplicația."
|
||||
},
|
||||
"breadcrumb": {
|
||||
"hoisted_badge": "Focalizat",
|
||||
"hoisted_badge_title": "Defocalizează",
|
||||
"workspace_badge": "Spațiu de lucru",
|
||||
"scroll_to_top_title": "Sari la începutul notiței",
|
||||
"create_new_note": "Crează subnotiță",
|
||||
"empty_hide_archived_notes": "Ascunde notițele arhivate"
|
||||
},
|
||||
"status_bar": {
|
||||
"language_title": "Schimbă limba conținutului",
|
||||
"note_info_title": "Afișează informații despre notiță precum data modificării și dimensiunea",
|
||||
"backlinks_one": "{{count}} legătură de retur",
|
||||
"backlinks_few": "{{count}} legături de retur",
|
||||
"backlinks_other": "{{count}} de legături de retur",
|
||||
"backlinks_title_one": "Afișează legătura de retur",
|
||||
"backlinks_title_few": "Afișează legăturile de retur",
|
||||
"backlinks_title_other": "Afișează legăturile de retur",
|
||||
"attachments_one": "{{count}} atașament",
|
||||
"attachments_few": "{{count}} atașamente",
|
||||
"attachments_other": "{{count}} de atașamente",
|
||||
"attachments_title_one": "Deschide atașamentul într-un tab nou",
|
||||
"attachments_title_few": "Deschide atașamentele într-un tab nou",
|
||||
"attachments_title_other": "Deschide atașamentele într-un tab nou",
|
||||
"attributes_one": "{{count}} atribut",
|
||||
"attributes_few": "{{count}} atribute",
|
||||
"attributes_other": "{{count}} de atribute",
|
||||
"attributes_title": "Atribute proprii și moștenite",
|
||||
"note_paths_one": "O cale",
|
||||
"note_paths_few": "{{count}} căi",
|
||||
"note_paths_other": "{{count}} de căi",
|
||||
"note_paths_title": "Căi ale notiței",
|
||||
"code_note_switcher": "Schimbă limbajul"
|
||||
},
|
||||
"attributes_panel": {
|
||||
"title": "Atributele notiței"
|
||||
},
|
||||
"right_pane": {
|
||||
"empty_message": "Nimic de afișat pentru această notiță",
|
||||
"empty_button": "Ascunde panoul",
|
||||
"toggle": "Comută panoul din dreapta",
|
||||
"custom_widget_go_to_source": "Mergi la codul sursă"
|
||||
},
|
||||
"pdf": {
|
||||
"attachments_one": "{{count}} atașament",
|
||||
"attachments_few": "{{count}} atașamente",
|
||||
"attachments_other": "{{count}} de atașamente",
|
||||
"layers_one": "{{count}} strat",
|
||||
"layers_few": "{{count}} straturi",
|
||||
"layers_other": "{{count}} de straturi",
|
||||
"pages_one": "{{count}} pagină",
|
||||
"pages_few": "{{count}} pagini",
|
||||
"pages_other": "{{count}} de pagini",
|
||||
"pages_alt": "Pagina {{pageNumber}}",
|
||||
"pages_loading": "Încărcare..."
|
||||
"shared_locally": "Partajată local"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1566,7 +1566,6 @@
|
||||
"shared-indicator-tooltip": "此筆記已公開分享",
|
||||
"shared-indicator-tooltip-with-url": "此筆記已公開分享至:{{- url}}",
|
||||
"subtree-hidden-tooltip_one": "從樹中隱藏的 {{count}} 篇子筆記",
|
||||
"subtree-hidden-tooltip_other": "",
|
||||
"subtree-hidden-moved-title": "已新增至 {{title}}",
|
||||
"subtree-hidden-moved-description-collection": "此集合隱藏其樹中的子筆記。",
|
||||
"subtree-hidden-moved-description-other": "子筆記隱藏於此筆記的樹中。"
|
||||
@@ -1603,11 +1602,7 @@
|
||||
"configure_launchbar": "設定啟動欄"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "此次查詢沒有返回任何數據",
|
||||
"not_executed": "查詢尚未執行。",
|
||||
"failed": "SQL 查詢執行失敗",
|
||||
"statement_result": "查詢結果",
|
||||
"execute_now": "立即執行"
|
||||
"no_rows": "此次查詢沒有返回任何數據"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "表"
|
||||
|
||||
@@ -14,10 +14,7 @@
|
||||
"edit_branch_prefix": "Редагувати префікс гілки",
|
||||
"help_on_tree_prefix": "Довідка щодо префіксу дерева",
|
||||
"prefix": "Префікс: ",
|
||||
"branch_prefix_saved": "Префікс гілки збережено.",
|
||||
"edit_branch_prefix_multiple": "Редагувати префікс гілки для {{count}} гілок",
|
||||
"branch_prefix_saved_multiple": "Префікс гілки збережено для {{count}} гілок.",
|
||||
"affected_branches": "Уражені гілки ({{count}}):"
|
||||
"branch_prefix_saved": "Префікс гілки збережено."
|
||||
},
|
||||
"about": {
|
||||
"app_version": "Версія програми:",
|
||||
@@ -73,17 +70,8 @@
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Не вдалося завантажити користувацький скрипт",
|
||||
"message": "Скрипт не вдалося виконати через:\n\n{{message}}"
|
||||
},
|
||||
"widget-list-error": {
|
||||
"title": "Не вдалося отримати список віджетів з сервера"
|
||||
},
|
||||
"widget-render-error": {
|
||||
"title": "Не вдалося відобразити користувацький віджет"
|
||||
},
|
||||
"widget-missing-parent": "Для власного віджета не визначено {{property}} обов'язкову властивість\n\nЯкщо цей скрипт призначений для запуску без елемента інтерфейсу користувача, використовуйте замість нього '#run=frontendStartup'.",
|
||||
"open-script-note": "Відкрити нотатку сценарію",
|
||||
"scripting-error": "Помилка користувацького скрипта: {{title}}"
|
||||
"message": "Скрипт з нотатки ID \"{{id}}\" з заголовком \"{{title}}\" не вдалося виконати через:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Масові дії",
|
||||
@@ -211,8 +199,7 @@
|
||||
"export_status": "Статус експорту",
|
||||
"export_in_progress": "Триває експорт: {{progressCount}}",
|
||||
"export_finished_successfully": "Експорт успішно завершено.",
|
||||
"format_pdf": "PDF – для друку або спільного використання.",
|
||||
"share-format": "HTML для веб-публікацій – використовує ту саму тему, що й для спільних нотаток, але може бути опублікований як статичний веб-сайт."
|
||||
"format_pdf": "PDF – для друку або спільного використання."
|
||||
},
|
||||
"help": {
|
||||
"title": "Шпаргалка",
|
||||
@@ -266,8 +253,7 @@
|
||||
"showSQLConsole": "показати консоль SQL",
|
||||
"other": "Інше",
|
||||
"quickSearch": "фокус на швидкому введенні пошуку",
|
||||
"inPageSearch": "пошук на сторінці",
|
||||
"editShortcuts": "Редагувати комбінації клавіш"
|
||||
"inPageSearch": "пошук на сторінці"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "Імпортувати в нотатку",
|
||||
@@ -863,10 +849,7 @@
|
||||
"note_icon": {
|
||||
"change_note_icon": "Змінити значок нотатки",
|
||||
"search": "Пошук:",
|
||||
"reset-default": "Скинути значок до стандартного значення",
|
||||
"search_placeholder_one": "Пошук {{number}} значка у {{count}} пакеті",
|
||||
"search_placeholder_few": "Пошук {{number}} значків у {{count}} пакетах",
|
||||
"search_placeholder_many": "Пошук {{number}} значків у {{count}} пакетах"
|
||||
"reset-default": "Скинути значок до стандартного значення"
|
||||
},
|
||||
"basic_properties": {
|
||||
"note_type": "Тип нотатки",
|
||||
@@ -901,7 +884,7 @@
|
||||
"file_type": "Тип файлу",
|
||||
"file_size": "Розмір файлу",
|
||||
"download": "Завантажити",
|
||||
"open": "Відкрити зовні",
|
||||
"open": "Відкрити",
|
||||
"upload_new_revision": "Завантажити нову версію",
|
||||
"upload_success": "Завантажено нову версію файлу.",
|
||||
"upload_failed": "Не вдалося завантажити нову версію файлу.",
|
||||
@@ -1606,19 +1589,13 @@
|
||||
"refresh-saved-search-results": "Оновити збережені результати пошуку",
|
||||
"create-child-note": "Створити дочірню нотатку",
|
||||
"unhoist": "Відкріпити",
|
||||
"toggle-sidebar": "Перемикання бічної панелі",
|
||||
"subtree-hidden-tooltip_one": "{{count}} дочірня нотатка, прихована від дерев",
|
||||
"subtree-hidden-tooltip_few": "{{count}} дочірніх нотатки, прихованих від дерев",
|
||||
"subtree-hidden-tooltip_many": "{{count}} дочірніх нотаток, прихованих від дерев"
|
||||
"toggle-sidebar": "Перемикання бічної панелі"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Тримати вікно зверху"
|
||||
},
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'",
|
||||
"print_report_collection_content_one": "{{count}} нотатку з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені.",
|
||||
"print_report_collection_content_few": "{{count}} нотатки з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені.",
|
||||
"print_report_collection_content_many": "{{count}} нотаток з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені."
|
||||
"could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "введіть тут заголовок нотатки..."
|
||||
@@ -1766,7 +1743,7 @@
|
||||
"unknown_widget": "Невідомий віджет для \"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Мову не встановлено",
|
||||
"not_set": "Не встановлено",
|
||||
"configure-languages": "Налаштувати мови..."
|
||||
},
|
||||
"content_language": {
|
||||
@@ -1833,7 +1810,7 @@
|
||||
"book_properties_config": {
|
||||
"hide-weekends": "Приховати вихідні",
|
||||
"display-week-numbers": "Відображення номерів тижнів",
|
||||
"map-style": "Стиль карти",
|
||||
"map-style": "Стиль карти:",
|
||||
"max-nesting-depth": "Максимальна глибина вкладення:",
|
||||
"raster": "Растр",
|
||||
"vector_light": "Вектор (Світла)",
|
||||
@@ -1886,7 +1863,7 @@
|
||||
"will_be_deleted_in": "Це вкладення буде автоматично видалено через {{time}}",
|
||||
"will_be_deleted_soon": "Це вкладення незабаром буде автоматично видалено",
|
||||
"deletion_reason": ", оскільки вкладення не має посилання у вмісті нотатки. Щоб запобігти видаленню, додайте посилання на вкладення назад у вміст або перетворіть вкладення на нотатку.",
|
||||
"role_and_size": "Роль: {{role}}, розмір: {{size}}, формат даних: {{- mimeType}}",
|
||||
"role_and_size": "Роль: {{role}}, Розмір: {{size}}",
|
||||
"link_copied": "Посилання на вкладення скопійовано в буфер обміну.",
|
||||
"unrecognized_role": "Нерозпізнана роль вкладення '{{role}}'."
|
||||
},
|
||||
@@ -1937,7 +1914,7 @@
|
||||
"import-into-note": "Імпортувати в нотатку",
|
||||
"apply-bulk-actions": "Застосувати масові дії",
|
||||
"converted-to-attachments": "({{count}}) нотаток перетворено на вкладення.",
|
||||
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток? Ця операція застосовується лише до нотаток із зображеннями, інші нотатки будуть пропущені.",
|
||||
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток?",
|
||||
"open-in-popup": "Швидке редагування",
|
||||
"archive": "Архівувати",
|
||||
"unarchive": "Розархівувати"
|
||||
@@ -2001,10 +1978,7 @@
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Список основних моментів",
|
||||
"options": "Параметри",
|
||||
"title_with_count_one": "{{count}} виділення",
|
||||
"title_with_count_few": "{{count}} виділення",
|
||||
"title_with_count_many": "{{count}} виділень"
|
||||
"options": "Параметри"
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "Видалити рядок"
|
||||
@@ -2077,36 +2051,5 @@
|
||||
},
|
||||
"collections": {
|
||||
"rendering_error": "Не вдалося показати вміст через помилку."
|
||||
},
|
||||
"status_bar": {
|
||||
"backlinks_one": "{{count}} зворотне посилання",
|
||||
"backlinks_few": "{{count}} зворотні посилання",
|
||||
"backlinks_many": "{{count}} зворотних посилань",
|
||||
"backlinks_title_one": "Переглянути зворотне посилання",
|
||||
"backlinks_title_few": "Переглянути зворотні посилання",
|
||||
"backlinks_title_many": "Переглянути зворотніх посилань",
|
||||
"attachments_one": "{{count}} вкладення",
|
||||
"attachments_few": "{{count}} вкладення",
|
||||
"attachments_many": "{{count}} вкладень",
|
||||
"attachments_title_one": "Переглянути вкладення в новій вкладці",
|
||||
"attachments_title_few": "Переглянути вкладення в новій вкладці",
|
||||
"attachments_title_many": "Переглянути вкладень в новій вкладці",
|
||||
"attributes_one": "{{count}} атрибут",
|
||||
"attributes_few": "{{count}} атрибути",
|
||||
"attributes_many": "{{count}} атрибутів",
|
||||
"note_paths_one": "{{count}} шлях",
|
||||
"note_paths_few": "{{count}} шляхи",
|
||||
"note_paths_many": "{{count}} шляхів"
|
||||
},
|
||||
"pdf": {
|
||||
"attachments_one": "{{count}} вкладення",
|
||||
"attachments_few": "{{count}} вкладення",
|
||||
"attachments_many": "{{count}} вкладень",
|
||||
"layers_one": "{{count}} шар",
|
||||
"layers_few": "{{count}} шари",
|
||||
"layers_many": "{{count}} шарів",
|
||||
"pages_one": "{{count}} сторінка",
|
||||
"pages_few": "{{count}} сторінки",
|
||||
"pages_many": "{{count}} сторінок"
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/client/src/types.d.ts
vendored
@@ -36,6 +36,7 @@ interface CustomGlobals {
|
||||
isProtectedSessionAvailable: boolean;
|
||||
isDev: boolean;
|
||||
isMainWindow: boolean;
|
||||
windowId: string;
|
||||
maxEntityChangeIdAtLoad: number;
|
||||
maxEntityChangeSyncIdAtLoad: number;
|
||||
assetPath: string;
|
||||
|
||||
@@ -7,6 +7,7 @@ import Component from "../components/component";
|
||||
import NoteContext from "../components/note_context";
|
||||
import FNote from "../entities/fnote";
|
||||
import attributes from "../services/attributes";
|
||||
import { isExperimentalFeatureEnabled } from "../services/experimental_features";
|
||||
import froca from "../services/froca";
|
||||
import { t } from "../services/i18n";
|
||||
import { copyImageReferenceToClipboard } from "../services/image";
|
||||
@@ -100,8 +101,7 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
|
||||
|
||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
|
||||
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap" || isSavedSqlite)
|
||||
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
|
||||
&& note.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
|
||||
@@ -265,13 +265,9 @@ function useNoteInfo() {
|
||||
const [ note, setNote ] = useState<FNote | null | undefined>();
|
||||
const [ type, setType ] = useState<ExtendedNoteType>();
|
||||
const [ mime, setMime ] = useState<string>();
|
||||
const refreshIdRef = useRef(0);
|
||||
|
||||
function refresh() {
|
||||
const refreshId = ++refreshIdRef.current;
|
||||
|
||||
getExtendedWidgetType(actualNote, noteContext).then(type => {
|
||||
if (refreshId !== refreshIdRef.current) return;
|
||||
setNote(actualNote);
|
||||
setType(type);
|
||||
setMime(actualNote?.mime);
|
||||
@@ -322,8 +318,6 @@ export async function getExtendedWidgetType(note: FNote | null | undefined, note
|
||||
resultingType = "noteMap";
|
||||
} else if (type === "text" && (await noteContext?.isReadOnly())) {
|
||||
resultingType = "readOnlyText";
|
||||
} else if (note.isTriliumSqlite()) {
|
||||
resultingType = "sqlConsole";
|
||||
} else if ((type === "code" || type === "mermaid") && (await noteContext?.isReadOnly())) {
|
||||
resultingType = "readOnlyCode";
|
||||
} else if (type === "text") {
|
||||
@@ -348,8 +342,9 @@ export function checkFullHeight(noteContext: NoteContext | undefined, type: Exte
|
||||
|
||||
// https://github.com/zadam/trilium/issues/2522
|
||||
const isBackendNote = noteContext?.noteId === "_backendLog";
|
||||
const isSqlNote = noteContext.note?.mime === "text/x-sqlite;schema=trilium";
|
||||
const isFullHeightNoteType = type && TYPE_MAPPINGS[type].isFullHeight;
|
||||
return (!noteContext?.hasNoteList() && isFullHeightNoteType)
|
||||
return (!noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|
||||
|| noteContext?.viewScope?.viewMode === "attachments"
|
||||
|| isBackendNote;
|
||||
}
|
||||
@@ -363,8 +358,8 @@ function showToast(type: "printing" | "exporting_pdf", progress: number = 0) {
|
||||
});
|
||||
}
|
||||
|
||||
function handlePrintReport(printReport?: PrintReport) {
|
||||
if (printReport?.type === "collection" && printReport.ignoredNoteIds.length > 0) {
|
||||
function handlePrintReport(printReport: PrintReport) {
|
||||
if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
|
||||
toast.showPersistent({
|
||||
id: "print-report",
|
||||
icon: "bx bx-collection",
|
||||
|
||||
@@ -217,7 +217,6 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
||||
id={inputId}
|
||||
type={LABEL_MAPPINGS[definition.labelType ?? "text"]}
|
||||
value={valueAttr.value}
|
||||
checked={definition.labelType === "boolean" ? valueAttr.value === "true" : undefined}
|
||||
placeholder={t("promoted_attributes.unset-field-placeholder")}
|
||||
data-attribute-id={valueAttr.attributeId}
|
||||
data-attribute-type={valueAttr.type}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./NoteList.css";
|
||||
|
||||
import { WebSocketMessage } from "@triliumnext/commons";
|
||||
import { Component, VNode } from "preact";
|
||||
import { VNode } from "preact";
|
||||
import { lazy, Suspense } from "preact/compat";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -120,9 +120,7 @@ export function CustomNoteList({ note, viewType, isEnabled: shouldEnable, notePa
|
||||
}
|
||||
|
||||
const ComponentToRender = viewType && props && isEnabled && (
|
||||
props.media === "print"
|
||||
? ViewComponents[viewType].print ?? ViewComponents[viewType].normal
|
||||
: ViewComponents[viewType].normal
|
||||
props.media === "print" ? ViewComponents[viewType].print : ViewComponents[viewType].normal
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AttributeRow, CreateChildrenResponse } from "@triliumnext/commons";
|
||||
|
||||
import { CreateChildrenResponse } from "@triliumnext/commons";
|
||||
import server from "../../../services/server";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { setAttribute, setLabel } from "../../../services/attributes";
|
||||
import server from "../../../services/server";
|
||||
import froca from "../../../services/froca";
|
||||
|
||||
interface NewEventOpts {
|
||||
title: string;
|
||||
@@ -10,7 +10,6 @@ interface NewEventOpts {
|
||||
endDate?: string | null;
|
||||
startTime?: string | null;
|
||||
endTime?: string | null;
|
||||
componentId?: string;
|
||||
}
|
||||
|
||||
interface ChangeEventOpts {
|
||||
@@ -18,48 +17,30 @@ interface ChangeEventOpts {
|
||||
endDate?: string | null;
|
||||
startTime?: string | null;
|
||||
endTime?: string | null;
|
||||
componentId?: string;
|
||||
}
|
||||
|
||||
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime, componentId }: NewEventOpts) {
|
||||
const attributes: Omit<AttributeRow, "noteId" | "attributeId">[] = [];
|
||||
attributes.push({
|
||||
type: "label",
|
||||
name: "startDate",
|
||||
value: startDate
|
||||
});
|
||||
if (endDate) {
|
||||
attributes.push({
|
||||
type: "label",
|
||||
name: "endDate",
|
||||
value: endDate
|
||||
});
|
||||
}
|
||||
if (startTime) {
|
||||
attributes.push({
|
||||
type: "label",
|
||||
name: "startTime",
|
||||
value: startTime
|
||||
});
|
||||
}
|
||||
if (endTime) {
|
||||
attributes.push({
|
||||
type: "label",
|
||||
name: "endTime",
|
||||
value: endTime
|
||||
});
|
||||
}
|
||||
|
||||
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime }: NewEventOpts) {
|
||||
// Create the note.
|
||||
await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
|
||||
const { note } = await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
|
||||
title,
|
||||
content: "",
|
||||
type: "text",
|
||||
attributes
|
||||
}, componentId);
|
||||
type: "text"
|
||||
});
|
||||
|
||||
// Set the attributes.
|
||||
setLabel(note.noteId, "startDate", startDate);
|
||||
if (endDate) {
|
||||
setLabel(note.noteId, "endDate", endDate);
|
||||
}
|
||||
if (startTime) {
|
||||
setLabel(note.noteId, "startTime", startTime);
|
||||
}
|
||||
if (endTime) {
|
||||
setLabel(note.noteId, "endTime", endTime);
|
||||
}
|
||||
}
|
||||
|
||||
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime, componentId }: ChangeEventOpts) {
|
||||
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime }: ChangeEventOpts) {
|
||||
// Don't store the end date if it's empty.
|
||||
if (endDate === startDate) {
|
||||
endDate = undefined;
|
||||
@@ -71,12 +52,12 @@ export async function changeEvent(note: FNote, { startDate, endDate, startTime,
|
||||
let endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endDate").shift()?.value||"endDate";
|
||||
|
||||
const noteId = note.noteId;
|
||||
setLabel(noteId, startAttribute, startDate, false, componentId);
|
||||
setAttribute(note, "label", endAttribute, endDate, componentId);
|
||||
setLabel(noteId, startAttribute, startDate);
|
||||
setAttribute(note, "label", endAttribute, endDate);
|
||||
|
||||
startAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:startTime").shift()?.value||"startTime";
|
||||
endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endTime").shift()?.value||"endTime";
|
||||
|
||||
setAttribute(note, "label", startAttribute, startTime, componentId);
|
||||
setAttribute(note, "label", endAttribute, endTime, componentId);
|
||||
setAttribute(note, "label", startAttribute, startTime);
|
||||
setAttribute(note, "label", endAttribute, endTime);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
|
||||
import { getArchiveMenuItem } from "../../../menus/context_menu_utils";
|
||||
import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker";
|
||||
import link_context_menu from "../../../menus/link_context_menu";
|
||||
import branches from "../../../services/branches";
|
||||
import { getArchiveMenuItem } from "../../../menus/context_menu_utils";
|
||||
import { t } from "../../../services/i18n";
|
||||
|
||||
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote, componentId?: string) {
|
||||
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -30,16 +30,16 @@ export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parent
|
||||
}
|
||||
|
||||
if (branchIdToDelete) {
|
||||
await branches.deleteNotes([ branchIdToDelete ], false, false, componentId);
|
||||
await branches.deleteNotes([ branchIdToDelete ], false, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ kind: "separator" },
|
||||
{
|
||||
kind: "custom",
|
||||
componentFn: () => NoteColorPicker({note})
|
||||
componentFn: () => NoteColorPicker({note: note})
|
||||
}
|
||||
],
|
||||
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, e, note.noteId),
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
|
||||
import clsx from "clsx";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import froca from "../../../services/froca";
|
||||
import server from "../../../services/server";
|
||||
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import server from "../../../services/server";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Event {
|
||||
startDate: string,
|
||||
@@ -106,8 +105,7 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
|
||||
|
||||
endDate = (endTime ? `${endDate}T${endTime}:00` : endDate);
|
||||
const eventData: EventInput = {
|
||||
id: note.noteId,
|
||||
title,
|
||||
title: title,
|
||||
start: startDate,
|
||||
url: `#${note.noteId}?popup`,
|
||||
noteId: note.noteId,
|
||||
@@ -150,12 +148,12 @@ async function parseCustomTitle(customTitlettributeName: string | null, note: FN
|
||||
}
|
||||
|
||||
async function buildDisplayedAttributes(note: FNote, calendarDisplayedAttributes: string[]) {
|
||||
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name));
|
||||
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name))
|
||||
const result: Array<[string, string]> = [];
|
||||
|
||||
for (const attribute of filteredDisplayedAttributes) {
|
||||
if (attribute.type === "label") result.push([attribute.name, attribute.value]);
|
||||
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""]);
|
||||
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""])
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, Local
|
||||
import { DateClickArg } from "@fullcalendar/interaction";
|
||||
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
import { RefObject } from "preact";
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext from "../../../components/app_context";
|
||||
import FNote from "../../../entities/fnote";
|
||||
@@ -17,7 +17,6 @@ import { isMobile } from "../../../services/utils";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import Button, { ButtonGroup } from "../../react/Button";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumEvent, useTriliumOption, useTriliumOptionInt } from "../../react/hooks";
|
||||
import { ParentComponent } from "../../react/react_utils";
|
||||
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import { changeEvent, newEvent } from "./api";
|
||||
@@ -88,7 +87,6 @@ export const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, (() => Promise<{ de
|
||||
};
|
||||
|
||||
export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarViewData>) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const calendarRef = useRef<FullCalendar>(null);
|
||||
|
||||
@@ -107,34 +105,26 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
const eventBuilder = useMemo(() => {
|
||||
if (!isCalendarRoot) {
|
||||
return async () => await buildEvents(noteIds);
|
||||
}
|
||||
}
|
||||
return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e);
|
||||
|
||||
}, [isCalendarRoot, noteIds]);
|
||||
|
||||
const plugins = usePlugins(isEditable, isCalendarRoot);
|
||||
const locale = useLocale();
|
||||
|
||||
const { eventDidMount } = useEventDisplayCustomization(note, parentComponent?.componentId);
|
||||
const editingProps = useEditing(note, isEditable, isCalendarRoot, parentComponent?.componentId);
|
||||
const { eventDidMount } = useEventDisplayCustomization(note);
|
||||
const editingProps = useEditing(note, isEditable, isCalendarRoot);
|
||||
|
||||
// React to changes.
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
const api = calendarRef.current;
|
||||
if (!api) return;
|
||||
|
||||
// Subnote attribute change.
|
||||
if (loadResults.getAttributeRows(parentComponent?.componentId).some((a) => noteIds.includes(a.noteId ?? ""))) {
|
||||
if (loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) // note title change.
|
||||
|| loadResults.getAttributeRows().some((a) => noteIds.includes(a.noteId ?? ""))) // subnote change.
|
||||
{
|
||||
// Defer execution after the load results are processed so that the event builder has the updated data to work with.
|
||||
setTimeout(() => api.refetchEvents(), 0);
|
||||
return; // early return since we'll refresh the events anyway
|
||||
}
|
||||
|
||||
// Title change.
|
||||
for (const noteId of loadResults.getNoteIds().filter(noteId => noteIds.includes(noteId))) {
|
||||
const event = api.getEventById(noteId);
|
||||
const note = froca.getNoteFromCache(noteId);
|
||||
if (!event || !note) continue;
|
||||
event.setProp("title", note.title);
|
||||
setTimeout(() => {
|
||||
calendarRef.current?.refetchEvents();
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -232,7 +222,7 @@ function useLocale() {
|
||||
return calendarLocale;
|
||||
}
|
||||
|
||||
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, componentId: string | undefined) {
|
||||
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
|
||||
const onCalendarSelection = useCallback(async (e: DateSelectArg) => {
|
||||
const { startDate, endDate } = parseStartEndDateFromEvent(e);
|
||||
if (!startDate) return;
|
||||
@@ -244,8 +234,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
|
||||
return;
|
||||
}
|
||||
|
||||
newEvent(note, { title, startDate, endDate, startTime, endTime, componentId });
|
||||
}, [ note, componentId ]);
|
||||
newEvent(note, { title, startDate, endDate, startTime, endTime });
|
||||
}, [ note ]);
|
||||
|
||||
const onEventChange = useCallback(async (e: EventChangeArg) => {
|
||||
const { startDate, endDate } = parseStartEndDateFromEvent(e.event);
|
||||
@@ -254,8 +244,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
|
||||
const { startTime, endTime } = parseStartEndTimeFromEvent(e.event);
|
||||
const note = await froca.getNote(e.event.extendedProps.noteId);
|
||||
if (!note) return;
|
||||
changeEvent(note, { startDate, endDate, startTime, endTime, componentId });
|
||||
}, [ componentId ]);
|
||||
changeEvent(note, { startDate, endDate, startTime, endTime });
|
||||
}, []);
|
||||
|
||||
// Called upon when clicking the day number in the calendar, opens or creates the day note but only if in a calendar root.
|
||||
const onDateClick = useCallback(async (e: DateClickArg) => {
|
||||
@@ -274,7 +264,7 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
|
||||
};
|
||||
}
|
||||
|
||||
function useEventDisplayCustomization(parentNote: FNote, componentId: string | undefined) {
|
||||
function useEventDisplayCustomization(parentNote: FNote) {
|
||||
const eventDidMount = useCallback((e: EventMountArg) => {
|
||||
const { iconClass, promotedAttributes } = e.event.extendedProps;
|
||||
|
||||
@@ -331,7 +321,7 @@ function useEventDisplayCustomization(parentNote: FNote, componentId: string | u
|
||||
const note = await froca.getNote(e.event.extendedProps.noteId);
|
||||
if (!note) return;
|
||||
|
||||
openCalendarContextMenu(contextMenuEvent, note, parentNote, componentId);
|
||||
openCalendarContextMenu(contextMenuEvent, note, parentNote);
|
||||
}
|
||||
|
||||
if (isMobile()) {
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
padding: 0 5px 0 10px;
|
||||
|
||||
.tabulator-tableholder {
|
||||
height: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.table-view-container {
|
||||
@@ -72,4 +68,4 @@
|
||||
inset-inline-start: 0;
|
||||
font-size: 1.5em;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
|
||||
import { EventCallBackMethods, Module, Options, Tabulator as VanillaTabulator } from "tabulator-tables";
|
||||
import "tabulator-tables/dist/css/tabulator.css";
|
||||
import "../../../../src/stylesheets/table.css";
|
||||
|
||||
import { isValidElement, RefObject } from "preact";
|
||||
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
|
||||
import { JSX } from "preact/jsx-runtime";
|
||||
import { EventCallBackMethods, Module, Options, Tabulator as VanillaTabulator } from "tabulator-tables";
|
||||
|
||||
import { ParentComponent, renderReactWidget } from "../../react/react_utils";
|
||||
import { JSX } from "preact/jsx-runtime";
|
||||
import { isValidElement, RefObject } from "preact";
|
||||
|
||||
interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"> {
|
||||
tabulatorRef?: RefObject<VanillaTabulator>;
|
||||
tabulatorRef: RefObject<VanillaTabulator>;
|
||||
className?: string;
|
||||
data?: T[];
|
||||
modules?: (new (table: VanillaTabulator) => Module)[];
|
||||
events?: Partial<EventCallBackMethods>;
|
||||
index?: keyof T;
|
||||
index: keyof T;
|
||||
footerElement?: string | HTMLElement | JSX.Element;
|
||||
onReady?: () => void;
|
||||
}
|
||||
@@ -45,9 +43,7 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
|
||||
|
||||
tabulator.on("tableBuilt", () => {
|
||||
tabulatorRef.current = tabulator;
|
||||
if (externalTabulatorRef) {
|
||||
externalTabulatorRef.current = tabulator;
|
||||
}
|
||||
externalTabulatorRef.current = tabulator;
|
||||
onReady?.();
|
||||
});
|
||||
|
||||
@@ -66,15 +62,12 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
|
||||
for (const [ eventName, handler ] of Object.entries(events)) {
|
||||
tabulator.off(eventName as keyof EventCallBackMethods, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, Object.values(events ?? {}));
|
||||
|
||||
// Change in data.
|
||||
useEffect(() => { tabulatorRef.current?.setData(data); }, [ data ]);
|
||||
useEffect(() => {
|
||||
if (!columns) return;
|
||||
tabulatorRef.current?.setColumns(columns);
|
||||
}, [ columns ]);
|
||||
useEffect(() => { tabulatorRef.current?.setData(data) }, [ data ]);
|
||||
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={className} />
|
||||
|
||||
@@ -82,10 +82,6 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail.full-height {
|
||||
flex-grow: 0;
|
||||
height: 100%;
|
||||
@@ -110,4 +106,4 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { ComponentChild } from "preact";
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import { ViewScope } from "../../services/link";
|
||||
import { formatDateTime } from "../../utils/formatters";
|
||||
import NoteIcon from "../note_icon";
|
||||
@@ -23,12 +22,12 @@ const supportedNoteTypes = new Set<NoteType>([
|
||||
export default function InlineTitle() {
|
||||
const { note, parentComponent, viewScope } = useNoteContext();
|
||||
const type = useNoteProperty(note, "type");
|
||||
const [ shown, setShown ] = useState(shouldShow(note, type, viewScope));
|
||||
const [ shown, setShown ] = useState(shouldShow(note?.noteId, type, viewScope));
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [ titleHidden, setTitleHidden ] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setShown(shouldShow(note, type, viewScope));
|
||||
setShown(shouldShow(note?.noteId, type, viewScope));
|
||||
}, [ note, type, viewScope ]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -70,10 +69,9 @@ export default function InlineTitle() {
|
||||
);
|
||||
}
|
||||
|
||||
function shouldShow(note: FNote | null | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
|
||||
function shouldShow(noteId: string | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
|
||||
if (viewScope?.viewMode !== "default") return false;
|
||||
if (note?.noteId?.startsWith("_options")) return true;
|
||||
if (note?.isTriliumSqlite()) return false;
|
||||
if (noteId?.startsWith("_options")) return true;
|
||||
return type && supportedNoteTypes.has(type);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function NoteTypeSwitcher() {
|
||||
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
|
||||
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
|
||||
|
||||
return (currentNoteType && supportedNoteTypes.has(currentNoteType) && !note?.isTriliumSqlite() &&
|
||||
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
|
||||
<div
|
||||
className="note-type-switcher"
|
||||
onWheel={onWheelHorizontalScroll}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "./StatusBar.css";
|
||||
|
||||
import { Locale, NOTE_TYPE_ICONS, NoteType } from "@triliumnext/commons";
|
||||
import { Locale, NoteType } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { type ComponentChildren, RefObject } from "preact";
|
||||
@@ -9,7 +9,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "p
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import FNote, { NOTE_TYPE_ICONS } from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ViewScope } from "../../services/link";
|
||||
|
||||
@@ -1232,9 +1232,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
refreshCtx.noteIdsToUpdate.add(noteId);
|
||||
}
|
||||
|
||||
const hasNotesToUpdateOrReload = refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0;
|
||||
const hasNoteReorderingChange = loadResults.getNoteReorderings().length > 0;
|
||||
if (hasNotesToUpdateOrReload || hasNoteReorderingChange) {
|
||||
if (refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0) {
|
||||
await this.#executeTreeUpdates(refreshCtx, loadResults);
|
||||
}
|
||||
|
||||
@@ -1395,7 +1393,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
for (const parentNoteId of loadResults.getNoteReorderings()) {
|
||||
for (const node of this.getNodesByNoteId(parentNoteId)) {
|
||||
console.log("Reordering ", node);
|
||||
if (node.isLoaded()) {
|
||||
this.sortChildren(node);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
|
||||
* for protected session or attachment information.
|
||||
*/
|
||||
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat" | "sqlConsole";
|
||||
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
|
||||
|
||||
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element | undefined);
|
||||
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
|
||||
@@ -140,10 +140,5 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
|
||||
view: () => import("./type_widgets/AiChat"),
|
||||
className: "ai-chat-widget-container",
|
||||
isFullHeight: true
|
||||
},
|
||||
sqlConsole: {
|
||||
view: () => import("./type_widgets/SqlConsole"),
|
||||
className: "sql-console-widget-container",
|
||||
isFullHeight: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.no-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding: 0.75em;
|
||||
color: var(--muted-text-color);
|
||||
height: 100%;
|
||||
|
||||
.tn-icon {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import "./NoItems.css";
|
||||
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface NoItemsProps {
|
||||
icon: string;
|
||||
text: string;
|
||||
children?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function NoItems({ icon, text, children }: NoItemsProps) {
|
||||
return (
|
||||
<div className="no-items">
|
||||
<Icon icon={icon} />
|
||||
{text}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -184,8 +184,7 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: N
|
||||
|
||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActionsCustomInnerProps) {
|
||||
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
|
||||
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap" || isSavedSqlite)
|
||||
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
|
||||
&& note.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && <ActionButton
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { useNoteContext } from "./react/hooks";
|
||||
|
||||
export default function ScrollPadding() {
|
||||
const { note, parentComponent, ntxId, viewScope } = useNoteContext();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [height, setHeight] = useState<number>(10);
|
||||
const isEnabled = ["text", "code"].includes(note?.type ?? "")
|
||||
&& viewScope?.viewMode === "default"
|
||||
&& !note?.isTriliumSqlite();
|
||||
const isEnabled = ["text", "code"].includes(note?.type ?? "") && viewScope?.viewMode === "default";
|
||||
|
||||
const refreshHeight = () => {
|
||||
if (!ref.current) return;
|
||||
@@ -40,6 +37,6 @@ export default function ScrollPadding() {
|
||||
style={{ height }}
|
||||
onClick={() => parentComponent.triggerCommand("scrollToEnd", { ntxId })}
|
||||
/>
|
||||
: <div />
|
||||
);
|
||||
: <div></div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,4 +40,22 @@ body.experimental-feature-new-layout #right-pane {
|
||||
.gutter-vertical + .card .card-header {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.no-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding: 0.75em;
|
||||
color: var(--muted-text-color);
|
||||
|
||||
.tn-icon {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import "./RightPanelContainer.css";
|
||||
|
||||
import Split from "@triliumnext/split.js";
|
||||
import { VNode } from "preact";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useState, useEffect, useRef, useCallback } from "preact/hooks";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import { WidgetsByParent } from "../../services/bundle";
|
||||
@@ -12,7 +12,7 @@ import options from "../../services/options";
|
||||
import { DEFAULT_GUTTER_SIZE } from "../../services/resizer";
|
||||
import Button from "../react/Button";
|
||||
import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumEvent, useTriliumOptionJson } from "../react/hooks";
|
||||
import NoItems from "../react/NoItems";
|
||||
import Icon from "../react/Icon";
|
||||
import LegacyRightPanelWidget from "../right_panel_widget";
|
||||
import HighlightsList from "./HighlightsList";
|
||||
import PdfAttachments from "./pdf/PdfAttachments";
|
||||
@@ -47,15 +47,14 @@ export default function RightPanelContainer({ widgetsByParent }: { widgetsByPare
|
||||
items.length > 0 ? (
|
||||
items
|
||||
) : (
|
||||
<NoItems
|
||||
icon="bx bx-sidebar"
|
||||
text={t("right_pane.empty_message")}
|
||||
>
|
||||
<div className="no-items">
|
||||
<Icon icon="bx bx-sidebar" />
|
||||
{t("right_pane.empty_message")}
|
||||
<Button
|
||||
text={t("right_pane.empty_button")}
|
||||
triggerCommand="toggleRightPane"
|
||||
/>
|
||||
</NoItems>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
7
apps/client/src/widgets/sql_result.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.sql-result-widget {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.sql-console-result-container td {
|
||||
white-space: preserve;
|
||||
}
|
||||
63
apps/client/src/widgets/sql_result.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { SqlExecuteResults } from "@triliumnext/commons";
|
||||
import { useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import "./sql_result.css";
|
||||
import { useState } from "preact/hooks";
|
||||
import Alert from "./react/Alert";
|
||||
import { t } from "../services/i18n";
|
||||
|
||||
export default function SqlResults() {
|
||||
const { note, ntxId } = useNoteContext();
|
||||
const [ results, setResults ] = useState<SqlExecuteResults>();
|
||||
|
||||
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, results }) => {
|
||||
if (eventNtxId !== ntxId) return;
|
||||
setResults(results);
|
||||
})
|
||||
|
||||
const isEnabled = note?.mime === "text/x-sqlite;schema=trilium";
|
||||
return (
|
||||
<div className={`sql-result-widget ${!isEnabled ? "hidden-ext" : ""}`}>
|
||||
{isEnabled && (
|
||||
results?.length === 1 && Array.isArray(results[0]) && results[0].length === 0 ? (
|
||||
<Alert type="info">
|
||||
{t("sql_result.no_rows")}
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="sql-console-result-container selectable-text">
|
||||
{results?.map(rows => {
|
||||
// inserts, updates
|
||||
if (typeof rows === "object" && !Array.isArray(rows)) {
|
||||
return <pre>{JSON.stringify(rows, null, "\t")}</pre>
|
||||
}
|
||||
|
||||
// selects
|
||||
return <SqlResultTable rows={rows} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SqlResultTable({ rows }: { rows: object[] }) {
|
||||
if (!rows.length) return;
|
||||
|
||||
return (
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{Object.keys(rows[0]).map(key => <th>{key}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{rows.map(row => (
|
||||
<tr>
|
||||
{Object.values(row).map(cell => <td>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
43
apps/client/src/widgets/sql_table_schemas.css
Normal file
@@ -0,0 +1,43 @@
|
||||
.sql-table-schemas-widget {
|
||||
padding: 12px;
|
||||
padding-inline-end: 10%;
|
||||
contain: none !important;
|
||||
}
|
||||
|
||||
.sql-table-schemas > .dropdown {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.sql-table-schemas button.btn {
|
||||
padding: 0.25rem 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 0.5;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sql-console-result-container {
|
||||
width: 100%;
|
||||
font-size: smaller;
|
||||
margin-top: 10px;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.table-schema td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dropdown .table-schema {
|
||||
font-family: var(--monospace-font-family);
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
/* Data type */
|
||||
.dropdown .table-schema td:nth-child(2) {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
46
apps/client/src/widgets/sql_table_schemas.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { t } from "../services/i18n";
|
||||
import { useNoteContext } from "./react/hooks";
|
||||
import "./sql_table_schemas.css";
|
||||
import { SchemaResponse } from "@triliumnext/commons";
|
||||
import server from "../services/server";
|
||||
import Dropdown from "./react/Dropdown";
|
||||
|
||||
export default function SqlTableSchemas() {
|
||||
const { note } = useNoteContext();
|
||||
const [ schemas, setSchemas ] = useState<SchemaResponse[]>();
|
||||
|
||||
useEffect(() => {
|
||||
server.get<SchemaResponse[]>("sql/schema").then(setSchemas);
|
||||
}, []);
|
||||
|
||||
const isEnabled = note?.mime === "text/x-sqlite;schema=trilium" && schemas;
|
||||
return (
|
||||
<div className={`sql-table-schemas-widget ${!isEnabled ? "hidden-ext" : ""}`}>
|
||||
{isEnabled && (
|
||||
<>
|
||||
{t("sql_table_schemas.tables")}{": "}
|
||||
|
||||
<span class="sql-table-schemas">
|
||||
{schemas.map(({ name, columns }) => (
|
||||
<>
|
||||
<Dropdown text={name} noSelectButtonStyle hideToggleArrow
|
||||
>
|
||||
<table className="table-schema">
|
||||
{columns.map(column => (
|
||||
<tr>
|
||||
<td>{column.name}</td>
|
||||
<td>{column.type}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
</Dropdown>
|
||||
{" "}
|
||||
</>
|
||||
))}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import "./Render.css";
|
||||
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import attributes from "../../services/attributes";
|
||||
import { t } from "../../services/i18n";
|
||||
import render from "../../services/render";
|
||||
import Alert from "../react/Alert";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
import render from "../../services/render";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import Alert from "../react/Alert";
|
||||
import "./Render.css";
|
||||
import { t } from "../../services/i18n";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
|
||||
export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
@@ -34,13 +31,6 @@ export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
|
||||
refresh();
|
||||
});
|
||||
|
||||
// Refresh on attribute change.
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows().some(a => a.type === "relation" && a.name === "renderNote" && attributes.isAffecting(a, note))) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
// Integration with search.
|
||||
useTriliumEvent("executeWithContentElement", ({ resolve, ntxId: eventNtxId }) => {
|
||||
if (eventNtxId !== ntxId) return;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
.sql-console-widget-container {
|
||||
.note-detail-split.split-vertical {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.note-detail-split-preview {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
background-color: var(--accented-background-color) !important;
|
||||
}
|
||||
|
||||
.sql-result-widget {
|
||||
height: 100%;
|
||||
|
||||
> .sql-console-result-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: smaller;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
|
||||
> .tabulator {
|
||||
--cell-vert-padding-size: 4px;
|
||||
|
||||
> .tabulator-tableholder {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> .tabulator-footer,
|
||||
> .tabulator-footer .tabulator-footer-contents {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sql-table-schemas-widget {
|
||||
padding: 12px;
|
||||
padding-inline-end: 10%;
|
||||
contain: none !important;
|
||||
|
||||
.sql-table-schemas {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
> .dropdown {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
button.btn {
|
||||
padding: 0.25rem 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 0.5;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table-schema td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dropdown .table-schema {
|
||||
font-family: var(--monospace-font-family);
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
/* Data type */
|
||||
.dropdown .table-schema td:nth-child(2) {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
import "./SqlConsole.css";
|
||||
|
||||
import { SchemaResponse, SqlExecuteResponse } from "@triliumnext/commons";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { ClipboardModule, EditModule, ExportModule, FilterModule, FormatModule, FrozenColumnsModule, KeybindingsModule, PageModule, ResizeColumnsModule, SelectRangeModule, SelectRowModule, SortModule } from "tabulator-tables";
|
||||
|
||||
import { t } from "../../services/i18n";
|
||||
import server from "../../services/server";
|
||||
import Tabulator from "../collections/table/tabulator";
|
||||
import Button from "../react/Button";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import NoItems from "../react/NoItems";
|
||||
import SplitEditor from "./helpers/SplitEditor";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
|
||||
export default function SqlConsole(props: TypeWidgetProps) {
|
||||
return (
|
||||
<SplitEditor
|
||||
noteType="code"
|
||||
{...props}
|
||||
editorBefore={<SqlTableSchemas {...props} />}
|
||||
previewContent={<SqlResults key={props.note.noteId} {...props} />}
|
||||
forceOrientation="vertical"
|
||||
splitOptions={{
|
||||
sizes: [ 70, 30 ]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SqlResults({ ntxId }: TypeWidgetProps) {
|
||||
const [ response, setResponse ] = useState<SqlExecuteResponse>();
|
||||
|
||||
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, response }) => {
|
||||
if (eventNtxId !== ntxId) return;
|
||||
setResponse(response);
|
||||
});
|
||||
|
||||
// Not yet executed.
|
||||
if (response === undefined) {
|
||||
return (
|
||||
<NoItems
|
||||
icon="bx bx-data"
|
||||
text={t("sql_result.not_executed")}
|
||||
>
|
||||
<Button
|
||||
text={t("sql_result.execute_now")}
|
||||
triggerCommand="runActiveNote"
|
||||
/>
|
||||
</NoItems>
|
||||
);
|
||||
}
|
||||
|
||||
// Executed but failed.
|
||||
if (response && !response.success) {
|
||||
return (
|
||||
<NoItems
|
||||
icon="bx bx-error"
|
||||
text={t("sql_result.failed")}
|
||||
>
|
||||
<pre className="sql-error-message selectable-text">{response.error}</pre>
|
||||
</NoItems>
|
||||
);
|
||||
}
|
||||
|
||||
// Zero results.
|
||||
if (response?.results.length === 1 && Array.isArray(response.results[0]) && response.results[0].length === 0) {
|
||||
return (
|
||||
<NoItems
|
||||
icon="bx bx-rectangle"
|
||||
text={t("sql_result.no_rows")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sql-result-widget">
|
||||
<div className="sql-console-result-container selectable-text">
|
||||
{response?.results.map((rows, index) => {
|
||||
// inserts, updates
|
||||
if (typeof rows === "object" && !Array.isArray(rows)) {
|
||||
return (
|
||||
<NoItems
|
||||
key={index}
|
||||
icon="bx bx-play"
|
||||
text={t("sql_result.statement_result")}
|
||||
>
|
||||
<pre key={index}>{JSON.stringify(rows, null, "\t")}</pre>
|
||||
</NoItems>
|
||||
);
|
||||
}
|
||||
|
||||
// selects
|
||||
return <SqlResultTable key={index} rows={rows} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SqlResultTable({ rows }: { rows: object[] }) {
|
||||
if (!rows.length) return;
|
||||
|
||||
return (
|
||||
<Tabulator
|
||||
layout="fitDataFill"
|
||||
modules={[ ResizeColumnsModule, SortModule, SelectRangeModule, ClipboardModule, KeybindingsModule, EditModule, ExportModule, SelectRowModule, FormatModule, FrozenColumnsModule, FilterModule, PageModule ]}
|
||||
selectableRange
|
||||
clipboard="copy"
|
||||
clipboardCopyRowRange="range"
|
||||
clipboardCopyConfig={{
|
||||
rowHeaders: false,
|
||||
columnHeaders: false
|
||||
}}
|
||||
pagination
|
||||
paginationSize={15}
|
||||
paginationSizeSelector
|
||||
paginationCounter="rows"
|
||||
height="100%"
|
||||
columns={[
|
||||
{
|
||||
title: "#",
|
||||
formatter: "rownum",
|
||||
width: 60,
|
||||
hozAlign: "right",
|
||||
frozen: true
|
||||
},
|
||||
...Object.keys(rows[0]).map(key => ({
|
||||
title: key,
|
||||
field: key,
|
||||
width: 250,
|
||||
minWidth: 100,
|
||||
widthGrow: 1,
|
||||
resizable: true,
|
||||
headerFilter: true as const
|
||||
}))
|
||||
]}
|
||||
data={rows}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SqlTableSchemas({ note }: TypeWidgetProps) {
|
||||
const [ schemas, setSchemas ] = useState<SchemaResponse[]>();
|
||||
|
||||
useEffect(() => {
|
||||
server.get<SchemaResponse[]>("sql/schema").then(setSchemas);
|
||||
}, []);
|
||||
|
||||
const isEnabled = note.isTriliumSqlite() && schemas;
|
||||
return (
|
||||
<div className={`sql-table-schemas-widget ${!isEnabled ? "hidden-ext" : ""}`}>
|
||||
{isEnabled && (
|
||||
<>
|
||||
{t("sql_table_schemas.tables")}{": "}
|
||||
|
||||
<span class="sql-table-schemas">
|
||||
{schemas.map(({ name, columns }) => (
|
||||
<Dropdown key={name} text={name} noSelectButtonStyle hideToggleArrow>
|
||||
<table className="table-schema">
|
||||
{columns.map(column => (
|
||||
<tr key={column.name}>
|
||||
<td>{column.name}</td>
|
||||
<td>{column.type}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
</Dropdown>
|
||||
))}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,9 +20,6 @@ export interface CanvasContent {
|
||||
appState: Partial<AppState>;
|
||||
}
|
||||
|
||||
/** Subset of the app state that should be persisted whenever they change. This explicitly excludes transient state like the current selection or zoom level. */
|
||||
type ImportantAppState = Pick<AppState, "gridModeEnabled" | "viewBackgroundColor">;
|
||||
|
||||
export default function useCanvasPersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> {
|
||||
const libraryChanged = useRef(false);
|
||||
|
||||
@@ -40,8 +37,6 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
|
||||
const libraryCache = useRef<LibraryItem[]>([]);
|
||||
const attachmentMetadata = useRef<AttachmentMetadata[]>([]);
|
||||
|
||||
const appStateToCompare = useRef<Partial<ImportantAppState>>({});
|
||||
|
||||
const spacedUpdate = useEditorSpacedUpdate({
|
||||
note,
|
||||
noteContext,
|
||||
@@ -52,6 +47,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
|
||||
|
||||
libraryCache.current = [];
|
||||
attachmentMetadata.current = [];
|
||||
currentSceneVersion.current = -1;
|
||||
|
||||
// load saved content into excalidraw canvas
|
||||
let content: CanvasContent = {
|
||||
@@ -69,9 +65,6 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
|
||||
|
||||
loadData(api, content, theme);
|
||||
|
||||
// Initialize tracking state after loading to prevent redundant updates from initial onChange events
|
||||
currentSceneVersion.current = getSceneVersion(api.getSceneElements());
|
||||
|
||||
// load the library state
|
||||
loadLibrary(note).then(({ libraryItems, metadata }) => {
|
||||
// Update the library and save to independent variables
|
||||
@@ -85,7 +78,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
|
||||
async getData() {
|
||||
const api = apiRef.current;
|
||||
if (!api) return;
|
||||
const { content, svg } = await getData(api, appStateToCompare);
|
||||
const { content, svg } = await getData(api);
|
||||
const attachments: SavedData["attachments"] = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svg, position: 0 }];
|
||||
|
||||
// libraryChanged is unset in dataSaved()
|
||||
@@ -156,47 +149,21 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
|
||||
const oldSceneVersion = currentSceneVersion.current;
|
||||
const newSceneVersion = getSceneVersion(apiRef.current.getSceneElements());
|
||||
|
||||
let hasChanges = (newSceneVersion !== oldSceneVersion);
|
||||
|
||||
// There are cases where the scene version does not change, but appState did.
|
||||
if (!hasChanges) {
|
||||
const importantAppState = appStateToCompare.current;
|
||||
const currentAppState = apiRef.current.getAppState();
|
||||
for (const key in importantAppState) {
|
||||
if (importantAppState[key as keyof ImportantAppState] !== currentAppState[key as keyof ImportantAppState]) {
|
||||
hasChanges = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
if (newSceneVersion !== oldSceneVersion) {
|
||||
spacedUpdate.resetUpdateTimer();
|
||||
spacedUpdate.scheduleUpdate();
|
||||
currentSceneVersion.current = newSceneVersion;
|
||||
}
|
||||
},
|
||||
onLibraryChange: (libraryItems) => {
|
||||
if (!apiRef.current || isReadOnly) return;
|
||||
|
||||
// Check if library actually changed by comparing with cached state
|
||||
const hasChanges =
|
||||
libraryItems.length !== libraryCache.current.length ||
|
||||
libraryItems.some(item => {
|
||||
const cachedItem = libraryCache.current.find(cached => cached.id === item.id);
|
||||
return !cachedItem || cachedItem.name !== item.name;
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
libraryChanged.current = true;
|
||||
spacedUpdate.resetUpdateTimer();
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
onLibraryChange: () => {
|
||||
libraryChanged.current = true;
|
||||
spacedUpdate.resetUpdateTimer();
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function getData(api: ExcalidrawImperativeAPI, appStateToCompare: RefObject<Partial<ImportantAppState>>) {
|
||||
async function getData(api: ExcalidrawImperativeAPI) {
|
||||
const elements = api.getSceneElements();
|
||||
const appState = api.getAppState();
|
||||
|
||||
@@ -221,12 +188,6 @@ async function getData(api: ExcalidrawImperativeAPI, appStateToCompare: RefObjec
|
||||
}
|
||||
});
|
||||
|
||||
const importantAppState: ImportantAppState = {
|
||||
gridModeEnabled: appState.gridModeEnabled,
|
||||
viewBackgroundColor: appState.viewBackgroundColor
|
||||
};
|
||||
appStateToCompare.current = importantAppState;
|
||||
|
||||
const content = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
@@ -236,7 +197,7 @@ async function getData(api: ExcalidrawImperativeAPI, appStateToCompare: RefObjec
|
||||
scrollX: appState.scrollX,
|
||||
scrollY: appState.scrollY,
|
||||
zoom: appState.zoom,
|
||||
...importantAppState
|
||||
gridModeEnabled: appState.gridModeEnabled
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import "./SplitEditor.css";
|
||||
|
||||
import Split from "@triliumnext/split.js";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import { DEFAULT_GUTTER_SIZE } from "../../../services/resizer";
|
||||
import utils, { isMobile } from "../../../services/utils";
|
||||
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
|
||||
import Admonition from "../../react/Admonition";
|
||||
import { useNoteBlob, useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
|
||||
import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
|
||||
import "./SplitEditor.css";
|
||||
import Split from "@triliumnext/split.js";
|
||||
import { DEFAULT_GUTTER_SIZE } from "../../../services/resizer";
|
||||
import { EditableCode, EditableCodeProps } from "../code/Code";
|
||||
import { ComponentChildren } from "preact";
|
||||
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
|
||||
|
||||
export interface SplitEditorProps extends EditableCodeProps {
|
||||
className?: string;
|
||||
@@ -17,8 +15,6 @@ export interface SplitEditorProps extends EditableCodeProps {
|
||||
splitOptions?: Split.Options;
|
||||
previewContent: ComponentChildren;
|
||||
previewButtons?: ComponentChildren;
|
||||
editorBefore?: ComponentChildren;
|
||||
forceOrientation?: "horizontal" | "vertical";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,24 +26,13 @@ export interface SplitEditorProps extends EditableCodeProps {
|
||||
* - Can display errors to the user via {@link setError}.
|
||||
* - Horizontal or vertical orientation for the editor/preview split, adjustable via the switch split orientation button floating button.
|
||||
*/
|
||||
export default function SplitEditor(props: SplitEditorProps) {
|
||||
const [ readOnly ] = useNoteLabelBoolean(props.note, "readOnly");
|
||||
|
||||
if (readOnly) {
|
||||
return <ReadOnlyView {...props} />;
|
||||
}
|
||||
|
||||
return <EditorWithSplit {...props} />;
|
||||
|
||||
}
|
||||
|
||||
function EditorWithSplit({ note, error, splitOptions, previewContent, previewButtons, className, editorBefore, forceOrientation, ...editorProps }: SplitEditorProps) {
|
||||
export default function SplitEditor({ note, error, splitOptions, previewContent, previewButtons, className, ...editorProps }: SplitEditorProps) {
|
||||
const splitEditorOrientation = useSplitOrientation();
|
||||
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const splitEditorOrientation = useSplitOrientation(forceOrientation);
|
||||
|
||||
const editor = (
|
||||
const editor = (!readOnly &&
|
||||
<div className="note-detail-split-editor-col">
|
||||
{editorBefore}
|
||||
<div className="note-detail-split-editor">
|
||||
<EditableCode
|
||||
note={note}
|
||||
@@ -63,14 +48,19 @@ function EditorWithSplit({ note, error, splitOptions, previewContent, previewBut
|
||||
</div>
|
||||
);
|
||||
|
||||
const preview = <PreviewContainer
|
||||
error={error}
|
||||
previewContent={previewContent}
|
||||
previewButtons={previewButtons}
|
||||
/>;
|
||||
const preview = (
|
||||
<div className="note-detail-split-preview-col">
|
||||
<div className={`note-detail-split-preview ${error ? "on-error" : ""}`}>
|
||||
{previewContent}
|
||||
</div>
|
||||
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
|
||||
{previewButtons}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!utils.isDesktop() || !containerRef.current) return;
|
||||
if (!utils.isDesktop() || !containerRef.current || readOnly) return;
|
||||
const elements = Array.from(containerRef.current?.children) as HTMLElement[];
|
||||
const splitInstance = Split(elements, {
|
||||
rtl: glob.isRtl,
|
||||
@@ -81,52 +71,15 @@ function EditorWithSplit({ note, error, splitOptions, previewContent, previewBut
|
||||
});
|
||||
|
||||
return () => splitInstance.destroy();
|
||||
}, [ splitEditorOrientation ]);
|
||||
}, [ readOnly, splitEditorOrientation ]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={`note-detail-split note-detail-printable ${`split-${splitEditorOrientation}`} ${className ?? ""}`}>
|
||||
<div ref={containerRef} className={`note-detail-split note-detail-printable ${"split-" + splitEditorOrientation} ${readOnly ? "split-read-only" : ""} ${className ?? ""}`}>
|
||||
{splitEditorOrientation === "horizontal"
|
||||
? <>{editor}{preview}</>
|
||||
: <>{preview}{editor}</>}
|
||||
? <>{editor}{preview}</>
|
||||
: <>{preview}{editor}</>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ReadOnlyView({ ...props }: SplitEditorProps) {
|
||||
const { note, onContentChanged } = props;
|
||||
const content = useNoteBlob(note);
|
||||
const onContentChangedRef = useRef(onContentChanged);
|
||||
|
||||
useEffect(() => {
|
||||
onContentChangedRef.current = onContentChanged;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onContentChangedRef.current?.(content?.content ?? "");
|
||||
}, [ content ]);
|
||||
|
||||
return (
|
||||
<div className={`note-detail-split note-detail-printable ${props.className} split-read-only`}>
|
||||
<PreviewContainer {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewContainer({ error, previewContent, previewButtons }: {
|
||||
error?: string | null;
|
||||
previewContent: ComponentChildren;
|
||||
previewButtons?: ComponentChildren;
|
||||
}) {
|
||||
return (
|
||||
<div className="note-detail-split-preview-col">
|
||||
<div className={`note-detail-split-preview ${error ? "on-error" : ""}`}>
|
||||
{previewContent}
|
||||
</div>
|
||||
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
|
||||
{previewButtons}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function PreviewButton(props: Omit<ActionButtonProps, "titlePosition">) {
|
||||
@@ -135,12 +88,11 @@ export function PreviewButton(props: Omit<ActionButtonProps, "titlePosition">) {
|
||||
className="tn-tool-button"
|
||||
noIconActionClass
|
||||
titlePosition="top"
|
||||
/>;
|
||||
/>
|
||||
}
|
||||
|
||||
function useSplitOrientation(forceOrientation?: "horizontal" | "vertical") {
|
||||
function useSplitOrientation() {
|
||||
const [ splitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
||||
if (forceOrientation) return forceOrientation;
|
||||
if (isMobile()) return "vertical";
|
||||
if (!splitEditorOrientation) return "horizontal";
|
||||
return splitEditorOrientation as "horizontal" | "vertical";
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { RefObject } from "preact";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import svgPanZoom from "svg-pan-zoom";
|
||||
|
||||
import { t } from "../../../services/i18n";
|
||||
import server from "../../../services/server";
|
||||
import toast from "../../../services/toast";
|
||||
import utils from "../../../services/utils";
|
||||
import { useElementSize, useTriliumEvent } from "../../react/hooks";
|
||||
import { RawHtmlBlock } from "../../react/RawHtml";
|
||||
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
|
||||
import { RawHtmlBlock } from "../../react/RawHtml";
|
||||
import server from "../../../services/server";
|
||||
import svgPanZoom from "svg-pan-zoom";
|
||||
import { RefObject } from "preact";
|
||||
import { useElementSize, useTriliumEvent } from "../../react/hooks";
|
||||
import utils from "../../../services/utils";
|
||||
import toast from "../../../services/toast";
|
||||
|
||||
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
|
||||
/**
|
||||
@@ -145,7 +144,7 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg: string | undefined) {
|
||||
@@ -182,7 +181,7 @@ function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg
|
||||
lastPanZoom.current = {
|
||||
pan: zoomInstance.getPan(),
|
||||
zoom: zoomInstance.getZoom()
|
||||
};
|
||||
}
|
||||
zoomRef.current = undefined;
|
||||
zoomInstance.destroy();
|
||||
};
|
||||
|
||||
@@ -191,6 +191,7 @@ function ExperimentalOptions() {
|
||||
values={filteredExperimentalFeatures}
|
||||
keyProperty="id"
|
||||
titleProperty="name"
|
||||
descriptionProperty="description"
|
||||
currentValue={enabledExperimentalFeatures} onChange={setEnabledExperimentalFeatures}
|
||||
/>
|
||||
</OptionsSection>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import FormCheckbox from "../../../react/FormCheckbox";
|
||||
|
||||
interface CheckboxListProps<T> {
|
||||
values: T[];
|
||||
keyProperty: keyof T;
|
||||
titleProperty?: keyof T;
|
||||
disabledProperty?: keyof T;
|
||||
descriptionProperty?: keyof T;
|
||||
currentValue: string[];
|
||||
onChange: (newValues: string[]) => void;
|
||||
columnWidth?: string;
|
||||
}
|
||||
|
||||
export default function CheckboxList<T>({ values, keyProperty, titleProperty, disabledProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
|
||||
export default function CheckboxList<T>({ values, keyProperty, titleProperty, disabledProperty, descriptionProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
|
||||
function toggleValue(value: string) {
|
||||
if (currentValue.includes(value)) {
|
||||
// Already there, needs removing.
|
||||
@@ -22,20 +25,17 @@ export default function CheckboxList<T>({ values, keyProperty, titleProperty, di
|
||||
return (
|
||||
<ul style={{ listStyleType: "none", marginBottom: 0, columnWidth: columnWidth ?? "400px" }}>
|
||||
{values.map(value => (
|
||||
<li>
|
||||
<label className="tn-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
value={String(value[keyProperty])}
|
||||
checked={currentValue.includes(String(value[keyProperty]))}
|
||||
disabled={!!(disabledProperty && value[disabledProperty])}
|
||||
onChange={e => toggleValue((e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
{String(value[titleProperty ?? keyProperty] ?? value[keyProperty])}
|
||||
</label>
|
||||
<li key={String(value[keyProperty])}>
|
||||
<FormCheckbox
|
||||
label={String(value[titleProperty ?? keyProperty] ?? value[keyProperty])}
|
||||
name={String(value[keyProperty])}
|
||||
currentValue={currentValue.includes(String(value[keyProperty]))}
|
||||
disabled={!!(disabledProperty && value[disabledProperty])}
|
||||
hint={value && (descriptionProperty ? String(value[descriptionProperty]) : undefined)}
|
||||
onChange={() => toggleValue(String(value[keyProperty]))}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,15 +93,7 @@ export default defineConfig(() => ({
|
||||
print: join(__dirname, "src", "print.tsx")
|
||||
},
|
||||
output: {
|
||||
entryFileNames: (chunk) => {
|
||||
// We enforce a hash in the main index file to avoid caching issues, this only works because we have the HTML entry point.
|
||||
if (chunk.name === "index") {
|
||||
return "src/[name]-[hash].js";
|
||||
}
|
||||
|
||||
// For EJS-rendered pages (e.g. login) we need to have a stable name.
|
||||
return "src/[name].js";
|
||||
},
|
||||
entryFileNames: "src/[name].js",
|
||||
chunkFileNames: "src/[name]-[hash].js",
|
||||
assetFileNames: "src/[name]-[hash].[ext]",
|
||||
manualChunks: {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "2.1.3",
|
||||
"better-sqlite3": "12.6.2",
|
||||
"better-sqlite3": "12.6.0",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-dl": "4.0.0",
|
||||
"electron-squirrel-startup": "1.0.1",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"electron": "40.0.0",
|
||||
"electron": "39.2.7",
|
||||
"@electron-forge/cli": "7.11.1",
|
||||
"@electron-forge/maker-deb": "7.11.1",
|
||||
"@electron-forge/maker-dmg": "7.11.1",
|
||||
|
||||
|
After Width: | Height: | Size: 545 B |
|
After Width: | Height: | Size: 727 B |
|
After Width: | Height: | Size: 828 B |
|
After Width: | Height: | Size: 931 B |
BIN
apps/desktop/src/assets/images/tray/closed-windowsTemplate.png
Normal file
|
After Width: | Height: | Size: 292 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 434 B |
|
After Width: | Height: | Size: 492 B |
@@ -6,6 +6,7 @@ import sqlInit from "@triliumnext/server/src/services/sql_init.js";
|
||||
import windowService from "@triliumnext/server/src/services/window.js";
|
||||
import tray from "@triliumnext/server/src/services/tray.js";
|
||||
import options from "@triliumnext/server/src/services/options.js";
|
||||
|
||||
import electronDebug from "electron-debug";
|
||||
import electronDl from "electron-dl";
|
||||
import { PRODUCT_NAME } from "./app-info";
|
||||
@@ -69,10 +70,12 @@ async function main() {
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
app.on("second-instance", (event, commandLine) => {
|
||||
app.on("second-instance", async (event, commandLine) => {
|
||||
const lastFocusedWindow = windowService.getLastFocusedWindow();
|
||||
if (commandLine.includes("--new-window")) {
|
||||
windowService.createExtraWindow("");
|
||||
const randomString = (await import("@triliumnext/server/src/services/utils.js")).randomString;
|
||||
const extraWindowId = randomString(4);
|
||||
windowService.createExtraWindow(extraWindowId, "");
|
||||
} else if (lastFocusedWindow) {
|
||||
if (lastFocusedWindow.isMinimized()) {
|
||||
lastFocusedWindow.restore();
|
||||
@@ -124,7 +127,8 @@ async function onReady() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await normalizeOpenNoteContexts();
|
||||
tray.createTray();
|
||||
} else {
|
||||
await windowService.createSetupWindow();
|
||||
@@ -133,6 +137,30 @@ async function onReady() {
|
||||
await windowService.registerGlobalShortcuts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Some windows may have closed abnormally, leaving closedAt as 0 in openNoteContexts.
|
||||
* This function normalizes those timestamps to the current time for correct sorting/filtering.
|
||||
*/
|
||||
async function normalizeOpenNoteContexts() {
|
||||
const savedWindows = options.getOptionJson("openNoteContexts") || [];
|
||||
const now = Date.now();
|
||||
|
||||
let changed = false;
|
||||
for (const win of savedWindows) {
|
||||
if (win.windowId !== "main" && win.closedAt === 0) {
|
||||
win.closedAt = now;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
const { default: cls } = (await import("@triliumnext/server/src/services/cls.js"));
|
||||
cls.wrap(() => {
|
||||
options.setOption("openNoteContexts", JSON.stringify(savedWindows));
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
function getElectronLocale() {
|
||||
const uiLocale = options.getOptionOrNull("locale");
|
||||
const formattingLocale = options.getOptionOrNull("formattingLocale");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Standalone tool to dump contents of Trilium document.db file into a directory tree of notes",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.6.2",
|
||||
"better-sqlite3": "12.6.0",
|
||||
"mime-types": "3.0.2",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"tsx": "4.21.0",
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
"description": "Desktop version of Trilium which imports the demo database (presented to new users at start-up) or the user guide and other documentation and saves the modifications for committing.",
|
||||
"dependencies": {
|
||||
"archiver": "7.0.1",
|
||||
"better-sqlite3": "12.6.2"
|
||||
"better-sqlite3": "12.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@triliumnext/client": "workspace:*",
|
||||
"@triliumnext/desktop": "workspace:*",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"electron": "40.0.0",
|
||||
"electron": "39.2.7",
|
||||
"fs-extra": "11.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -185,9 +185,6 @@ async function exportData(noteId: string, format: ExportFormat, outputPath: stri
|
||||
return components.join("/");
|
||||
});
|
||||
|
||||
// Remove data-list-item-id created by CKEditor for lists
|
||||
content = content.replace(/ data-list-item-id="[^"]*"/g, "");
|
||||
|
||||
return content;
|
||||
|
||||
function findAttachment(targetAttachmentId: string) {
|
||||
|
||||
@@ -43,7 +43,7 @@ test("Highlights list is displayed", async ({ page, context }) => {
|
||||
await app.closeAllTabs();
|
||||
await app.goToNoteInNewTab("Highlights list");
|
||||
|
||||
await expect(app.sidebar).toContainText("10 highlights");
|
||||
await expect(app.sidebar).toContainText(/highlights/i);
|
||||
const rootList = app.sidebar.locator(".highlights-list ol");
|
||||
let index = 0;
|
||||
for (const highlightedEl of ["Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2"]) {
|
||||
|
||||
@@ -59,7 +59,7 @@ export default class App {
|
||||
|
||||
// Wait for the page to load.
|
||||
if (url === "/") {
|
||||
await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test");
|
||||
await expect(this.noteTree).toContainText("Trilium Integration Test");
|
||||
if (!preserveTabs) {
|
||||
await this.closeAllTabs();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.13.0-bullseye-slim AS builder
|
||||
FROM node:24.12.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# 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
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.13.0-bullseye-slim
|
||||
FROM node:24.12.0-bullseye-slim
|
||||
# Install only runtime dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.13.0-alpine AS builder
|
||||
FROM node:24.12.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# 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
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.13.0-alpine
|
||||
FROM node:24.12.0-alpine
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.13.0-alpine AS builder
|
||||
FROM node:24.12.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# 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
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.13.0-alpine
|
||||
FROM node:24.12.0-alpine
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.13.0-bullseye-slim AS builder
|
||||
FROM node:24.12.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# 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
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.13.0-bullseye-slim
|
||||
FROM node:24.12.0-bullseye-slim
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.6.2"
|
||||
"better-sqlite3": "12.6.0"
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
"proxy-nginx-subdir": "docker run --name trilium-nginx-subdir --rm --network=host -v ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx:latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.6.2",
|
||||
"better-sqlite3": "12.6.0",
|
||||
"html-to-text": "9.0.5",
|
||||
"node-html-parser": "7.0.2",
|
||||
"sucrase": "3.35.1"
|
||||
@@ -70,11 +70,11 @@
|
||||
"@types/xml2js": "0.4.14",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"axios": "1.13.3",
|
||||
"axios": "1.13.2",
|
||||
"bindings": "1.5.0",
|
||||
"bootstrap": "5.3.8",
|
||||
"chardet": "2.1.1",
|
||||
"cheerio": "1.2.0",
|
||||
"cheerio": "1.1.2",
|
||||
"chokidar": "5.0.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"compression": "1.8.1",
|
||||
@@ -82,8 +82,8 @@
|
||||
"csrf-csrf": "3.2.2",
|
||||
"debounce": "3.0.0",
|
||||
"debug": "4.4.3",
|
||||
"ejs": "4.0.1",
|
||||
"electron": "40.0.0",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "39.2.7",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
@@ -91,7 +91,7 @@
|
||||
"express-http-proxy": "2.1.2",
|
||||
"express-openid-connect": "2.19.4",
|
||||
"express-rate-limit": "8.2.1",
|
||||
"express-session": "1.19.0",
|
||||
"express-session": "1.18.2",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
"fs-extra": "11.3.3",
|
||||
"helmet": "8.1.0",
|
||||
@@ -99,7 +99,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.8.0",
|
||||
"i18next": "25.7.4",
|
||||
"i18next-fs-backend": "2.6.1",
|
||||
"image-type": "6.0.0",
|
||||
"ini": "6.0.0",
|
||||
@@ -126,7 +126,7 @@
|
||||
"swagger-jsdoc": "6.2.8",
|
||||
"time2fa": "1.4.2",
|
||||
"tmp": "0.2.5",
|
||||
"turnish": "1.8.0",
|
||||
"turndown": "7.2.2",
|
||||
"unescape": "1.0.1",
|
||||
"vite": "7.3.1",
|
||||
"ws": "8.19.0",
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import "./services/handlers.js";
|
||||
import "./becca/becca_loader.js";
|
||||
|
||||
import compression from "compression";
|
||||
import cookieParser from "cookie-parser";
|
||||
import ejs from "ejs";
|
||||
import express from "express";
|
||||
import { auth } from "express-openid-connect";
|
||||
import helmet from "helmet";
|
||||
import { t } from "i18next";
|
||||
import path from "path";
|
||||
import favicon from "serve-favicon";
|
||||
|
||||
import cookieParser from "cookie-parser";
|
||||
import helmet from "helmet";
|
||||
import compression from "compression";
|
||||
import config from "./services/config.js";
|
||||
import utils, { getResourceDir, isDev } from "./services/utils.js";
|
||||
import assets from "./routes/assets.js";
|
||||
import routes from "./routes/routes.js";
|
||||
import custom from "./routes/custom.js";
|
||||
import error_handlers from "./routes/error_handlers.js";
|
||||
import routes from "./routes/routes.js";
|
||||
import config from "./services/config.js";
|
||||
import { startScheduledCleanup } from "./services/erase.js";
|
||||
import log from "./services/log.js";
|
||||
import openID from "./services/open_id.js";
|
||||
import { RESOURCE_DIR } from "./services/resource_dir.js";
|
||||
import sql_init from "./services/sql_init.js";
|
||||
import utils, { getResourceDir, isDev } from "./services/utils.js";
|
||||
import { auth } from "express-openid-connect";
|
||||
import openID from "./services/open_id.js";
|
||||
import { t } from "i18next";
|
||||
import eventService from "./services/events.js";
|
||||
import log from "./services/log.js";
|
||||
import "./services/handlers.js";
|
||||
import "./becca/becca_loader.js";
|
||||
import { RESOURCE_DIR } from "./services/resource_dir.js";
|
||||
|
||||
export default async function buildApp() {
|
||||
const app = express();
|
||||
@@ -35,7 +33,7 @@ export default async function buildApp() {
|
||||
|
||||
// view engine setup
|
||||
app.set("views", path.join(assetsDir, "views"));
|
||||
app.engine("ejs", (filePath, options, callback) => ejs.renderFile(filePath, options, callback));
|
||||
app.engine("ejs", (await import("ejs")).renderFile);
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.use((req, res, next) => {
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
|
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 230 B |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Database/Manually altering the database/3_SQL Console_image.png
generated
vendored
Normal file
|
After Width: | Height: | Size: 265 B |
@@ -1,43 +1,36 @@
|
||||
<p>The SQL Console is Trilium's built-in database editor.</p>
|
||||
<p>It can be accessed by going to the <a class="reference-link" href="#root/_help_x3i7MxGccDuM">Global menu</a> →
|
||||
<p>It can be accessed by going to the <a href="#root/_help_Vc8PjrjAGuOp">global menu</a> →
|
||||
Advanced → Open SQL Console.</p>
|
||||
<p>
|
||||
<img src="SQL Console_image.png">
|
||||
</p>
|
||||
<h3>Interaction</h3>
|
||||
<ul>
|
||||
<li>Hovering the mouse over one of the tables listed at the top of the document
|
||||
will show the columns and their data type.</li>
|
||||
<li>Only one SQL statement can be run at once.</li>
|
||||
<li>To run the statement, press the <em>Execute</em> icon.</li>
|
||||
<li>For queries that return a result, the data will displayed in a table.</li>
|
||||
<li>For statements (e.g. <code spellcheck="false">INSERT</code>, <code spellcheck="false">UPDATE</code>),
|
||||
the number of affected rows is displayed.</li>
|
||||
</ul>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1124/571;" src="2_SQL Console_image.png"
|
||||
width="1124" height="571">
|
||||
</figure>
|
||||
|
||||
<h3>Interacting with the table</h3>
|
||||
<p>After executing a query, a table with the results will be displayed:</p>
|
||||
<ul>
|
||||
<li>Clicking on a column allows sorting ascending or descending.</li>
|
||||
<li>Underneath each column there is an input field which allows filtering
|
||||
by text.</li>
|
||||
<li>Press <kbd>Ctrl</kbd>+<kbd>C</kbd> to copy the current cell to clipboard.</li>
|
||||
<li>Multiple cells can be selected by dragging or by holding <kbd>Shift</kbd> +
|
||||
arrow keys</li>
|
||||
<li>Results are paginated for performance reasons. The controls at the bottom
|
||||
of the table can be used to navigate through pages.</li>
|
||||
<li>
|
||||
<p>Hovering the mouse over one of the tables listed at the top of the document
|
||||
will show the columns and their data type.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Only one SQL statement can be run at once.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>To run the statement, press the
|
||||
<img src="3_SQL Console_image.png">icon.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>For queries that return a result, the data will displayed in a table.</p>
|
||||
<p>
|
||||
<img src="1_SQL Console_image.png">
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Saved SQL console</h3>
|
||||
<p>SQL queries or commands can be saved into a dedicated note.</p>
|
||||
<p>To do so, simply write the query and press the
|
||||
<img src="1_SQL Console_image.png">button. Once saved, the note will appear in <a class="reference-link"
|
||||
href="#root/_help_l0tKav7yLHGF">Day Notes</a>.</p>
|
||||
<p>The note can be locked for editing by pressing the <em>Lock</em> button
|
||||
in the note actions section near the title bar (on the <a class="reference-link"
|
||||
href="#root/_help_IjZS7iK5EXtb">New Layout</a>, or in the <a class="reference-link"
|
||||
href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> area if using the old
|
||||
layout). When editing is locked, the SQL statement is hidden from view.</p>
|
||||
<img src="2_SQL Console_image.png">button. Once saved, the note will appear in <a href="#root/_help_l0tKav7yLHGF">Day Notes</a>.</p>
|
||||
<ul>
|
||||
<li>The SQL expression will not be displayed by default, but it can still
|
||||
be viewed by going to the note context menu and selecting <em>Note source</em>.</li>
|
||||
<li>The expression cannot be modified. If needed, recreate it by copying the
|
||||
statement back into the SQL console and then saving it again.</li>
|
||||
</ul>
|
||||
34
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Sharing.html
generated
vendored
@@ -38,17 +38,17 @@ class="image">
|
||||
</th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Table of contents.</li>
|
||||
<li>Syntax highlight of code blocks, provided a language is selected (does
|
||||
<li data-list-item-id="e26b4ce9ba4e9dfe224d04e0f341925ed">Table of contents.</li>
|
||||
<li data-list-item-id="e9707fdfa2c92d66690cf932f7e647253">Syntax highlight of code blocks, provided a language is selected (does
|
||||
not work if “Auto-detected” is enabled).</li>
|
||||
<li>Rendering for math equations.</li>
|
||||
<li><a href="#root/_help_nBAXQFj20hS1">Including notes</a> (only if the included
|
||||
<li data-list-item-id="e84420a10c6d64bd107edb6e867c91d4b">Rendering for math equations.</li>
|
||||
<li data-list-item-id="e10834dcd0619d77ae2e94d3695bedf58"><a href="#root/_help_nBAXQFj20hS1">Including notes</a> (only if the included
|
||||
notes are also shared).</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Inline Mermaid diagrams are not rendered.</li>
|
||||
<li data-list-item-id="e41cc4139377f9f88d653d1eb8ca47bb4">Inline Mermaid diagrams are not rendered.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -57,12 +57,12 @@ class="image">
|
||||
</th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Basic support (displaying the contents of the note in a monospace font).</li>
|
||||
<li data-list-item-id="e291ae6d5130677b4c99f7c3bdbe974b4">Basic support (displaying the contents of the note in a monospace font).</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>No syntax highlight.</li>
|
||||
<li data-list-item-id="e0270680bbdd7a129306e61e11691e36d">No syntax highlight.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -95,12 +95,12 @@ class="image">
|
||||
</th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>The child notes are displayed in a fixed format. </li>
|
||||
<li data-list-item-id="ea031e1d4149eb443ace756234490c5a4">The child notes are displayed in a fixed format. </li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>More advanced view types such as the calendar view are not supported.</li>
|
||||
<li data-list-item-id="ea4a9d424aec2afbaecc07bbf64b7bebd">More advanced view types such as the calendar view are not supported.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -109,12 +109,12 @@ class="image">
|
||||
</th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>The diagram is displayed as a vector image.</li>
|
||||
<li data-list-item-id="e582d283f2b1b30cbe5ae35d8e01b2bf2">The diagram is displayed as a vector image.</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>No further interaction supported.</li>
|
||||
<li data-list-item-id="e33268686446e3c217077201bb5964364">No further interaction supported.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -123,12 +123,12 @@ class="image">
|
||||
</th>
|
||||
<td>
|
||||
<ul>
|
||||
<li>The diagram is displayed as a vector image.</li>
|
||||
<li data-list-item-id="e443dd0e97c30cb12c77e8906a71569ea">The diagram is displayed as a vector image.</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>No further interaction supported.</li>
|
||||
<li data-list-item-id="efe151ef3f3826c825416417525fb5fb2">No further interaction supported.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -144,7 +144,7 @@ class="image">
|
||||
<td>The diagram is displayed as a vector image.</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>No further interaction supported.</li>
|
||||
<li data-list-item-id="ed3b4fb473042f6e32b4502d4fa11a767">No further interaction supported.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -160,7 +160,7 @@ class="image">
|
||||
<td>Basic interaction (downloading the file).</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>No further interaction supported.</li>
|
||||
<li data-list-item-id="ed87e836a39d127ebcbb33e9e59045afb">No further interaction supported.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -392,8 +392,8 @@ for (const attr of parentNote.attributes) {
|
||||
<p>Indicates to web crawlers that the page should not be indexed of this
|
||||
note by:</p>
|
||||
<ul>
|
||||
<li>Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li>
|
||||
<li>Setting the <code>noindex, follow</code> meta tag.</li>
|
||||
<li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li>
|
||||
<li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,79 +1,16 @@
|
||||
<p>Trilium can import ENEX files, which are used by Evernote for backup/export.
|
||||
One ENEX file represents the content (notes and resources) of one notebook.</p>
|
||||
<p>Trilium can import ENEX files which are used by Evernote for backup/export.
|
||||
One ENEX file represents content (notes and resources) of one notebook.</p>
|
||||
<h2>Export ENEX from Evernote</h2>
|
||||
<p>To export ENEX files from Evernote, you can use:</p>
|
||||
<ul>
|
||||
<li>Evernote desktop application. See Evernote <a href="https://help.evernote.com/hc/en-us/articles/209005557-Export-Notes-and-Notebooks-as-ENEX-or-HTML">documentation</a>.
|
||||
Note that the limitation of this method is that you can only export 100
|
||||
notes at a time or one notebook at a time.</li>
|
||||
<li>A third-party <a href="https://github.com/vzhd1701/evernote-backup">evernote-backup</a> CLI
|
||||
tool. This tool can export all of your notebooks in bulk.</li>
|
||||
</ul>
|
||||
<p>To export ENEX file, you need to have a <em>legacy</em> desktop version
|
||||
of Evernote (i.e. not web/mobile). Right click on notebook and select export
|
||||
and follow the wizard.</p>
|
||||
<h2>Import ENEX in Trilium</h2>
|
||||
<p>Once you have your ENEX files, do the following to import them in Trilium:</p>
|
||||
<ol>
|
||||
<li>In the Trilium note tree, right-click the note under which you want to
|
||||
import one or more of your ENEX files. The notes in the files will be imported
|
||||
as child notes of the selected note.</li>
|
||||
<li>Click Import into note.</li>
|
||||
<li>Choose your ENEX file or files and click Import.</li>
|
||||
<li>During the import, you will see "Import in progress" message. If the import
|
||||
is successful, the message will change to “Import finished successfully”
|
||||
and then disappear.</li>
|
||||
<li>We recommend you to check the imported notes and their attachments to
|
||||
verify that you haven’t lost any data.</li>
|
||||
</ol>
|
||||
<p>A non-exhaustive list of what the importer preserves:</p>
|
||||
<ul>
|
||||
<li>Attachments</li>
|
||||
<li>The hierarchy of headings (these are shifted to start with H2 because
|
||||
H1 is reserved for note title, see <a href="#root/_help_Gr6xFaF6ioJ5">Headings</a>)</li>
|
||||
<li>Tables</li>
|
||||
<li>Bulleted lists</li>
|
||||
<li>Numbered lists</li>
|
||||
<li>Bold</li>
|
||||
<li>Italics</li>
|
||||
<li>Strikethrough</li>
|
||||
<li>Highlights</li>
|
||||
<li>Font colors</li>
|
||||
<li>Soft line breaks</li>
|
||||
<li>External links</li>
|
||||
</ul>
|
||||
<p>However, we do not guarantee that all of your formatting will be imported
|
||||
100% correctly.</p>
|
||||
<p>Once you have ENEX file, you can import it to Trilium. Right click on
|
||||
some note (to which you want to import the file), click on "Import" and
|
||||
select the ENEX file.</p>
|
||||
<p>After importing the ENEX file, go over the imported notes and resources
|
||||
to be sure the import went well, and you didn't lose any data.</p>
|
||||
<h2>Limitations</h2>
|
||||
<ul>
|
||||
<li>The size limit of one import is 250Mb. If the total size of your files
|
||||
is larger, you can increase the <a href="#root/_help_WOcw2SLH6tbX">upload limit</a>,
|
||||
or divide your files, and run the import as many times as necessary.</li>
|
||||
<li>All resources (except for images) are created as notes’ attachments.</li>
|
||||
<li>If you have HTML inside ENEX files, the HTML formatting may be broken
|
||||
or lost after import in Trilium. See <a class="reference-link" href="#root/_help_wy8So3yZZlH9">Reporting issues</a>.</li>
|
||||
</ul>
|
||||
<h3>Internal links</h3>
|
||||
<p>The importer cannot transform Evernote internal links into Trilium internal
|
||||
links because Evernote internal note IDs are not preserved in ENEX files.</p>
|
||||
<p>If you want to restore the internal links in Trilium after you import
|
||||
all of your ENEX files, you can use or adapt this custom script:
|
||||
<a
|
||||
class="reference-link" href="#root/_help_dj3j8dG4th4l">Process internal links by title</a>
|
||||
</p>
|
||||
<p>The script does the following:</p>
|
||||
<ol>
|
||||
<li>It finds all Evernote internal links.</li>
|
||||
<li>For each one, it checks if its link text matches a note title, and if
|
||||
yes, it replaces the Evernote link with an internal Trilium link. If not,
|
||||
it leaves the Evernote link in place.</li>
|
||||
<li>If it finds more than one note with a matching note title, it leaves the
|
||||
Evernote link in place.</li>
|
||||
<li>It outputs the results in a log that you can see in the respective code
|
||||
note in Trilium.</li>
|
||||
</ol>
|
||||
<p>The script has the following limitations:</p>
|
||||
<ul>
|
||||
<li>It will not fix links to anchors and links to notes that you renamed in
|
||||
Evernote after you created the links.</li>
|
||||
<li>Some note titles might not be well identified, even if they exist. This
|
||||
is especially the case if the note title contains some special characters.
|
||||
Should this be problematic, consider <a class="reference-link" href="#root/_help_wy8So3yZZlH9">Reporting issues</a>.</li>
|
||||
</ul>
|
||||
<p>All resources (except for images) are created as note's attachments.</p>
|
||||
<p>HTML inside ENEX files is not exactly valid so some formatting maybe broken
|
||||
or lost. You can report major problems into <a href="https://github.com/TriliumNext/Trilium/issues">Trilium issue tracker</a>.</p>
|
||||
@@ -1,35 +0,0 @@
|
||||
const query = `note.type = "text" and note.content *=* "evernote:///view/"`;
|
||||
const notes = api.searchForNotes(query);
|
||||
|
||||
for (const note of notes) {
|
||||
api.log(`Processing note ${note.title}...`);
|
||||
|
||||
const content = note.getContent();
|
||||
const $ = api.cheerio.load(content);
|
||||
|
||||
$("a").each((i, el) => {
|
||||
const $el = $(el);
|
||||
|
||||
const url = $el.attr("href");
|
||||
if (!url.startsWith("evernote:///")) return;
|
||||
|
||||
const text = $el.text();
|
||||
const matchingNotes = api.searchForNotes(`note.title = "${text}"`);
|
||||
if (matchingNotes.length === 0) {
|
||||
api.log(`No matching notes for "${text}..."`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchingNotes.length > 1) {
|
||||
api.log(`Found multiple matching notes for "${text}". Skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingNote = matchingNotes[0];
|
||||
|
||||
api.log(`Found matching note: ${matchingNote.title} ${matchingNote.noteId}`);
|
||||
$el.attr("href", `#root/${matchingNote.noteId}`);
|
||||
$el.addClass("reference-link");
|
||||
});
|
||||
note.setContent($("body").html());
|
||||
}
|
||||
@@ -8,37 +8,39 @@
|
||||
the number of items stays small. When a note has a large number of notes
|
||||
(in the order of thousands or tens of thousands), two problems arise:</p>
|
||||
<ul>
|
||||
<li>Navigating between notes becomes cumbersome and the tree itself gets cluttered
|
||||
<li data-list-item-id="e536c86d371061c12f76f7de2a0af67be">Navigating between notes becomes cumbersome and the tree itself gets cluttered
|
||||
with a large amount of notes.</li>
|
||||
<li>The large amount of notes can slow down the application considerably.</li>
|
||||
<li data-list-item-id="ecc37d6c4d0430254e98615842b94429d">The large amount of notes can slow down the application considerably.</li>
|
||||
</ul>
|
||||
<p>Since v0.102.0, Trilium allows the tree to hide the child notes of particular
|
||||
notes. This works for both <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a> and
|
||||
notes. This works for both <a class="reference-link" href="#root/pOsGYCXsbNQG/_help_GTwFsgaA0lCt">Collections</a> and
|
||||
normal notes.</p>
|
||||
<h2>Interaction</h2>
|
||||
<p>When the subtree of a note is hidden, there are a few subtle changes:</p>
|
||||
<ul>
|
||||
<li>To indicate that the subtree is hidden, the note will not have an expand
|
||||
<li data-list-item-id="ec1ce3d2030f36e4847f3bbd9468d28e3">To indicate that the subtree is hidden, the note will not have an expand
|
||||
button and it will display the number of children to the right.</li>
|
||||
<li>It's not possible to add a new note directly from the tree.
|
||||
<li
|
||||
data-list-item-id="ea99d38ea6c8a816cf2ab7a7e73cfcac5">It's not possible to add a new note directly from the tree.
|
||||
<ul>
|
||||
<li>For <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>,
|
||||
<li data-list-item-id="ef0132a903a11e9f667b2b2f4c4fff17a">For <a class="reference-link" href="#root/pOsGYCXsbNQG/_help_GTwFsgaA0lCt">Collections</a>,
|
||||
it's best to use the built-in mechanism to create notes (for example by
|
||||
creating a new point on a geo-map, or by adding a new row in a table).</li>
|
||||
<li>For normal notes, it's still possible to create children via other means
|
||||
such as using the <a class="reference-link" href="#root/_help_hrZ1D00cLbal">Internal (reference) links</a> system.</li>
|
||||
<li
|
||||
data-list-item-id="e7db44100046c8c79bf79841285aacd1f">For normal notes, it's still possible to create children via other means
|
||||
such as using the <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/QEAPj01N5f7w/_help_hrZ1D00cLbal">Internal (reference) links</a> system.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Notes can be dragged from outside the note, case in which they will be
|
||||
cloned into it.
|
||||
<ul>
|
||||
<li>Instead of switching to the child notes that were copied, the parent note
|
||||
is highlighted instead.</li>
|
||||
<li>A notification will indicate this behavior.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Similarly, features such as cut/copy and then paste into the note will
|
||||
also work.</li>
|
||||
</li>
|
||||
<li data-list-item-id="eb049f46cf91db6de113af1099a14944e">Notes can be dragged from outside the note, case in which they will be
|
||||
cloned into it.
|
||||
<ul>
|
||||
<li data-list-item-id="e96d9b7a0755e9c054bab5db4fc1aa25e">Instead of switching to the child notes that were copied, the parent note
|
||||
is highlighted instead.</li>
|
||||
<li data-list-item-id="ec667e3f94a0cfa3fa41ce38d3ed6ee95">A notification will indicate this behavior.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="eb64670dd7ace6764c18602b440f88049">Similarly, features such as cut/copy and then paste into the note will
|
||||
also work.</li>
|
||||
</ul>
|
||||
<h2>Spotlighting</h2>
|
||||
<figure class="image image-style-align-right">
|
||||
@@ -50,11 +52,12 @@
|
||||
<p>During this state, the note remains under its normal hierarchy, so that
|
||||
its easy to tell its location. In addition, this means that:</p>
|
||||
<ul>
|
||||
<li>The note position is clearly visible when using the <a class="reference-link"
|
||||
href="#root/_help_eIg8jdvaoNNd">Search</a>.</li>
|
||||
<li>The note can still be operated on from the tree, such as adding a
|
||||
<li data-list-item-id="e2490369eb3d99ca694dba23a3410abef">The note position is clearly visible when using the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>.</li>
|
||||
<li
|
||||
data-list-item-id="e041d3807f80dc77b022540b0551b8376">The note can still be operated on from the tree, such as adding a
|
||||
<a
|
||||
class="reference-link" href="#root/_help_TBwsyfadTA18">Branch prefix</a> or moving it outside the collection.</li>
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/IakOLONlIfGI/_help_TBwsyfadTA18">Branch prefix</a> or moving it outside the collection.</li>
|
||||
</ul>
|
||||
<p>The note appears in italics to indicate its temporary display. When switching
|
||||
to another note, the spotlighted note will disappear.</p>
|
||||
@@ -64,27 +67,29 @@
|
||||
This is intentional to avoid displaying a partial state of the subtree.</p>
|
||||
</aside>
|
||||
<h2>Working with collections</h2>
|
||||
<p>By default, some of the <a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a> will
|
||||
<p>By default, some of the <a class="reference-link" href="#root/pOsGYCXsbNQG/_help_GTwFsgaA0lCt">Collections</a> will
|
||||
automatically hide their child notes, for example the <a class="reference-link"
|
||||
href="#root/_help_CtBQqbwXDx1w">Kanban Board</a> or the <a class="reference-link"
|
||||
href="#root/_help_2FvYrpmOXm29">Table</a>.</p>
|
||||
href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_CtBQqbwXDx1w">Kanban Board</a> or
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_2FvYrpmOXm29">Table</a>.</p>
|
||||
<p>The reasoning behind this is that collections are generally opaque to
|
||||
the rest of the notes and they can generate a large amount of sub-notes
|
||||
since they intentionally lack structure (in order to allow easy swapping
|
||||
between views).</p>
|
||||
<p>Some types of collections have the child notes intentionally shown, for
|
||||
example the legacy ones (Grid and List), but also the <a class="reference-link"
|
||||
href="#root/_help_zP3PMqaG71Ct">Presentation</a> which requires the tree
|
||||
structure in order to organize and edit the slides.</p>
|
||||
href="#root/pOsGYCXsbNQG/GTwFsgaA0lCt/_help_zP3PMqaG71Ct">Presentation</a> which
|
||||
requires the tree structure in order to organize and edit the slides.</p>
|
||||
<p>To toggle this behavior:</p>
|
||||
<ul>
|
||||
<li>In the <a class="reference-link" href="#root/_help_IjZS7iK5EXtb">New Layout</a>,
|
||||
<li data-list-item-id="e6d8c8c98802d70f13df626ea1f062122">In the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_IjZS7iK5EXtb">New Layout</a>,
|
||||
press the Options button underneath the title and uncheck <em>Hide child notes in tree</em>.</li>
|
||||
<li>Right click the collection note in the <a class="reference-link"
|
||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> and select <em>Advanced</em> → <em>Show subtree</em>.</li>
|
||||
<li
|
||||
data-list-item-id="e2398432e127c54239d679a6b13d8390b">Right click the collection note in the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||
select <em>Advanced</em> → <em>Show subtree</em>.</li>
|
||||
</ul>
|
||||
<h2>Working with normal notes</h2>
|
||||
<p>It's possible to hide the subtree for normal notes as well, not just collections.
|
||||
To do so, right click the note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||
To do so, right click the note in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||
select <em>Advanced</em> → <em>Hide subtree.</em>
|
||||
</p>
|
||||
8
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
@@ -148,10 +148,10 @@
|
||||
<td>
|
||||
<p>Which view to display in the calendar:</p>
|
||||
<ul>
|
||||
<li><code>timeGridWeek</code> for the <em>week</em> view;</li>
|
||||
<li><code>dayGridMonth</code> for the <em>month</em> view;</li>
|
||||
<li><code>multiMonthYear</code> for the <em>year</em> view;</li>
|
||||
<li><code>listMonth</code> for the <em>list</em> view.</li>
|
||||
<li data-list-item-id="e2cd230dc41f41fe91ee74d7d1fa87372"><code>timeGridWeek</code> for the <em>week</em> view;</li>
|
||||
<li data-list-item-id="eee1dba4c6cc51ebd53d0a0dd52044cd6"><code>dayGridMonth</code> for the <em>month</em> view;</li>
|
||||
<li data-list-item-id="ed8721a76a1865dac882415f662ed45b9"><code>multiMonthYear</code> for the <em>year</em> view;</li>
|
||||
<li data-list-item-id="edf09a13759102d98dac34c33eb690c05"><code>listMonth</code> for the <em>list</em> view.</li>
|
||||
</ul>
|
||||
<p>Any other value will be dismissed and the default view (month) will be
|
||||
used instead.</p>
|
||||
|
||||
@@ -4,30 +4,17 @@
|
||||
<p>Trilium Web Clipper is a web browser extension which allows user to clip
|
||||
text, screenshots, whole pages and short notes and save them directly to
|
||||
Trilium Notes.</p>
|
||||
<h2>Supported browsers</h2>
|
||||
<p>Trilium Web Clipper officially supports the following web browsers:</p>
|
||||
<ul>
|
||||
<li>Mozilla Firefox, using Manifest v2.</li>
|
||||
<li>Google Chrome, using Manifest v3. Theoretically the extension should work
|
||||
on other Chromium-based browsers as well, but they are not officially supported.</li>
|
||||
</ul>
|
||||
<h2>Obtaining the extension</h2>
|
||||
<aside class="admonition warning">
|
||||
<p>The extension is currently under development. A preview with unsigned
|
||||
extensions is available on <a href="https://github.com/TriliumNext/Trilium/actions/runs/21318809414">GitHub Actions</a>.</p>
|
||||
<p>We have already submitted the extension to both Chrome and Firefox web
|
||||
stores, but they are pending validation.</p>
|
||||
</aside>
|
||||
<p>Project is hosted <a href="https://github.com/TriliumNext/web-clipper">here</a>.</p>
|
||||
<p>Firefox and Chrome are supported browsers, but the chrome build should
|
||||
work on other chromium based browsers as well.</p>
|
||||
<h2>Functionality</h2>
|
||||
<ul>
|
||||
<li>select text and clip it with the right-click context menu</li>
|
||||
<li>click on an image or link and save it through context menu</li>
|
||||
<li>save whole page from the popup or context menu</li>
|
||||
<li>save screenshot (with crop tool) from either popup or context menu</li>
|
||||
<li
|
||||
>create short text note from popup</li>
|
||||
<li>create short text note from popup</li>
|
||||
</ul>
|
||||
<h2>Location of clippings</h2>
|
||||
<p>Trilium will save these clippings as a new child note under a "clipper
|
||||
inbox" note.</p>
|
||||
<p>By default, that's the <a href="#root/_help_l0tKav7yLHGF">day note</a> but you
|
||||
@@ -36,76 +23,21 @@
|
||||
spellcheck="false">clipperInbox</code>, on any other note.</p>
|
||||
<p>If there's multiple clippings from the same page (and on the same day),
|
||||
then they will be added to the same note.</p>
|
||||
<h2>Keyboard shortcuts</h2>
|
||||
<p>Keyboard shortcuts are available for most functions:</p>
|
||||
<p><strong>Extension is available from:</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>Save selected text: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> (Mac: <kbd>⌘</kbd>+<kbd>⇧</kbd>+<kbd>S</kbd>)</li>
|
||||
<li
|
||||
>Save whole page: <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> (Mac: <kbd>⌥</kbd>+<kbd>⇧</kbd>+<kbd>S</kbd>)</li>
|
||||
<li
|
||||
>Save screenshot: <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>E</kbd> (Mac: <kbd>⌘</kbd>+<kbd>⇧</kbd>+<kbd>E</kbd>)</li>
|
||||
</ul>
|
||||
<p>To set custom shortcuts, follow the directions for your browser.</p>
|
||||
<ul>
|
||||
<li><strong>Firefox</strong>: <code spellcheck="false">about:addons</code> →
|
||||
Gear icon ⚙️ → Manage extension shortcuts</li>
|
||||
<li><strong>Chrome</strong>: <code spellcheck="false">chrome://extensions/shortcuts</code>
|
||||
<li><a href="https://github.com/TriliumNext/web-clipper/releases">Project release page</a> -
|
||||
.xpi for Firefox and .zip for Chromium based browsers.</li>
|
||||
<li><a href="https://chromewebstore.google.com/detail/trilium-web-clipper/dfhgmnfclbebfobmblelddiejjcijbjm">Chrome Web Store</a>
|
||||
</li>
|
||||
</ul>
|
||||
<aside class="admonition note">
|
||||
<p>On Firefox, the default shortcuts interfere with some browser features.
|
||||
As such, the keyboard combinations will not trigger the Web Clipper action.
|
||||
To fix this, simply change the keyboard shortcut to something that works.
|
||||
The defaults will be adjusted in future versions.</p>
|
||||
</aside>
|
||||
<h2>Configuration</h2>
|
||||
<p>The extension needs to connect to a running Trilium instance. By default,
|
||||
it scans a port range on the local computer to find a desktop Trilium instance.</p>
|
||||
<p>It's also possible to configure the <a href="#root/_help_WOcw2SLH6tbX">server</a> address
|
||||
if you don't run the desktop application, or want it to work without the
|
||||
desktop application running.</p>
|
||||
<h2>Testing development versions</h2>
|
||||
<p>Development versions are version pre-release versions, generally meant
|
||||
for testing purposes. These are not available in the Google or Firefox
|
||||
web stores, but can be downloaded from either:</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/TriliumNext/Trilium/releases">GitHub Releases</a> by
|
||||
looking for releases starting with <em>Web Clipper.</em>
|
||||
</li>
|
||||
<li>Artifacts in GitHub Actions, by looking for the <a href="https://github.com/TriliumNext/Trilium/actions/workflows/web-clipper.yml"><em>Deploy web clipper extension </em>workflow</a>.
|
||||
Once a workflow run is selected, the ZIP files are available in the <em>Artifacts</em> section,
|
||||
under the name <code spellcheck="false">web-clipper-extension</code>.</li>
|
||||
</ul>
|
||||
<h3>For Chrome</h3>
|
||||
<ol>
|
||||
<li>Download <code spellcheck="false">trilium-web-clipper-[x.y.z]-chrome.zip</code>.</li>
|
||||
<li
|
||||
>Extract the archive.</li>
|
||||
<li>In Chrome, navigate to <code spellcheck="false">chrome://extensions/</code>
|
||||
</li>
|
||||
<li>Toggle <em>Developer Mode</em> in top-right of the page.</li>
|
||||
<li>Press the <em>Load unpacked</em> button near the header.</li>
|
||||
<li>Point to the extracted directory from step (2).</li>
|
||||
</ol>
|
||||
<h3>For Firefox</h3>
|
||||
<aside class="admonition warning">
|
||||
<p>Firefox prevents installation of unsigned packages in the “retail” version.
|
||||
To be able to install extensions from disk, consider using <em>Firefox Developer Edition</em> or
|
||||
a non-branded version of Firefox (e.g. <em>GNU IceCat</em>).</p>
|
||||
<p>One time, go to <code spellcheck="false">about:config</code> and change
|
||||
<code
|
||||
spellcheck="false">xpinstall.signatures.required</code>to <code spellcheck="false">false</code>.</p>
|
||||
</aside>
|
||||
<ol>
|
||||
<li>Navigate to <code spellcheck="false">about:addons</code>.</li>
|
||||
<li>Select <em>Extensions</em> in the left-side navigation.</li>
|
||||
<li>Press the <em>Gear</em> icon on the right of the <em>Manage Your Extensions</em> title.</li>
|
||||
<li
|
||||
>Select <em>Install Add-on From File…</em>
|
||||
</li>
|
||||
<li>Point it to <code spellcheck="false">trilium-web-clipper-[x.y.z]-firefox.zip</code>.</li>
|
||||
<li
|
||||
>Press the <em>Add</em> button to confirm.</li>
|
||||
</ol>
|
||||
<h2>Credits</h2>
|
||||
<p>Some parts of the code are based on the <a href="https://github.com/laurent22/joplin/tree/master/Clipper">Joplin Notes browser extension</a>.</p>
|
||||
<h2>Username</h2>
|
||||
<p>Older versions of Trilium (before 0.50) required username & password
|
||||
to authenticate, but this is no longer the case. You may enter anything
|
||||
in that field, it will not have any effect.</p>
|
||||
68
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text.html
generated
vendored
@@ -33,12 +33,12 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Headings (section titles, paragraph)</li>
|
||||
<li>Font size</li>
|
||||
<li>Bold, italic, underline, strike-through</li>
|
||||
<li>Superscript, subscript</li>
|
||||
<li>Font color & background color</li>
|
||||
<li>Remove formatting</li>
|
||||
<li data-list-item-id="e04c84d59d44645ee89b2a8541ed99f90">Headings (section titles, paragraph)</li>
|
||||
<li data-list-item-id="e39d25bd3d8bd06185b9d259e5827d451">Font size</li>
|
||||
<li data-list-item-id="e1f7e2a2f4b03449d82bdf5b5c6ea8d44">Bold, italic, underline, strike-through</li>
|
||||
<li data-list-item-id="e3decae72884f65b4d538151b6a297072">Superscript, subscript</li>
|
||||
<li data-list-item-id="e59adf00fef65304c163ae190fac5e92a">Font color & background color</li>
|
||||
<li data-list-item-id="ed3f09156147a2769e91db111c76376e2">Remove formatting</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -47,9 +47,9 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Bulleted lists</li>
|
||||
<li>Numbered lists</li>
|
||||
<li>To-do lists</li>
|
||||
<li data-list-item-id="ee87806a913900d85d8f018af81f41df8">Bulleted lists</li>
|
||||
<li data-list-item-id="e3ae314e365fa418ca6e0f061d63834c5">Numbered lists</li>
|
||||
<li data-list-item-id="ee84e08694165f95430046cb34f4cd123">To-do lists</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -58,8 +58,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Block quotes</li>
|
||||
<li>Admonitions</li>
|
||||
<li data-list-item-id="e2892dc35a0d4b7ad65daffb8f9404daa">Block quotes</li>
|
||||
<li data-list-item-id="e7297e3ad1002f8de15aa0bd66c6f3f22">Admonitions</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -68,10 +68,10 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Basic tables</li>
|
||||
<li>Merging cells</li>
|
||||
<li>Styling tables and cells.</li>
|
||||
<li>Table captions</li>
|
||||
<li data-list-item-id="eb358a4567d93f66004f4195df2dda05a">Basic tables</li>
|
||||
<li data-list-item-id="e6135a555d6c63c30e4b84806a4870830">Merging cells</li>
|
||||
<li data-list-item-id="e29ac76563d0998b28fb1baf94dbdac8c">Styling tables and cells.</li>
|
||||
<li data-list-item-id="e372446e81fdedada64b8bed89ca93d1a">Table captions</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -80,9 +80,9 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Inline code</li>
|
||||
<li>Code blocks</li>
|
||||
<li>Keyboard shortcuts</li>
|
||||
<li data-list-item-id="eb260b76afcbc07bd9d4ceec4e000e8a0">Inline code</li>
|
||||
<li data-list-item-id="e9864352286369ebe7b41c1599f498de8">Code blocks</li>
|
||||
<li data-list-item-id="ee62fb9ed7f349178e8f2a2bd9ec8cd74">Keyboard shortcuts</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -91,7 +91,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Footnotes</li>
|
||||
<li data-list-item-id="edf62ec004eff35cfcb7e361deef19aaf">Footnotes</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -100,7 +100,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Images</li>
|
||||
<li data-list-item-id="ebe6277e643041403489c3ceb30c36f7f">Images</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -109,8 +109,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>External links</li>
|
||||
<li>Internal Trilium links</li>
|
||||
<li data-list-item-id="e3f988be2f259bb40607cb61541955395">External links</li>
|
||||
<li data-list-item-id="e3f91cc4f0cccd2c077cc306bacd68ef2">Internal Trilium links</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -119,7 +119,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Include note</li>
|
||||
<li data-list-item-id="eac8015a64bce7b749cc67d1599062007">Include note</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -128,12 +128,12 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Symbols</li>
|
||||
<li><a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a>
|
||||
<li data-list-item-id="e5cdf5d3885ec0ea67f924b4b8fe5c483">Symbols</li>
|
||||
<li data-list-item-id="e95082e6642ed5b1eec6e4e116b899a40"><a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a>
|
||||
</li>
|
||||
<li>Mermaid diagrams</li>
|
||||
<li>Horizontal ruler</li>
|
||||
<li>Page break</li>
|
||||
<li data-list-item-id="ecbef6a358a5b8d27f0d3e08bbc750aa9">Mermaid diagrams</li>
|
||||
<li data-list-item-id="e6e97ee14dd29b7ccf53227107e5dc72d">Horizontal ruler</li>
|
||||
<li data-list-item-id="e6198c7c535c249faec2e8906775f11de">Page break</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -142,12 +142,12 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Indentation
|
||||
<li data-list-item-id="e0c14456cb83d483b07ea432ef9d4728e">Indentation
|
||||
<ul>
|
||||
<li>Markdown import</li>
|
||||
<li data-list-item-id="e2029812c5e105c595590f70ee227631e">Markdown import</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference-link" href="#root/_help_2x0ZAX9ePtzV">Cut to subnote</a>
|
||||
<li data-list-item-id="ea1ee012286e05190c89c9f4e64cf2036"><a class="reference-link" href="#root/_help_2x0ZAX9ePtzV">Cut to subnote</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
@@ -157,11 +157,11 @@
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li><a class="reference-link" href="#root/_help_ZlN4nump6EbW">Slash Commands</a>
|
||||
<li data-list-item-id="e1ab173193a533ccf33dccfd0cb916f1f"><a class="reference-link" href="#root/_help_ZlN4nump6EbW">Slash Commands</a>
|
||||
</li>
|
||||
<li><a class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>
|
||||
<li data-list-item-id="e564b978c09fe5adf476b331b1e0640e3"><a class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>
|
||||
</li>
|
||||
<li><a class="reference-link" href="#root/_help_5wZallV2Qo1t">Format Painter</a>
|
||||
<li data-list-item-id="e756306c31d9beffbba3820b6d1b9bc61"><a class="reference-link" href="#root/_help_5wZallV2Qo1t">Format Painter</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
<td>
|
||||
<p>Defines on which events script should run. Possible values are:</p>
|
||||
<ul>
|
||||
<li><code>frontendStartup</code> - when Trilium frontend starts up (or is refreshed),
|
||||
<li data-list-item-id="e244b14e102cf1b0d4954e8fd455ea77b"><code>frontendStartup</code> - when Trilium frontend starts up (or is refreshed),
|
||||
but not on mobile.</li>
|
||||
<li><code>mobileStartup</code> - when Trilium frontend starts up (or is refreshed),
|
||||
<li data-list-item-id="ea8f8ca86e7b351dd86108848ccb9103a"><code>mobileStartup</code> - when Trilium frontend starts up (or is refreshed),
|
||||
on mobile.</li>
|
||||
<li><code>backendStartup</code> - when Trilium backend starts up</li>
|
||||
<li><code>hourly</code> - run once an hour. You can use additional label <code>runAtHour</code> to
|
||||
<li data-list-item-id="e658488cf1a0862603088ef384e41b8b6"><code>backendStartup</code> - when Trilium backend starts up</li>
|
||||
<li data-list-item-id="ef40ba992fc450d33a18ca4cb031eca66"><code>hourly</code> - run once an hour. You can use additional label <code>runAtHour</code> to
|
||||
specify at which hour, on the back-end.</li>
|
||||
<li><code>daily</code> - run once a day, on the back-end</li>
|
||||
<li data-list-item-id="e07458d4f55b6eb42468a5535b8425c5f"><code>daily</code> - run once a day, on the back-end</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||