mirror of
https://github.com/zadam/trilium.git
synced 2026-02-06 14:39:29 +01:00
Compare commits
380 Commits
fix/subtre
...
renovate/n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df4fe1f06f | ||
|
|
f0b30c5e91 | ||
|
|
73e3196124 | ||
|
|
8a7bcc316e | ||
|
|
a2921cb982 | ||
|
|
29ce004974 | ||
|
|
026ba5ddce | ||
|
|
ab0585609a | ||
|
|
14a8bdb0c0 | ||
|
|
397d04dd88 | ||
|
|
fbb0bb7491 | ||
|
|
ee987dae99 | ||
|
|
720281a8db | ||
|
|
ff0c89e5a3 | ||
|
|
442937f540 | ||
|
|
dc01b787c1 | ||
|
|
c709b5d34c | ||
|
|
f8b386e42d | ||
|
|
a82f8ce3ad | ||
|
|
529d45b762 | ||
|
|
d31135bf21 | ||
|
|
75a77acefe | ||
|
|
199bfc8a37 | ||
|
|
e82ae762f0 | ||
|
|
f90cc9aff7 | ||
|
|
ec915177ad | ||
|
|
5adee3e217 | ||
|
|
278d82645e | ||
|
|
e66f13b471 | ||
|
|
0e2955b57e | ||
|
|
2a4d5ec1ec | ||
|
|
07ce63de69 | ||
|
|
b539862eef | ||
|
|
ac4be3f8a8 | ||
|
|
0dc2d07b58 | ||
|
|
b097f9dc21 | ||
|
|
8aa4a97480 | ||
|
|
3e54d0ceae | ||
|
|
e1cec3404a | ||
|
|
9a2b7fbda1 | ||
|
|
e0b4ebed93 | ||
|
|
e00e3999c5 | ||
|
|
2cbe96d815 | ||
|
|
ac3b289c9e | ||
|
|
62534e0e93 | ||
|
|
b802c3174c | ||
|
|
aee1a6e1f0 | ||
|
|
46f1cd38e0 | ||
|
|
7f891ef523 | ||
|
|
06af5e15cd | ||
|
|
af89a0a883 | ||
|
|
fa72eb2edb | ||
|
|
c1ea94423b | ||
|
|
1e70d066bd | ||
|
|
ab89f16e7c | ||
|
|
8c848a4cb5 | ||
|
|
e021a54d2d | ||
|
|
70523574b0 | ||
|
|
66e0f1ab19 | ||
|
|
671a05470e | ||
|
|
99eec0c41e | ||
|
|
48d06dcb06 | ||
|
|
e38df0c731 | ||
|
|
0f3f49915e | ||
|
|
416144265b | ||
|
|
2e7ced8e60 | ||
|
|
fe02871e91 | ||
|
|
bb05aeeaf7 | ||
|
|
846358ccb0 | ||
|
|
dabc779727 | ||
|
|
08fd2ec64b | ||
|
|
c42c06d048 | ||
|
|
e951d60800 | ||
|
|
0d8453f6a7 | ||
|
|
52b41b1bb0 | ||
|
|
c7265017b3 | ||
|
|
634e0b6d30 | ||
|
|
1c260f5890 | ||
|
|
177aedeaae | ||
|
|
3f6f3d2565 | ||
|
|
38489dbfeb | ||
|
|
ff2a3d6a28 | ||
|
|
2c74697fb1 | ||
|
|
110e9200b9 | ||
|
|
6e792f9735 | ||
|
|
51d8b13a81 | ||
|
|
a05691fd07 | ||
|
|
d25e7915d9 | ||
|
|
1a025dfef3 | ||
|
|
84cc4194aa | ||
|
|
331a56277c | ||
|
|
bc2915adb9 | ||
|
|
703fe9a71b | ||
|
|
6c50664046 | ||
|
|
673cbc97e1 | ||
|
|
3e83766099 | ||
|
|
b453589077 | ||
|
|
654fa18ab1 | ||
|
|
7b0d91534c | ||
|
|
8d4801bb6f | ||
|
|
9e36f4f625 | ||
|
|
d83a824812 | ||
|
|
ca128f2fa9 | ||
|
|
c02642d0f9 | ||
|
|
7340709111 | ||
|
|
56d0383372 | ||
|
|
49d33ea19a | ||
|
|
979fa0359a | ||
|
|
c7381d058a | ||
|
|
5db298f031 | ||
|
|
171d948a00 | ||
|
|
facd56cdf4 | ||
|
|
ba17ec4be4 | ||
|
|
6c163b5479 | ||
|
|
79649805b8 | ||
|
|
220ca8a570 | ||
|
|
348c00f86d | ||
|
|
12b641b522 | ||
|
|
6855bc1de6 | ||
|
|
a9c5b99ae8 | ||
|
|
76f36e2fd3 | ||
|
|
afe710321c | ||
|
|
0d444daaca | ||
|
|
c8a0c9fd23 | ||
|
|
79f07ae923 | ||
|
|
c77e7a568b | ||
|
|
ff9ec2057b | ||
|
|
6a313b99e4 | ||
|
|
8a92370042 | ||
|
|
411a59ec54 | ||
|
|
2d4022044d | ||
|
|
bbc5ebd76b | ||
|
|
e9c90fcde8 | ||
|
|
911f78867f | ||
|
|
5507cc5abc | ||
|
|
0e5aa401ef | ||
|
|
d48473ab87 | ||
|
|
734efaf40c | ||
|
|
f89718d88a | ||
|
|
aa4942a0da | ||
|
|
90bb162a88 | ||
|
|
0382a4b30e | ||
|
|
490d940cd1 | ||
|
|
b090eb9359 | ||
|
|
cb9e67ce84 | ||
|
|
c4d131dd23 | ||
|
|
2667f266bf | ||
|
|
8258936d6c | ||
|
|
e4e7449078 | ||
|
|
11b020e859 | ||
|
|
72d6b83ec5 | ||
|
|
fbe5152cb3 | ||
|
|
6bef01f755 | ||
|
|
f0d4b4a6d9 | ||
|
|
a54fe62643 | ||
|
|
eb4bbd49fb | ||
|
|
ab4f1bd4f4 | ||
|
|
f8b414c354 | ||
|
|
82a21624c3 | ||
|
|
1b212ac720 | ||
|
|
c36ce3ea14 | ||
|
|
841fab77a8 | ||
|
|
fd6f910824 | ||
|
|
ce9ca1917d | ||
|
|
c702fb273c | ||
|
|
4c72d8691b | ||
|
|
35ac5fc514 | ||
|
|
92991cc03c | ||
|
|
76492475e3 | ||
|
|
e2363d860c | ||
|
|
c06a90913a | ||
|
|
e88c0f7326 | ||
|
|
2a4280b5bf | ||
|
|
d3c733c57f | ||
|
|
f91add3cd4 | ||
|
|
90e3f7508a | ||
|
|
5e981da4df | ||
|
|
2ddd5d75fc | ||
|
|
88f509cbb6 | ||
|
|
ca161bc881 | ||
|
|
676ca44cdf | ||
|
|
5d0c91202f | ||
|
|
a166f049d5 | ||
|
|
0dc4692dfc | ||
|
|
996607d096 | ||
|
|
bd907ea008 | ||
|
|
b1573b1f3b | ||
|
|
246849ce94 | ||
|
|
2c2c68261a | ||
|
|
e38a0361bf | ||
|
|
cbf879bd32 | ||
|
|
808625e564 | ||
|
|
c3b4c2f7d4 | ||
|
|
14f521fdd7 | ||
|
|
087831df5a | ||
|
|
6b0542a5bf | ||
|
|
ac57856f00 | ||
|
|
2ab1587df0 | ||
|
|
632aa6e003 | ||
|
|
9142f2df4b | ||
|
|
eea4cbbd6c | ||
|
|
bb31c20282 | ||
|
|
f8ab206744 | ||
|
|
4129b3a96e | ||
|
|
0de704bd3e | ||
|
|
5f20ce87a7 | ||
|
|
ff80154fda | ||
|
|
0d99cf9fb9 | ||
|
|
a5306b2067 | ||
|
|
e9b826e498 | ||
|
|
e7f356b87c | ||
|
|
d2abde714f | ||
|
|
20eaa79079 | ||
|
|
3cb74bb844 | ||
|
|
a72d4f425a | ||
|
|
8f3545624e | ||
|
|
8f6cfe8a04 | ||
|
|
bec7943e05 | ||
|
|
a486f5951e | ||
|
|
e8158aadec | ||
|
|
b6f107b85b | ||
|
|
5abd27f252 | ||
|
|
39648b6df8 | ||
|
|
0a7b2e3304 | ||
|
|
48db6e1756 | ||
|
|
2a38af5db6 | ||
|
|
740b1093d7 | ||
|
|
6b70412f6e | ||
|
|
43f147ec60 | ||
|
|
bf0fc57493 | ||
|
|
325b8b886c | ||
|
|
a02bbdc550 | ||
|
|
4285fd7708 | ||
|
|
96fa6eac44 | ||
|
|
05fa1ef2fb | ||
|
|
13aebc060e | ||
|
|
1aae4098d6 | ||
|
|
3367bb2e5b | ||
|
|
49fc5e1559 | ||
|
|
3a9b448a83 | ||
|
|
a45fb975c0 | ||
|
|
e070fc2f52 | ||
|
|
5f8aac31e1 | ||
|
|
07f2e2eafc | ||
|
|
2b41fb7108 | ||
|
|
81c18f1869 | ||
|
|
61fd2fe87d | ||
|
|
054cb974f1 | ||
|
|
50a19ecd74 | ||
|
|
1232909a3b | ||
|
|
9ba0e076a6 | ||
|
|
ede91c645d | ||
|
|
b6a91723e7 | ||
|
|
fddd73fdb1 | ||
|
|
ffbe8f9dc4 | ||
|
|
f80763ffb4 | ||
|
|
f65aa1b875 | ||
|
|
0c4de9a5e0 | ||
|
|
1822970e23 | ||
|
|
80615d0382 | ||
|
|
c1002ed52a | ||
|
|
83e585ed35 | ||
|
|
0e164b9daa | ||
|
|
1c9f8a2540 | ||
|
|
88ba4451eb | ||
|
|
305f195539 | ||
|
|
fbc6b853ed | ||
|
|
f3bcab813a | ||
|
|
2fa9b2e4dd | ||
|
|
fc756ba46e | ||
|
|
85a82124b1 | ||
|
|
b5cfbc92af | ||
|
|
e4f260e242 | ||
|
|
7c6c0c1fbd | ||
|
|
2f5f4dbebd | ||
|
|
94adc6af95 | ||
|
|
e3ea703f3d | ||
|
|
a14753e5f3 | ||
|
|
2ffd55c2d8 | ||
|
|
ad0ee8da32 | ||
|
|
e5cfe37b17 | ||
|
|
9247dd8b17 | ||
|
|
2f69b1d6e1 | ||
|
|
9c43930e7b | ||
|
|
f18ecfa524 | ||
|
|
b6f7453ed9 | ||
|
|
4a0b77dabb | ||
|
|
de7687b3ab | ||
|
|
85f2ec9d92 | ||
|
|
ed6b8dfc9b | ||
|
|
1539a6eabc | ||
|
|
6424d96703 | ||
|
|
15f6ac8e89 | ||
|
|
f9803a4694 | ||
|
|
13de9975e3 | ||
|
|
a64c4ef66f | ||
|
|
36e85eb3a7 | ||
|
|
20ffe7e082 | ||
|
|
ea1dc58b9a | ||
|
|
cd292ad605 | ||
|
|
03d9a6c0e5 | ||
|
|
fa54a2e67c | ||
|
|
7f2530470d | ||
|
|
a284934136 | ||
|
|
2ca6606508 | ||
|
|
bb1c691b34 | ||
|
|
19d3e1b11c | ||
|
|
e35e64caaa | ||
|
|
a3cf72c76c | ||
|
|
710e95bdee | ||
|
|
d281fb7065 | ||
|
|
e669d5041b | ||
|
|
d8d91451c8 | ||
|
|
b0910baaf0 | ||
|
|
ce3f70adc3 | ||
|
|
c0d5c26f0c | ||
|
|
1bf8a76cc3 | ||
|
|
7b1f74a413 | ||
|
|
3d2fde77a5 | ||
|
|
e4cc3bef73 | ||
|
|
f384c422c4 | ||
|
|
a373d2e7e0 | ||
|
|
1aaf630979 | ||
|
|
b7ac3aba72 | ||
|
|
ed89250624 | ||
|
|
d9a7f0c7fe | ||
|
|
a07405bec3 | ||
|
|
4c6efeb0d8 | ||
|
|
501b380d5e | ||
|
|
ddc4e34dcd | ||
|
|
d668d9f24d | ||
|
|
0ec4423ad4 | ||
|
|
51313ff0d5 | ||
|
|
37381b7c36 | ||
|
|
6de632d117 | ||
|
|
4a8fa7293b | ||
|
|
b75a2e9592 | ||
|
|
c45c1b0f93 | ||
|
|
af7057f062 | ||
|
|
85bf1eb4ec | ||
|
|
bd45043a36 | ||
|
|
fbb41168a2 | ||
|
|
674fe4fa20 | ||
|
|
4db86f9322 | ||
|
|
5c814155d2 | ||
|
|
c08fb9af16 | ||
|
|
1dac4fea9c | ||
|
|
ac19000ad0 | ||
|
|
221182389a | ||
|
|
5d5947f676 | ||
|
|
8fc889ae08 | ||
|
|
ff46493775 | ||
|
|
a1cb3b8371 | ||
|
|
51131433d3 | ||
|
|
eaccd641ed | ||
|
|
3e2b647f06 | ||
|
|
0fbf9bafbc | ||
|
|
924a5e3110 | ||
|
|
be71a4b5c4 | ||
|
|
1cb5a13ea4 | ||
|
|
e145cd80a9 | ||
|
|
58ea661d4b | ||
|
|
ba317eff3f | ||
|
|
ead0e14118 | ||
|
|
d3dd20b50f | ||
|
|
d621fb4105 | ||
|
|
a239604dad | ||
|
|
f7986b9049 | ||
|
|
0a34ca031a | ||
|
|
ce63fec413 | ||
|
|
719451bf23 | ||
|
|
10a27cbe86 | ||
|
|
3c8a066f76 | ||
|
|
6856a98d50 | ||
|
|
120b767a68 | ||
|
|
8c0d4cde86 | ||
|
|
280697f2f7 | ||
|
|
0650be664d | ||
|
|
60c61f553a | ||
|
|
022c967781 |
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check if PRs have conflicts
|
||||
uses: eps1lon/actions-label-merge-conflict@v3
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
if: ${{ github.repository == vars.REPO_MAIN }}
|
||||
with:
|
||||
dirtyLabel: "merge-conflicts"
|
||||
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"
|
||||
|
||||
2
.github/workflows/deploy-docs.yml
vendored
2
.github/workflows/deploy-docs.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
- name: Deploy
|
||||
uses: ./.github/actions/deploy-to-cloudflare-pages
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
if: ${{ github.repository == vars.REPO_MAIN }}
|
||||
with:
|
||||
project_name: "trilium-docs"
|
||||
comment_body: "📚 Documentation preview is ready"
|
||||
|
||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -26,7 +26,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
nightly-electron:
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
if: ${{ github.repository == vars.REPO_MAIN }}
|
||||
name: Deploy nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
path: apps/desktop/upload
|
||||
|
||||
nightly-server:
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
if: ${{ github.repository == vars.REPO_MAIN }}
|
||||
name: Deploy server nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -42,5 +42,8 @@
|
||||
},
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "*", "severity": "warn" }
|
||||
],
|
||||
"cSpell.words": [
|
||||
"Trilium"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
"keywords": [],
|
||||
"author": "Elian Doran <contact@eliandoran.me>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.28.1",
|
||||
"packageManager": "pnpm@10.28.2",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.14.9",
|
||||
"@redocly/cli": "2.15.1",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.3",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"typedoc": "0.28.16",
|
||||
"typedoc-plugin-missing-exports": "4.1.2"
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
<body id="trilium-app">
|
||||
<noscript>Trilium requires JavaScript to be enabled.</noscript>
|
||||
|
||||
<div id="context-menu-cover"></div>
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
||||
|
||||
<!-- Required to match the PWA's top bar color with the theme -->
|
||||
<!-- This works even when the user directly changes --root-background in CSS -->
|
||||
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>
|
||||
|
||||
<script src="./index.ts" type="module"></script>
|
||||
<script src="./src/index.ts" type="module"></script>
|
||||
|
||||
<!-- Required for correct loading of scripts in Electron -->
|
||||
<script>
|
||||
@@ -27,7 +27,7 @@
|
||||
"@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.6.2",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/codemirror": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
@@ -43,7 +43,7 @@
|
||||
"debounce": "3.0.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"globals": "17.0.0",
|
||||
"globals": "17.3.0",
|
||||
"i18next": "25.8.0",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "4.0.0",
|
||||
@@ -56,12 +56,12 @@
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"mind-elixir": "5.6.1",
|
||||
"mind-elixir": "5.7.1",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.28.2",
|
||||
"react-i18next": "16.5.3",
|
||||
"react-window": "2.2.5",
|
||||
"preact": "10.28.3",
|
||||
"react-i18next": "16.5.4",
|
||||
"react-window": "2.2.6",
|
||||
"reveal.js": "5.2.1",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
@@ -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",
|
||||
"happy-dom": "20.5.0",
|
||||
"lightningcss": "1.31.1",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.5"
|
||||
"vite-plugin-static-copy": "3.2.0"
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,6 @@ if (utils.isElectron()) {
|
||||
electronContextMenu.setupContextMenu();
|
||||
}
|
||||
|
||||
if (utils.isPWA()) {
|
||||
initPWATopbarColor();
|
||||
}
|
||||
|
||||
function initOnElectron() {
|
||||
const electron: typeof Electron = utils.dynamicRequire("electron");
|
||||
electron.ipcRenderer.on("globalShortcut", async (event, actionName) => appContext.triggerCommand(actionName));
|
||||
@@ -99,15 +95,22 @@ function initFullScreenDetection(currentWindow: Electron.BrowserWindow) {
|
||||
}
|
||||
|
||||
function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {
|
||||
const material = style.getPropertyValue("--background-material").trim();
|
||||
if (window.glob.platform === "win32") {
|
||||
const material = style.getPropertyValue("--background-material");
|
||||
// TriliumNextTODO: find a nicer way to make TypeScript happy – unfortunately TS did not like Array.includes here
|
||||
const bgMaterialOptions = ["auto", "none", "mica", "acrylic", "tabbed"] as const;
|
||||
const foundBgMaterialOption = bgMaterialOptions.find((bgMaterialOption) => material === bgMaterialOption);
|
||||
if (foundBgMaterialOption) {
|
||||
currentWindow.setBackgroundMaterial(foundBgMaterialOption);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.glob.platform === "darwin") {
|
||||
const bgMaterialOptions = [ "popover", "tooltip", "titlebar", "selection", "menu", "sidebar", "header", "sheet", "window", "hud", "fullscreen-ui", "content", "under-window", "under-page" ] as const;
|
||||
const foundBgMaterialOption = bgMaterialOptions.find((bgMaterialOption) => material === bgMaterialOption);
|
||||
if (foundBgMaterialOption) {
|
||||
currentWindow.setVibrancy(foundBgMaterialOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,20 +130,3 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) {
|
||||
const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote;
|
||||
nativeTheme.themeSource = themeSource;
|
||||
}
|
||||
|
||||
function initPWATopbarColor() {
|
||||
const tracker = $("#background-color-tracker");
|
||||
|
||||
if (tracker.length) {
|
||||
const applyThemeColor = () => {
|
||||
let meta = $("meta[name='theme-color']");
|
||||
if (!meta.length) {
|
||||
meta = $(`<meta name="theme-color">`).appendTo($("head"));
|
||||
}
|
||||
meta.attr("content", tracker.css("color"));
|
||||
};
|
||||
|
||||
tracker.on("transitionend", applyThemeColor);
|
||||
applyThemeColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ async function initJQuery() {
|
||||
}
|
||||
|
||||
async function setupGlob() {
|
||||
const response = await fetch(`/bootstrap${window.location.search}`);
|
||||
const response = await fetch(`./bootstrap${window.location.search}`);
|
||||
const json = await response.json();
|
||||
|
||||
window.global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
|
||||
|
||||
76
apps/client/src/layouts/mobile_layout.css
Normal file
76
apps/client/src/layouts/mobile_layout.css
Normal file
@@ -0,0 +1,76 @@
|
||||
#background-color-tracker {
|
||||
color: var(--main-background-color) !important;
|
||||
}
|
||||
|
||||
span.keyboard-shortcut,
|
||||
kbd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.25em;
|
||||
padding-inline-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
.quick-search {
|
||||
margin: 0;
|
||||
}
|
||||
.quick-search .dropdown-menu {
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
/* #region Tree */
|
||||
.tree-wrapper {
|
||||
max-height: 100%;
|
||||
margin-top: 0px;
|
||||
overflow-y: auto;
|
||||
contain: content;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.fancytree-title {
|
||||
margin-inline-start: 0.6em !important;
|
||||
}
|
||||
|
||||
.fancytree-node {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-expander {
|
||||
width: 24px !important;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander {
|
||||
width: 24px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander:after {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 4px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tree-wrapper .collapse-tree-button,
|
||||
.tree-wrapper .scroll-to-active-note-button,
|
||||
.tree-wrapper .tree-settings-button {
|
||||
position: fixed;
|
||||
margin-inline-end: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-wrapper .unhoist-button {
|
||||
font-size: 200%;
|
||||
}
|
||||
/* #endregion */
|
||||
@@ -1,128 +1,41 @@
|
||||
import "./mobile_layout.css";
|
||||
|
||||
import type AppContext from "../components/app_context.js";
|
||||
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
|
||||
import CloseZenModeButton from "../widgets/close_zen_button.js";
|
||||
import NoteList from "../widgets/collections/NoteList.jsx";
|
||||
import ContentHeader from "../widgets/containers/content_header.js";
|
||||
import FlexContainer from "../widgets/containers/flex_container.js";
|
||||
import RootContainer from "../widgets/containers/root_container.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
|
||||
import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
||||
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||
import FindWidget from "../widgets/find.js";
|
||||
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
|
||||
import InlineTitle from "../widgets/layout/InlineTitle.jsx";
|
||||
import NoteBadges from "../widgets/layout/NoteBadges.jsx";
|
||||
import NoteTitleActions from "../widgets/layout/NoteTitleActions.jsx";
|
||||
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
|
||||
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
|
||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
||||
import NoteTitleWidget from "../widgets/note_title.js";
|
||||
import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import { useNoteContext } from "../widgets/react/hooks.jsx";
|
||||
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
|
||||
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
|
||||
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
|
||||
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import SharedInfoWidget from "../widgets/shared_info.js";
|
||||
import TabRowWidget from "../widgets/tab_row.js";
|
||||
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
|
||||
import { applyModals } from "./layout_commons.js";
|
||||
|
||||
const MOBILE_CSS = `
|
||||
<style>
|
||||
span.keyboard-shortcut,
|
||||
kbd {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.25em;
|
||||
padding-inline-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
.quick-search {
|
||||
margin: 0;
|
||||
}
|
||||
.quick-search .dropdown-menu {
|
||||
max-width: 350px;
|
||||
}
|
||||
</style>`;
|
||||
|
||||
const FANCYTREE_CSS = `
|
||||
<style>
|
||||
.tree-wrapper {
|
||||
max-height: 100%;
|
||||
margin-top: 0px;
|
||||
overflow-y: auto;
|
||||
contain: content;
|
||||
padding-inline-start: 10px;
|
||||
}
|
||||
|
||||
.fancytree-custom-icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.fancytree-title {
|
||||
font-size: 1.5em;
|
||||
margin-inline-start: 0.6em !important;
|
||||
}
|
||||
|
||||
.fancytree-node {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.fancytree-node .fancytree-expander:before {
|
||||
font-size: 2em !important;
|
||||
}
|
||||
|
||||
span.fancytree-expander {
|
||||
width: 24px !important;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander {
|
||||
width: 24px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.fancytree-loading span.fancytree-expander:after {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 4px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tree-wrapper .collapse-tree-button,
|
||||
.tree-wrapper .scroll-to-active-note-button,
|
||||
.tree-wrapper .tree-settings-button {
|
||||
position: fixed;
|
||||
margin-inline-end: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-wrapper .unhoist-button {
|
||||
font-size: 200%;
|
||||
}
|
||||
</style>`;
|
||||
|
||||
export default class MobileLayout {
|
||||
getRootWidget(appContext: typeof AppContext) {
|
||||
const rootContainer = new RootContainer(true)
|
||||
.setParent(appContext)
|
||||
.class("horizontal-layout")
|
||||
.cssBlock(MOBILE_CSS)
|
||||
.child(new FlexContainer("column").id("mobile-sidebar-container"))
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
@@ -136,7 +49,7 @@ export default class MobileLayout {
|
||||
.css("padding-inline-start", "0")
|
||||
.css("padding-inline-end", "0")
|
||||
.css("contain", "content")
|
||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget()))
|
||||
)
|
||||
.child(
|
||||
new ScreenContainer("detail", "row")
|
||||
@@ -147,30 +60,28 @@ export default class MobileLayout {
|
||||
new NoteWrapperWidget()
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
.class("title-row note-split-title")
|
||||
.contentSized()
|
||||
.css("font-size", "larger")
|
||||
.css("align-items", "center")
|
||||
.child(<ToggleSidebarButton />)
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(<NoteBadges />)
|
||||
.child(<MobileDetailMenu />)
|
||||
)
|
||||
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
||||
.child(<PromotedAttributes />)
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
.filling()
|
||||
.contentSized()
|
||||
.child(new ContentHeader()
|
||||
.child(<ReadOnlyNoteInfoBar />)
|
||||
.child(<SharedInfoWidget />)
|
||||
)
|
||||
.child(<InlineTitle />)
|
||||
.child(<NoteTitleActions />)
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" />)
|
||||
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />)
|
||||
.child(<SearchResult />)
|
||||
.child(<FilePropertiesWrapper />)
|
||||
)
|
||||
.child(<MobileEditorToolbar />)
|
||||
.child(new FindWidget())
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -179,7 +90,6 @@ export default class MobileLayout {
|
||||
new FlexContainer("column")
|
||||
.contentSized()
|
||||
.id("mobile-bottom-bar")
|
||||
.child(new TabRowWidget().css("height", "40px"))
|
||||
.child(new FlexContainer("row")
|
||||
.class("horizontal")
|
||||
.css("height", "53px")
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { KeyboardActionNames } from "@triliumnext/commons";
|
||||
import { h, JSX, render } from "preact";
|
||||
|
||||
import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js";
|
||||
import note_tooltip from "../services/note_tooltip.js";
|
||||
import utils from "../services/utils.js";
|
||||
import { h, JSX, render } from "preact";
|
||||
|
||||
export interface ContextMenuOptions<T> {
|
||||
x: number;
|
||||
@@ -62,17 +63,17 @@ export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEve
|
||||
|
||||
class ContextMenu {
|
||||
private $widget: JQuery<HTMLElement>;
|
||||
private $cover: JQuery<HTMLElement>;
|
||||
private $cover?: JQuery<HTMLElement>;
|
||||
private options?: ContextMenuOptions<any>;
|
||||
private isMobile: boolean;
|
||||
|
||||
constructor() {
|
||||
this.$widget = $("#context-menu-container");
|
||||
this.$cover = $("#context-menu-cover");
|
||||
this.$widget.addClass("dropend");
|
||||
this.isMobile = utils.isMobile();
|
||||
|
||||
if (this.isMobile) {
|
||||
this.$cover = $("#context-menu-cover");
|
||||
this.$cover.on("click", () => this.hide());
|
||||
} else {
|
||||
$(document).on("click", (e) => this.hide());
|
||||
@@ -91,7 +92,7 @@ class ContextMenu {
|
||||
}
|
||||
|
||||
this.$widget.toggleClass("mobile-bottom-menu", !this.options.forcePositionOnMobile);
|
||||
this.$cover.addClass("show");
|
||||
this.$cover?.addClass("show");
|
||||
$("body").addClass("context-menu-shown");
|
||||
|
||||
this.$widget.empty();
|
||||
@@ -140,16 +141,14 @@ class ContextMenu {
|
||||
} else {
|
||||
left = this.options.x - contextMenuWidth + CONTEXT_MENU_OFFSET;
|
||||
}
|
||||
} else if (contextMenuWidth && this.options.x + contextMenuWidth - CONTEXT_MENU_OFFSET > clientWidth - CONTEXT_MENU_PADDING) {
|
||||
// Overflow: right
|
||||
left = clientWidth - contextMenuWidth - CONTEXT_MENU_PADDING;
|
||||
} else if (this.options.x - CONTEXT_MENU_OFFSET < CONTEXT_MENU_PADDING) {
|
||||
// Overflow: left
|
||||
left = CONTEXT_MENU_PADDING;
|
||||
} else {
|
||||
if (contextMenuWidth && this.options.x + contextMenuWidth - CONTEXT_MENU_OFFSET > clientWidth - CONTEXT_MENU_PADDING) {
|
||||
// Overflow: right
|
||||
left = clientWidth - contextMenuWidth - CONTEXT_MENU_PADDING;
|
||||
} else if (this.options.x - CONTEXT_MENU_OFFSET < CONTEXT_MENU_PADDING) {
|
||||
// Overflow: left
|
||||
left = CONTEXT_MENU_PADDING;
|
||||
} else {
|
||||
left = this.options.x - CONTEXT_MENU_OFFSET;
|
||||
}
|
||||
left = this.options.x - CONTEXT_MENU_OFFSET;
|
||||
}
|
||||
|
||||
this.$widget
|
||||
@@ -249,7 +248,7 @@ class ContextMenu {
|
||||
if ("uiIcon" in item || "checked" in item) {
|
||||
const icon = (item.checked ? "bx bx-check" : item.uiIcon);
|
||||
if (icon) {
|
||||
$icon.addClass(icon);
|
||||
$icon.addClass([icon, "tn-icon"]);
|
||||
} else {
|
||||
$icon.append(" ");
|
||||
}
|
||||
@@ -261,7 +260,7 @@ class ContextMenu {
|
||||
.append(item.title);
|
||||
|
||||
if ("badges" in item && item.badges) {
|
||||
for (let badge of item.badges) {
|
||||
for (const badge of item.badges) {
|
||||
const badgeElement = $(`<span class="badge">`).text(badge.title);
|
||||
|
||||
if (badge.className) {
|
||||
@@ -352,7 +351,7 @@ class ContextMenu {
|
||||
async hide() {
|
||||
this.options?.onHide?.();
|
||||
this.$widget.removeClass("show");
|
||||
this.$cover.removeClass("show");
|
||||
this.$cover?.removeClass("show");
|
||||
$("body").removeClass("context-menu-shown");
|
||||
this.$widget.hide();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import treeService from "../services/tree.js";
|
||||
import froca from "../services/froca.js";
|
||||
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import server from "../services/server.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type { ContextMenuCommandData,FilteredCommandNames } from "../components/app_context.js";
|
||||
import type { SelectMenuItemEventListener } from "../components/events.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import server from "../services/server.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import type { FilteredCommandNames, ContextMenuCommandData } from "../components/app_context.js";
|
||||
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
|
||||
|
||||
type LauncherCommandNames = FilteredCommandNames<ContextMenuCommandData>;
|
||||
|
||||
@@ -32,8 +32,8 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener<
|
||||
const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null;
|
||||
const parentNoteId = this.node.getParent().data.noteId;
|
||||
|
||||
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers";
|
||||
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers";
|
||||
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers" || note?.noteId === "_lbMobileVisibleLaunchers";
|
||||
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers" || note?.noteId === "_lbMobileAvailableLaunchers";
|
||||
const isVisibleItem = parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers";
|
||||
const isAvailableItem = parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers";
|
||||
const isItem = isVisibleItem || isAvailableItem;
|
||||
|
||||
9
apps/client/src/services/content_renderer.css
Normal file
9
apps/client/src/services/content_renderer.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.rendered-content.no-preview > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
font-size: 500%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./content_renderer.css";
|
||||
|
||||
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||
import WheelZoom from 'vanilla-js-wheel-zoom';
|
||||
|
||||
@@ -71,18 +73,9 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
|
||||
|
||||
$renderedContent.append($("<div>").append("<div>This note is protected and to access it you need to enter password.</div>").append("<br/>").append($button));
|
||||
} else if (entity instanceof FNote) {
|
||||
$renderedContent
|
||||
.css("display", "flex")
|
||||
.css("flex-direction", "column");
|
||||
$renderedContent.addClass("no-preview");
|
||||
$renderedContent.append(
|
||||
$("<div>")
|
||||
.css("display", "flex")
|
||||
.css("justify-content", "space-around")
|
||||
.css("align-items", "center")
|
||||
.css("height", "100%")
|
||||
.css("font-size", "500%")
|
||||
.css("flex-grow", "1")
|
||||
.append($("<span>").addClass(entity.getIcon()))
|
||||
$("<div>").append($("<span>").addClass(entity.getIcon()))
|
||||
);
|
||||
|
||||
if (entity.type === "webView" && entity.hasLabel("webViewSrc")) {
|
||||
|
||||
@@ -49,7 +49,7 @@ function createClassForColor(colorString: string | null) {
|
||||
return clsx("use-note-color", className, colorsWithHue.has(className) && "with-hue");
|
||||
}
|
||||
|
||||
function parseColor(color: string) {
|
||||
export function parseColor(color: string) {
|
||||
try {
|
||||
return Color(color.toLowerCase());
|
||||
} catch (ex) {
|
||||
@@ -77,7 +77,7 @@ function adjustColorLightness(color: ColorInstance, lightThemeMaxLightness: numb
|
||||
}
|
||||
|
||||
/** Returns the hue of the specified color, or undefined if the color is grayscale. */
|
||||
function getHue(color: ColorInstance) {
|
||||
export function getHue(color: ColorInstance) {
|
||||
const hslColor = color.hsl();
|
||||
if (hslColor.saturationl() > 0) {
|
||||
return hslColor.hue();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { t } from "./i18n";
|
||||
import options from "./options";
|
||||
import { isMobile } from "./utils";
|
||||
|
||||
export interface ExperimentalFeature {
|
||||
id: string;
|
||||
@@ -21,7 +22,7 @@ let enabledFeatures: Set<ExperimentalFeatureId> | null = null;
|
||||
|
||||
export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): boolean {
|
||||
if (featureId === "new-layout") {
|
||||
return options.is("newLayout");
|
||||
return (isMobile() || options.is("newLayout"));
|
||||
}
|
||||
|
||||
return getEnabledFeatures().has(featureId);
|
||||
@@ -29,7 +30,7 @@ export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId):
|
||||
|
||||
export function getEnabledExperimentalFeatureIds() {
|
||||
const values = [ ...getEnabledFeatures().values() ];
|
||||
if (options.is("newLayout")) {
|
||||
if (isMobile() || options.is("newLayout")) {
|
||||
values.push("new-layout");
|
||||
}
|
||||
return values;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
--bs-body-color: var(--main-text-color) !important;
|
||||
--bs-body-bg: var(--main-background-color) !important;
|
||||
--ck-mention-list-max-height: 500px;
|
||||
--tn-modal-max-height: 90vh;
|
||||
--tn-modal-max-height: 90svh;
|
||||
|
||||
--tree-item-light-theme-max-color-lightness: 50;
|
||||
--tree-item-dark-theme-min-color-lightness: 75;
|
||||
@@ -111,6 +111,7 @@ body.mobile #root-widget.virtual-keyboard-opened #mobile-bottom-bar {
|
||||
}
|
||||
|
||||
#mobile-bottom-bar {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
padding-bottom: var(--mobile-bottom-offset);
|
||||
}
|
||||
|
||||
@@ -224,10 +225,6 @@ body.mobile .modal .modal-dialog {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body.mobile .modal .modal-content {
|
||||
border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.component {
|
||||
contain: size;
|
||||
}
|
||||
@@ -413,6 +410,7 @@ body.desktop .tabulator-popup-container,
|
||||
|
||||
.dropdown-menu.static {
|
||||
box-shadow: unset;
|
||||
backdrop-filter: unset !important;
|
||||
}
|
||||
|
||||
.dropend .dropdown-toggle::after {
|
||||
@@ -458,7 +456,7 @@ body.desktop .tabulator-popup-container,
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu:not(#context-menu-container) .dropdown-item,
|
||||
.dropdown-menu:not(#context-menu-container) .dropdown-item,
|
||||
body.desktop .dropdown-menu .dropdown-toggle,
|
||||
body #context-menu-container .dropdown-item > span,
|
||||
body.mobile .dropdown .dropdown-submenu > span {
|
||||
@@ -466,6 +464,15 @@ body.mobile .dropdown .dropdown-submenu > span {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
body.mobile .dropdown .dropdown-submenu {
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item span.keyboard-shortcut,
|
||||
.dropdown-item *:not(.keyboard-shortcut) > kbd {
|
||||
flex-grow: 1;
|
||||
@@ -1255,7 +1262,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
z-index: 2500;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@@ -1534,7 +1541,8 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
|
||||
@media (max-width: 991px) {
|
||||
body.mobile #launcher-pane .dropdown.global-menu > .dropdown-menu.show,
|
||||
body.mobile #launcher-container .dropdown > .dropdown-menu.show {
|
||||
body.mobile #launcher-container .dropdown > .dropdown-menu.show,
|
||||
body.mobile .dropdown-menu.mobile-bottom-menu.show {
|
||||
--dropdown-bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size));
|
||||
position: fixed !important;
|
||||
bottom: var(--dropdown-bottom) !important;
|
||||
@@ -1546,6 +1554,16 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
max-height: calc(var(--tn-modal-max-height) - var(--dropdown-bottom));
|
||||
}
|
||||
|
||||
body.mobile #launcher-container .dropdown > .dropdown-menu.show {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-menu.mobile-bottom-menu.show {
|
||||
--dropdown-bottom: 0px;
|
||||
padding-bottom: calc(max(var(--menu-padding-size), env(safe-area-inset-bottom))) !important;
|
||||
}
|
||||
|
||||
#mobile-sidebar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -1614,6 +1632,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
|
||||
body.mobile .modal-content {
|
||||
overflow-y: auto;
|
||||
border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0;
|
||||
}
|
||||
|
||||
body.mobile .modal-footer {
|
||||
@@ -1669,39 +1688,16 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
#detail-container {
|
||||
background: var(--main-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
body.mobile.force-fixed-tree #mobile-sidebar-wrapper {
|
||||
padding-top: 0;
|
||||
position: static;
|
||||
height: 40vh;
|
||||
width: 100vw;
|
||||
transform: none !important;
|
||||
background-color: var(--left-pane-background-color) !important;
|
||||
border-bottom: 0.5px solid var(--main-border-color);
|
||||
}
|
||||
body.mobile {
|
||||
.modal-dialog {
|
||||
margin: var(--bs-modal-margin);
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #mobile-sidebar-container {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #mobile-sidebar-wrapper .quick-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree .component > button.bx-sidebar {
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #mobile-rest-container {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
body.mobile.force-fixed-tree #detail-container {
|
||||
flex-grow: 1;
|
||||
.modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2617,14 +2613,14 @@ iframe.print-iframe {
|
||||
}
|
||||
}
|
||||
|
||||
#root-widget.virtual-keyboard-opened .note-split:not(:focus-within) {
|
||||
#root-widget.virtual-keyboard-opened .note-split:not(.active) {
|
||||
max-height: 80px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop .title-row {
|
||||
.title-row {
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
--left-pane-collapsed-border-color: #0009;
|
||||
--left-pane-background-color: #1f1f1f;
|
||||
--left-pane-text-color: #aaaaaa;
|
||||
--left-pane-icon-color: #c5c5c5;
|
||||
--left-pane-item-hover-background: #ffffff0d;
|
||||
--left-pane-item-selected-background: #ffffff25;
|
||||
--left-pane-item-selected-color: #dfdfdf;
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
--left-pane-collapsed-border-color: #0000000d;
|
||||
--left-pane-background-color: #f2f2f2;
|
||||
--left-pane-text-color: #383838;
|
||||
--left-pane-icon-color: currentColor;
|
||||
--left-pane-item-hover-background: rgba(0, 0, 0, 0.032);
|
||||
--left-pane-item-selected-background: white;
|
||||
--left-pane-item-selected-color: black;
|
||||
|
||||
@@ -47,9 +47,14 @@
|
||||
}
|
||||
|
||||
/* The toolbar show / hide button for the current text block */
|
||||
.ck.ck-block-toolbar-button {
|
||||
:root .ck.ck-block-toolbar-button {
|
||||
--ck-color-block-toolbar-button: var(--muted-text-color);
|
||||
--ck-color-button-on-background: transparent;
|
||||
--ck-color-button-on-color: currentColor;
|
||||
--ck-color-button-on-color: var(--ck-editor-toolbar-button-on-color);
|
||||
translate: -40% 0;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
z-index: 1600;
|
||||
}
|
||||
|
||||
:root .ck.ck-toolbar .ck-button:not(.ck-disabled):active,
|
||||
@@ -517,6 +522,10 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck
|
||||
* EDITOR'S CONTENT
|
||||
*/
|
||||
|
||||
.note-detail-editable-text-editor > .ck-placeholder {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Code Blocks
|
||||
*/
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* SEARCH PAGE
|
||||
*/
|
||||
|
||||
/* Button bar */
|
||||
.search-definition-widget .search-setting-table tbody:last-child div {
|
||||
.search-definition-widget .search-setting-table .search-actions-container {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -143,7 +143,7 @@
|
||||
/*
|
||||
* OPTIONS PAGES
|
||||
*/
|
||||
|
||||
|
||||
:root {
|
||||
--options-card-min-width: 500px;
|
||||
--options-card-max-width: 900px;
|
||||
@@ -156,6 +156,10 @@
|
||||
--preferred-max-content-width: var(--options-card-max-width);
|
||||
}
|
||||
|
||||
.note-split.options .collection-properties {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Create a gap at the top of the option pages */
|
||||
.note-detail-content-widget-content.options>*:first-child {
|
||||
margin-top: var(--options-first-item-top-margin, 1em);
|
||||
@@ -331,4 +335,4 @@ nav.options-section-tabs + .options-section {
|
||||
|
||||
.etapi-options-section div {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,30 @@ body.mobile {
|
||||
|
||||
/* #region Mica */
|
||||
|
||||
/* Quirk: --background-material is read before "theme-supports-background-effects" class
|
||||
* is applied. Apply the matterial even if the theme doesn't support it. */
|
||||
body.background-effects.platform-win32 {
|
||||
/* Quirk: --background-material is read before "theme-supports-background-effects" class
|
||||
* is applied. Apply the matterial even if the theme doesn't support it. */
|
||||
--background-material: tabbed;
|
||||
&.layout-vertical {
|
||||
--background-material: mica;
|
||||
}
|
||||
|
||||
&.layout-horizontal {
|
||||
--background-material: tabbed;
|
||||
}
|
||||
}
|
||||
|
||||
body.background-effects.theme-supports-background-effects.platform-win32 {
|
||||
body.background-effects.platform-darwin {
|
||||
/** Reference: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc **/
|
||||
&.layout-vertical {
|
||||
--background-material: under-window;
|
||||
}
|
||||
|
||||
&.layout-horizontal {
|
||||
--background-material: hud;
|
||||
}
|
||||
}
|
||||
|
||||
body.background-effects.theme-supports-background-effects {
|
||||
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
|
||||
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
|
||||
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
|
||||
@@ -56,33 +73,29 @@ body.background-effects.theme-supports-background-effects.platform-win32 {
|
||||
--root-background: transparent;
|
||||
}
|
||||
|
||||
body.background-effects.platform-win32.layout-vertical {
|
||||
--background-material: mica;
|
||||
}
|
||||
|
||||
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical {
|
||||
body.background-effects.theme-supports-background-effects.layout-vertical {
|
||||
--left-pane-background-color: var(--window-background-color-bgfx);
|
||||
--center-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx);
|
||||
--right-pane-background-color: var(--right-pane-background-color-bgfx);
|
||||
}
|
||||
|
||||
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal {
|
||||
body.background-effects.theme-supports-background-effects.layout-horizontal {
|
||||
--center-pane-background-color-bgfx: var(--center-pane-horiz-layout-background-color-bgfx);
|
||||
--gutter-color: var(--left-pane-background-color);
|
||||
}
|
||||
|
||||
body.background-effects.theme-supports-background-effects.platform-win32,
|
||||
body.background-effects.theme-supports-background-effects.platform-win32 #root-widget {
|
||||
body.background-effects.theme-supports-background-effects,
|
||||
body.background-effects.theme-supports-background-effects #root-widget {
|
||||
background: var(--window-background-color-bgfx) !important;
|
||||
}
|
||||
|
||||
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal #horizontal-main-container,
|
||||
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical #vertical-main-container {
|
||||
body.background-effects.theme-supports-background-effects.layout-horizontal #horizontal-main-container,
|
||||
body.background-effects.theme-supports-background-effects.layout-vertical #vertical-main-container {
|
||||
background-color: var(--root-background);
|
||||
}
|
||||
|
||||
/* Note split with background effects */
|
||||
body.background-effects.theme-supports-background-effects.platform-win32 #center-pane .note-split.bgfx {
|
||||
body.background-effects.theme-supports-background-effects #center-pane .note-split.bgfx {
|
||||
--note-split-background-color: var(--center-pane-background-color-bgfx);
|
||||
}
|
||||
|
||||
@@ -726,18 +739,12 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
|
||||
transform: translateX(-25%);
|
||||
}
|
||||
|
||||
body.mobile .fancytree-expander::before,
|
||||
body.mobile .fancytree-title,
|
||||
body.mobile .fancytree-node > span {
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
body.mobile #mobile-sidebar-container {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
body.mobile:not(.force-fixed-tree) #mobile-sidebar-wrapper {
|
||||
body.mobile #mobile-sidebar-wrapper {
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
border-inline-end: 1px solid var(--subtle-border-color);
|
||||
@@ -756,9 +763,9 @@ body.mobile .fancytree-node > span {
|
||||
|
||||
#left-pane .fancytree-custom-icon {
|
||||
margin-top: 0; /* Use this to align the icon with the tree view item's caption */
|
||||
color: var(--custom-color, var(--left-pane-icon-color));
|
||||
}
|
||||
|
||||
|
||||
#left-pane span.fancytree-active .fancytree-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
@@ -1054,7 +1061,7 @@ body.layout-horizontal .tab-row-widget-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.desktop:not(.background-effects.platform-win32) #root-widget.horizontal-layout {
|
||||
body.desktop:not(.background-effects) #root-widget.horizontal-layout {
|
||||
background-color: var(--root-background) !important;
|
||||
}
|
||||
|
||||
@@ -1259,7 +1266,7 @@ body.layout-horizontal #rest-pane > .classic-toolbar-widget {
|
||||
#center-pane .note-split {
|
||||
padding-top: 2px;
|
||||
background-color: var(--note-split-background-color, var(--main-background-color));
|
||||
transition: border-color 250ms ease-in;
|
||||
transition: border-color 150ms ease-out;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
@@ -1309,7 +1316,7 @@ body.mobile .note-title {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
body.desktop .title-row {
|
||||
/* Aligns the "Create new split" button with the note menu button (the three dots button) */
|
||||
padding-inline-end: 3px;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"widget-render-error": {
|
||||
"title": "فشل عرض عنصر واجهة مستخدم React مخصص"
|
||||
},
|
||||
"widget-missing-parent": "لا تحتوي الأداة المخصصة على خاصية إلزامية '{{property}}'.\n\nإذا كان من المفترض تشغيل هذا البرنامج النصي بدون عنصر واجهة مستخدم، فاستخدم '#run=frontendStartup' بدلاً من ذلك."
|
||||
"widget-missing-parent": "لا تحتوي الأداة المخصصة على خاصية إلزامية '{{property}}'.\n\nإذا كان من المفترض تشغيل هذا البرنامج النصي بدون عنصر واجهة مستخدم، فاستخدم '#run=frontendStartup' بدلاً من ذلك.",
|
||||
"open-script-note": "فتح ملاحظة برمجية",
|
||||
"scripting-error": "خطأ في النص البرمجي المخصص: {{title}}"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "أضافة رابط",
|
||||
@@ -37,7 +39,9 @@
|
||||
"search_note": "البحث عن الملاحظة بالاسم",
|
||||
"link_title": "عنوان الرابط",
|
||||
"button_add_link": "اضافة رابط",
|
||||
"help_on_links": "مساعدة حول الارتباطات التشعبية"
|
||||
"help_on_links": "مساعدة حول الارتباطات التشعبية",
|
||||
"link_title_mirrors": "عنوان الرابط يعكس العنوان الحالي للملاحظة",
|
||||
"link_title_arbitrary": "يمكن تغيير عنوان الرابط حسب الرغبة"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix": "تعديل بادئة الفرع",
|
||||
|
||||
@@ -1782,8 +1782,8 @@
|
||||
"desktop-application": "桌面应用程序",
|
||||
"native-title-bar": "原生标题栏",
|
||||
"native-title-bar-description": "对于 Windows 和 macOS,关闭原生标题栏可使应用程序看起来更紧凑。在 Linux 上,保留原生标题栏可以更好地与系统集成。",
|
||||
"background-effects": "启用背景效果(仅适用于 Windows 11)",
|
||||
"background-effects-description": "Mica 效果为应用窗口添加模糊且时尚的背景,营造出深度感和现代外观。「原生标题栏」必須被禁用。",
|
||||
"background-effects": "启用背景效果",
|
||||
"background-effects-description": "为应用窗口添加模糊且时尚的背景,营造出深度感和现代外观。「原生标题栏」必須被禁用。",
|
||||
"restart-app-button": "重启应用程序以查看更改",
|
||||
"zoom-factor": "缩放系数"
|
||||
},
|
||||
@@ -1802,7 +1802,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "创建一个新的子笔记并将其添加到地图中",
|
||||
"create-child-note-instruction": "单击地图以在该位置创建新笔记,或按 Escape 以取消。",
|
||||
"unable-to-load-map": "无法加载地图。"
|
||||
"unable-to-load-map": "无法加载地图。",
|
||||
"create-child-note-text": "添加标记"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "打开位置",
|
||||
@@ -2117,7 +2118,7 @@
|
||||
},
|
||||
"call_to_action": {
|
||||
"background_effects_title": "背景效果现已推出稳定版本",
|
||||
"background_effects_message": "在 Windows 装置上,背景效果现在已完全稳定。背景效果通过模糊背后的背景,为使用者界面增添一抹色彩。此技术也用于其他应用程序,例如 Windows 资源管理器。",
|
||||
"background_effects_message": "在 Windows 和 macOS 设备上,背景效果现在已稳定。背景效果通过模糊背后的背景,为使用者界面增添一抹色彩。",
|
||||
"background_effects_button": "启用背景效果",
|
||||
"next_theme_title": "试用新 Trilium 主题",
|
||||
"next_theme_message": "当前使用旧版主题,要试用新主题吗?",
|
||||
@@ -2253,5 +2254,12 @@
|
||||
"pages_alt": "第{{pageNumber}}页",
|
||||
"pages_loading": "加载中...",
|
||||
"layers_other": "{{count}} 层"
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "在 {{platform}} 上可用"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_other": "{{count}} 选项卡",
|
||||
"more_options": "更多选项"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
},
|
||||
"open-script-note": "Script-Notiz öffnen",
|
||||
"widget-render-error": {
|
||||
"title": "Eine externe React Integration konnte nicht dargestellt werden"
|
||||
"title": "Benutzerdefiniertes React-Widget konnte nicht dargestellt werden"
|
||||
},
|
||||
"widget-missing-parent": "Der externen Integration fehlt die erforderliche Eigenschaft '{{property}}'\n\nFalls dieses Skript ohne UI-Element ausgeführt werden soll, benutze stattdessen '#run=frontendStartup'.",
|
||||
"widget-missing-parent": "Benutzerdefiniertes Widget hat die erforderliche '{{property}}'-Eigenschaft nicht korrekt definiert.\n\nFalls dieses Skript ohne UI-Element ausgeführt werden soll, benutze stattdessen '#run=frontendStartup'.",
|
||||
"scripting-error": "Benutzerdefinierter Skriptfehler: {{title}}"
|
||||
},
|
||||
"add_link": {
|
||||
@@ -129,7 +129,7 @@
|
||||
"scrollToActiveNote": "Scrolle zur aktiven Notiz",
|
||||
"jumpToParentNote": "Zur übergeordneten Notiz springen",
|
||||
"collapseWholeTree": "Reduziere den gesamten Notizbaum",
|
||||
"collapseSubTree": "Teilbaum einklappen",
|
||||
"collapseSubTree": "Zweig einklappen",
|
||||
"tabShortcuts": "Tab-Tastenkürzel",
|
||||
"newTabNoteLink": "auf den Notizlink öffnet die Notiz in einem neuen Tab",
|
||||
"onlyInDesktop": "Nur im Desktop (Electron Build)",
|
||||
@@ -230,7 +230,7 @@
|
||||
"move_to": {
|
||||
"dialog_title": "Notizen verschieben nach ...",
|
||||
"notes_to_move": "Notizen zum Verschieben",
|
||||
"target_parent_note": "Ziel-Elternnotiz",
|
||||
"target_parent_note": "Übergeordnete Notiz bestimmen",
|
||||
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
|
||||
"move_button": "Zur ausgewählten Notiz wechseln",
|
||||
"error_no_path": "Kein Weg, auf den man sich bewegen kann.",
|
||||
@@ -333,8 +333,8 @@
|
||||
"target_note_title": "Eine Beziehung ist eine benannte Verbindung zwischen Quellnotiz und Zielnotiz.",
|
||||
"target_note": "Zielnotiz",
|
||||
"promoted_title": "Das heraufgestufte Attribut wird deutlich in der Notiz angezeigt.",
|
||||
"promoted": "Gefördert",
|
||||
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für heraufgestufte Attribute angezeigt werden soll.",
|
||||
"promoted": "Hervorgehoben",
|
||||
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für hervorgehobene Attribute angezeigt werden soll.",
|
||||
"promoted_alias": "Alias",
|
||||
"multiplicity_title": "Multiplizität definiert, wie viele Attribute mit demselben Namen erstellt werden können – maximal 1 oder mehr als 1.",
|
||||
"multiplicity": "Vielzahl",
|
||||
@@ -367,7 +367,7 @@
|
||||
"disable_versioning": "deaktiviert die automatische Versionierung. Nützlich z.B. große, aber unwichtige Notizen – z.B. große JS-Bibliotheken, die für die Skripterstellung verwendet werden",
|
||||
"calendar_root": "Markiert eine Notiz, die als Basis für Tagesnotizen verwendet werden soll. Nur einer sollte als solcher gekennzeichnet sein.",
|
||||
"archived": "Notizen mit dieser Bezeichnung werden standardmäßig nicht in den Suchergebnissen angezeigt (auch nicht in den Dialogen „Springen zu“, „Link hinzufügen“ usw.).",
|
||||
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht in den Notizexport einbezogen",
|
||||
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht im Notizexport inkludiert",
|
||||
"run": "Definiert, bei welchen Ereignissen das Skript ausgeführt werden soll. Mögliche Werte sind:\n<ul>\n<li>frontendStartup - wenn das Trilium-Frontend startet (oder aktualisiert wird), außer auf mobilen Geräten.</li>\n<li>mobileStartup - wenn das Trilium-Frontend auf einem mobilen Gerät startet (oder aktualisiert wird).</li>\n<li>backendStartup - wenn das Trilium-Backend startet</li>\n<li>hourly - einmal pro Stunde ausführen. Du kannst das zusätzliche Label <code>runAtHour</code> verwenden, um die genaue Stunde festzulegen.</li>\n<li>daily - einmal pro Tag ausführen</li>\n</ul>",
|
||||
"run_on_instance": "Definiere, auf welcher Trilium-Instanz dies ausgeführt werden soll. Standardmäßig alle Instanzen.",
|
||||
"run_at_hour": "Zu welcher Stunde soll das laufen? Sollte zusammen mit <code>#runu003dhourly</code> verwendet werden. Kann für mehr Läufe im Laufe des Tages mehrfach definiert werden.",
|
||||
@@ -376,7 +376,7 @@
|
||||
"sort_direction": "ASC (Standard) oder DESC",
|
||||
"sort_folders_first": "Ordner (Notizen mit Unternotizen) sollten oben sortiert werden",
|
||||
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen)",
|
||||
"hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden",
|
||||
"hide_promoted_attributes": "Hervorgehobene Attribute für diese Notiz ausblenden",
|
||||
"read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.",
|
||||
"auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst",
|
||||
"app_css": "markiert CSS-Notizen, die in die Trilium-Anwendung geladen werden und somit zur Änderung des Aussehens von Trilium verwendet werden können.",
|
||||
@@ -416,13 +416,13 @@
|
||||
"toc": "<code>#toc</code> oder <code>#tocu003dshow</code> erzwingen die Anzeige des Inhaltsverzeichnisses, <code>#tocu003dhide</code> erzwingt das Ausblenden. Wenn die Bezeichnung nicht vorhanden ist, wird die globale Einstellung beachtet",
|
||||
"color": "Definiert die Farbe der Notiz im Notizbaum, in Links usw. Verwende einen beliebigen gültigen CSS-Farbwert wie „rot“ oder #a13d5f",
|
||||
"keyboard_shortcut": "Definiert eine Tastenkombination, die sofort zu dieser Notiz springt. Beispiel: „Strg+Alt+E“. Erfordert ein Neuladen des Frontends, damit die Änderung wirksam wird.",
|
||||
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Unterbaum nicht angezeigt werden kann.",
|
||||
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Zweig nicht angezeigt werden kann.",
|
||||
"execute_button": "Titel der Schaltfläche, welche die aktuelle Codenotiz ausführt",
|
||||
"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",
|
||||
"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_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 einem bestimmten Zweig erstellt wurden. Erstelle es in diesem Fall auf der Stammnotiz und mache es vererbbar. Eine neue Notiz, die innerhalb des Zweigs (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)",
|
||||
"run_on_note_content_change": "Wird ausgeführt, wenn der Inhalt einer Notiz geändert wird (einschließlich der Erstellung von Notizen).",
|
||||
@@ -433,8 +433,8 @@
|
||||
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. gelöscht. beim Verschieben der Notiz (alter Zweig/Link wird gelöscht).",
|
||||
"run_on_attribute_creation": "wird ausgeführt, wenn für die Notiz ein neues Attribut erstellt wird, das diese Beziehung definiert",
|
||||
"run_on_attribute_change": " wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
|
||||
"relation_template": "Die Attribute der Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Der Inhalt und der Unterbaum der Notiz werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
|
||||
"inherit": "Die Attribute einer Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributvererbung in der Dokumentation.",
|
||||
"relation_template": "Die Attribute der Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Der Inhalt und der Zweig werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
|
||||
"inherit": "Die Attribute einer Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributsvererbung in der Dokumentation.",
|
||||
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll",
|
||||
"widget_relation": "Das Ziel dieser Beziehung wird ausgeführt und als Widget in der Seitenleiste gerendert",
|
||||
"share_css": "CSS-Hinweis, der in die Freigabeseite eingefügt wird. Die CSS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge auch die Verwendung von „share_hidden_from_tree“ und „share_omit_default_css“.",
|
||||
@@ -646,7 +646,7 @@
|
||||
"reset_zoom_level": "Zoomstufe zurücksetzen",
|
||||
"zoom_in": "Hineinzoomen",
|
||||
"configure_launchbar": "Konfiguriere die Starterleiste",
|
||||
"show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen",
|
||||
"show_shared_notes_subtree": "Zweig „Freigegebene Notizen“ anzeigen",
|
||||
"advanced": "Erweitert",
|
||||
"open_dev_tools": "Öffne die Entwicklungstools",
|
||||
"open_sql_console": "Öffne die SQL-Konsole",
|
||||
@@ -655,7 +655,7 @@
|
||||
"show_backend_log": "Backend-Protokoll anzeigen",
|
||||
"reload_hint": "Ein Neuladen kann bei einigen visuellen Störungen Abhilfe schaffen, ohne die gesamte App neu starten zu müssen.",
|
||||
"reload_frontend": "Frontend neu laden",
|
||||
"show_hidden_subtree": "Versteckten Teilbaum anzeigen",
|
||||
"show_hidden_subtree": "Versteckten Zweig anzeigen",
|
||||
"show_help": "Hilfe anzeigen",
|
||||
"about": "Über Trilium Notes",
|
||||
"logout": "Abmelden",
|
||||
@@ -703,8 +703,8 @@
|
||||
"export_as_image_png": "PNG (Raster)",
|
||||
"export_as_image_svg": "SVG (Vektor)",
|
||||
"note_map": "Notizen Karte",
|
||||
"view_revisions": "Änderungshistorie...",
|
||||
"advanced": "Fortgeschritten"
|
||||
"view_revisions": "Notizrevisionen...",
|
||||
"advanced": "Erweitert"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler"
|
||||
@@ -798,7 +798,7 @@
|
||||
"expand_tooltip": "Erweitert die direkten Unterelemente dieser Sammlung (eine Ebene tiefer). Für weitere Optionen auf den Pfeil rechts klicken.",
|
||||
"expand_first_level": "Direkte Unterelemente erweitern",
|
||||
"expand_nth_level": "{{depth}} Ebenen erweitern",
|
||||
"hide_child_notes": "Unterknoten im Baum ausblenden"
|
||||
"hide_child_notes": "Unternotizen im Baum ausblenden"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
|
||||
@@ -842,7 +842,7 @@
|
||||
"note_size": "Notengröße",
|
||||
"note_size_info": "Die Notizgröße bietet eine grobe Schätzung des Speicherbedarfs für diese Notiz. Es berücksichtigt den Inhalt der Notiz und den Inhalt ihrer Notizrevisionen.",
|
||||
"calculate": "berechnen",
|
||||
"subtree_size": "(Teilbaumgröße: {{size}} in {{count}} Notizen)",
|
||||
"subtree_size": "(Zweiggröße: {{size}} in {{count}} Notizen)",
|
||||
"title": "Notizinfo",
|
||||
"mime": "MIME Typ",
|
||||
"show_similar_notes": "Zeige ähnliche Notizen"
|
||||
@@ -871,7 +871,7 @@
|
||||
"owned_attributes": "Eigene Attribute"
|
||||
},
|
||||
"promoted_attributes": {
|
||||
"promoted_attributes": "Übergebene Attribute",
|
||||
"promoted_attributes": "Hervorgehobene Attribute",
|
||||
"url_placeholder": "http://website...",
|
||||
"open_external_link": "Externen Link öffnen",
|
||||
"unknown_label_type": "Unbekannter Labeltyp „{{type}}“",
|
||||
@@ -1115,7 +1115,7 @@
|
||||
"vacuum_database": {
|
||||
"title": "Datenbank aufräumen",
|
||||
"description": "Dadurch wird die Datenbank neu erstellt, was normalerweise zu einer kleineren Datenbankdatei führt. Es werden keine Daten tatsächlich geändert.",
|
||||
"button_text": "Vakuumdatenbank",
|
||||
"button_text": "Datenbank aufräumen",
|
||||
"vacuuming_database": "Datenbank wird geleert...",
|
||||
"database_vacuumed": "Die Datenbank wurde geleert"
|
||||
},
|
||||
@@ -1156,7 +1156,7 @@
|
||||
},
|
||||
"ribbon": {
|
||||
"widgets": "Multifunktionsleisten-Widgets",
|
||||
"promoted_attributes_message": "Die Multifunktionsleisten-Registerkarte „Heraufgestufte Attribute“ wird automatisch geöffnet, wenn in der Notiz heraufgestufte Attribute vorhanden sind",
|
||||
"promoted_attributes_message": "Die „Hervorgehobene Attribute“-Leiste wird automatisch geöffnet, wenn in der Notiz hervorgehobene Attribute vorhanden sind",
|
||||
"edited_notes_message": "Die Multifunktionsleisten-Registerkarte „Bearbeitete Notizen“ wird bei Tagesnotizen automatisch geöffnet"
|
||||
},
|
||||
"theme": {
|
||||
@@ -1445,19 +1445,19 @@
|
||||
"insert-note-after": "Notiz dahinter einfügen",
|
||||
"insert-child-note": "Unternotiz einfügen",
|
||||
"delete": "Löschen",
|
||||
"search-in-subtree": "Im Notizbaum suchen",
|
||||
"search-in-subtree": "Im Zweig suchen",
|
||||
"hoist-note": "Notiz-Fokus setzen",
|
||||
"unhoist-note": "Notiz-Fokus aufheben",
|
||||
"edit-branch-prefix": "Zweig-Präfix bearbeiten",
|
||||
"advanced": "Erweitert",
|
||||
"expand-subtree": "Unterzweig aufklappen",
|
||||
"collapse-subtree": "Notizbaum einklappen",
|
||||
"expand-subtree": "Zweig aufklappen",
|
||||
"collapse-subtree": "Zweig einklappen",
|
||||
"sort-by": "Sortieren nach...",
|
||||
"recent-changes-in-subtree": "Kürzliche Änderungen im Notizbaum",
|
||||
"recent-changes-in-subtree": "Kürzliche Änderungen im Zweig",
|
||||
"convert-to-attachment": "Als Anhang konvertieren",
|
||||
"copy-note-path-to-clipboard": "Notiz-Pfad in die Zwischenablage kopieren",
|
||||
"protect-subtree": "Notizbaum schützen",
|
||||
"unprotect-subtree": "Notizenbaum-Schutz aufheben",
|
||||
"protect-subtree": "Zweig schützen",
|
||||
"unprotect-subtree": "Zweig-Schutz aufheben",
|
||||
"copy-clone": "Kopieren / Klonen",
|
||||
"clone-to": "Klonen nach...",
|
||||
"cut": "Ausschneiden",
|
||||
@@ -1474,12 +1474,12 @@
|
||||
"archive": "Archiviere",
|
||||
"unarchive": "Entarchivieren",
|
||||
"open-in-a-new-window": "In neuem Fenster öffnen",
|
||||
"hide-subtree": "Teilbaum ausblenden",
|
||||
"show-subtree": "Teilbaum anzeigen"
|
||||
"hide-subtree": "Zweig ausblenden",
|
||||
"show-subtree": "Zweig anzeigen"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
|
||||
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
|
||||
"shared_publicly": "Diese Notiz ist öffentlich freigegeben über {{- link}}.",
|
||||
"shared_locally": "Diese Notiz ist lokal freigegeben über {{- link}}.",
|
||||
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
},
|
||||
"note_types": {
|
||||
@@ -1514,10 +1514,10 @@
|
||||
"toggle-off-hint": "Notiz ist geschützt, klicken, um den Schutz aufzuheben"
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "Teilen",
|
||||
"toggle-on-title": "Notiz teilen",
|
||||
"shared": "Freigegeben",
|
||||
"toggle-on-title": "Notiz freigeben",
|
||||
"toggle-off-title": "Notiz-Freigabe aufheben",
|
||||
"shared-branch": "Diese Notiz existiert nur als geteilte Notiz, das Aufheben der Freigabe würde sie löschen. Möchtest du fortfahren und die Notiz damit löschen?",
|
||||
"shared-branch": "Diese Notiz existiert nur als freigegebene Notiz, das Aufheben der Freigabe würde sie löschen. Möchtest du fortfahren und die Notiz damit löschen?",
|
||||
"inherited": "Die Notiz kann hier nicht von der Freigabe entfernt werden, da sie über Vererbung von einer übergeordneten Notiz geteilt wird."
|
||||
},
|
||||
"template_switch": {
|
||||
@@ -1566,15 +1566,15 @@
|
||||
"unhoist": "Fokus verlassen",
|
||||
"toggle-sidebar": "Seitenleiste ein-/ausblenden",
|
||||
"dropping-not-allowed": "Ablegen von Notizen an dieser Stelle ist nicht zulässig.",
|
||||
"clone-indicator-tooltip": "Diese Notiz hat {{- count}} Elterknoten: {{- parents}}",
|
||||
"clone-indicator-tooltip-single": "Diese Notiz ist geklont (1 weiterer Elternknoten: {{- parent}})",
|
||||
"shared-indicator-tooltip": "Diese Notiz ist öffentlich einsehbar",
|
||||
"shared-indicator-tooltip-with-url": "Diese Notiz ist unter {{- url}} öffentlich einsehbar",
|
||||
"subtree-hidden-tooltip_one": "{{count}} Unterknoten, der im Baum ausgeblendet ist",
|
||||
"subtree-hidden-tooltip_other": "{{count}} Unterknoten, die im Baum ausgeblendet sind",
|
||||
"clone-indicator-tooltip": "Diese Notiz hat {{- count}} übergeordnete Knoten: {{- parents}}",
|
||||
"clone-indicator-tooltip-single": "Diese Notiz ist geklont (1 weitere Quelle: {{- parent}})",
|
||||
"shared-indicator-tooltip": "Diese Notiz ist öffentlich freigegeben",
|
||||
"shared-indicator-tooltip-with-url": "Diese Notiz ist öffentlich freigegeben unter: {{- url}}",
|
||||
"subtree-hidden-tooltip_one": "{{count}} untergeordnete Notiz, die im Baum ausgeblendet ist",
|
||||
"subtree-hidden-tooltip_other": "{{count}} untergeordnete Notizen, die im Baum ausgeblendet sind",
|
||||
"subtree-hidden-moved-title": "Zu {{title}} hinzugefügt",
|
||||
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre Unternotizem im Baum aus.",
|
||||
"subtree-hidden-moved-description-other": "Diese Sammlung blendet ihre Unterknoten im Baum aus."
|
||||
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre Unternotizen im Baum aus.",
|
||||
"subtree-hidden-moved-description-other": "Untergeordnete Notizen sind im Baum für diese Notiz ausgeblendet."
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Dieses Fenster immer oben halten"
|
||||
@@ -1586,8 +1586,8 @@
|
||||
"print_report_title": "Druckreport",
|
||||
"print_report_collection_details_button": "Details anzeigen",
|
||||
"print_report_collection_details_ignored_notes": "Ignorierte Notizen",
|
||||
"print_report_collection_content_one": "{{count}} Notiz in der Sammlung konnte nicht gedruckt werden, weil sie nicht unterstützt ist oder geschützt ist.",
|
||||
"print_report_collection_content_other": "{{count}} Notizen in der Sammlung konnten nicht gedruckt werden, weil sie nicht unterstützt sind oder geschützt sind."
|
||||
"print_report_collection_content_one": "{{count}} Notiz in der Sammlung konnte nicht gedruckt werden, weil sie nicht unterstützt oder geschützt ist.",
|
||||
"print_report_collection_content_other": "{{count}} Notizen in der Sammlung konnten nicht gedruckt werden, weil sie nicht unterstützt oder geschützt sind."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "Titel der Notiz hier eingeben…",
|
||||
@@ -1751,8 +1751,8 @@
|
||||
"desktop-application": "Desktop Anwendung",
|
||||
"native-title-bar": "Native Anwendungsleiste",
|
||||
"native-title-bar-description": "In Windows und macOS, sorgt das Deaktivieren der nativen Anwendungsleiste für ein kompakteres Aussehen. Unter Linux, sorgt das Aktivieren der nativen Anwendungsleiste für eine bessere Integration mit anderen Teilen des Systems.",
|
||||
"background-effects": "Hintergrundeffekte aktivieren (nur Windows 11)",
|
||||
"background-effects-description": "Der Mica Effekt fügt einen unscharfen, stylischen Hintergrund in Anwendungsfenstern ein. Dieser erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
|
||||
"background-effects": "Hintergrundeffekte aktivieren",
|
||||
"background-effects-description": "Fügt einen unscharfen, stylischen Hintergrund in das Anwendungsfenstern ein. Dies erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
|
||||
"restart-app-button": "Anwendung neustarten um Änderungen anzuwenden",
|
||||
"zoom-factor": "Zoomfaktor"
|
||||
},
|
||||
@@ -1771,7 +1771,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Neue Unternotiz anlegen und zur Karte hinzufügen",
|
||||
"create-child-note-instruction": "Auf die Karte klicken, um eine neue Notiz an der Stelle zu erstellen oder Escape drücken um abzubrechen.",
|
||||
"unable-to-load-map": "Karte konnte nicht geladen werden."
|
||||
"unable-to-load-map": "Karte konnte nicht geladen werden.",
|
||||
"create-child-note-text": "Marker hinzufügen"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Ort öffnen",
|
||||
@@ -2000,7 +2001,7 @@
|
||||
"check_share_root": "Status des Freigabe-Roots prüfen",
|
||||
"share_root_found": "Freigabe-Root-Notiz '{{noteTitle}}' ist bereit",
|
||||
"share_root_not_found": "Keine Notiz mit #shareRoot Label gefunden",
|
||||
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht geteilt"
|
||||
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht freigegeben"
|
||||
},
|
||||
"tasks": {
|
||||
"due": {
|
||||
@@ -2118,8 +2119,8 @@
|
||||
"show_attachments_description": "Notizanhänge anzeigen",
|
||||
"search_notes_title": "Suche Notiz",
|
||||
"search_notes_description": "Öffne erweiterte Suche",
|
||||
"search_subtree_title": "Im Unterzweig suchen",
|
||||
"search_subtree_description": "Im aktuellen Unterzweig suchen",
|
||||
"search_subtree_title": "Im Zweig suchen",
|
||||
"search_subtree_description": "Im aktuellen Zweig suchen",
|
||||
"search_history_title": "Zeige Suchhistorie",
|
||||
"search_history_description": "Zeige vorherige Suchen",
|
||||
"configure_launch_bar_title": "Startleiste anpassen",
|
||||
@@ -2133,7 +2134,7 @@
|
||||
"next_theme_message": "Es wird aktuell das alte Design verwendet. Möchten Sie das neue Design ausprobieren?",
|
||||
"next_theme_button": "Teste das neue Design",
|
||||
"background_effects_title": "Hintergrundeffekte sind jetzt zuverlässig nutzbar",
|
||||
"background_effects_message": "Auf Windows-Geräten sind die Hintergrundeffekte nun vollständig stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird. Diese Technik wird auch in anderen Anwendungen wie dem Windows-Explorer eingesetzt.",
|
||||
"background_effects_message": "Auf Windows- und macOS-Geräten sind die Hintergrundeffekte nun stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird.",
|
||||
"background_effects_button": "Aktiviere Hintergrundeffekte",
|
||||
"dismiss": "Ablehnen",
|
||||
"new_layout_title": "Neues Layout",
|
||||
@@ -2188,9 +2189,9 @@
|
||||
"new_layout_description": "Probiere das neue Layout für eine modernere Darstellung und verbesserte Benutzbarkeit aus. Kann sich in Zukunft stark ändern."
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "Bei der Kommunikation mit dem Server ist ein Fehler aufgetreten",
|
||||
"unknown_http_error_title": "Kommunikationsfehler mit dem Server",
|
||||
"unknown_http_error_content": "Statuscode: {{statusCode}}\nURL: {{method}} {{url}}\nNachricht: {{message}}",
|
||||
"traefik_blocks_requests": "Der Traefik Reverse-Proxy hat ein fatales Update bekommen, welche die Kommunikation mit dem Server stört."
|
||||
"traefik_blocks_requests": "Der Traefik Reverse-Proxy hat eine Änderung erfahren, welches die Kommunikation mit dem Server beeinflusst."
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "Zur vorherigen Notiz zurück kehren",
|
||||
@@ -2205,30 +2206,30 @@
|
||||
"empty_hide_archived_notes": "Archivierte Notizen ausblenden"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "Nicht Änderbar",
|
||||
"read_only_explicit_description": "Diese Notiz wurde händisch als nicht änderbar markiert.\nKlicke hier um sie temporär zu bearbeiten.",
|
||||
"read_only_auto": "Automatisch nicht änderbar",
|
||||
"read_only_auto_description": "Diese Notiz wurde automatisch aus Leistungsgründen als nicht änderbar markiert. Dieses automatische Limit kann in den Einstellungen angepasst werden.\n\nKlicke hier, um sie temporär zu bearbeiten.",
|
||||
"read_only_explicit": "Schreibgeschützt",
|
||||
"read_only_explicit_description": "Diese Notiz wurde händisch schreibgeschützt.\nKlicke hier um sie temporär zu bearbeiten.",
|
||||
"read_only_auto": "Automatisch schreibgeschützt",
|
||||
"read_only_auto_description": "Diese Notiz wurde automatisch aus Leistungsgründen als schreibgeschützt markiert. Dieses automatische Limit kann in den Einstellungen angepasst werden.\n\nKlicke hier, um sie temporär zu bearbeiten.",
|
||||
"read_only_temporarily_disabled": "Temporär bearbeitbar",
|
||||
"read_only_temporarily_disabled_description": "Diese Notiz ist aktuell bearbeitbar, ist aber normalerweise nicht änderbar. Sobald du zu einer anderen Notiz navigierst, kehrt diese Notiz in ihren Normalzustand zurück.\n\nKlicke hier, um die Notiz wieder nicht änderbar zu machen.",
|
||||
"shared_publicly": "Öffentlich geteilt",
|
||||
"shared_locally": "Lokal geteilt",
|
||||
"read_only_temporarily_disabled_description": "Diese Notiz ist aktuell bearbeitbar, ist aber normalerweise schreibgeschützt. Sobald du zu einer anderen Notiz navigierst wird diese wieder schreibgeschützt.\n\nKlicke hier, um die Notiz wieder schreibgeschützt zu machen.",
|
||||
"shared_publicly": "Öffentlich freigegeben",
|
||||
"shared_locally": "Lokal freigegeben",
|
||||
"shared_copy_to_clipboard": "Link in die Zwischenablage kopieren",
|
||||
"shared_open_in_browser": "Link öffnen",
|
||||
"shared_unshare": "Teilen aufheben",
|
||||
"shared_open_in_browser": "Link im Browser öffnen",
|
||||
"shared_unshare": "Freigabe aufheben",
|
||||
"clipped_note": "Internetschnellverweis",
|
||||
"clipped_note_description": "Diese Notiz wurde von {{url}} übernommen.\n\nKlicke hier, um zum Ursprung zu gehen.",
|
||||
"clipped_note_description": "Diese Notiz wurde von {{url}} übernommen.\n\nKlicke hier, um zur Quelle zu gehen.",
|
||||
"execute_script": "Skript ausführen",
|
||||
"execute_script_description": "Diese Notiz ist eine Skriptnotiz. Klicke hier, um das Skript auszuführen.",
|
||||
"execute_sql": "SQL ausführen",
|
||||
"execute_sql_description": "Diese Notiz ist eine SQL-Notiz. Klicke hier, um die SQL-Abfrage auszuführen.",
|
||||
"save_status_saved": "Gespeichert",
|
||||
"save_status_saving": "Speichern...",
|
||||
"save_status_saving": "Speichere...",
|
||||
"save_status_unsaved": "Nicht gespeichert",
|
||||
"save_status_error": "Speichern fehlgeschlagen",
|
||||
"save_status_saving_tooltip": "Änderungen werden gespeichert.",
|
||||
"save_status_unsaved_tooltip": "Es gibt ungespeicherte Änderungen, welche gleich automatisch gespeichert werden.",
|
||||
"save_status_error_tooltip": "Beim speichern der Notiz ist ein Fehler aufgetreten. Wenn möglich, versuche die Notiz woandershin zu kopieren und die Applikation neu zu laden."
|
||||
"save_status_error_tooltip": "Beim speichern der Notiz ist ein Fehler aufgetreten. Wenn möglich, versuche die Notiz woandershin zu kopieren und die Anwendung neu zu laden."
|
||||
},
|
||||
"status_bar": {
|
||||
"language_title": "Inhaltssprache ändern",
|
||||
@@ -2241,7 +2242,7 @@
|
||||
"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_one": "{{count}} Attribut",
|
||||
"attributes_other": "{{count}} Attribute",
|
||||
"attributes_title": "Eigene und geerbte Attribute",
|
||||
"note_paths_one": "{{count}} Pfad",
|
||||
@@ -2254,9 +2255,9 @@
|
||||
},
|
||||
"right_pane": {
|
||||
"empty_message": "Für diese Notiz gibt es nichts anzuzeigen",
|
||||
"empty_button": "Anzeige ausblenden",
|
||||
"toggle": "Rechte Anzeige umschalten",
|
||||
"custom_widget_go_to_source": "Zum Ursprungscode"
|
||||
"empty_button": "Leiste ausblenden",
|
||||
"toggle": "Rechte Leiste umschalten",
|
||||
"custom_widget_go_to_source": "Zum Quellcode"
|
||||
},
|
||||
"pdf": {
|
||||
"attachments_one": "{{count}} Anhang",
|
||||
@@ -2266,6 +2267,14 @@
|
||||
"pages_one": "{{count}} Seite",
|
||||
"pages_other": "{{count}} Seiten",
|
||||
"pages_alt": "Seite {{pageNumber}}",
|
||||
"pages_loading": "Laden..."
|
||||
"pages_loading": "Lädt..."
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Verfügbar auf {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} Tab",
|
||||
"title_other": "{{count}} Tabs",
|
||||
"more_options": "Weitere Optionen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +662,8 @@
|
||||
"show-cheatsheet": "Show Cheatsheet",
|
||||
"toggle-zen-mode": "Zen Mode",
|
||||
"new-version-available": "New Update Available",
|
||||
"download-update": "Get Version {{latestVersion}}"
|
||||
"download-update": "Get Version {{latestVersion}}",
|
||||
"search_notes": "Search notes"
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Exit Zen Mode"
|
||||
@@ -745,7 +746,7 @@
|
||||
"button_title": "Export diagram as SVG"
|
||||
},
|
||||
"relation_map_buttons": {
|
||||
"create_child_note_title": "Create new child note and add it into this relation map",
|
||||
"create_child_note_title": "Create child note and add it to map",
|
||||
"reset_pan_zoom_title": "Reset pan & zoom to initial coordinates and magnification",
|
||||
"zoom_in_title": "Zoom In",
|
||||
"zoom_out_title": "Zoom Out"
|
||||
@@ -760,7 +761,9 @@
|
||||
"delete_this_note": "Delete this note",
|
||||
"note_revisions": "Note revisions",
|
||||
"error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'",
|
||||
"error_unrecognized_command": "Unrecognized command {{command}}"
|
||||
"error_unrecognized_command": "Unrecognized command {{command}}",
|
||||
"backlinks": "Backlinks",
|
||||
"content_language_switcher": "Content language: {{language}}"
|
||||
},
|
||||
"note_icon": {
|
||||
"change_note_icon": "Change note icon",
|
||||
@@ -905,6 +908,7 @@
|
||||
"debug": "debug",
|
||||
"debug_description": "Debug will print extra debugging information into the console to aid in debugging complex queries",
|
||||
"action": "action",
|
||||
"option": "option",
|
||||
"search_button": "Search",
|
||||
"search_execute": "Search & Execute actions",
|
||||
"save_to_note": "Save to note",
|
||||
@@ -1958,8 +1962,8 @@
|
||||
"desktop-application": "Desktop Application",
|
||||
"native-title-bar": "Native title bar",
|
||||
"native-title-bar-description": "For Windows and macOS, keeping the native title bar off makes the application look more compact. On Linux, keeping the native title bar on integrates better with the rest of the system.",
|
||||
"background-effects": "Enable background effects (Windows 11 only)",
|
||||
"background-effects-description": "The Mica effect adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
|
||||
"background-effects": "Enable background effects",
|
||||
"background-effects-description": "Adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
|
||||
"restart-app-button": "Restart the application to view the changes",
|
||||
"zoom-factor": "Zoom factor"
|
||||
},
|
||||
@@ -1977,6 +1981,7 @@
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Create a new child note and add it to the map",
|
||||
"create-child-note-text": "Add marker",
|
||||
"create-child-note-instruction": "Click on the map to create a new note at that location or press Escape to dismiss.",
|
||||
"unable-to-load-map": "Unable to load map."
|
||||
},
|
||||
@@ -2152,7 +2157,7 @@
|
||||
"next_theme_message": "You are currently using the legacy theme, would you like to try the new theme?",
|
||||
"next_theme_button": "Try the new theme",
|
||||
"background_effects_title": "Background effects are now stable",
|
||||
"background_effects_message": "On Windows devices, background effects are now fully stable. The background effects adds a touch of color to the user interface by blurring the background behind it. This technique is also used in other applications such as Windows Explorer.",
|
||||
"background_effects_message": "On Windows and macOS devices, background effects are now stable. The background effects adds a touch of color to the user interface by blurring the background behind it.",
|
||||
"background_effects_button": "Enable background effects",
|
||||
"new_layout_title": "New layout",
|
||||
"new_layout_message": "We’ve introduced a modernized layout for Trilium. The ribbon has been removed and seamlessly integrated into the main interface, with a new status bar and expandable sections (such as promoted attributes) taking over key functions.\n\nThe new layout is enabled by default, and can be temporarily disabled via Options → Appearance.",
|
||||
@@ -2267,5 +2272,16 @@
|
||||
"pages_other": "{{count}} pages",
|
||||
"pages_alt": "Page {{pageNumber}}",
|
||||
"pages_loading": "Loading..."
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Available on {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} tab",
|
||||
"title_other": "{{count}} tabs",
|
||||
"more_options": "More options"
|
||||
},
|
||||
"bookmark_buttons": {
|
||||
"bookmarks": "Bookmarks"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,11 +799,11 @@
|
||||
"board": "Tablero",
|
||||
"include_archived_notes": "Mostrar notas archivadas",
|
||||
"presentation": "Presentación",
|
||||
"expand_tooltip": "Expande las notas hijas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
|
||||
"expand_tooltip": "Expande las subnotas 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"
|
||||
"hide_child_notes": "Ocultar subnotas en el árbol"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "Aún no hay notas editadas en este día...",
|
||||
@@ -849,7 +849,8 @@
|
||||
"calculate": "calcular",
|
||||
"subtree_size": "(tamaño del subárbol: {{size}} en {{count}} notas)",
|
||||
"title": "Información de nota",
|
||||
"mime": "Tipo MIME"
|
||||
"mime": "Tipo MIME",
|
||||
"show_similar_notes": "Mostrar notas similares"
|
||||
},
|
||||
"note_map": {
|
||||
"open_full": "Ampliar al máximo",
|
||||
@@ -912,7 +913,8 @@
|
||||
"search_parameters": "Parámetros de búsqueda",
|
||||
"unknown_search_option": "Opción de búsqueda desconocida {{searchOptionName}}",
|
||||
"search_note_saved": "La nota de búsqueda se ha guardado en {{- notePathTitle}}",
|
||||
"actions_executed": "Las acciones han sido ejecutadas."
|
||||
"actions_executed": "Las acciones han sido ejecutadas.",
|
||||
"view_options": "Ver opciones:"
|
||||
},
|
||||
"similar_notes": {
|
||||
"title": "Notas similares",
|
||||
@@ -1015,7 +1017,13 @@
|
||||
},
|
||||
"editable_text": {
|
||||
"placeholder": "Escribe aquí el contenido de tu nota...",
|
||||
"auto-detect-language": "Detectado automáticamente"
|
||||
"auto-detect-language": "Detectado automáticamente",
|
||||
"editor_crashed_title": "El editor de texto ha dejado de responder",
|
||||
"editor_crashed_content": "Su contenido ha sido recuperado con éxito, pero puede que algunos de sus cambios más recientes no se hayan guardado.",
|
||||
"editor_crashed_details_button": "Ver más detalles...",
|
||||
"editor_crashed_details_intro": "Si experimenta este error varias veces, considere informarlo en GitHub adjuntando la siguiente información.",
|
||||
"editor_crashed_details_title": "Información técnica",
|
||||
"keeps-crashing": "El componente de edición sigue fallando. Por favor, intente reiniciar Trilium. Si el problema persiste, considere crear un informe de fallos."
|
||||
},
|
||||
"empty": {
|
||||
"open_note_instruction": "Abra una nota escribiendo el título de la nota en la entrada a continuación o elija una nota en el árbol.",
|
||||
@@ -1331,8 +1339,8 @@
|
||||
"code_mime_types": {
|
||||
"title": "Tipos MIME disponibles en el menú desplegable",
|
||||
"tooltip_syntax_highlighting": "Resaltado de sintaxis",
|
||||
"tooltip_code_block_syntax": "Bloques de Código en notas de Texto",
|
||||
"tooltip_code_note_syntax": "Notas de Código"
|
||||
"tooltip_code_block_syntax": "Bloques de código en Notas de texto",
|
||||
"tooltip_code_note_syntax": "Notas de código"
|
||||
},
|
||||
"vim_key_bindings": {
|
||||
"use_vim_keybindings_in_code_notes": "Combinaciones de teclas Vim",
|
||||
@@ -1409,16 +1417,16 @@
|
||||
"markdown": "Estilo Markdown"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Lista de aspectos destacados",
|
||||
"description": "Puede personalizar la lista de aspectos destacados que se muestra en el panel derecho:",
|
||||
"title": "Lista de puntos destacados",
|
||||
"description": "Puede personalizar la lista de puntos destacados que se muestra en el panel derecho:",
|
||||
"bold": "Texto en negrita",
|
||||
"italic": "Texto en cursiva",
|
||||
"underline": "Texto subrayado",
|
||||
"color": "Texto con color",
|
||||
"bg_color": "Texto con color de fondo",
|
||||
"visibility_title": "Visibilidad de la lista de aspectos destacados",
|
||||
"visibility_description": "Puede ocultar el widget de aspectos destacados por nota agregando una etiqueta #hideHighlightWidget.",
|
||||
"shortcut_info": "Puede configurar un método abreviado de teclado para alternar rápidamente el panel derecho (incluidos los aspectos destacados) en Opciones -> Atajos (nombre 'toggleRightPane')."
|
||||
"visibility_title": "Visibilidad de la lista de puntos destacados",
|
||||
"visibility_description": "Puede ocultar el widget de puntos destacados por nota agregando la etiqueta #hideHighlightWidget.",
|
||||
"shortcut_info": "Puede configurar un método abreviado de teclado para alternar rápidamente el panel derecho (incluidos los puntos destacados) en Opciones -> Atajos (nombre 'toggleRightPane')."
|
||||
},
|
||||
"table_of_contents": {
|
||||
"title": "Tabla de contenido",
|
||||
@@ -1606,7 +1614,7 @@
|
||||
},
|
||||
"bookmark_switch": {
|
||||
"bookmark": "Marcador",
|
||||
"bookmark_this_note": "Añadir esta nota a marcadores en el panel lateral izquierdo",
|
||||
"bookmark_this_note": "Agregar esta nota a marcadores en el panel lateral izquierdo",
|
||||
"remove_bookmark": "Eliminar marcador"
|
||||
},
|
||||
"editability_select": {
|
||||
@@ -1654,7 +1662,10 @@
|
||||
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres? Esta operación solo aplica a notas de Imagen, otras notas serán omitidas.",
|
||||
"open-in-popup": "Edición rápida",
|
||||
"archive": "Archivar",
|
||||
"unarchive": "Desarchivar"
|
||||
"unarchive": "Desarchivar",
|
||||
"open-in-a-new-window": "Abrir en una nueva ventana",
|
||||
"hide-subtree": "Ocultar subárbol",
|
||||
"show-subtree": "Mostrar subárbol"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Esta nota está compartida públicamente en {{- link}}.",
|
||||
@@ -1715,7 +1726,13 @@
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Lista de destacados",
|
||||
"options": "Opciones"
|
||||
"options": "Opciones",
|
||||
"title_with_count_one": "{{count}} punto destacado",
|
||||
"title_with_count_many": "{{count}} puntos destacados",
|
||||
"title_with_count_other": "{{count}} puntos destacados",
|
||||
"modal_title": "Configurar la lista de puntos destacados",
|
||||
"menu_configure": "Configurar la lista de puntos destacados...",
|
||||
"no_highlights": "Ningún punto destacado encontrado."
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Búsqueda rápida",
|
||||
@@ -1738,7 +1755,18 @@
|
||||
"refresh-saved-search-results": "Refrescar resultados de búsqueda guardados",
|
||||
"create-child-note": "Crear subnota",
|
||||
"unhoist": "Desanclar",
|
||||
"toggle-sidebar": "Alternar barra lateral"
|
||||
"toggle-sidebar": "Alternar barra lateral",
|
||||
"dropping-not-allowed": "No está permitido soltar notas en esta ubicación.",
|
||||
"clone-indicator-tooltip": "Esta nota tiene {{- count}} padres: {{- parents}}",
|
||||
"clone-indicator-tooltip-single": "Esta nota está clonada (1 padre adicional: {{- parent}})",
|
||||
"shared-indicator-tooltip": "Esta nota está compartida públicamente",
|
||||
"shared-indicator-tooltip-with-url": "Esta nota está compartida públicamente en: {{- url}}",
|
||||
"subtree-hidden-tooltip_one": "{{count}} subnota que está oculta del árbol",
|
||||
"subtree-hidden-tooltip_many": "{{count}} subnotas que están ocultas del árbol",
|
||||
"subtree-hidden-tooltip_other": "{{count}} subnotas que están ocultas del árbol",
|
||||
"subtree-hidden-moved-title": "Agregado a {{title}}",
|
||||
"subtree-hidden-moved-description-collection": "Esta colección oculta sus subnotas en el árbol.",
|
||||
"subtree-hidden-moved-description-other": "Las subnotas están ocultas en el árbol para esta nota."
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Mantener esta ventana en la parte superior"
|
||||
@@ -1749,10 +1777,21 @@
|
||||
"printing_pdf": "Exportando a PDF en curso..",
|
||||
"print_report_collection_content_one": "{{count}} nota en la colección no se puede imprimir porque no son compatibles o está protegida.",
|
||||
"print_report_collection_content_many": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
|
||||
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas."
|
||||
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
|
||||
"print_report_title": "Imprimir informe",
|
||||
"print_report_collection_details_button": "Ver detalles",
|
||||
"print_report_collection_details_ignored_notes": "Notas ignoradas"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "escriba el título de la nota aquí..."
|
||||
"placeholder": "escriba el título de la nota aquí...",
|
||||
"created_on": "Creado en <Value />",
|
||||
"last_modified": "Modificado en <Value />",
|
||||
"note_type_switcher_label": "Cambiar de {{type}} a:",
|
||||
"note_type_switcher_others": "Otro tipo de nota",
|
||||
"note_type_switcher_templates": "Plantilla",
|
||||
"note_type_switcher_collection": "Colección",
|
||||
"edited_notes": "Notas editadas en este día",
|
||||
"promoted_attributes": "Atributos promovidos"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "No se han encontrado notas para los parámetros de búsqueda dados.",
|
||||
@@ -1762,7 +1801,11 @@
|
||||
"configure_launchbar": "Configurar barra de lanzamiento"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "No se han devuelto filas para esta consulta"
|
||||
"no_rows": "No se han devuelto filas para esta consulta",
|
||||
"not_executed": "La consulta aún no ha sido ejecutada.",
|
||||
"failed": "La ejecución de la consulta SQL ha fallado",
|
||||
"statement_result": "Resultado de declaración",
|
||||
"execute_now": "Ejecutar ahora"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tablas"
|
||||
@@ -1781,7 +1824,8 @@
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Tabla de contenido",
|
||||
"options": "Opciones"
|
||||
"options": "Opciones",
|
||||
"no_headings": "Sin encabezados."
|
||||
},
|
||||
"watched_file_update_status": {
|
||||
"file_last_modified": "Archivo <code class=\"file-path\"></code> ha sido modificado por última vez en<span class=\"file-last-modified\"></span>.",
|
||||
@@ -1893,14 +1937,15 @@
|
||||
"open_note_in_new_tab": "Abrir nota en una pestaña nueva",
|
||||
"open_note_in_new_split": "Abrir nota en una nueva división",
|
||||
"open_note_in_new_window": "Abrir nota en una nueva ventana",
|
||||
"open_note_in_popup": "Edición rápida"
|
||||
"open_note_in_popup": "Edición rápida",
|
||||
"open_note_in_other_split": "Abrir nota en la otra división"
|
||||
},
|
||||
"electron_integration": {
|
||||
"desktop-application": "Aplicación de escritorio",
|
||||
"native-title-bar": "Barra de título nativa",
|
||||
"native-title-bar-description": "Para Windows y macOS, quitar la barra de título nativa hace que la aplicación se vea más compacta. En Linux, mantener la barra de título nativa hace que se integre mejor con el resto del sistema.",
|
||||
"background-effects": "Habilitar efectos de fondo (sólo en Windows 11)",
|
||||
"background-effects-description": "El efecto Mica agrega un fondo borroso y elegante a las ventanas de la aplicación, creando profundidad y un aspecto moderno. \"Título nativo de la barra\" debe deshabilitarse.",
|
||||
"background-effects": "Habilitar efectos de fondo",
|
||||
"background-effects-description": "Agrega un fondo borroso y elegante a las ventanas de la aplicación, creando profundidad y un aspecto moderno. \"Título nativo de la barra\" debe deshabilitarse.",
|
||||
"restart-app-button": "Reiniciar la aplicación para ver los cambios",
|
||||
"zoom-factor": "Factor de zoom"
|
||||
},
|
||||
@@ -1919,7 +1964,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Crear una nueva subnota y agregarla al mapa",
|
||||
"create-child-note-instruction": "Dé clic en el mapa para crear una nueva nota en esa ubicación o presione Escape para cancelar.",
|
||||
"unable-to-load-map": "No se puede cargar el mapa."
|
||||
"unable-to-load-map": "No se puede cargar el mapa.",
|
||||
"create-child-note-text": "Agregar marcador"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Abrir ubicación",
|
||||
@@ -1962,10 +2008,11 @@
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "Idioma no establecido",
|
||||
"configure-languages": "Configurar idiomas..."
|
||||
"configure-languages": "Configurar idiomas...",
|
||||
"help-on-languages": "Ayuda en idiomas de contenido..."
|
||||
},
|
||||
"content_language": {
|
||||
"title": "Contenido de idiomas",
|
||||
"title": "Idiomas de contenido",
|
||||
"description": "Seleccione uno o más idiomas que deben aparecer en la selección del idioma en la sección Propiedades Básicas de una nota de texto de solo lectura o editable. Esto permitirá características tales como corrección de ortografía o soporte de derecha a izquierda."
|
||||
},
|
||||
"switch_layout_button": {
|
||||
@@ -1980,7 +2027,8 @@
|
||||
"button_title": "Exportar diagrama como PNG"
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "El diagrama no pudo ser exportado a PNG."
|
||||
"export_to_png": "El diagrama no pudo ser exportado a PNG.",
|
||||
"export_to_svg": "El diagrama no pudo ser exportado a SVG."
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "Apariencia",
|
||||
@@ -2083,9 +2131,12 @@
|
||||
"next_theme_message": "Estás usando actualmente el tema heredado. ¿Te gustaría probar el nuevo tema?",
|
||||
"next_theme_button": "Prueba el nuevo tema",
|
||||
"background_effects_title": "Los efectos de fondo son ahora estables",
|
||||
"background_effects_message": "En los dispositivos Windows, los efectos de fondo ya son totalmente estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás. Esta técnica también se utiliza en otras aplicaciones como el Explorador de Windows.",
|
||||
"background_effects_message": "En los dispositivos Windows y macOS, los efectos de fondo ya son estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás.",
|
||||
"background_effects_button": "Activar efectos de fondo",
|
||||
"dismiss": "Desestimar"
|
||||
"dismiss": "Desestimar",
|
||||
"new_layout_title": "Nuevo diseño",
|
||||
"new_layout_message": "Hemos introducido un diseño modernizado para Trilium. La cinta se ha eliminado y se ha integrado perfectamente en la interfaz principal, con una nueva barra de estado y secciones ampliables (como los atributos promovidos) que tienen funciones clave.\n\nEl nuevo diseño está habilitado por defecto, y puede ser deshabilitado temporalmente a través de Opciones → Apariencia.",
|
||||
"new_layout_button": "Más información"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Rendimiento",
|
||||
@@ -2100,7 +2151,10 @@
|
||||
},
|
||||
"settings_appearance": {
|
||||
"related_code_blocks": "Esquema de colores para bloques de código en notas de texto",
|
||||
"related_code_notes": "Esquema de colores para notas de código"
|
||||
"related_code_notes": "Esquema de colores para notas de código",
|
||||
"ui": "Interfaz de usuario",
|
||||
"ui_old_layout": "Antiguo diseño",
|
||||
"ui_new_layout": "Nuevo diseño"
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
@@ -2148,7 +2202,12 @@
|
||||
"attributes_other": "{{count}} atributos",
|
||||
"note_paths_one": "{{count}} ruta",
|
||||
"note_paths_many": "{{count}} rutas",
|
||||
"note_paths_other": "{{count}} rutas"
|
||||
"note_paths_other": "{{count}} rutas",
|
||||
"language_title": "Cambiar el idioma del contenido",
|
||||
"note_info_title": "Ver información de la nota (p. e., fechas, tamaño de la nota)",
|
||||
"attributes_title": "Atributos propios y atributos heredados",
|
||||
"note_paths_title": "Rutas de nota",
|
||||
"code_note_switcher": "Cambiar modo de idioma"
|
||||
},
|
||||
"pdf": {
|
||||
"attachments_one": "{{count}} adjunto",
|
||||
@@ -2159,6 +2218,78 @@
|
||||
"layers_other": "{{count}} capas",
|
||||
"pages_one": "{{count}} página",
|
||||
"pages_many": "{{count}} páginas",
|
||||
"pages_other": "{{count}} páginas"
|
||||
"pages_other": "{{count}} páginas",
|
||||
"pages_alt": "Página {{pageNumber}}",
|
||||
"pages_loading": "Cargando..."
|
||||
},
|
||||
"experimental_features": {
|
||||
"title": "Opciones experimentales",
|
||||
"disclaimer": "Estas opciones son experimentales y pueden causar inestabilidad. Úselas con precaución.",
|
||||
"new_layout_name": "Nuevo diseño",
|
||||
"new_layout_description": "Pruebe el nuevo diseño para tener un aspecto más moderno y usabilidad mejorada. Sujeto a grandes cambios en las próximas versiones."
|
||||
},
|
||||
"popup-editor": {
|
||||
"maximize": "Cambiar a editor completo"
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "Error de comunicación con el servidor",
|
||||
"unknown_http_error_content": "Código de estado: {{statusCode}}\nURL: {{method}} {{url}}\nMensaje: {{message}}",
|
||||
"traefik_blocks_requests": "Si está usando el proxy inverso Traefik, este introdujo un cambio que afecta la comunicación con el servidor."
|
||||
},
|
||||
"tab_history_navigation_buttons": {
|
||||
"go-back": "Volver a la nota anterior",
|
||||
"go-forward": "Avanzar a la siguiente nota"
|
||||
},
|
||||
"breadcrumb": {
|
||||
"hoisted_badge": "Anclada",
|
||||
"hoisted_badge_title": "Desanclar",
|
||||
"workspace_badge": "Espacio de trabajo",
|
||||
"scroll_to_top_title": "Saltar al inicio de la nota",
|
||||
"create_new_note": "Crear nueva subnota",
|
||||
"empty_hide_archived_notes": "Ocultar notas archivadas"
|
||||
},
|
||||
"breadcrumb_badges": {
|
||||
"read_only_explicit": "Sólo lectura",
|
||||
"read_only_explicit_description": "Esta nota se ha fijado manualmente como sólo lectura.\nHaga clic para editarla temporalmente.",
|
||||
"read_only_auto": "Sólo lectura automática",
|
||||
"read_only_auto_description": "Esta nota se fijó automáticamente con el modo de sólo lectura por razones de rendimiento. Este límite automático es ajustable desde los ajustes.\n\nHaga clic para editarla temporalmente.",
|
||||
"read_only_temporarily_disabled": "Temporalmente editable",
|
||||
"read_only_temporarily_disabled_description": "Esta nota actualmente es editable, pero normalmente es de sólo lectura. La nota volverá a ser de sólo lectura tan pronto como navegue a otra nota.\n\nHaga clic para volver a habilitar el modo de sólo lectura.",
|
||||
"shared_publicly": "Compartida públicamente",
|
||||
"shared_locally": "Compartida localmente",
|
||||
"shared_copy_to_clipboard": "Copiar enlace al portapapeles",
|
||||
"shared_open_in_browser": "Abrir enlace en el navegador",
|
||||
"shared_unshare": "Eliminar compartido",
|
||||
"clipped_note_description": "Esta nota fue tomada originalmente de {{url}}.\n\nHaga clic para navegar a la página web de origen.",
|
||||
"execute_script": "Ejecutar script",
|
||||
"execute_script_description": "Esta nota es una nota de script. Haga clic para ejecutar el script.",
|
||||
"execute_sql": "Ejecutar SQL",
|
||||
"execute_sql_description": "Esta nota es una nota SQL. Haga clic para ejecutar la consulta SQL.",
|
||||
"save_status_saved": "Guardado",
|
||||
"save_status_saving": "Guardando...",
|
||||
"save_status_unsaved": "Sin guardar",
|
||||
"save_status_error": "Fallo al guardar",
|
||||
"save_status_saving_tooltip": "Los cambios están siendo guardados.",
|
||||
"save_status_unsaved_tooltip": "Hay cambios sin guardar. Se guardarán automáticamente en un momento.",
|
||||
"save_status_error_tooltip": "Se produjo un error al guardar la nota. Si es posible, trate de copiar el contenido de la nota en otro lugar y recargar la aplicación.",
|
||||
"clipped_note": "Clip web"
|
||||
},
|
||||
"attributes_panel": {
|
||||
"title": "Atributos de nota"
|
||||
},
|
||||
"right_pane": {
|
||||
"empty_message": "Nada que mostrar para esta nota",
|
||||
"empty_button": "Ocultar el panel",
|
||||
"toggle": "Alternar panel derecho",
|
||||
"custom_widget_go_to_source": "Ir al código fuente"
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Disponible en {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} pestaña",
|
||||
"title_many": "{{count}} pestañas",
|
||||
"title_other": "{{count}} pestañas",
|
||||
"more_options": "Más opciones"
|
||||
}
|
||||
}
|
||||
|
||||
5
apps/client/src/translations/ga/translation.json
Normal file
5
apps/client/src/translations/ga/translation.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"global_menu": {
|
||||
"about": "Maidir le Trilium Notes"
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Eror kritikal",
|
||||
"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."
|
||||
"message": "Telah terjadi eror kritikal 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": {
|
||||
"title": "Gagal menginisialisasi widget",
|
||||
@@ -36,7 +36,12 @@
|
||||
"add_link": {
|
||||
"add_link": "Tambah tautan",
|
||||
"help_on_links": "Bantuan pada tautan",
|
||||
"note": "Catatan"
|
||||
"note": "Catatan",
|
||||
"search_note": "cari catatan berdasarkan nama",
|
||||
"link_title_mirrors": "judul tautan mencerminkan judul catatan saat ini",
|
||||
"link_title_arbitrary": "judul tautan dapat diubah secara bebas",
|
||||
"link_title": "Judul tautan",
|
||||
"button_add_link": "Tambah tautan"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix_multiple": "Edit prefiks cabang untuk {{count}} cabang",
|
||||
@@ -73,5 +78,8 @@
|
||||
"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)."
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Duplikat catatan ke…"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,10 @@
|
||||
"apply-bulk-actions": "Applica azioni in blocco",
|
||||
"converted-to-attachments": "{{count}} note sono state convertite in allegati.",
|
||||
"convert-to-attachment-confirm": "Sei sicuro di voler convertire le note selezionate in allegati delle note principali? Questa operazione si applica solo alle note immagine, le altre note verranno ignorate.",
|
||||
"open-in-popup": "Modifica rapida"
|
||||
"open-in-popup": "Modifica rapida",
|
||||
"open-in-a-new-window": "Apri in una nuova finestra",
|
||||
"hide-subtree": "Nascondi sottostruttura",
|
||||
"show-subtree": "Mostra sottoalbero"
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"cut": "Taglia",
|
||||
@@ -1378,7 +1381,8 @@
|
||||
"expand_tooltip": "Espande i figli diretti di questa raccolta (a un livello di profondità). Per ulteriori opzioni, premere la freccia a destra.",
|
||||
"expand_first_level": "Espandi figli diretti",
|
||||
"expand_nth_level": "Espandi {{depth}} livelli",
|
||||
"expand_all_levels": "Espandi tutti i livelli"
|
||||
"expand_all_levels": "Espandi tutti i livelli",
|
||||
"hide_child_notes": "Nascondi note secondarie nell'albero"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "Nessuna nota modificata per questo giorno...",
|
||||
@@ -1899,7 +1903,13 @@
|
||||
"clone-indicator-tooltip": "Questa nota ha {{- count}} genitori: {{- parents}}",
|
||||
"clone-indicator-tooltip-single": "Questa nota è stata clonata (1 genitore aggiuntivo: {{- parent}})",
|
||||
"shared-indicator-tooltip": "Questa nota è condivisa pubblicamente",
|
||||
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}"
|
||||
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}",
|
||||
"subtree-hidden-tooltip_one": "{{count}} nota secondaria nascosta dall'albero",
|
||||
"subtree-hidden-tooltip_many": "{{count}} note secondarie nascoste dall'albero",
|
||||
"subtree-hidden-tooltip_other": "{{count}} note secondarie nascoste dall'albero",
|
||||
"subtree-hidden-moved-title": "Aggiunto a {{title}}",
|
||||
"subtree-hidden-moved-description-collection": "Questa raccolta nasconde le sue note secondarie nell'albero.",
|
||||
"subtree-hidden-moved-description-other": "Le note secondarie sono nascoste nell'albero di questa nota."
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Mantieni la finestra in primo piano"
|
||||
@@ -1934,7 +1944,11 @@
|
||||
"configure_launchbar": "Configura Launchbar"
|
||||
},
|
||||
"sql_result": {
|
||||
"no_rows": "Nessuna riga è stata restituita per questa query"
|
||||
"no_rows": "Nessuna riga è stata restituita per questa query",
|
||||
"not_executed": "La query non è stata ancora eseguita.",
|
||||
"failed": "Esecuzione query SQL non riuscita",
|
||||
"statement_result": "Risultato della dichiarazione",
|
||||
"execute_now": "Esegui ora"
|
||||
},
|
||||
"watched_file_update_status": {
|
||||
"file_last_modified": "Il file <code class=\"file-path\"></code> è stato modificato l'ultima volta il <span class=\"file-last-modified\"></span>.",
|
||||
|
||||
@@ -1757,8 +1757,8 @@
|
||||
"desktop-application": "デスクトップアプリケーション",
|
||||
"native-title-bar": "ネイティブタイトルバー",
|
||||
"native-title-bar-description": "WindowsとmacOSでは、ネイティブタイトルバーをオフにしておくと、アプリケーションがよりコンパクトに見えます。Linuxでは、ネイティブタイトルバーを表示したままの方が、他のシステムとの統一性が高まります。",
|
||||
"background-effects": "背景効果を有効化(Windows 11のみ)",
|
||||
"background-effects-description": "Mica効果は、アプリのウィンドウにぼかされたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
|
||||
"background-effects": "背景効果を有効化",
|
||||
"background-effects-description": "アプリウィンドウにぼかしの効いたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
|
||||
"restart-app-button": "アプリケーションを再起動して変更を反映",
|
||||
"zoom-factor": "ズーム倍率"
|
||||
},
|
||||
@@ -2008,7 +2008,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "新しい子ノートを作成し、マップに追加する",
|
||||
"create-child-note-instruction": "地図をクリックしてその場所に新しいノートを作成するか、Esc キーを押して閉じます。",
|
||||
"unable-to-load-map": "マップを読み込めません。"
|
||||
"unable-to-load-map": "マップを読み込めません。",
|
||||
"create-child-note-text": "マーカーを追加"
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "現在位置を表示",
|
||||
@@ -2044,7 +2045,7 @@
|
||||
"next_theme_message": "現在、レガシーテーマを使用しています。新しいテーマを試してみませんか?",
|
||||
"next_theme_button": "新しいテーマを試す",
|
||||
"background_effects_title": "背景効果が安定しました",
|
||||
"background_effects_message": "Windowsデバイスでは、背景効果が完全に安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。この技術は、Windowsエクスプローラーなどの他のアプリケーションでも使用されています。",
|
||||
"background_effects_message": "WindowsおよびmacOSデバイスで、背景効果が安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。",
|
||||
"background_effects_button": "背景効果を有効にする",
|
||||
"dismiss": "却下",
|
||||
"new_layout_title": "新しいレイアウト",
|
||||
@@ -2253,5 +2254,12 @@
|
||||
"pages_other": "{{count}} ページ",
|
||||
"pages_alt": "ページ {{pageNumber}}",
|
||||
"pages_loading": "読み込み中..."
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "{{platform}} で利用可能"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_other": "{{count}} タブ",
|
||||
"more_options": "その他のオプション"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1761,8 +1761,8 @@
|
||||
"show-recent-notes": "Afișează notițele recente"
|
||||
},
|
||||
"electron_integration": {
|
||||
"background-effects": "Activează efectele de fundal (doar pentru Windows 11)",
|
||||
"background-effects-description": "Efectul Mica adaugă un fundal estompat și elegant ferestrelor aplicațiilor, creând profunzime și un aspect modern. Opțiunea „Bară de titlu nativă” trebuie să fie dezactivată.",
|
||||
"background-effects": "Activează efectele de fundal",
|
||||
"background-effects-description": "Adaugă un fundal estompat și elegant ferestrelor aplicațiilor, creând profunzime și un aspect modern. Opțiunea „Bară de titlu nativă” trebuie să fie dezactivată.",
|
||||
"desktop-application": "Aplicația desktop",
|
||||
"native-title-bar": "Bară de titlu nativă",
|
||||
"native-title-bar-description": "Pentru Windows și macOS, dezactivarea bării de titlu native face aplicația să pară mai compactă. Pe Linux, păstrarea bării integrează mai bine aplicația cu restul sistemului de operare.",
|
||||
@@ -1781,7 +1781,8 @@
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Crează o notiță nouă și adaug-o pe hartă",
|
||||
"unable-to-load-map": "Nu s-a putut încărca harta.",
|
||||
"create-child-note-instruction": "Click pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a anula."
|
||||
"create-child-note-instruction": "Click pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a anula.",
|
||||
"create-child-note-text": "Adaugă marcaj"
|
||||
},
|
||||
"duration": {
|
||||
"days": "zile",
|
||||
@@ -2127,7 +2128,7 @@
|
||||
},
|
||||
"call_to_action": {
|
||||
"background_effects_title": "Efectele de fundal sunt acum stabile",
|
||||
"background_effects_message": "Pe dispozitive cu Windows, efectele de fundal sunt complet stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei. Această tehnică este folosită și în alte aplicații precum Windows Explorer.",
|
||||
"background_effects_message": "Pe dispozitive cu Windows și macOS, efectele de fundal sunt stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei.",
|
||||
"background_effects_button": "Activează efectele de fundal",
|
||||
"next_theme_title": "Încercați noua temă Trilium",
|
||||
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
|
||||
@@ -2281,5 +2282,14 @@
|
||||
"pages_other": "{{count}} de pagini",
|
||||
"pages_alt": "Pagina {{pageNumber}}",
|
||||
"pages_loading": "Încărcare..."
|
||||
},
|
||||
"platform_indicator": {
|
||||
"available_on": "Disponibil pe {{platform}}"
|
||||
},
|
||||
"mobile_tab_switcher": {
|
||||
"title_one": "{{count}} tab",
|
||||
"title_few": "{{count}} taburi",
|
||||
"title_other": "{{count}} de taburi",
|
||||
"more_options": "Mai multe opțiuni"
|
||||
}
|
||||
}
|
||||
|
||||
83
apps/client/src/widgets/Backlinks.css
Normal file
83
apps/client/src/widgets/Backlinks.css
Normal file
@@ -0,0 +1,83 @@
|
||||
.tn-backlinks-widget .backlinks-items {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: static;
|
||||
width: unset;
|
||||
|
||||
> li {
|
||||
--border-radius: 8px;
|
||||
|
||||
max-width: 600px;
|
||||
padding: 10px 20px;
|
||||
background: var(--card-background-color);
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
/* Card header */
|
||||
& > span:first-child {
|
||||
display: block;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
/* Note path */
|
||||
> small {
|
||||
flex: 100%;
|
||||
order: -1;
|
||||
font-size: .65rem;
|
||||
|
||||
.note-path {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note icon */
|
||||
> .tn-icon {
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
|
||||
/* Note title */
|
||||
> a {
|
||||
margin-inline-start: 4px;
|
||||
color: currentColor;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Card content - excerpt */
|
||||
.backlink-excerpt {
|
||||
all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */
|
||||
display: block;
|
||||
|
||||
margin: 8px 0;
|
||||
border-radius: 4px;
|
||||
background: var(--quick-search-result-content-background);
|
||||
padding: 8px;
|
||||
font-size: .75rem;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./Backlinks.css";
|
||||
|
||||
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
|
||||
import { VNode } from "preact";
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
@@ -54,21 +56,12 @@ export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [
|
||||
OpenTriliumApiDocsButton,
|
||||
SaveToNoteButton,
|
||||
RelationMapButtons,
|
||||
GeoMapButtons,
|
||||
CopyImageReferenceButton,
|
||||
ExportImageButtons,
|
||||
InAppHelpButton,
|
||||
Backlinks
|
||||
];
|
||||
|
||||
export const MOBILE_FLOATING_BUTTONS: FloatingButtonsList = [
|
||||
RefreshBackendLogButton,
|
||||
EditButton,
|
||||
RelationMapButtons,
|
||||
ExportImageButtons,
|
||||
Backlinks
|
||||
];
|
||||
|
||||
/**
|
||||
* Floating buttons that should be hidden in popup editor (Quick edit).
|
||||
*/
|
||||
@@ -98,10 +91,10 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
|
||||
/>;
|
||||
}
|
||||
|
||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
|
||||
function ToggleReadOnlyButton({ note, 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) || isSavedSqlite)
|
||||
&& note.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && <FloatingButton
|
||||
@@ -243,17 +236,6 @@ function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingB
|
||||
);
|
||||
}
|
||||
|
||||
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
|
||||
const isEnabled = viewType === "geoMap" && !isReadOnly;
|
||||
return isEnabled && (
|
||||
<FloatingButton
|
||||
icon="bx bx-plus-circle"
|
||||
text={t("geo-map.create-child-note-title")}
|
||||
onClick={() => triggerEvent("geoMapCreateChildNote")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonContext) {
|
||||
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
|
||||
const isEnabled = (
|
||||
@@ -305,7 +287,7 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
|
||||
|
||||
function InAppHelpButton({ note }: FloatingButtonContext) {
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
const isEnabled = !!helpUrl;
|
||||
const isEnabled = note.type !== "book" && !!helpUrl;
|
||||
|
||||
return isEnabled && (
|
||||
<FloatingButton
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
font-family: var(--detail-font-family);
|
||||
font-size: var(--detail-font-size);
|
||||
contain: none;
|
||||
|
||||
&.fixed-tree {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.fixed-note-tree-container {
|
||||
height: 60%;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
overflow: auto;
|
||||
|
||||
.tree-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.prefers-centered-content .note-detail {
|
||||
@@ -12,4 +36,4 @@ body.prefers-centered-content .note-detail {
|
||||
|
||||
.note-detail > * {
|
||||
contain: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "./NoteDetail.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { isValidElement, VNode } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -12,8 +13,9 @@ import { t } from "../services/i18n";
|
||||
import protected_session_holder from "../services/protected_session_holder";
|
||||
import toast from "../services/toast.js";
|
||||
import { dynamicRequire, isElectron, isMobile } from "../services/utils";
|
||||
import NoteTreeWidget from "./note_tree";
|
||||
import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
|
||||
import { useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import { useLegacyWidget, useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||
import { NoteListWithLinks } from "./react/NoteList";
|
||||
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||
|
||||
@@ -36,6 +38,7 @@ export default function NoteDetail() {
|
||||
const [ noteTypesToRender, setNoteTypesToRender ] = useState<{ [ key in ExtendedNoteType ]?: (props: TypeWidgetProps) => VNode }>({});
|
||||
const [ activeNoteType, setActiveNoteType ] = useState<ExtendedNoteType>();
|
||||
const widgetRequestId = useRef(0);
|
||||
const hasFixedTree = note && noteContext?.hoistedNoteId === "_lbMobileRoot" && isMobile() && note.noteId.startsWith("_lbMobile");
|
||||
|
||||
const props: TypeWidgetProps = {
|
||||
note: note!,
|
||||
@@ -119,13 +122,6 @@ export default function NoteDetail() {
|
||||
}
|
||||
});
|
||||
|
||||
// Fixed tree for launch bar config on mobile.
|
||||
useEffect(() => {
|
||||
if (!isMobile) return;
|
||||
const hasFixedTree = noteContext?.hoistedNoteId === "_lbMobileRoot";
|
||||
document.body.classList.toggle("force-fixed-tree", hasFixedTree);
|
||||
}, [ note ]);
|
||||
|
||||
// Handle toast notifications.
|
||||
useEffect(() => {
|
||||
if (!isElectron()) return;
|
||||
@@ -215,8 +211,13 @@ export default function NoteDetail() {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
class={`component note-detail ${isFullHeight ? "full-height" : ""}`}
|
||||
class={clsx("component note-detail", {
|
||||
"full-height": isFullHeight,
|
||||
"fixed-tree": hasFixedTree
|
||||
})}
|
||||
>
|
||||
{hasFixedTree && <FixedTree noteContext={noteContext} />}
|
||||
|
||||
{Object.entries(noteTypesToRender).map(([ itemType, Element ]) => {
|
||||
return <NoteDetailWrapper
|
||||
Element={Element}
|
||||
@@ -231,6 +232,11 @@ export default function NoteDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
function FixedTree({ noteContext }: { noteContext: NoteContext }) {
|
||||
const [ treeEl ] = useLegacyWidget(() => new NoteTreeWidget(), { noteContext });
|
||||
return <div class="fixed-note-tree-container">{treeEl}</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a single note type widget, in order to keep it in the DOM even after the user has switched away to another note type. This allows faster loading of the same note type again. The properties are cached, so that they are updated only
|
||||
* while the widget is visible, to avoid rendering in the background. When not visible, the DOM element is simply hidden.
|
||||
|
||||
@@ -5,6 +5,7 @@ import clsx from "clsx";
|
||||
import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact";
|
||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../components/note_context";
|
||||
import FAttribute from "../entities/fattribute";
|
||||
import FNote from "../entities/fnote";
|
||||
import { Attribute } from "../services/attribute_parser";
|
||||
@@ -40,8 +41,8 @@ type OnChangeEventData = TargetedEvent<HTMLInputElement, Event> | InputEvent | J
|
||||
type OnChangeListener = (e: OnChangeEventData) => Promise<void>;
|
||||
|
||||
export default function PromotedAttributes() {
|
||||
const { note, componentId } = useNoteContext();
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
const { note, componentId, noteContext } = useNoteContext();
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext);
|
||||
return <PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />;
|
||||
}
|
||||
|
||||
@@ -74,12 +75,12 @@ export function PromotedAttributesContent({ note, componentId, cells, setCells }
|
||||
*
|
||||
* The cells are returned as a state since they can also be altered internally if needed, for example to add a new empty cell.
|
||||
*/
|
||||
export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string, noteContext: NoteContext | undefined): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
const [ cells, setCells ] = useState<Cell[]>();
|
||||
|
||||
function refresh() {
|
||||
if (!note || viewType === "table") {
|
||||
if (!note || viewType === "table" || noteContext?.viewScope?.viewMode !== "default") {
|
||||
setCells([]);
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +125,7 @@ export function usePromotedAttributeData(note: FNote | null | undefined, compone
|
||||
setCells(cells);
|
||||
}
|
||||
|
||||
useEffect(refresh, [ note, viewType ]);
|
||||
useEffect(refresh, [ note, viewType, noteContext ]);
|
||||
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
|
||||
if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) {
|
||||
refresh();
|
||||
|
||||
@@ -29,7 +29,6 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
const isVerticalLayout = !isHorizontalLayout;
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus();
|
||||
const isMobileLocal = isMobile();
|
||||
const logoRef = useRef<SVGSVGElement>(null);
|
||||
useStaticTooltip(logoRef);
|
||||
|
||||
@@ -44,9 +43,12 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
|
||||
</div>}
|
||||
</>}
|
||||
noDropdownListStyle
|
||||
onShown={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover") : undefined}
|
||||
onHidden={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover") : undefined}
|
||||
mobileBackdrop
|
||||
>
|
||||
{isMobile() && <>
|
||||
<MenuItem command="searchNotes" icon="bx bx-search" text={t("global_menu.search_notes")} />
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
<MenuItem command="openNewWindow" icon="bx bx-window-open" text={t("global_menu.open_new_window")} />
|
||||
<MenuItem command="showShareSubtree" icon="bx bx-share-alt" text={t("global_menu.show_shared_notes_subtree")} />
|
||||
@@ -107,8 +109,7 @@ function BrowserOnlyOptions() {
|
||||
|
||||
function DevelopmentOptions({ dropStart }: { dropStart: boolean }) {
|
||||
return <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem disabled>Development Options</FormListItem>
|
||||
<FormListHeader text="Development Options" />
|
||||
<FormDropdownSubmenu icon="bx bx-test-tube" title="Experimental features" dropStart={dropStart}>
|
||||
{experimentalFeatures.map((feature) => (
|
||||
<ExperimentalFeatureToggle key={feature.id} experimentalFeature={feature as ExperimentalFeature} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.note-list-widget {
|
||||
min-height: 0;
|
||||
max-width: var(--max-content-width); /* Inherited from .note-split */
|
||||
|
||||
|
||||
overflow: auto;
|
||||
contain: none !important;
|
||||
}
|
||||
@@ -11,10 +11,6 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.note-list-widget .note-list {
|
||||
padding-block: 10px;
|
||||
}
|
||||
|
||||
.note-list-widget.full-height,
|
||||
.note-list-widget.full-height .note-list-widget-content {
|
||||
height: 100%;
|
||||
@@ -25,8 +21,12 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
|
||||
}
|
||||
|
||||
/* #region Pagination */
|
||||
.note-list-pager span.current-page {
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
.note-list-pager {
|
||||
font-size: 1rem;
|
||||
|
||||
span.current-page {
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
/* #endregion */
|
||||
|
||||
@@ -2,25 +2,28 @@
|
||||
position: relative;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
--card-font-size: 0.9em;
|
||||
--card-line-height: 1.2;
|
||||
--card-padding: 0.6em;
|
||||
}
|
||||
|
||||
body.mobile .board-view {
|
||||
scroll-snap-type: x mandatory;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.board-view-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
padding-inline: 12px;
|
||||
padding-block: 4px;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
body.mobile .board-view-container {
|
||||
scroll-snap-type: x mandatory;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.board-view-container .board-column {
|
||||
@@ -352,4 +355,4 @@ body.mobile .board-view-container .board-column {
|
||||
font-size: 0.9em;
|
||||
max-width: 200px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import "./index.css";
|
||||
import { ColumnMap, getBoardData } from "./data";
|
||||
|
||||
import { createContext, TargetedKeyboardEvent } from "preact";
|
||||
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import toast from "../../../services/toast";
|
||||
import CollectionProperties from "../../note_bars/CollectionProperties";
|
||||
import FormTextArea from "../../react/FormTextArea";
|
||||
import FormTextBox from "../../react/FormTextBox";
|
||||
import { useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
|
||||
import Icon from "../../react/Icon";
|
||||
import { t } from "../../../services/i18n";
|
||||
import Api from "./api";
|
||||
import FormTextBox from "../../react/FormTextBox";
|
||||
import { createContext, TargetedKeyboardEvent } from "preact";
|
||||
import { onWheelHorizontalScroll } from "../../widget_utils";
|
||||
import Column from "./column";
|
||||
import BoardApi from "./api";
|
||||
import FormTextArea from "../../react/FormTextArea";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import NoteAutocomplete from "../../react/NoteAutocomplete";
|
||||
import toast from "../../../services/toast";
|
||||
import { onWheelHorizontalScroll } from "../../widget_utils";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import Api from "./api";
|
||||
import BoardApi from "./api";
|
||||
import Column from "./column";
|
||||
import { ColumnMap, getBoardData } from "./data";
|
||||
|
||||
export interface BoardViewData {
|
||||
columns?: BoardColumnData[];
|
||||
@@ -145,7 +148,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
||||
const insertBefore = mouseX < columnMiddle;
|
||||
|
||||
// Calculate the target position
|
||||
let targetIndex = insertBefore ? index : index + 1;
|
||||
const targetIndex = insertBefore ? index : index + 1;
|
||||
|
||||
setColumnDropPosition(targetIndex);
|
||||
}, [draggedColumn]);
|
||||
@@ -159,15 +162,14 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
||||
}, [draggedColumn, columnDropPosition, handleColumnDrop]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="board-view"
|
||||
onWheel={onWheelHorizontalScroll}
|
||||
>
|
||||
<div className="board-view">
|
||||
<CollectionProperties note={parentNote} />
|
||||
<BoardViewContext.Provider value={boardViewContext}>
|
||||
{byColumn && columns && <div
|
||||
className="board-view-container"
|
||||
onDragOver={handleColumnDragOver}
|
||||
onDrop={handleContainerDrop}
|
||||
onWheel={onWheelHorizontalScroll}
|
||||
>
|
||||
{columns.map((column, index) => (
|
||||
<>
|
||||
@@ -194,7 +196,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
|
||||
</div>}
|
||||
</BoardViewContext.Provider>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMode: boolean }) {
|
||||
@@ -218,26 +220,26 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo
|
||||
tabIndex={300}
|
||||
>
|
||||
{!isCreatingNewColumn
|
||||
? <>
|
||||
<Icon icon="bx bx-plus" />{" "}
|
||||
{t("board_view.add-column")}
|
||||
</>
|
||||
: (
|
||||
<TitleEditor
|
||||
placeholder={t("board_view.add-column-placeholder")}
|
||||
save={async (columnName) => {
|
||||
const created = await api.addNewColumn(columnName);
|
||||
if (!created) {
|
||||
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
|
||||
}
|
||||
}}
|
||||
dismiss={() => setIsCreatingNewColumn(false)}
|
||||
isNewItem
|
||||
mode={isInRelationMode ? "relation" : "normal"}
|
||||
/>
|
||||
)}
|
||||
? <>
|
||||
<Icon icon="bx bx-plus" />{" "}
|
||||
{t("board_view.add-column")}
|
||||
</>
|
||||
: (
|
||||
<TitleEditor
|
||||
placeholder={t("board_view.add-column-placeholder")}
|
||||
save={async (columnName) => {
|
||||
const created = await api.addNewColumn(columnName);
|
||||
if (!created) {
|
||||
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
|
||||
}
|
||||
}}
|
||||
dismiss={() => setIsCreatingNewColumn(false)}
|
||||
isNewItem
|
||||
mode={isInRelationMode ? "relation" : "normal"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
|
||||
@@ -302,26 +304,26 @@ export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, is
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<NoteAutocomplete
|
||||
inputRef={inputRef}
|
||||
noteId={currentValue ?? ""}
|
||||
opts={{
|
||||
hideAllButtons: true,
|
||||
allowCreatingNotes: true
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
dismiss();
|
||||
}
|
||||
}}
|
||||
onBlur={() => dismiss()}
|
||||
noteIdChanged={(newValue) => {
|
||||
save(newValue);
|
||||
dismiss();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<NoteAutocomplete
|
||||
inputRef={inputRef}
|
||||
noteId={currentValue ?? ""}
|
||||
opts={{
|
||||
hideAllButtons: true,
|
||||
allowCreatingNotes: true
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
dismiss();
|
||||
}
|
||||
}}
|
||||
onBlur={() => dismiss()}
|
||||
noteIdChanged={(newValue) => {
|
||||
save(newValue);
|
||||
dismiss();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,16 @@
|
||||
outline: 0;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
padding: 10px;
|
||||
padding: 0;
|
||||
|
||||
@media (max-width: 991px) {
|
||||
padding: 0;
|
||||
|
||||
th {
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-view a,
|
||||
@@ -43,6 +52,7 @@
|
||||
--fc-border-color: var(--main-border-color);
|
||||
--fc-neutral-bg-color: var(--launcher-pane-background-color);
|
||||
--fc-list-event-hover-bg-color: var(--left-pane-item-hover-background);
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.calendar-container .fc-list-sticky .fc-list-day > * {
|
||||
@@ -59,33 +69,35 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* #region Header */
|
||||
.calendar-view .calendar-header {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.calendar-view .collection-properties {
|
||||
.center-container {
|
||||
justify-content: center;
|
||||
|
||||
.calendar-view .calendar-header .btn {
|
||||
min-width: unset !important;
|
||||
}
|
||||
.title {
|
||||
min-width: 150px;
|
||||
font-size: 1.2em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-view .calendar-header > .title {
|
||||
flex-grow: 1;
|
||||
font-size: 1.3rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
>div {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
body.desktop:not(.zen) .calendar-view .calendar-header {
|
||||
padding-block-start: 4px;
|
||||
padding-inline-end: 5em;
|
||||
}
|
||||
.right-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.search-result-widget-content .calendar-view .calendar-header {
|
||||
padding-inline-end: unset !important;
|
||||
.center-container {
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
min-width: 110px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
/* #region Events */
|
||||
|
||||
@@ -93,7 +105,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
|
||||
* week, month, year views
|
||||
*/
|
||||
|
||||
.calendar-container a.fc-event {
|
||||
.calendar-container a.fc-event {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -126,7 +138,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
|
||||
.calendar-view a.fc-timegrid-event,
|
||||
.calendar-view a.fc-daygrid-event {
|
||||
--border-color: transparent;
|
||||
|
||||
|
||||
border: 2px solid;
|
||||
border-left-width: 4px;
|
||||
border-color: var(--border-color) var(--border-color) var(--border-color)
|
||||
@@ -175,7 +187,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* List view
|
||||
*/
|
||||
|
||||
@@ -188,4 +200,4 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
|
||||
--fc-event-border-color: var(--custom-color);
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
/* #endregion */
|
||||
|
||||
@@ -14,8 +14,11 @@ import dialog from "../../../services/dialog";
|
||||
import froca from "../../../services/froca";
|
||||
import { t } from "../../../services/i18n";
|
||||
import { isMobile } from "../../../services/utils";
|
||||
import CollectionProperties from "../../note_bars/CollectionProperties";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import Button, { ButtonGroup } from "../../react/Button";
|
||||
import Dropdown from "../../react/Dropdown";
|
||||
import { FormListItem } from "../../react/FormList";
|
||||
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";
|
||||
@@ -41,24 +44,28 @@ const CALENDAR_VIEWS = [
|
||||
{
|
||||
type: "timeGridWeek",
|
||||
name: t("calendar.week"),
|
||||
icon: "bx bx-calendar-week",
|
||||
previousText: t("calendar.week_previous"),
|
||||
nextText: t("calendar.week_next")
|
||||
},
|
||||
{
|
||||
type: "dayGridMonth",
|
||||
name: t("calendar.month"),
|
||||
icon: "bx bx-calendar",
|
||||
previousText: t("calendar.month_previous"),
|
||||
nextText: t("calendar.month_next")
|
||||
},
|
||||
{
|
||||
type: "multiMonthYear",
|
||||
name: t("calendar.year"),
|
||||
icon: "bx bx-layer",
|
||||
previousText: t("calendar.year_previous"),
|
||||
nextText: t("calendar.year_next")
|
||||
},
|
||||
{
|
||||
type: "listMonth",
|
||||
name: t("calendar.list"),
|
||||
icon: "bx bx-list-ol",
|
||||
previousText: t("calendar.month_previous"),
|
||||
nextText: t("calendar.month_next")
|
||||
}
|
||||
@@ -140,7 +147,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
|
||||
return (plugins &&
|
||||
<div className="calendar-view" ref={containerRef} tabIndex={100}>
|
||||
<CalendarHeader calendarRef={calendarRef} />
|
||||
<CalendarCollectionProperties note={note} calendarRef={calendarRef} />
|
||||
<Calendar
|
||||
events={eventBuilder}
|
||||
calendarRef={calendarRef}
|
||||
@@ -169,28 +176,67 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
);
|
||||
}
|
||||
|
||||
function CalendarHeader({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
|
||||
function CalendarCollectionProperties({ note, calendarRef }: {
|
||||
note: FNote;
|
||||
calendarRef: RefObject<FullCalendar>;
|
||||
}) {
|
||||
const { title, viewType: currentViewType } = useOnDatesSet(calendarRef);
|
||||
const currentViewData = CALENDAR_VIEWS.find(v => calendarRef.current && v.type === currentViewType);
|
||||
const isMobileLocal = isMobile();
|
||||
|
||||
return (
|
||||
<div className="calendar-header">
|
||||
<span className="title">{title}</span>
|
||||
<CollectionProperties
|
||||
note={note}
|
||||
centerChildren={<>
|
||||
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} onClick={() => calendarRef.current?.prev()} />
|
||||
<span className="title">{title}</span>
|
||||
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} onClick={() => calendarRef.current?.next()} />
|
||||
<Button text={t("calendar.today")} onClick={() => calendarRef.current?.today()} />
|
||||
{isMobileLocal && <MobileCalendarViewSwitcher calendarRef={calendarRef} />}
|
||||
</>}
|
||||
rightChildren={<>
|
||||
{!isMobileLocal && <DesktopCalendarViewSwitcher calendarRef={calendarRef} />}
|
||||
</>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function DesktopCalendarViewSwitcher({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
|
||||
const { viewType: currentViewType } = useOnDatesSet(calendarRef);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup>
|
||||
{CALENDAR_VIEWS.map(viewData => (
|
||||
<Button
|
||||
text={viewData.name.toLocaleLowerCase()}
|
||||
key={viewData.type}
|
||||
text={viewData.name}
|
||||
className={currentViewType === viewData.type ? "active" : ""}
|
||||
onClick={() => calendarRef.current?.changeView(viewData.type)}
|
||||
/>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
<Button text={t("calendar.today").toLocaleLowerCase()} onClick={() => calendarRef.current?.today()} />
|
||||
<ButtonGroup>
|
||||
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} frame onClick={() => calendarRef.current?.prev()} />
|
||||
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} frame onClick={() => calendarRef.current?.next()} />
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileCalendarViewSwitcher({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
|
||||
const { viewType: currentViewType } = useOnDatesSet(calendarRef);
|
||||
const currentViewTypeData = CALENDAR_VIEWS.find(view => view.type === currentViewType);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
text={currentViewTypeData?.name}
|
||||
>
|
||||
{CALENDAR_VIEWS.map(viewData => (
|
||||
<FormListItem
|
||||
key={viewData.type}
|
||||
selected={currentViewType === viewData.type}
|
||||
icon={viewData.icon}
|
||||
onClick={() => calendarRef.current?.changeView(viewData.type)}
|
||||
>{viewData.name}</FormListItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,17 +263,18 @@ function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
|
||||
}
|
||||
|
||||
function useLocale() {
|
||||
const [ locale ] = useTriliumOption("locale");
|
||||
const [ formattingLocale ] = useTriliumOption("formattingLocale");
|
||||
const [ calendarLocale, setCalendarLocale ] = useState<LocaleInput>();
|
||||
|
||||
useEffect(() => {
|
||||
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale];
|
||||
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale] ?? LOCALE_MAPPINGS[locale];
|
||||
if (correspondingLocale) {
|
||||
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
|
||||
} else {
|
||||
setCalendarLocale(undefined);
|
||||
}
|
||||
});
|
||||
}, [formattingLocale, locale]);
|
||||
|
||||
return calendarLocale;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .collection-properties {
|
||||
position: relative;
|
||||
z-index: 2000;
|
||||
}
|
||||
}
|
||||
|
||||
.geo-map-container {
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
import Map from "./map";
|
||||
import "./index.css";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks";
|
||||
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
|
||||
|
||||
import { divIcon, GPXOptions, LatLng, LeafletMouseEvent } from "leaflet";
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import Marker, { GpxTrack } from "./marker";
|
||||
import froca from "../../../services/froca";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import markerIcon from "leaflet/dist/images/marker-icon.png";
|
||||
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext from "../../../components/app_context";
|
||||
import { createNewNote, moveMarker } from "./api";
|
||||
import openContextMenu, { openMapContextMenu } from "./context_menu";
|
||||
import toast from "../../../services/toast";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import branches from "../../../services/branches";
|
||||
import froca from "../../../services/froca";
|
||||
import { t } from "../../../services/i18n";
|
||||
import server from "../../../services/server";
|
||||
import branches from "../../../services/branches";
|
||||
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSlider } from "../../react/TouchBar";
|
||||
import toast from "../../../services/toast";
|
||||
import CollectionProperties from "../../note_bars/CollectionProperties";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import { ButtonOrActionButton } from "../../react/Button";
|
||||
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks";
|
||||
import { ParentComponent } from "../../react/react_utils";
|
||||
import TouchBar, { TouchBarButton, TouchBarSlider } from "../../react/TouchBar";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import { createNewNote, moveMarker } from "./api";
|
||||
import openContextMenu, { openMapContextMenu } from "./context_menu";
|
||||
import Map from "./map";
|
||||
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
|
||||
import Marker, { GpxTrack } from "./marker";
|
||||
|
||||
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
|
||||
const DEFAULT_ZOOM = 2;
|
||||
@@ -50,7 +55,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
useEffect(() => { froca.getNotes(noteIds).then(setNotes) }, [ noteIds ]);
|
||||
useEffect(() => { froca.getNotes(noteIds).then(setNotes); }, [ noteIds ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!note) return;
|
||||
@@ -60,7 +65,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
|
||||
|
||||
// Note creation.
|
||||
useTriliumEvent("geoMapCreateChildNote", () => {
|
||||
toast.showPersistent({
|
||||
toast.showPersistent({
|
||||
icon: "plus",
|
||||
id: "geo-new-note",
|
||||
title: "New note",
|
||||
@@ -130,6 +135,19 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
|
||||
|
||||
return (
|
||||
<div className={`geo-view ${state === State.NewNote ? "placing-note" : ""}`}>
|
||||
<CollectionProperties
|
||||
note={note}
|
||||
rightChildren={<>
|
||||
<ToggleReadOnlyButton note={note} />
|
||||
<ButtonOrActionButton
|
||||
icon="bx bx-plus"
|
||||
text={t("geo-map.create-child-note-text")}
|
||||
title={t("geo-map.create-child-note-title")}
|
||||
triggerCommand="geoMapCreateChildNote"
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
</>}
|
||||
/>
|
||||
{ coordinates !== undefined && zoom !== undefined && <Map
|
||||
apiRef={apiRef} containerRef={containerRef}
|
||||
coordinates={coordinates}
|
||||
@@ -151,6 +169,16 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleReadOnlyButton({ note }: { note: FNote }) {
|
||||
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
|
||||
|
||||
return <ActionButton
|
||||
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
|
||||
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
|
||||
onClick={() => setReadOnly(!isReadOnly)}
|
||||
/>;
|
||||
}
|
||||
|
||||
function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) {
|
||||
const mime = useNoteProperty(note, "mime");
|
||||
const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE);
|
||||
@@ -204,7 +232,7 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
|
||||
onDragged={editable ? onDragged : undefined}
|
||||
onClick={!editable ? onClick : undefined}
|
||||
onContextMenu={onContextMenu}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
function NoteGpxTrack({ note }: { note: FNote }) {
|
||||
@@ -238,7 +266,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
|
||||
color: note.getLabelValue("color") ?? "blue"
|
||||
}
|
||||
}), [ color, iconClass ]);
|
||||
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />
|
||||
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />;
|
||||
}
|
||||
|
||||
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
|
||||
@@ -292,5 +320,5 @@ function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | unde
|
||||
enabled={state === State.Normal}
|
||||
/>
|
||||
</TouchBar>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import attribute_renderer from "../../../services/attribute_renderer";
|
||||
import content_renderer from "../../../services/content_renderer";
|
||||
import { t } from "../../../services/i18n";
|
||||
import link from "../../../services/link";
|
||||
import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean } from "../../react/hooks";
|
||||
import CollectionProperties from "../../note_bars/CollectionProperties";
|
||||
import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean, useNoteProperty } from "../../react/hooks";
|
||||
import Icon from "../../react/Icon";
|
||||
import NoteLink from "../../react/NoteLink";
|
||||
import { ViewModeProps } from "../interface";
|
||||
@@ -19,11 +20,18 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
|
||||
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
|
||||
const { pageNotes, ...pagination } = usePagination(note, noteIds);
|
||||
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const hasCollectionProperties = [ "book", "search" ].includes(noteType ?? "");
|
||||
|
||||
return (
|
||||
<div class="note-list list-view">
|
||||
<CollectionProperties
|
||||
note={note}
|
||||
centerChildren={<Pager {...pagination} />}
|
||||
/>
|
||||
|
||||
{ noteIds.length > 0 && <div class="note-list-wrapper">
|
||||
<Pager {...pagination} />
|
||||
{!hasCollectionProperties && <Pager {...pagination} />}
|
||||
|
||||
<div class="note-list-container use-tn-links">
|
||||
{pageNotes?.map(childNote => (
|
||||
@@ -45,11 +53,18 @@ export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
|
||||
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
|
||||
const { pageNotes, ...pagination } = usePagination(note, noteIds);
|
||||
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const hasCollectionProperties = [ "book", "search" ].includes(noteType ?? "");
|
||||
|
||||
return (
|
||||
<div class="note-list grid-view">
|
||||
<CollectionProperties
|
||||
note={note}
|
||||
centerChildren={<Pager {...pagination} />}
|
||||
/>
|
||||
|
||||
<div class="note-list-wrapper">
|
||||
<Pager {...pagination} />
|
||||
{!hasCollectionProperties && <Pager {...pagination} />}
|
||||
|
||||
<div class="note-list-container use-tn-links">
|
||||
{pageNotes?.map(childNote => (
|
||||
@@ -140,7 +155,7 @@ function NoteAttributes({ note }: { note: FNote }) {
|
||||
return <span className="note-list-attributes" ref={ref} />;
|
||||
}
|
||||
|
||||
function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
|
||||
export function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
|
||||
note: FNote;
|
||||
trim?: boolean;
|
||||
noChildrenList?: boolean;
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
.presentation-button-bar {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
|
||||
.floating-buttons-children {
|
||||
top: 0;
|
||||
}
|
||||
.presentation-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.presentation-container {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { ViewModeMedia, ViewModeProps } from "../interface";
|
||||
import "./index.css";
|
||||
|
||||
import { RefObject } from "preact";
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import Reveal from "reveal.js";
|
||||
import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw";
|
||||
import slideCustomStylesheet from "./slidejs.css?raw";
|
||||
import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model";
|
||||
import ShadowDom from "../../react/ShadowDom";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import "./index.css";
|
||||
import { RefObject } from "preact";
|
||||
|
||||
import { openInCurrentNoteContext } from "../../../components/note_context";
|
||||
import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
|
||||
import { t } from "../../../services/i18n";
|
||||
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import CollectionProperties from "../../note_bars/CollectionProperties";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
|
||||
import ShadowDom from "../../react/ShadowDom";
|
||||
import { ViewModeMedia, ViewModeProps } from "../interface";
|
||||
import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model";
|
||||
import slideCustomStylesheet from "./slidejs.css?raw";
|
||||
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
|
||||
|
||||
export default function PresentationView({ note, noteIds, media, onReady, onProgressChanged }: ViewModeProps<{}>) {
|
||||
const [ presentation, setPresentation ] = useState<PresentationModel>();
|
||||
@@ -51,14 +54,17 @@ export default function PresentationView({ note, noteIds, media, onReady, onProg
|
||||
|
||||
if (media === "screen") {
|
||||
return (
|
||||
<>
|
||||
<div class="presentation-view">
|
||||
<CollectionProperties
|
||||
note={note}
|
||||
rightChildren={<ButtonOverlay containerRef={containerRef} api={api} />}
|
||||
/>
|
||||
<ShadowDom
|
||||
className="presentation-container"
|
||||
containerRef={containerRef}
|
||||
>{content}</ShadowDom>
|
||||
<ButtonOverlay containerRef={containerRef} api={api} />
|
||||
</>
|
||||
)
|
||||
</div>
|
||||
);
|
||||
} else if (media === "print") {
|
||||
// Printing needs a query parameter that is read by Reveal.js.
|
||||
const url = new URL(window.location.href);
|
||||
@@ -108,42 +114,34 @@ function ButtonOverlay({ containerRef, api }: { containerRef: RefObject<HTMLDivE
|
||||
}, [ api ]);
|
||||
|
||||
return (
|
||||
<div className="presentation-button-bar">
|
||||
<div className="floating-buttons-children">
|
||||
<ActionButton
|
||||
className="floating-button"
|
||||
icon="bx bx-edit"
|
||||
text={t("presentation_view.edit-slide")}
|
||||
noIconActionClass
|
||||
onClick={e => {
|
||||
const currentSlide = api?.getCurrentSlide();
|
||||
const noteId = getNoteIdFromSlide(currentSlide);
|
||||
<>
|
||||
<ActionButton
|
||||
icon="bx bx-edit"
|
||||
text={t("presentation_view.edit-slide")}
|
||||
onClick={e => {
|
||||
const currentSlide = api?.getCurrentSlide();
|
||||
const noteId = getNoteIdFromSlide(currentSlide);
|
||||
|
||||
if (noteId) {
|
||||
openInCurrentNoteContext(e, noteId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
if (noteId) {
|
||||
openInCurrentNoteContext(e, noteId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<ActionButton
|
||||
className="floating-button"
|
||||
icon="bx bx-grid-horizontal"
|
||||
text={t("presentation_view.slide-overview")}
|
||||
active={isOverviewActive}
|
||||
noIconActionClass
|
||||
onClick={() => api?.toggleOverview()}
|
||||
/>
|
||||
<ActionButton
|
||||
icon="bx bx-grid-horizontal"
|
||||
text={t("presentation_view.slide-overview")}
|
||||
active={isOverviewActive}
|
||||
onClick={() => api?.toggleOverview()}
|
||||
/>
|
||||
|
||||
<ActionButton
|
||||
className="floating-button"
|
||||
icon="bx bx-fullscreen"
|
||||
text={t("presentation_view.start-presentation")}
|
||||
noIconActionClass
|
||||
onClick={() => containerRef.current?.requestFullscreen()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
<ActionButton
|
||||
icon="bx bx-fullscreen"
|
||||
text={t("presentation_view.start-presentation")}
|
||||
onClick={() => containerRef.current?.requestFullscreen()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Presentation({ presentation, setApi } : { presentation: PresentationModel, setApi: (api: Reveal.Api | undefined) => void }) {
|
||||
@@ -179,7 +177,7 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod
|
||||
api.destroy();
|
||||
setRevealApi(undefined);
|
||||
setApi(undefined);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,19 +189,19 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod
|
||||
<div className="slides">
|
||||
{presentation.slides?.map(slide => {
|
||||
if (!slide.verticalSlides) {
|
||||
return <Slide key={slide.noteId} slide={slide} />
|
||||
} else {
|
||||
return (
|
||||
<section>
|
||||
<Slide key={slide.noteId} slide={slide} />
|
||||
{slide.verticalSlides.map(slide => <Slide key={slide.noteId} slide={slide} /> )}
|
||||
</section>
|
||||
);
|
||||
return <Slide key={slide.noteId} slide={slide} />;
|
||||
}
|
||||
return (
|
||||
<section>
|
||||
<Slide key={slide.noteId} slide={slide} />
|
||||
{slide.verticalSlides.map(slide => <Slide key={slide.noteId} slide={slide} /> )}
|
||||
</section>
|
||||
);
|
||||
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
position: relative;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
padding: 0 5px 0 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tabulator-tableholder {
|
||||
height: unset !important;
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import { TableData } from "./rows";
|
||||
import { useLegacyWidget } from "../../react/hooks";
|
||||
import Tabulator from "./tabulator";
|
||||
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
|
||||
import { useContextMenu } from "./context_menu";
|
||||
import { ParentComponent } from "../../react/react_utils";
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import Button from "../../react/Button";
|
||||
import "./index.css";
|
||||
import useRowTableEditing from "./row_editing";
|
||||
import useColTableEditing from "./col_editing";
|
||||
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
|
||||
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { DataTreeModule, EditModule, FormatModule, FrozenColumnsModule, InteractionModule, MoveColumnsModule, MoveRowsModule, Options, PersistenceModule, ResizeColumnsModule, RowComponent,SortModule, Tabulator as VanillaTabulator} from 'tabulator-tables';
|
||||
|
||||
import { t } from "../../../services/i18n";
|
||||
import SpacedUpdate from "../../../services/spaced_update";
|
||||
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
|
||||
import CollectionProperties from "../../note_bars/CollectionProperties";
|
||||
import { ButtonOrActionButton } from "../../react/Button";
|
||||
import { useLegacyWidget } from "../../react/hooks";
|
||||
import { ParentComponent } from "../../react/react_utils";
|
||||
import { ViewModeProps } from "../interface";
|
||||
import useColTableEditing from "./col_editing";
|
||||
import { useContextMenu } from "./context_menu";
|
||||
import useData, { TableConfig } from "./data";
|
||||
import useRowTableEditing from "./row_editing";
|
||||
import { TableData } from "./rows";
|
||||
import Tabulator from "./tabulator";
|
||||
|
||||
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
|
||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||
@@ -36,7 +38,7 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||
dataTreeChildIndent: 20,
|
||||
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
|
||||
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`
|
||||
}
|
||||
};
|
||||
}, [ hasChildren ]);
|
||||
|
||||
const rowFormatter = useCallback((row: RowComponent) => {
|
||||
@@ -46,6 +48,16 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||
|
||||
return (
|
||||
<div className="table-view">
|
||||
<CollectionProperties
|
||||
note={note}
|
||||
rightChildren={note.type !== "search" &&
|
||||
<>
|
||||
<ButtonOrActionButton triggerCommand="addNewRow" icon="bx bx-plus" text={t("table_view.new-row")} />
|
||||
<ButtonOrActionButton triggerCommand="addNewTableColumn" icon="bx bx-carousel" text={t("table_view.new-column")} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
{rowData !== undefined && persistenceProps && (
|
||||
<>
|
||||
<Tabulator
|
||||
@@ -54,7 +66,6 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||
columns={columnDefs ?? []}
|
||||
data={rowData}
|
||||
modules={[ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ]}
|
||||
footerElement={<TableFooter note={note} />}
|
||||
events={{
|
||||
...contextMenuEvents,
|
||||
...rowEditingEvents
|
||||
@@ -67,24 +78,11 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||
rowFormatter={rowFormatter}
|
||||
{...dataTreeProps}
|
||||
/>
|
||||
<TableFooter note={note} />
|
||||
</>
|
||||
)}
|
||||
{attributeDetailWidgetEl}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TableFooter({ note }: { note: FNote }) {
|
||||
return (note.type !== "search" &&
|
||||
<div className="tabulator-footer">
|
||||
<div className="tabulator-footer-contents">
|
||||
<Button triggerCommand="addNewRow" icon="bx bx-plus" text={t("table_view.new-row")} />
|
||||
{" "}
|
||||
<Button triggerCommand="addNewTableColumn" icon="bx bx-carousel" text={t("table_view.new-column")} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function usePersistence(viewConfig: TableConfig | null | undefined, saveConfig: (newConfig: TableConfig) => void) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { EventData } from "../../components/app_context.js";
|
||||
import { LOCALES } from "@triliumnext/commons";
|
||||
import { readCssVar } from "../../utils/css-var.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
import options from "../../services/options.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
import { EventData } from "../../components/app_context.js";
|
||||
import { getEnabledExperimentalFeatureIds } from "../../services/experimental_features.js";
|
||||
import options from "../../services/options.js";
|
||||
import utils, { isMobile } from "../../services/utils.js";
|
||||
import { readCssVar } from "../../utils/css-var.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import FlexContainer from "./flex_container.js";
|
||||
|
||||
/**
|
||||
* The root container is the top-most widget/container, from which the entire layout derives.
|
||||
@@ -17,14 +18,12 @@ import { getEnabledExperimentalFeatureIds } from "../../services/experimental_fe
|
||||
* - `#root-container.vertical-layout`, if the current layout is horizontal.
|
||||
*/
|
||||
export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
private originalViewportHeight: number;
|
||||
|
||||
constructor(isHorizontalLayout: boolean) {
|
||||
super(isHorizontalLayout ? "column" : "row");
|
||||
|
||||
this.id("root-widget");
|
||||
this.css("height", "100dvh");
|
||||
this.originalViewportHeight = getViewportHeight();
|
||||
}
|
||||
|
||||
render(): JQuery<HTMLElement> {
|
||||
@@ -39,6 +38,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
this.#setThemeCapabilities();
|
||||
this.#setLocaleAndDirection(options.get("locale"));
|
||||
this.#setExperimentalFeatures();
|
||||
this.#initPWATopbarColor();
|
||||
|
||||
return super.render();
|
||||
}
|
||||
@@ -64,8 +64,12 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
}
|
||||
|
||||
#onMobileResize() {
|
||||
const currentViewportHeight = getViewportHeight();
|
||||
const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight);
|
||||
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
// If viewport is significantly smaller, keyboard is likely open
|
||||
const isKeyboardOpened = windowHeight - viewportHeight > 150;
|
||||
|
||||
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
|
||||
}
|
||||
|
||||
@@ -88,7 +92,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
}
|
||||
|
||||
#setBackdropEffects() {
|
||||
const enabled = options.is("backdropEffectsEnabled");
|
||||
const enabled = options.is("backdropEffectsEnabled") && !isMobile();
|
||||
document.body.classList.toggle("backdrop-effects-disabled", !enabled);
|
||||
}
|
||||
|
||||
@@ -96,7 +100,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
// Supports background effects
|
||||
|
||||
const useBgfx = readCssVar(document.documentElement, "allow-background-effects")
|
||||
.asBoolean(false);
|
||||
.asBoolean(false);
|
||||
|
||||
document.body.classList.toggle("theme-supports-background-effects", useBgfx);
|
||||
}
|
||||
@@ -112,8 +116,23 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
|
||||
document.body.lang = locale;
|
||||
document.body.dir = correspondingLocale?.rtl ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
#initPWATopbarColor() {
|
||||
if (!utils.isPWA()) return;
|
||||
const tracker = $("#background-color-tracker");
|
||||
|
||||
if (tracker.length) {
|
||||
const applyThemeColor = () => {
|
||||
let meta = $("meta[name='theme-color']");
|
||||
if (!meta.length) {
|
||||
meta = $(`<meta name="theme-color">`).appendTo($("head"));
|
||||
}
|
||||
meta.attr("content", tracker.css("color"));
|
||||
};
|
||||
|
||||
tracker.on("transitionend", applyThemeColor);
|
||||
applyThemeColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getViewportHeight() {
|
||||
return window.visualViewport?.height ?? window.innerHeight;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
.scrolling-container {
|
||||
--content-margin-inline: 24px;
|
||||
|
||||
overflow: auto;
|
||||
scroll-behavior: smooth;
|
||||
position: relative;
|
||||
|
||||
> .inline-title,
|
||||
> .note-detail > .note-detail-editable-text,
|
||||
> .note-list-widget:not(.full-height) {
|
||||
padding-inline: 24px;
|
||||
> .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor,
|
||||
> .note-list-widget:not(.full-height) .note-list-wrapper {
|
||||
margin-inline: var(--content-margin-inline);
|
||||
}
|
||||
|
||||
> .inline-title {
|
||||
padding-inline: var(--content-margin-inline);
|
||||
}
|
||||
|
||||
> .note-detail > .note-detail-editable-text > .note-detail-editable-text-editor {
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.note-split.type-code:not(.mime-text-x-sqlite) {
|
||||
|
||||
@@ -91,8 +91,9 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-editable-text {
|
||||
padding: 0 1em;
|
||||
.modal.popup-editor-dialog .note-detail-editable-text-editor {
|
||||
margin: 0 28px;
|
||||
overflow: visible; /* Allow selection rectangle to go outside of the editor area */
|
||||
}
|
||||
|
||||
.modal.popup-editor-dialog .note-detail-file {
|
||||
|
||||
@@ -5,13 +5,15 @@ import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import tree from "../../services/tree";
|
||||
import utils from "../../services/utils";
|
||||
import NoteList from "../collections/NoteList";
|
||||
import FloatingButtons from "../FloatingButtons";
|
||||
import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
||||
import { DESKTOP_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
|
||||
import NoteBadges from "../layout/NoteBadges";
|
||||
import NoteIcon from "../note_icon";
|
||||
import NoteTitleWidget from "../note_title";
|
||||
import NoteDetail from "../NoteDetail";
|
||||
@@ -23,8 +25,6 @@ import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar";
|
||||
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
|
||||
import FormattingToolbar from "../ribbon/FormattingToolbar";
|
||||
import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar";
|
||||
import NoteBadges from "../layout/NoteBadges";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function PopupEditor() {
|
||||
const [ noteContext, setNoteContext ] = useState(new NoteContext("_popup-editor"));
|
||||
const isMobile = utils.isMobile();
|
||||
const items = useMemo(() => {
|
||||
const baseItems = isMobile ? MOBILE_FLOATING_BUTTONS : DESKTOP_FLOATING_BUTTONS;
|
||||
const baseItems = isMobile ? [] : DESKTOP_FLOATING_BUTTONS;
|
||||
return baseItems.filter(item => !POPUP_HIDDEN_FLOATING_BUTTONS.includes(item));
|
||||
}, [ isMobile ]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import appContext from "../../components/app_context";
|
||||
import { t } from "../../services/i18n";
|
||||
import options from "../../services/options";
|
||||
import utils from "../../services/utils";
|
||||
import utils, { isMac } from "../../services/utils";
|
||||
|
||||
/**
|
||||
* A "call-to-action" is an interactive message for the user, generally to present new features.
|
||||
@@ -41,10 +41,6 @@ export interface CallToAction {
|
||||
}[];
|
||||
}
|
||||
|
||||
function isNextTheme() {
|
||||
return [ "next", "next-light", "next-dark" ].includes(options.get("theme"));
|
||||
}
|
||||
|
||||
const CALL_TO_ACTIONS: CallToAction[] = [
|
||||
{
|
||||
id: "new_layout",
|
||||
@@ -63,7 +59,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
|
||||
id: "background_effects",
|
||||
title: t("call_to_action.background_effects_title"),
|
||||
message: t("call_to_action.background_effects_message"),
|
||||
enabled: () => false,
|
||||
enabled: () => (isMac() && !options.is("backgroundEffects")),
|
||||
buttons: [
|
||||
{
|
||||
text: t("call_to_action.background_effects_button"),
|
||||
@@ -78,7 +74,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
|
||||
id: "next_theme",
|
||||
title: t("call_to_action.next_theme_title"),
|
||||
message: t("call_to_action.next_theme_message"),
|
||||
enabled: () => !isNextTheme(),
|
||||
enabled: () => ![ "next", "next-light", "next-dark" ].includes(options.get("theme")),
|
||||
buttons: [
|
||||
{
|
||||
text: t("call_to_action.next_theme_button"),
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { useContext, useMemo } from "preact/hooks";
|
||||
import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||
import { CSSProperties } from "preact";
|
||||
import type FNote from "../../entities/fnote";
|
||||
import { useChildNotes, useNoteLabelBoolean } from "../react/hooks";
|
||||
import "./BookmarkButtons.css";
|
||||
|
||||
import { CSSProperties } from "preact";
|
||||
import { useContext, useMemo } from "preact/hooks";
|
||||
|
||||
import type FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { FormDropdownSubmenu, FormListItem } from "../react/FormList";
|
||||
import { useChildNotes, useNote, useNoteIcon, useNoteLabelBoolean } from "../react/hooks";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { CustomNoteLauncher } from "./GenericButtons";
|
||||
import ResponsiveContainer from "../react/ResponsiveContainer";
|
||||
import { CustomNoteLauncher, launchCustomNoteLauncher } from "./GenericButtons";
|
||||
import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||
|
||||
const PARENT_NOTE_ID = "_lbBookmarks";
|
||||
|
||||
@@ -19,17 +24,64 @@ export default function BookmarkButtons() {
|
||||
const childNotes = useChildNotes(PARENT_NOTE_ID);
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
|
||||
</div>
|
||||
)
|
||||
<ResponsiveContainer
|
||||
desktop={
|
||||
<div style={style}>
|
||||
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
|
||||
</div>
|
||||
}
|
||||
mobile={
|
||||
<LaunchBarDropdownButton
|
||||
icon="bx bx-bookmark"
|
||||
title={t("bookmark_buttons.bookmarks")}
|
||||
>
|
||||
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
|
||||
</LaunchBarDropdownButton>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SingleBookmark({ note }: { note: FNote }) {
|
||||
const [ bookmarkFolder ] = useNoteLabelBoolean(note, "bookmarkFolder");
|
||||
return bookmarkFolder
|
||||
? <BookmarkFolder note={note} />
|
||||
: <CustomNoteLauncher launcherNote={note} getTargetNoteId={() => note.noteId} />
|
||||
return <ResponsiveContainer
|
||||
desktop={
|
||||
bookmarkFolder
|
||||
? <BookmarkFolder note={note} />
|
||||
: <CustomNoteLauncher launcherNote={note} getTargetNoteId={() => note.noteId} />
|
||||
}
|
||||
mobile={<MobileBookmarkItem noteId={note.noteId} bookmarkFolder={bookmarkFolder} />}
|
||||
/>;
|
||||
}
|
||||
|
||||
function MobileBookmarkItem({ noteId, bookmarkFolder }: { noteId: string, bookmarkFolder: boolean }) {
|
||||
const note = useNote(noteId);
|
||||
const noteIcon = useNoteIcon(note);
|
||||
if (!note) return null;
|
||||
|
||||
return (
|
||||
!bookmarkFolder
|
||||
? <FormListItem icon={noteIcon} onClick={(e) => launchCustomNoteLauncher(e, { launcherNote: note, getTargetNoteId: () => note.noteId })}>{note.title}</FormListItem>
|
||||
: <MobileBookmarkFolder note={note} />
|
||||
);
|
||||
}
|
||||
|
||||
function MobileBookmarkFolder({ note }: { note: FNote }) {
|
||||
const childNotes = useChildNotes(note.noteId);
|
||||
|
||||
return (
|
||||
<FormDropdownSubmenu icon="bx bx-folder" title={note.title}>
|
||||
{childNotes.map(childNote => (
|
||||
<FormListItem
|
||||
key={childNote.noteId}
|
||||
icon={childNote.getIcon()}
|
||||
onClick={(e) => launchCustomNoteLauncher(e, { launcherNote: childNote, getTargetNoteId: () => childNote.noteId })}
|
||||
>
|
||||
{childNote.title}
|
||||
</FormListItem>
|
||||
))}
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
function BookmarkFolder({ note }: { note: FNote }) {
|
||||
@@ -55,5 +107,5 @@ function BookmarkFolder({ note }: { note: FNote }) {
|
||||
</ul>
|
||||
</div>
|
||||
</LaunchBarDropdownButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,32 +7,18 @@ import { isCtrlKey } from "../../services/utils";
|
||||
import { useGlobalShortcut, useNoteLabel } from "../react/hooks";
|
||||
import { LaunchBarActionButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
|
||||
|
||||
export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNoteId }: {
|
||||
export function CustomNoteLauncher(props: {
|
||||
launcherNote: FNote;
|
||||
getTargetNoteId: (launcherNote: FNote) => string | null | Promise<string | null>;
|
||||
getHoistedNoteId?: (launcherNote: FNote) => string | null;
|
||||
keyboardShortcut?: string;
|
||||
}) {
|
||||
const { launcherNote, getTargetNoteId } = props;
|
||||
const { icon, title } = useLauncherIconAndTitle(launcherNote);
|
||||
|
||||
const launch = useCallback(async (evt: MouseEvent | KeyboardEvent) => {
|
||||
if (evt.which === 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetNoteId = await getTargetNoteId(launcherNote);
|
||||
if (!targetNoteId) return;
|
||||
|
||||
const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
const ctrlKey = isCtrlKey(evt);
|
||||
|
||||
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
|
||||
const activate = !!evt.shiftKey;
|
||||
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate);
|
||||
} else {
|
||||
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault);
|
||||
}
|
||||
}, [ launcherNote, getTargetNoteId, getHoistedNoteId ]);
|
||||
await launchCustomNoteLauncher(evt, props);
|
||||
}, [ props ]);
|
||||
|
||||
// Keyboard shortcut.
|
||||
const [ shortcut ] = useNoteLabel(launcherNote, "keyboardShortcut");
|
||||
@@ -54,3 +40,24 @@ export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNo
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function launchCustomNoteLauncher(evt: MouseEvent | KeyboardEvent, { launcherNote, getTargetNoteId, getHoistedNoteId }: {
|
||||
launcherNote: FNote;
|
||||
getTargetNoteId: (launcherNote: FNote) => string | null | Promise<string | null>;
|
||||
getHoistedNoteId?: (launcherNote: FNote) => string | null;
|
||||
}) {
|
||||
if (evt.which === 3) return;
|
||||
|
||||
const targetNoteId = await getTargetNoteId(launcherNote);
|
||||
if (!targetNoteId) return;
|
||||
|
||||
const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
const ctrlKey = isCtrlKey(evt);
|
||||
|
||||
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
|
||||
const activate = !!evt.shiftKey;
|
||||
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate);
|
||||
} else {
|
||||
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useCallback, useLayoutEffect, useState } from "preact/hooks";
|
||||
import FNote from "../../entities/fnote";
|
||||
import froca from "../../services/froca";
|
||||
import { isDesktop, isMobile } from "../../services/utils";
|
||||
import TabSwitcher from "../mobile_widgets/TabSwitcher";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import { onWheelHorizontalScroll } from "../widget_utils";
|
||||
import BookmarkButtons from "./BookmarkButtons";
|
||||
@@ -97,6 +98,8 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
|
||||
return <QuickSearchLauncherWidget />;
|
||||
case "aiChatLauncher":
|
||||
return <AiChatButton launcherNote={note} />;
|
||||
case "mobileTabSwitcher":
|
||||
return <TabSwitcher />;
|
||||
default:
|
||||
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import clsx from "clsx";
|
||||
import { createContext } from "preact";
|
||||
import { useContext } from "preact/hooks";
|
||||
|
||||
@@ -18,12 +19,12 @@ export interface LauncherNoteProps {
|
||||
launcherNote: FNote;
|
||||
}
|
||||
|
||||
export function LaunchBarActionButton(props: Omit<ActionButtonProps, "className" | "noIconActionClass" | "titlePosition">) {
|
||||
export function LaunchBarActionButton({ className, ...props }: Omit<ActionButtonProps, "noIconActionClass" | "titlePosition">) {
|
||||
const { isHorizontalLayout } = useContext(LaunchBarContext);
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className="button-widget launcher-button"
|
||||
className={clsx("button-widget launcher-button", className)}
|
||||
noIconActionClass
|
||||
titlePosition={isHorizontalLayout ? "bottom" : "right"}
|
||||
{...props}
|
||||
@@ -48,6 +49,7 @@ export function LaunchBarDropdownButton({ children, icon, dropdownOptions, ...pr
|
||||
placement: isHorizontalLayout ? "bottom" : "right"
|
||||
}
|
||||
}}
|
||||
mobileBackdrop
|
||||
{...props}
|
||||
>{children}</Dropdown>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Tooltip } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { ComponentChild } from "preact";
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import type React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
|
||||
@@ -50,6 +50,9 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body.desktop .title-actions {
|
||||
> .collapsible,
|
||||
> .note-type-switcher {
|
||||
padding-inline-start: calc(24px - var(--title-actions-padding-start));
|
||||
@@ -57,3 +60,4 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
import "./NoteTitleActions.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import CollectionProperties from "../note_bars/CollectionProperties";
|
||||
import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
|
||||
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
||||
import SimpleBadge from "../react/Badge";
|
||||
import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible";
|
||||
import { useNoteContext, useNoteLabel, useNoteProperty, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
|
||||
import NoteLink, { NewNoteLink } from "../react/NoteLink";
|
||||
import { NewNoteLink } from "../react/NoteLink";
|
||||
import { useEditedNotes } from "../ribbon/EditedNotesTab";
|
||||
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
|
||||
import NoteTypeSwitcher from "./NoteTypeSwitcher";
|
||||
|
||||
export default function NoteTitleActions() {
|
||||
const { note, ntxId, componentId, noteContext } = useNoteContext();
|
||||
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
|
||||
const { note, ntxId, componentId, noteContext, viewScope } = useNoteContext();
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
|
||||
return (
|
||||
<div className="title-actions">
|
||||
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
|
||||
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
|
||||
{!isHiddenNote && note && noteType === "book" && <CollectionProperties note={note} />}
|
||||
<EditedNotes />
|
||||
<NoteTypeSwitcher />
|
||||
{(!viewScope?.viewMode || viewScope.viewMode === "default") && <NoteTypeSwitcher />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -49,7 +44,7 @@ function PromotedAttributes({ note, componentId, noteContext }: {
|
||||
componentId: string,
|
||||
noteContext: NoteContext | undefined
|
||||
}) {
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext);
|
||||
const [ expanded, setExpanded ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -58,101 +58,12 @@
|
||||
|
||||
.dropdown-note-info {
|
||||
padding: 1em !important;
|
||||
|
||||
ul {
|
||||
--row-block-margin: .2em;
|
||||
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: calc(0px - var(--row-block-margin));
|
||||
margin-bottom: 12px;
|
||||
display: table;
|
||||
|
||||
li {
|
||||
display: table-row;
|
||||
|
||||
> strong {
|
||||
display: table-cell;
|
||||
padding: var(--row-block-margin) 0;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: table-cell;
|
||||
user-select: text;
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-note-paths {
|
||||
.note-paths-widget {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.note-path-intro {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.note-path-list {
|
||||
margin: 12px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
/* Note path card */
|
||||
li {
|
||||
--border-radius: 6px;
|
||||
|
||||
position: relative;
|
||||
background: var(--card-background-color);
|
||||
padding: 8px 20px 8px 25px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Current path arrow */
|
||||
&.path-current::before {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
content: "\ee8f";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
bottom: 0;
|
||||
font-family: "boxicons";
|
||||
font-size: .75em;
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Note path segment */
|
||||
a {
|
||||
margin-inline: 2px;
|
||||
padding-inline: 2px;
|
||||
color: currentColor;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
|
||||
/* The last segment of the note path */
|
||||
&.basename {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.backlinks-widget > .dropdown-menu {
|
||||
@@ -160,84 +71,6 @@
|
||||
|
||||
max-height: 60vh;
|
||||
overflow-y: scroll;
|
||||
|
||||
/* Backlink card */
|
||||
li {
|
||||
--border-radius: 8px;
|
||||
|
||||
max-width: 600px;
|
||||
padding: 10px 20px;
|
||||
background: var(--card-background-color);
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
/* Card header */
|
||||
& > span:first-child {
|
||||
display: block;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
/* Note path */
|
||||
> small {
|
||||
flex: 100%;
|
||||
order: -1;
|
||||
font-size: .65rem;
|
||||
|
||||
.note-path {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note icon */
|
||||
> .tn-icon {
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
|
||||
/* Note title */
|
||||
> a {
|
||||
margin-inline-start: 4px;
|
||||
color: currentColor;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Card content - excerpt */
|
||||
& > span:nth-child(2) > div {
|
||||
all: unset; /* TODO: Remove after disposing the old style from FloatingButtons.css */
|
||||
display: block;
|
||||
|
||||
margin: 8px 0;
|
||||
border-radius: 4px;
|
||||
background: var(--quick-search-result-content-background);
|
||||
padding: 8px;
|
||||
font-size: .75rem;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,16 +117,50 @@
|
||||
|
||||
}
|
||||
|
||||
div.similar-notes-widget div.similar-notes-wrapper {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
button.select-button:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout .note-info-content {
|
||||
ul {
|
||||
--row-block-margin: .2em;
|
||||
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: calc(0px - var(--row-block-margin));
|
||||
margin-bottom: 12px;
|
||||
display: table;
|
||||
|
||||
li {
|
||||
display: table-row;
|
||||
|
||||
> strong {
|
||||
display: table-cell;
|
||||
padding: var(--row-block-margin) 0;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: table-cell;
|
||||
user-select: text;
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout div.similar-notes-widget div.similar-notes-wrapper {
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
body.experimental-feature-new-layout.mobile div.similar-notes-widget div.similar-notes-wrapper {
|
||||
max-height: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
margin: 0 !important;
|
||||
padding: 0;
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function StatusBar() {
|
||||
similarNotesShown: activePane === "similar-notes",
|
||||
setSimilarNotesShown: (shown) => setActivePane(shown && "similar-notes")
|
||||
};
|
||||
const isHiddenNote = note?.isInHiddenSubtree();
|
||||
const isHiddenNote = note?.isHiddenCompletely();
|
||||
|
||||
return (
|
||||
<div className="status-bar">
|
||||
@@ -212,8 +212,8 @@ export function getLocaleName(locale: Locale | null | undefined) {
|
||||
|
||||
//#region Note info & Similar
|
||||
interface NoteInfoContext extends StatusBarContext {
|
||||
similarNotesShown: boolean;
|
||||
setSimilarNotesShown: (value: boolean) => void;
|
||||
similarNotesShown?: boolean;
|
||||
setSimilarNotesShown?: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function NoteInfoBadge(context: NoteInfoContext) {
|
||||
@@ -225,7 +225,7 @@ export function NoteInfoBadge(context: NoteInfoContext) {
|
||||
|
||||
// Keyboard shortcut.
|
||||
useTriliumEvent("toggleRibbonTabNoteInfo", () => enabled && dropdownRef.current?.show());
|
||||
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown(!similarNotesShown));
|
||||
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown && setSimilarNotesShown(!similarNotesShown));
|
||||
|
||||
return (enabled &&
|
||||
<StatusBarDropdown
|
||||
@@ -242,8 +242,8 @@ export function NoteInfoBadge(context: NoteInfoContext) {
|
||||
);
|
||||
}
|
||||
|
||||
function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }: NoteInfoContext & {
|
||||
dropdownRef: RefObject<BootstrapDropdown>;
|
||||
export function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }: Pick<NoteInfoContext, "note" | "setSimilarNotesShown"> & {
|
||||
dropdownRef?: RefObject<BootstrapDropdown>;
|
||||
noteType: NoteType;
|
||||
}) {
|
||||
const { metadata, ...sizeProps } = useNoteMetadata(note);
|
||||
@@ -251,7 +251,7 @@ function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }:
|
||||
const noteTypeMapping = useMemo(() => NOTE_TYPES.find(t => t.type === noteType), [ noteType ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="note-info-content">
|
||||
<ul>
|
||||
{originalFileName && <NoteInfoValue text={t("file_properties.original_file_name")} value={originalFileName} />}
|
||||
<NoteInfoValue text={t("note_info_widget.created")} value={formatDateTime(metadata?.dateCreated)} />
|
||||
@@ -262,14 +262,14 @@ function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }:
|
||||
<NoteInfoValue text={t("note_info_widget.note_size")} title={t("note_info_widget.note_size_info")} value={<NoteSizeWidget {...sizeProps} />} />
|
||||
</ul>
|
||||
|
||||
<LinkButton
|
||||
{setSimilarNotesShown && <LinkButton
|
||||
text={t("note_info_widget.show_similar_notes")}
|
||||
onClick={() => {
|
||||
dropdownRef.current?.hide();
|
||||
dropdownRef?.current?.hide();
|
||||
setSimilarNotesShown(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) {
|
||||
const count = useBacklinkCount(note, viewScope?.viewMode === "default");
|
||||
return (note && count > 0 &&
|
||||
<StatusBarDropdown
|
||||
className="backlinks-badge backlinks-widget"
|
||||
className="backlinks-badge backlinks-widget tn-backlinks-widget"
|
||||
icon="bx bx-link"
|
||||
text={t("status_bar.backlinks", { count })}
|
||||
title={t("status_bar.backlinks_title", { count })}
|
||||
|
||||
139
apps/client/src/widgets/mobile_widgets/TabSwitcher.css
Normal file
139
apps/client/src/widgets/mobile_widgets/TabSwitcher.css
Normal file
@@ -0,0 +1,139 @@
|
||||
#launcher-container .mobile-tab-switcher {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: attr(data-tab-count);
|
||||
font-family: var(--main-font-family);
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.modal.tab-bar-modal {
|
||||
.modal-dialog {
|
||||
min-height: 85vh;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1em;
|
||||
|
||||
@media (min-width: 850px) {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.tab-card {
|
||||
background: var(--card-background-color);
|
||||
border-radius: 1em;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.with-hue {
|
||||
background-color: hsl(var(--bg-hue), 8.8%, 11.2%);
|
||||
border-color: hsl(var(--bg-hue), 9.4%, 25.1%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
outline: 4px solid var(--more-accented-background-color);
|
||||
background: var(--card-background-hover-color);
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 0.4em 0.5em;
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.1);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
color: var(--custom-color, inherit);
|
||||
flex-shrink: 0;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.1);
|
||||
}
|
||||
|
||||
>.tn-icon {
|
||||
margin-inline-end: 0.4em;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
font-size: 0.9em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.icon-action {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-preview {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 0.5em;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
&.type-text {
|
||||
padding: 10px;
|
||||
--ck-content-todo-list-checkmark-size: 8px;
|
||||
|
||||
p { margin-bottom: 0.2em;}
|
||||
hr { margin-block: 0.1em; height: 1px; }
|
||||
h2 { font-size: 1.20em; }
|
||||
h3 { font-size: 1.15em; }
|
||||
h4 { font-size: 1.10em; }
|
||||
h5 { font-size: 1.05em}
|
||||
h6 { font-size: 1em; }
|
||||
ul, ol { margin: 0 }
|
||||
}
|
||||
|
||||
&.type-book,
|
||||
&.type-contentWidget,
|
||||
&.type-search,
|
||||
&.type-empty,
|
||||
&.type-relationMap,
|
||||
&.type-launcher,
|
||||
&.tab-preview-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25em;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
font-size: 500%;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-split {
|
||||
.preview-placeholder {
|
||||
font-size: 250%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
.tn-link {
|
||||
color: var(--main-text-color);
|
||||
width: 40%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
267
apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx
Normal file
267
apps/client/src/widgets/mobile_widgets/TabSwitcher.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
import "./TabSwitcher.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { ComponentChild } from "preact";
|
||||
import { createPortal, Fragment } from "preact/compat";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import appContext, { CommandNames } from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import contextMenu from "../../menus/context_menu";
|
||||
import { getHue, parseColor } from "../../services/css_class_manager";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import type { ViewMode, ViewScope } from "../../services/link";
|
||||
import { NoteContent } from "../collections/legacy/ListOrGridView";
|
||||
import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets";
|
||||
import { ICON_MAPPINGS } from "../note_bars/CollectionProperties";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { useActiveNoteContext, useNoteIcon, useTriliumEvents } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import LinkButton from "../react/LinkButton";
|
||||
import Modal from "../react/Modal";
|
||||
|
||||
const VIEW_MODE_ICON_MAPPINGS: Record<Exclude<ViewMode, "default">, string> = {
|
||||
source: "bx bx-code",
|
||||
"contextual-help": "bx bx-help-circle",
|
||||
"note-map": "bx bxs-network-chart",
|
||||
attachments: "bx bx-paperclip",
|
||||
};
|
||||
|
||||
export default function TabSwitcher() {
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const mainNoteContexts = useMainNoteContexts();
|
||||
|
||||
return (
|
||||
<>
|
||||
<LaunchBarActionButton
|
||||
className="mobile-tab-switcher"
|
||||
icon="bx bx-rectangle"
|
||||
text="Tabs"
|
||||
onClick={() => setShown(true)}
|
||||
data-tab-count={mainNoteContexts.length > 99 ? "∞" : mainNoteContexts.length}
|
||||
/>
|
||||
{createPortal(<TabBarModal mainNoteContexts={mainNoteContexts} shown={shown} setShown={setShown} />, document.body)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TabBarModal({ mainNoteContexts, shown, setShown }: {
|
||||
mainNoteContexts: NoteContext[];
|
||||
shown: boolean;
|
||||
setShown: (newValue: boolean) => void;
|
||||
}) {
|
||||
const [ fullyShown, setFullyShown ] = useState(false);
|
||||
const selectTab = useCallback((noteContextToActivate: NoteContext) => {
|
||||
appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId);
|
||||
setShown(false);
|
||||
}, [ setShown ]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="tab-bar-modal"
|
||||
size="xl"
|
||||
title={t("mobile_tab_switcher.title", { count: mainNoteContexts.length})}
|
||||
show={shown}
|
||||
onShown={() => setFullyShown(true)}
|
||||
customTitleBarButtons={[
|
||||
{
|
||||
iconClassName: "bx bx-dots-vertical-rounded",
|
||||
title: t("mobile_tab_switcher.more_options"),
|
||||
onClick(e) {
|
||||
contextMenu.show<CommandNames>({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
{ title: t("tab_row.new_tab"), command: "openNewTab", uiIcon: "bx bx-plus" },
|
||||
{ title: t("tab_row.reopen_last_tab"), command: "reopenLastTab", uiIcon: "bx bx-undo", enabled: appContext.tabManager.recentlyClosedTabs.length !== 0 },
|
||||
{ kind: "separator" },
|
||||
{ title: t("tab_row.close_all_tabs"), command: "closeAllTabs", uiIcon: "bx bx-trash destructive-action-icon" },
|
||||
],
|
||||
selectMenuItemHandler: ({ command }) => {
|
||||
if (command) {
|
||||
appContext.triggerCommand(command);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
]}
|
||||
footer={<>
|
||||
<LinkButton
|
||||
text={t("tab_row.new_tab")}
|
||||
onClick={() => {
|
||||
appContext.triggerCommand("openNewTab");
|
||||
setShown(false);
|
||||
}}
|
||||
/>
|
||||
</>}
|
||||
scrollable
|
||||
onHidden={() => {
|
||||
setShown(false);
|
||||
setFullyShown(false);
|
||||
}}
|
||||
>
|
||||
<TabBarModelContent mainNoteContexts={mainNoteContexts} selectTab={selectTab} shown={fullyShown} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function TabBarModelContent({ mainNoteContexts, selectTab, shown }: {
|
||||
mainNoteContexts: NoteContext[];
|
||||
shown: boolean;
|
||||
selectTab: (noteContextToActivate: NoteContext) => void;
|
||||
}) {
|
||||
const activeNoteContext = useActiveNoteContext();
|
||||
const tabRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||
|
||||
// Scroll to active tab.
|
||||
useEffect(() => {
|
||||
if (!shown || !activeNoteContext?.ntxId) return;
|
||||
const correspondingEl = tabRefs.current[activeNoteContext.ntxId];
|
||||
requestAnimationFrame(() => {
|
||||
correspondingEl?.scrollIntoView();
|
||||
});
|
||||
}, [ activeNoteContext, shown ]);
|
||||
|
||||
return (
|
||||
<div className="tabs">
|
||||
{mainNoteContexts.map((noteContext) => (
|
||||
<Tab
|
||||
key={noteContext.ntxId}
|
||||
noteContext={noteContext}
|
||||
activeNtxId={activeNoteContext.ntxId}
|
||||
selectTab={selectTab}
|
||||
containerRef={el => (tabRefs.current[noteContext.ntxId ?? ""] = el)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
|
||||
containerRef: (el: HTMLDivElement | null) => void;
|
||||
noteContext: NoteContext;
|
||||
selectTab: (noteContextToActivate: NoteContext) => void;
|
||||
activeNtxId: string | null | undefined;
|
||||
}) {
|
||||
const { note } = noteContext;
|
||||
const colorClass = note?.getColorClass() || '';
|
||||
const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext);
|
||||
const subContexts = noteContext.getSubContexts();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
class={clsx("tab-card", {
|
||||
active: noteContext.ntxId === activeNtxId,
|
||||
"with-hue": workspaceTabBackgroundColorHue !== undefined,
|
||||
"with-split": subContexts.length > 1
|
||||
})}
|
||||
onClick={() => selectTab(noteContext)}
|
||||
style={{
|
||||
"--bg-hue": workspaceTabBackgroundColorHue
|
||||
}}
|
||||
>
|
||||
{subContexts.map(subContext => (
|
||||
<Fragment key={subContext.ntxId}>
|
||||
<TabHeader noteContext={subContext} colorClass={colorClass} />
|
||||
<TabPreviewContent note={subContext.note} viewScope={subContext.viewScope} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TabHeader({ noteContext, colorClass }: { noteContext: NoteContext, colorClass: string }) {
|
||||
const iconClass = useNoteIcon(noteContext.note);
|
||||
const [ navigationTitle, setNavigationTitle ] = useState<string | null>(null);
|
||||
|
||||
// Manage the title for read-only notes
|
||||
useEffect(() => {
|
||||
noteContext?.getNavigationTitle().then(setNavigationTitle);
|
||||
}, [noteContext]);
|
||||
|
||||
return (
|
||||
<header className={colorClass}>
|
||||
{noteContext.note && <Icon icon={iconClass} />}
|
||||
<span className="title">{navigationTitle ?? noteContext.note?.title ?? t("tab_row.new_tab")}</span>
|
||||
{noteContext.isMainContext() && <ActionButton
|
||||
icon="bx bx-x"
|
||||
text={t("tab_row.close_tab")}
|
||||
onClick={(e) => {
|
||||
// We are closing a tab, so we need to prevent propagation for click (activate tab).
|
||||
e.stopPropagation();
|
||||
appContext.tabManager.removeNoteContext(noteContext.ntxId);
|
||||
}}
|
||||
/>}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function TabPreviewContent({ note, viewScope }: {
|
||||
note: FNote | null,
|
||||
viewScope: ViewScope | undefined
|
||||
}) {
|
||||
let el: ComponentChild;
|
||||
let isPlaceholder = true;
|
||||
|
||||
if (!note) {
|
||||
el = <PreviewPlaceholder icon="bx bx-plus" />;
|
||||
} else if (note.type === "book") {
|
||||
el = <PreviewPlaceholder icon={ICON_MAPPINGS[note.getLabelValue("viewType") ?? ""] ?? "bx bx-book"} />;
|
||||
} else if (viewScope?.viewMode && viewScope.viewMode !== "default") {
|
||||
el = <PreviewPlaceholder icon={VIEW_MODE_ICON_MAPPINGS[viewScope?.viewMode ?? ""] ?? "bx bx-empty"} />;
|
||||
} else {
|
||||
el = <NoteContent
|
||||
note={note}
|
||||
highlightedTokens={undefined}
|
||||
trim
|
||||
includeArchivedNotes={false}
|
||||
/>;
|
||||
isPlaceholder = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("tab-preview", `type-${note?.type ?? "empty"}`, { "tab-preview-placeholder": isPlaceholder })}>{el}</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PreviewPlaceholder({ icon}: {
|
||||
icon: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="preview-placeholder">
|
||||
<Icon icon={icon} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) {
|
||||
if (!noteContext.hoistedNoteId) return;
|
||||
const hoistedNote = froca.getNoteFromCache(noteContext.hoistedNoteId);
|
||||
if (!hoistedNote) return;
|
||||
|
||||
const workspaceTabBackgroundColor = hoistedNote.getWorkspaceTabBackgroundColor();
|
||||
if (!workspaceTabBackgroundColor) return;
|
||||
|
||||
try {
|
||||
const parsedColor = parseColor(workspaceTabBackgroundColor);
|
||||
if (!parsedColor) return;
|
||||
return getHue(parsedColor);
|
||||
} catch (e) {
|
||||
// Colors are non-critical, simply ignore.
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
function useMainNoteContexts() {
|
||||
const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts());
|
||||
|
||||
useTriliumEvents([ "newNoteContextCreated", "noteContextRemoved" ] , () => {
|
||||
setNoteContexts(appContext.tabManager.getMainNoteContexts());
|
||||
});
|
||||
|
||||
return noteContexts;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.code-note-switcher-modal .dropdown-menu {
|
||||
background: none !important;
|
||||
}
|
||||
@@ -1,74 +1,240 @@
|
||||
import { useContext } from "preact/hooks";
|
||||
import appContext, { CommandMappings } from "../../components/app_context";
|
||||
import contextMenu, { MenuItem } from "../../menus/context_menu";
|
||||
import branches from "../../services/branches";
|
||||
import "./mobile_detail_menu.css";
|
||||
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import { createPortal, useRef, useState } from "preact/compat";
|
||||
|
||||
import FNote, { NotePathRecord } from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import note_create from "../../services/note_create";
|
||||
import tree from "../../services/tree";
|
||||
import server from "../../services/server";
|
||||
import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions";
|
||||
import { getLocaleName, NoteInfoContent } from "../layout/StatusBar";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import BasicWidget from "../basic_widget";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
|
||||
import { useNoteContext, useNoteProperty } from "../react/hooks";
|
||||
import Modal from "../react/Modal";
|
||||
import { NoteTypeCodeNoteList, useLanguageSwitcher, useMimeTypes } from "../ribbon/BasicPropertiesTab";
|
||||
import { NoteContextMenu } from "../ribbon/NoteActions";
|
||||
import NoteActionsCustom from "../ribbon/NoteActionsCustom";
|
||||
import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab";
|
||||
import SimilarNotesTab from "../ribbon/SimilarNotesTab";
|
||||
import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector";
|
||||
|
||||
export default function MobileDetailMenu() {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const dropdownRef = useRef<BootstrapDropdown | null>(null);
|
||||
const { note, noteContext, parentComponent, ntxId, viewScope, hoistedNoteId } = useNoteContext();
|
||||
const subContexts = noteContext?.getMainContext().getSubContexts() ?? [];
|
||||
const isMainContext = noteContext?.isMainContext();
|
||||
const [ backlinksModalShown, setBacklinksModalShown ] = useState(false);
|
||||
const [ notePathsModalShown, setNotePathsModalShown ] = useState(false);
|
||||
const [ noteInfoModalShown, setNoteInfoModalShown ] = useState(false);
|
||||
const [ similarNotesModalShown, setSimilarNotesModalShown ] = useState(false);
|
||||
const [ codeNoteSwitcherModalShown, setCodeNoteSwitcherModalShown ] = useState(false);
|
||||
const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId);
|
||||
const backlinksCount = useBacklinkCount(note, viewScope?.viewMode === "default");
|
||||
|
||||
function closePane() {
|
||||
// Wait first for the context menu to be dismissed, otherwise the backdrop stays on.
|
||||
requestAnimationFrame(() => {
|
||||
parentComponent.triggerCommand("closeThisNoteSplit", { ntxId });
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
icon="bx bx-dots-vertical-rounded"
|
||||
text=""
|
||||
onClick={(e) => {
|
||||
const ntxId = (parentComponent as BasicWidget | null)?.getClosestNtxId();
|
||||
if (!ntxId) return;
|
||||
<div style={{ contain: "none" }}>
|
||||
{note ? (
|
||||
<NoteContextMenu
|
||||
dropdownRef={dropdownRef}
|
||||
note={note} noteContext={noteContext}
|
||||
itemsAtStart={<>
|
||||
<div className="form-list-row">
|
||||
<div className="form-list-col">
|
||||
<FormListItem
|
||||
icon="bx bx-link"
|
||||
onClick={() => setBacklinksModalShown(true)}
|
||||
disabled={backlinksCount === 0}
|
||||
>{t("status_bar.backlinks", { count: backlinksCount })}</FormListItem>
|
||||
</div>
|
||||
<div className="form-list-col">
|
||||
<FormListItem
|
||||
icon="bx bx-directions"
|
||||
onClick={() => setNotePathsModalShown(true)}
|
||||
disabled={(sortedNotePaths?.length ?? 0) <= 1}
|
||||
>{t("status_bar.note_paths", { count: sortedNotePaths?.length })}</FormListItem>
|
||||
</div>
|
||||
</div>
|
||||
<FormDropdownDivider />
|
||||
|
||||
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
|
||||
const subContexts = noteContext.getMainContext().getSubContexts();
|
||||
const isMainContext = noteContext?.isMainContext();
|
||||
const note = noteContext.note;
|
||||
{noteContext && ntxId && <NoteActionsCustom note={note} noteContext={noteContext} ntxId={ntxId} />}
|
||||
<FormListItem
|
||||
onClick={() => noteContext?.notePath && note_create.createNote(noteContext.notePath)}
|
||||
icon="bx bx-plus"
|
||||
>{t("mobile_detail_menu.insert_child_note")}</FormListItem>
|
||||
{subContexts.length < 2 && <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
onClick={(e) => {
|
||||
// We have to manually manage the hide because otherwise the old note context gets activated.
|
||||
e.stopPropagation();
|
||||
dropdownRef.current?.hide();
|
||||
parentComponent.triggerCommand("openNewNoteSplit", { ntxId });
|
||||
}}
|
||||
icon="bx bx-dock-right"
|
||||
>{t("create_pane_button.create_new_split")}</FormListItem>
|
||||
</>}
|
||||
{!isMainContext && <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
icon="bx bx-x"
|
||||
onClick={closePane}
|
||||
>{t("close_pane_button.close_this_pane")}</FormListItem>
|
||||
</>}
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
itemsNearNoteSettings={<>
|
||||
{note.type === "text" && <ContentLanguageSelector note={note} />}
|
||||
{note.type === "code" && <FormListItem icon={"bx bx-code"} onClick={() => setCodeNoteSwitcherModalShown(true)}>{t("status_bar.code_note_switcher")}</FormListItem>}
|
||||
<FormListItem icon="bx bx-info-circle" onClick={() => setNoteInfoModalShown(true)}>{t("note_info_widget.title")}</FormListItem>
|
||||
<FormListItem icon="bx bx-bar-chart" onClick={() => setSimilarNotesModalShown(true)}>{t("similar_notes.title")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
icon="bx bx-x"
|
||||
onClick={closePane}
|
||||
text={t("close_pane_button.close_this_pane")}
|
||||
/>
|
||||
)}
|
||||
|
||||
const items: (MenuItem<keyof CommandMappings>)[] = [
|
||||
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" },
|
||||
{ title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" },
|
||||
{ kind: "separator" },
|
||||
{ title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" },
|
||||
{ kind: "separator" },
|
||||
subContexts.length < 2 && { title: t("create_pane_button.create_new_split"), command: "openNewNoteSplit", uiIcon: "bx bx-dock-right" },
|
||||
!isMainContext && { title: t("close_pane_button.close_this_pane"), command: "closeThisNoteSplit", uiIcon: "bx bx-x" }
|
||||
].filter(i => !!i) as MenuItem<keyof CommandMappings>[];
|
||||
|
||||
const lastItem = items.at(-1);
|
||||
if (lastItem && "kind" in lastItem && lastItem.kind === "separator") {
|
||||
items.pop();
|
||||
}
|
||||
|
||||
contextMenu.show<keyof CommandMappings>({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items,
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === "insertChildNote") {
|
||||
note_create.createNote(appContext.tabManager.getActiveContextNotePath() ?? undefined);
|
||||
} else if (command === "delete") {
|
||||
const notePath = appContext.tabManager.getActiveContextNotePath();
|
||||
if (!notePath) {
|
||||
throw new Error("Cannot get note path to delete.");
|
||||
}
|
||||
|
||||
const branchId = await tree.getBranchIdFromUrl(notePath);
|
||||
|
||||
if (!branchId) {
|
||||
throw new Error(t("mobile_detail_menu.error_cannot_get_branch_id", { notePath }));
|
||||
}
|
||||
|
||||
if (await branches.deleteNotes([branchId]) && parentComponent) {
|
||||
parentComponent.triggerCommand("setActiveScreen", { screen: "tree" });
|
||||
}
|
||||
} else if (command && parentComponent) {
|
||||
parentComponent.triggerCommand(command, { ntxId });
|
||||
}
|
||||
},
|
||||
forcePositionOnMobile: true
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
{createPortal((
|
||||
<>
|
||||
<BacklinksModal note={note} modalShown={backlinksModalShown} setModalShown={setBacklinksModalShown} />
|
||||
<NotePathsModal note={note} modalShown={notePathsModalShown} notePath={noteContext?.notePath} sortedNotePaths={sortedNotePaths} setModalShown={setNotePathsModalShown} />
|
||||
<NoteInfoModal note={note} modalShown={noteInfoModalShown} setModalShown={setNoteInfoModalShown} />
|
||||
<SimilarNotesModal note={note} modalShown={similarNotesModalShown} setModalShown={setSimilarNotesModalShown} />
|
||||
<CodeNoteSwitcherModal note={note} modalShown={codeNoteSwitcherModalShown} setModalShown={setCodeNoteSwitcherModalShown} />
|
||||
</>
|
||||
), document.body)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ContentLanguageSelector({ note }: { note: FNote | null | undefined }) {
|
||||
const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note);
|
||||
const { activeLocale, processedLocales } = useProcessedLocales(locales, DEFAULT_LOCALE, currentNoteLanguage ?? DEFAULT_LOCALE.id);
|
||||
|
||||
return (
|
||||
<FormDropdownSubmenu
|
||||
icon="bx bx-globe"
|
||||
title={t("mobile_detail_menu.content_language_switcher", { language: getLocaleName(activeLocale ?? DEFAULT_LOCALE) })}
|
||||
>
|
||||
{processedLocales.map((locale, index) =>
|
||||
(typeof locale === "object") ? (
|
||||
<FormListItem
|
||||
key={locale.id}
|
||||
rtl={locale.rtl}
|
||||
checked={locale.id === currentNoteLanguage}
|
||||
onClick={() => setCurrentNoteLanguage(locale.id)}
|
||||
>{locale.name}</FormListItem>
|
||||
) : (
|
||||
<FormDropdownDivider key={`divider-${index}`} />
|
||||
)
|
||||
)}
|
||||
</FormDropdownSubmenu>
|
||||
);
|
||||
}
|
||||
|
||||
interface WithModal {
|
||||
modalShown: boolean;
|
||||
setModalShown: (shown: boolean) => void;
|
||||
}
|
||||
|
||||
function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="backlinks-modal tn-backlinks-widget"
|
||||
size="md"
|
||||
title={t("mobile_detail_menu.backlinks")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
<ul className="backlinks-items">
|
||||
{note && <BacklinksList note={note} />}
|
||||
</ul>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalShown }: { note: FNote | null | undefined, sortedNotePaths: NotePathRecord[] | undefined, notePath: string | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="note-paths-modal"
|
||||
size="md"
|
||||
title={t("note_paths.title")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
{note && (
|
||||
<NotePathsWidget
|
||||
sortedNotePaths={sortedNotePaths}
|
||||
currentNotePath={notePath}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteInfoModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="note-info-modal"
|
||||
size="md"
|
||||
title={t("note_info_widget.title")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
{note && <NoteInfoContent note={note} noteType={note.type} />}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function SimilarNotesModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
return (
|
||||
<Modal
|
||||
className="similar-notes-modal"
|
||||
size="md"
|
||||
title={t("similar_notes.title")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
<SimilarNotesTab note={note} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeNoteSwitcherModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
|
||||
const currentNoteMime = useNoteProperty(note, "mime");
|
||||
const mimeTypes = useMimeTypes();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="code-note-switcher-modal"
|
||||
size="md"
|
||||
title={t("status_bar.code_note_switcher")}
|
||||
show={modalShown}
|
||||
onHidden={() => setModalShown(false)}
|
||||
>
|
||||
<div className="dropdown-menu static show">
|
||||
{note && <NoteTypeCodeNoteList
|
||||
currentMimeType={currentNoteMime}
|
||||
mimeTypes={mimeTypes}
|
||||
changeNoteType={(type, mime) => {
|
||||
server.put(`notes/${note.noteId}/type`, { type, mime });
|
||||
setModalShown(false);
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.collection-properties {
|
||||
padding: 0;
|
||||
padding: 0.55em 12px;
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
align-items: center;
|
||||
@@ -14,7 +14,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
>div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.center-container {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
button.btn {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
flex-wrap: wrap;
|
||||
padding: 0.55em 1em;
|
||||
|
||||
>div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import "./CollectionProperties.css";
|
||||
|
||||
import { t } from "i18next";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { useContext, useRef } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { ViewTypeOptions } from "../collections/interface";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
|
||||
import FormTextBox from "../react/FormTextBox";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useNoteProperty, useTriliumEvent } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config";
|
||||
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
|
||||
|
||||
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
|
||||
export const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
|
||||
grid: "bx bxs-grid",
|
||||
list: "bx bx-list-ul",
|
||||
calendar: "bx bx-calendar",
|
||||
@@ -28,15 +26,26 @@ const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
|
||||
presentation: "bx bx-rectangle"
|
||||
};
|
||||
|
||||
export default function CollectionProperties({ note }: { note: FNote }) {
|
||||
export default function CollectionProperties({ note, centerChildren, rightChildren }: {
|
||||
note: FNote;
|
||||
centerChildren?: ComponentChildren;
|
||||
rightChildren?: ComponentChildren;
|
||||
}) {
|
||||
const [ viewType, setViewType ] = useViewType(note);
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
|
||||
return (
|
||||
return ([ "book", "search" ].includes(noteType ?? "") &&
|
||||
<div className="collection-properties">
|
||||
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
|
||||
<ViewOptions note={note} viewType={viewType} />
|
||||
<div className="spacer" />
|
||||
<HelpButton note={note} />
|
||||
<div className="left-container">
|
||||
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
|
||||
<ViewOptions note={note} viewType={viewType} />
|
||||
</div>
|
||||
<div className="center-container">
|
||||
{centerChildren}
|
||||
</div>
|
||||
<div className="right-container">
|
||||
{rightChildren}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -222,15 +231,3 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function HelpButton({ note }: { note: FNote }) {
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
|
||||
return (helpUrl && (
|
||||
<ActionButton
|
||||
icon="bx bx-help-circle"
|
||||
onClick={(() => openInAppHelpFromUrl(helpUrl))}
|
||||
text={t("help-button.title")}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
@@ -117,3 +117,35 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile .modal.icon-switcher {
|
||||
.modal-dialog {
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: unset;
|
||||
transform: unset;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .filter-row {
|
||||
padding: 0.25em var(--bs-modal-padding) 0.5em var(--bs-modal-padding);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-list {
|
||||
margin: auto;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
|
||||
span {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,24 @@ import { IconRegistry } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { t } from "i18next";
|
||||
import { CSSProperties, RefObject } from "preact";
|
||||
import { CSSProperties } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import type React from "react";
|
||||
import { CellComponentProps, Grid } from "react-window";
|
||||
|
||||
import FNote from "../entities/fnote";
|
||||
import attributes from "../services/attributes";
|
||||
import server from "../services/server";
|
||||
import { isDesktop, isMobile } from "../services/utils";
|
||||
import ActionButton from "./react/ActionButton";
|
||||
import Dropdown from "./react/Dropdown";
|
||||
import { FormDropdownDivider, FormListItem } from "./react/FormList";
|
||||
import FormTextBox from "./react/FormTextBox";
|
||||
import { useNoteContext, useNoteLabel, useStaticTooltip } from "./react/hooks";
|
||||
import { useNoteContext, useNoteLabel, useStaticTooltip, useWindowSize } from "./react/hooks";
|
||||
import Modal from "./react/Modal";
|
||||
|
||||
const ICON_SIZE = isMobile() ? 56 : 48;
|
||||
|
||||
interface IconToCountCache {
|
||||
iconClassToCountMap: Record<string, number>;
|
||||
@@ -36,6 +42,10 @@ export default function NoteIcon() {
|
||||
setIcon(note?.getIcon());
|
||||
}, [ note, iconClass, workspaceIconClass ]);
|
||||
|
||||
if (isMobile()) {
|
||||
return <MobileNoteIconSwitcher note={note} icon={icon} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="note-icon-widget"
|
||||
@@ -47,16 +57,47 @@ export default function NoteIcon() {
|
||||
hideToggleArrow
|
||||
disabled={viewScope?.viewMode !== "default"}
|
||||
>
|
||||
{ note && <NoteIconList note={note} dropdownRef={dropdownRef} /> }
|
||||
{ note && <NoteIconList note={note} onHide={() => dropdownRef?.current?.hide()} columnCount={12} /> }
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteIconList({ note, dropdownRef }: {
|
||||
note: FNote,
|
||||
dropdownRef: RefObject<BootstrapDropdown>;
|
||||
function MobileNoteIconSwitcher({ note, icon }: {
|
||||
note: FNote | null | undefined;
|
||||
icon: string | null | undefined;
|
||||
}) {
|
||||
const [ modalShown, setModalShown ] = useState(false);
|
||||
const { windowWidth } = useWindowSize();
|
||||
|
||||
return (note &&
|
||||
<div className="note-icon-widget">
|
||||
<ActionButton
|
||||
className="note-icon"
|
||||
icon={icon ?? "bx bx-empty"}
|
||||
text={t("note_icon.change_note_icon")}
|
||||
onClick={() => setModalShown(true)}
|
||||
/>
|
||||
|
||||
{createPortal((
|
||||
<Modal
|
||||
title={t("note_icon.change_note_icon")}
|
||||
size="xl"
|
||||
show={modalShown} onHidden={() => setModalShown(false)}
|
||||
className="icon-switcher note-icon-widget"
|
||||
scrollable
|
||||
>
|
||||
<NoteIconList note={note} onHide={() => setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />
|
||||
</Modal>
|
||||
), document.body)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteIconList({ note, onHide, columnCount }: {
|
||||
note: FNote;
|
||||
onHide: () => void;
|
||||
columnCount: number;
|
||||
}) {
|
||||
const searchBoxRef = useRef<HTMLInputElement>(null);
|
||||
const iconListRef = useRef<HTMLDivElement>(null);
|
||||
const [ search, setSearch ] = useState<string>();
|
||||
const [ filterByPrefix, setFilterByPrefix ] = useState<string | null>(null);
|
||||
@@ -72,53 +113,22 @@ function NoteIconList({ note, dropdownRef }: {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="filter-row">
|
||||
<span>{t("note_icon.search")}</span>
|
||||
<FormTextBox
|
||||
inputRef={searchBoxRef}
|
||||
type="text"
|
||||
name="icon-search"
|
||||
placeholder={ filterByPrefix
|
||||
? t("note_icon.search_placeholder_filtered", {
|
||||
number: filteredIcons.length ?? 0,
|
||||
name: glob.iconRegistry.sources.find(s => s.prefix === filterByPrefix)?.name ?? ""
|
||||
})
|
||||
: t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })}
|
||||
currentValue={search} onChange={setSearch}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{getIconLabels(note).length > 0 && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ActionButton
|
||||
icon="bx bx-reset"
|
||||
text={t("note_icon.reset-default")}
|
||||
onClick={() => {
|
||||
if (!note) return;
|
||||
for (const label of getIconLabels(note)) {
|
||||
attributes.removeAttributeById(note.noteId, label.attributeId);
|
||||
}
|
||||
dropdownRef?.current?.hide();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{glob.iconRegistry.sources.length > 0 && <Dropdown
|
||||
buttonClassName="bx bx-filter-alt"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
title={t("note_icon.filter")}
|
||||
>
|
||||
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
|
||||
</Dropdown>}
|
||||
</div>
|
||||
<FilterRow
|
||||
note={note}
|
||||
filterByPrefix={filterByPrefix}
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
setFilterByPrefix={setFilterByPrefix}
|
||||
filteredIcons={filteredIcons}
|
||||
onHide={onHide}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="icon-list"
|
||||
ref={iconListRef}
|
||||
style={{
|
||||
width: (columnCount * ICON_SIZE + 10),
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// Make sure we are not clicking on something else than a button.
|
||||
const clickedTarget = e.target as HTMLElement;
|
||||
@@ -129,18 +139,19 @@ function NoteIconList({ note, dropdownRef }: {
|
||||
const attributeToSet = note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass";
|
||||
attributes.setLabel(note.noteId, attributeToSet, iconClass);
|
||||
}
|
||||
dropdownRef?.current?.hide();
|
||||
onHide();
|
||||
}}
|
||||
>
|
||||
{filteredIcons.length ? (
|
||||
<Grid
|
||||
columnCount={12}
|
||||
columnWidth={48}
|
||||
rowCount={Math.ceil(filteredIcons.length / 12)}
|
||||
rowHeight={48}
|
||||
columnCount={columnCount}
|
||||
columnWidth={ICON_SIZE}
|
||||
rowCount={Math.ceil(filteredIcons.length / columnCount)}
|
||||
rowHeight={ICON_SIZE}
|
||||
cellComponent={IconItemCell}
|
||||
cellProps={{
|
||||
filteredIcons
|
||||
filteredIcons,
|
||||
columnCount
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -151,12 +162,97 @@ function NoteIconList({ note, dropdownRef }: {
|
||||
);
|
||||
}
|
||||
|
||||
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{
|
||||
function FilterRow({ note, filterByPrefix, search, setSearch, setFilterByPrefix, filteredIcons, onHide }: {
|
||||
note: FNote;
|
||||
filterByPrefix: string | null;
|
||||
search: string | undefined;
|
||||
setSearch: (value: string | undefined) => void;
|
||||
setFilterByPrefix: (value: string | null) => void;
|
||||
filteredIcons: IconWithName[];
|
||||
}>): React.JSX.Element {
|
||||
const iconIndex = rowIndex * 12 + columnIndex;
|
||||
onHide: () => void;
|
||||
}) {
|
||||
const searchBoxRef = useRef<HTMLInputElement>(null);
|
||||
const hasCustomIcon = getIconLabels(note).length > 0;
|
||||
|
||||
function resetToDefaultIcon() {
|
||||
if (!note) return;
|
||||
for (const label of getIconLabels(note)) {
|
||||
attributes.removeAttributeById(note.noteId, label.attributeId);
|
||||
}
|
||||
onHide();
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="filter-row">
|
||||
<span>{t("note_icon.search")}</span>
|
||||
<FormTextBox
|
||||
inputRef={searchBoxRef}
|
||||
type="text"
|
||||
name="icon-search"
|
||||
placeholder={ filterByPrefix
|
||||
? t("note_icon.search_placeholder_filtered", {
|
||||
number: filteredIcons.length ?? 0,
|
||||
name: glob.iconRegistry.sources.find(s => s.prefix === filterByPrefix)?.name ?? ""
|
||||
})
|
||||
: t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })}
|
||||
currentValue={search} onChange={setSearch}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{isDesktop()
|
||||
? <>
|
||||
{hasCustomIcon && (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<ActionButton
|
||||
icon="bx bx-reset"
|
||||
text={t("note_icon.reset-default")}
|
||||
onClick={resetToDefaultIcon}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{<Dropdown
|
||||
buttonClassName="bx bx-filter-alt"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
title={t("note_icon.filter")}
|
||||
>
|
||||
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
|
||||
</Dropdown>}
|
||||
</> : (
|
||||
<Dropdown
|
||||
buttonClassName="bx bx-dots-vertical-rounded"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
dropdownContainerClassName="mobile-bottom-menu"
|
||||
>
|
||||
{hasCustomIcon && <>
|
||||
<FormListItem
|
||||
icon="bx bx-reset"
|
||||
onClick={resetToDefaultIcon}
|
||||
disabled={!hasCustomIcon}
|
||||
>{t("note_icon.reset-default")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons, columnCount }: CellComponentProps<{
|
||||
filteredIcons: IconWithName[];
|
||||
columnCount: number;
|
||||
}>) {
|
||||
const iconIndex = rowIndex * columnCount + columnIndex;
|
||||
const iconData = filteredIcons[iconIndex] as IconWithName | undefined;
|
||||
if (!iconData) return <></>;
|
||||
if (!iconData) return <></> as React.ReactElement;
|
||||
|
||||
const { id, terms, iconPack } = iconData;
|
||||
return (
|
||||
@@ -166,7 +262,7 @@ function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellCompo
|
||||
title={t("note_icon.icon_tooltip", { name: terms?.[0] ?? id, iconPack })}
|
||||
style={style as CSSProperties}
|
||||
/>
|
||||
);
|
||||
) as React.ReactElement;
|
||||
}
|
||||
|
||||
function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {
|
||||
@@ -183,7 +279,7 @@ function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {
|
||||
checked={filterByPrefix === "bx"}
|
||||
onClick={() => setFilterByPrefix("bx")}
|
||||
>{t("note_icon.filter-default")}</FormListItem>
|
||||
<FormDropdownDivider />
|
||||
{glob.iconRegistry.sources.length > 1 && <FormDropdownDivider />}
|
||||
|
||||
{glob.iconRegistry.sources.map(({ prefix, name, icon }) => (
|
||||
prefix !== "bx" && <FormListItem
|
||||
|
||||
@@ -109,4 +109,29 @@ body.experimental-feature-new-layout {
|
||||
--input-focus-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile .title-row {
|
||||
.icon-action:not(.note-icon) {
|
||||
--icon-button-size: 45px;
|
||||
--icon-button-icon-ratio: 0.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-actions {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.note-badges {
|
||||
margin-inline: 0.5em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-icon-widget {
|
||||
margin-inline: 0.5em;
|
||||
|
||||
.note-icon {
|
||||
--icon-button-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#left-pane .tree-wrapper {
|
||||
.tree-wrapper {
|
||||
.note-indicator-icon.subtree-hidden-badge {
|
||||
font-family: inherit !important;
|
||||
margin-inline: 0.5em;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import { useStaticTooltip } from "./hooks";
|
||||
import keyboard_actions from "../../services/keyboard_actions";
|
||||
import { HTMLAttributes } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu"> {
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import keyboard_actions from "../../services/keyboard_actions";
|
||||
import { useStaticTooltip } from "./hooks";
|
||||
|
||||
export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu" | "style"> {
|
||||
text: string;
|
||||
titlePosition?: "top" | "right" | "bottom" | "left";
|
||||
icon: string;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import type { ComponentChildren, RefObject } from "preact";
|
||||
import type { CSSProperties } from "preact/compat";
|
||||
import { useMemo } from "preact/hooks";
|
||||
import { memo } from "preact/compat";
|
||||
import { useMemo } from "preact/hooks";
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import { isDesktop } from "../../services/utils";
|
||||
import ActionButton from "./ActionButton";
|
||||
import Icon from "./Icon";
|
||||
|
||||
export interface ButtonProps {
|
||||
@@ -78,7 +81,7 @@ export function ButtonGroup({ children }: { children: ComponentChildren }) {
|
||||
<div className="btn-group" role="group">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function SplitButton({ text, icon, children, ...restProps }: {
|
||||
@@ -103,7 +106,17 @@ export function SplitButton({ text, icon, children, ...restProps }: {
|
||||
{children}
|
||||
</ul>
|
||||
</ButtonGroup>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function ButtonOrActionButton(props: {
|
||||
text: string;
|
||||
icon: string;
|
||||
} & Pick<ButtonProps, "onClick" | "triggerCommand" | "disabled" | "title">) {
|
||||
if (isDesktop()) {
|
||||
return <Button {...props} />;
|
||||
}
|
||||
return <ActionButton {...props} />;
|
||||
}
|
||||
|
||||
export default Button;
|
||||
|
||||
@@ -17,21 +17,34 @@
|
||||
.collapsible-body {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&.fully-expanded {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
padding-top: 0.5em;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.collapsible-title .arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-transition {
|
||||
.collapsible-body {
|
||||
transition: height 250ms ease-in;
|
||||
}
|
||||
|
||||
.collapsible-inner-body {
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
|
||||
const { height } = useElementSize(innerRef) ?? {};
|
||||
const contentId = useUniqueName();
|
||||
const [ transitionEnabled, setTransitionEnabled ] = useState(false);
|
||||
const [ fullyExpanded, setFullyExpanded ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
@@ -35,6 +36,21 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded) {
|
||||
if (transitionEnabled) {
|
||||
const timeout = setTimeout(() => {
|
||||
setFullyExpanded(true);
|
||||
}, 250);
|
||||
return () => clearTimeout(timeout);
|
||||
} else {
|
||||
setFullyExpanded(true);
|
||||
}
|
||||
} else {
|
||||
setFullyExpanded(false);
|
||||
}
|
||||
}, [expanded, transitionEnabled])
|
||||
|
||||
return (
|
||||
<div className={clsx("collapsible", className, {
|
||||
expanded,
|
||||
@@ -53,7 +69,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
|
||||
<div
|
||||
id={contentId}
|
||||
ref={bodyRef}
|
||||
className="collapsible-body"
|
||||
className={clsx("collapsible-body", {"fully-expanded": fullyExpanded})}
|
||||
style={{ height: expanded ? height : "0" }}
|
||||
aria-hidden={!expanded}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ComponentChildren, HTMLAttributes } from "preact";
|
||||
import { CSSProperties, HTMLProps } from "preact/compat";
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { isMobile } from "../../services/utils";
|
||||
import { useTooltip, useUniqueName } from "./hooks";
|
||||
|
||||
type DataAttributes = {
|
||||
@@ -32,9 +33,10 @@ export interface DropdownProps extends Pick<HTMLProps<HTMLDivElement>, "id" | "c
|
||||
dropdownRef?: MutableRef<BootstrapDropdown | null>;
|
||||
titlePosition?: "top" | "right" | "bottom" | "left";
|
||||
titleOptions?: Partial<Tooltip.Options>;
|
||||
mobileBackdrop?: boolean;
|
||||
}
|
||||
|
||||
export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions }: DropdownProps) {
|
||||
export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions, mobileBackdrop }: DropdownProps) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const dropdownContainerRef = useRef<HTMLUListElement | null>(null);
|
||||
@@ -74,12 +76,18 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
|
||||
setShown(true);
|
||||
externalOnShown?.();
|
||||
hideTooltip();
|
||||
}, [ hideTooltip ]);
|
||||
if (mobileBackdrop && isMobile()) {
|
||||
document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover");
|
||||
}
|
||||
}, [ hideTooltip, mobileBackdrop ]);
|
||||
|
||||
const onHidden = useCallback(() => {
|
||||
setShown(false);
|
||||
externalOnHidden?.();
|
||||
}, []);
|
||||
if (mobileBackdrop && isMobile()) {
|
||||
document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover");
|
||||
}
|
||||
}, [ mobileBackdrop ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import ActionButton, { ActionButtonProps } from "./ActionButton";
|
||||
import Button, { ButtonProps } from "./Button";
|
||||
import { FormListItem, FormListItemOpts } from "./FormList";
|
||||
|
||||
interface FormFileUploadProps {
|
||||
export interface FormFileUploadProps {
|
||||
name?: string;
|
||||
onChange: (files: FileList | null) => void;
|
||||
multiple?: boolean;
|
||||
@@ -75,3 +76,25 @@ export function FormFileUploadActionButton({ onChange, ...buttonProps }: Omit<Ac
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link FormFileUploadButton}, but uses an {@link FormListItem} instead of a normal {@link Button}.
|
||||
* @param param the change listener for the file upload and the properties for the button.
|
||||
*/
|
||||
export function FormFileUploadFormListItem({ onChange, children, ...buttonProps }: Omit<FormListItemOpts, "onClick"> & Pick<FormFileUploadProps, "onChange">) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormListItem
|
||||
{...buttonProps}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
>{children}</FormListItem>
|
||||
<FormFileUpload
|
||||
inputRef={inputRef}
|
||||
hidden
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,3 +27,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-list-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1em;
|
||||
|
||||
.form-list-col {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export interface FormListBadge {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface FormListItemOpts {
|
||||
export interface FormListItemOpts {
|
||||
children: ComponentChildren;
|
||||
icon?: string;
|
||||
value?: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { HTMLAttributes } from "preact";
|
||||
|
||||
interface IconProps extends Pick<HTMLAttributes<HTMLSpanElement>, "className" | "onClick"> {
|
||||
interface IconProps extends Pick<HTMLAttributes<HTMLSpanElement>, "className" | "onClick" | "title"> {
|
||||
icon?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useRef, useMemo } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ComponentChildren } from "preact";
|
||||
import type { CSSProperties, RefObject } from "preact/compat";
|
||||
import { openDialog } from "../../services/dialog";
|
||||
import { Modal as BootstrapModal } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { ComponentChildren, CSSProperties, RefObject } from "preact";
|
||||
import { memo } from "preact/compat";
|
||||
import { useEffect, useMemo, useRef } from "preact/hooks";
|
||||
|
||||
import { openDialog } from "../../services/dialog";
|
||||
import { t } from "../../services/i18n";
|
||||
import { useSyncedRef } from "./hooks";
|
||||
|
||||
interface CustomTitleBarButton {
|
||||
title: string;
|
||||
iconClassName: string;
|
||||
onClick: () => void;
|
||||
onClick: (e: MouseEvent) => void;
|
||||
}
|
||||
|
||||
export interface ModalProps {
|
||||
@@ -80,7 +80,7 @@ export interface ModalProps {
|
||||
noFocus?: boolean;
|
||||
}
|
||||
|
||||
export default function Modal({ children, className, size, title, customTitleBarButtons: titleBarButtons, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable, keepInDom, noFocus }: ModalProps) {
|
||||
export default function Modal({ children, className, size, title, customTitleBarButtons: titleBarButtons, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable, keepInDom, noFocus }: ModalProps) {
|
||||
const modalRef = useSyncedRef<HTMLDivElement>(externalModalRef);
|
||||
const modalInstanceRef = useRef<BootstrapModal>();
|
||||
const elementToFocus = useRef<Element | null>();
|
||||
@@ -116,7 +116,7 @@ export default function Modal({ children, className, size, title, customTitleBar
|
||||
focus: !noFocus
|
||||
}).then(($widget) => {
|
||||
modalInstanceRef.current = BootstrapModal.getOrCreateInstance($widget[0]);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
modalInstanceRef.current?.hide();
|
||||
}
|
||||
@@ -159,13 +159,12 @@ export default function Modal({ children, className, size, title, customTitleBar
|
||||
|
||||
{titleBarButtons?.filter((b) => b !== null).map((titleBarButton) => (
|
||||
<button type="button"
|
||||
className={clsx("custom-title-bar-button bx", titleBarButton.iconClassName)}
|
||||
title={titleBarButton.title}
|
||||
onClick={titleBarButton.onClick}>
|
||||
</button>
|
||||
className={clsx("custom-title-bar-button bx", titleBarButton.iconClassName)}
|
||||
title={titleBarButton.title}
|
||||
onClick={titleBarButton.onClick} />
|
||||
))}
|
||||
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label={t("modal.close")}></button>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label={t("modal.close")} />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
12
apps/client/src/widgets/react/ResponsiveContainer.tsx
Normal file
12
apps/client/src/widgets/react/ResponsiveContainer.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
import { isMobile } from "../../services/utils";
|
||||
|
||||
interface ResponsiveContainerProps {
|
||||
mobile?: ComponentChildren;
|
||||
desktop?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function ResponsiveContainer({ mobile, desktop }: ResponsiveContainerProps) {
|
||||
return (isMobile() ? mobile : desktop);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MimeType, NoteType, ToggleInParentResponse } from "@triliumnext/commons";
|
||||
import { ComponentChildren } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
||||
|
||||
@@ -117,19 +116,18 @@ export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note
|
||||
onClick={() => changeNoteType(type, mime)}
|
||||
>{title}</FormListItem>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
disabled
|
||||
>
|
||||
<strong>{title}</strong>
|
||||
</FormListItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem
|
||||
checked={checked}
|
||||
disabled
|
||||
>
|
||||
<strong>{title}</strong>
|
||||
</FormListItem>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
|
||||
{!noCodeNotes && <NoteTypeCodeNoteList mimeTypes={mimeTypes} changeNoteType={changeNoteType} setModalShown={setModalShown} />}
|
||||
@@ -141,7 +139,7 @@ export function NoteTypeCodeNoteList({ currentMimeType, mimeTypes, changeNoteTyp
|
||||
currentMimeType?: string;
|
||||
mimeTypes: MimeType[];
|
||||
changeNoteType(type: NoteType, mime: string): void;
|
||||
setModalShown(shown: boolean): void;
|
||||
setModalShown?(shown: boolean): void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -155,8 +153,10 @@ export function NoteTypeCodeNoteList({ currentMimeType, mimeTypes, changeNoteTyp
|
||||
</FormListItem>
|
||||
))}
|
||||
|
||||
<FormDropdownDivider />
|
||||
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
|
||||
{setModalShown && <>
|
||||
<FormDropdownDivider />
|
||||
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ function ProtectedNoteSwitch({ note }: { note?: FNote | null }) {
|
||||
onChange={(shouldProtect) => note && protected_session.protectNote(note.noteId, shouldProtect, false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function EditabilitySelect({ note }: { note?: FNote | null }) {
|
||||
@@ -417,9 +417,9 @@ function findTypeTitle(type?: NoteType, mime?: string | null) {
|
||||
const found = mimeTypes.find((mt) => mt.mime === mime);
|
||||
|
||||
return found ? found.title : mime;
|
||||
} else {
|
||||
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
|
||||
|
||||
return noteType ? noteType.title : type;
|
||||
}
|
||||
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
|
||||
|
||||
return noteType ? noteType.title : type;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ConvertToAttachmentResponse } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import { RefObject } from "preact";
|
||||
import { ComponentChildren, RefObject } from "preact";
|
||||
import { useContext, useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import appContext, { CommandNames } from "../../components/app_context";
|
||||
@@ -22,7 +22,7 @@ import MovePaneButton from "../buttons/move_pane_button";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormDropdownDivider, FormDropdownSubmenu, FormListHeader, FormListItem, FormListToggleableItem } from "../react/FormList";
|
||||
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useSyncedRef, useTriliumEvent, useTriliumOption } from "../react/hooks";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { NoteTypeDropdownContent, useNoteBookmarkState, useShareState } from "./BasicPropertiesTab";
|
||||
import NoteActionsCustom from "./NoteActionsCustom";
|
||||
@@ -63,8 +63,14 @@ function RevisionsButton({ note }: { note: FNote }) {
|
||||
|
||||
type ItemToFocus = "basic-properties";
|
||||
|
||||
function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) {
|
||||
const dropdownRef = useRef<BootstrapDropdown>(null);
|
||||
export function NoteContextMenu({ note, noteContext, itemsAtStart, itemsNearNoteSettings, dropdownRef: externalDropdownRef }: {
|
||||
note: FNote,
|
||||
noteContext?: NoteContext,
|
||||
itemsAtStart?: ComponentChildren;
|
||||
itemsNearNoteSettings?: ComponentChildren;
|
||||
dropdownRef?: RefObject<BootstrapDropdown>;
|
||||
}) {
|
||||
const dropdownRef = useSyncedRef<BootstrapDropdown>(externalDropdownRef, null);
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const noteType = useNoteProperty(note, "type") ?? "";
|
||||
const [viewType] = useNoteLabel(note, "viewType");
|
||||
@@ -99,12 +105,15 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
dropdownRef={dropdownRef}
|
||||
buttonClassName={ isNewLayout ? "bx bx-dots-horizontal-rounded" : "bx bx-dots-vertical-rounded" }
|
||||
className="note-actions"
|
||||
dropdownContainerClassName="mobile-bottom-menu"
|
||||
hideToggleArrow
|
||||
noSelectButtonStyle
|
||||
noDropdownListStyle
|
||||
iconAction
|
||||
onHidden={() => itemToFocusRef.current = null }
|
||||
mobileBackdrop
|
||||
>
|
||||
{itemsAtStart}
|
||||
|
||||
{isReadOnly && <>
|
||||
<CommandItem icon="bx bx-pencil" text={t("read-only-info.edit-note")}
|
||||
@@ -123,6 +132,8 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
|
||||
<FormDropdownDivider />
|
||||
</>}
|
||||
|
||||
{itemsNearNoteSettings}
|
||||
|
||||
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
|
||||
disabled={isInOptionsOrHelp || note.type === "search"}
|
||||
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />
|
||||
@@ -277,7 +288,7 @@ function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?:
|
||||
);
|
||||
}
|
||||
|
||||
function CommandItem({ icon, text, title, command, disabled }: { icon: string, text: string, title?: string, command: CommandNames | (() => void), disabled?: boolean, destructive?: boolean }) {
|
||||
export function CommandItem({ icon, text, title, command, disabled }: { icon: string, text: string, title?: string, command: CommandNames | (() => void), disabled?: boolean, destructive?: boolean }) {
|
||||
return <FormListItem
|
||||
icon={icon}
|
||||
title={title}
|
||||
|
||||
3
apps/client/src/widgets/ribbon/NoteActionsCustom.css
Normal file
3
apps/client/src/widgets/ribbon/NoteActionsCustom.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body.mobile .note-actions-custom:not(:empty) {
|
||||
margin-bottom: calc(var(--bs-dropdown-divider-margin-y) * 2);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./NoteActionsCustom.css";
|
||||
|
||||
import { NoteType } from "@triliumnext/commons";
|
||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
@@ -7,11 +9,12 @@ import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { getHelpUrlForNote } from "../../services/in_app_help";
|
||||
import { downloadFileNote, openNoteExternally } from "../../services/open";
|
||||
import { openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { isMobile, openInAppHelpFromUrl } from "../../services/utils";
|
||||
import { ViewTypeOptions } from "../collections/interface";
|
||||
import { buildSaveSqlToNoteHandler } from "../FloatingButtonsDefinitions";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import { FormFileUploadActionButton } from "../react/FormFileUpload";
|
||||
import ActionButton, { ActionButtonProps } from "../react/ActionButton";
|
||||
import { FormFileUploadActionButton, FormFileUploadFormListItem, FormFileUploadProps } from "../react/FormFileUpload";
|
||||
import { FormListItem } from "../react/FormList";
|
||||
import { useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import { buildUploadNewFileRevisionListener } from "./FilePropertiesTab";
|
||||
@@ -32,6 +35,8 @@ interface NoteActionsCustomInnerProps extends NoteActionsCustomProps {
|
||||
viewType: ViewTypeOptions | null | undefined;
|
||||
}
|
||||
|
||||
const cachedIsMobile = isMobile();
|
||||
|
||||
/**
|
||||
* Part of {@link NoteActions} on the new layout, but are rendered with a slight spacing
|
||||
* from the rest of the note items and the buttons differ based on the note type.
|
||||
@@ -115,7 +120,7 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps
|
||||
onChange: (files: FileList | null) => void;
|
||||
}) {
|
||||
return (
|
||||
<FormFileUploadActionButton
|
||||
<NoteActionWithFileUpload
|
||||
icon="bx bx-folder-open"
|
||||
text={t("image_properties.upload_new_revision")}
|
||||
disabled={!note.isContentAvailable()}
|
||||
@@ -125,8 +130,8 @@ function UploadNewRevisionButton({ note, onChange }: NoteActionsCustomInnerProps
|
||||
}
|
||||
|
||||
function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
return (
|
||||
<ActionButton
|
||||
return (!cachedIsMobile &&
|
||||
<NoteAction
|
||||
icon="bx bx-link-external"
|
||||
text={t("file_properties.open")}
|
||||
disabled={note.isProtected}
|
||||
@@ -137,7 +142,7 @@ function OpenExternallyButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
|
||||
function DownloadFileButton({ note, parentComponent, ntxId }: NoteActionsCustomInnerProps) {
|
||||
return (
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
icon="bx bx-download"
|
||||
text={t("file_properties.download")}
|
||||
disabled={!note.isContentAvailable()}
|
||||
@@ -149,7 +154,7 @@ function DownloadFileButton({ note, parentComponent, ntxId }: NoteActionsCustomI
|
||||
//#region Floating buttons
|
||||
function CopyReferenceToClipboardButton({ ntxId, noteType, parentComponent }: NoteActionsCustomInnerProps) {
|
||||
return (["mermaid", "canvas", "mindMap", "image"].includes(noteType) &&
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
text={t("image_properties.copy_reference_to_clipboard")}
|
||||
icon="bx bx-copy"
|
||||
onClick={() => parentComponent?.triggerEvent("copyImageReferenceToClipboard", { ntxId })}
|
||||
@@ -161,7 +166,7 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not
|
||||
const isEnabled = (note.noteId === "_backendLog" || noteType === "render") && isDefaultViewMode;
|
||||
|
||||
return (isEnabled &&
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
text={t("backend_log.refresh")}
|
||||
icon="bx bx-refresh"
|
||||
onClick={() => parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })}
|
||||
@@ -170,11 +175,11 @@ function RefreshButton({ note, noteType, isDefaultViewMode, parentComponent, not
|
||||
}
|
||||
|
||||
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: NoteActionsCustomInnerProps) {
|
||||
const isShown = note.type === "mermaid" && note.isContentAvailable() && isDefaultViewMode;
|
||||
const isShown = note.type === "mermaid" && !cachedIsMobile && note.isContentAvailable() && isDefaultViewMode;
|
||||
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
|
||||
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
|
||||
|
||||
return isShown && <ActionButton
|
||||
return isShown && <NoteAction
|
||||
text={upcomingOrientation === "vertical" ? t("switch_layout_button.title_vertical") : t("switch_layout_button.title_horizontal")}
|
||||
icon={upcomingOrientation === "vertical" ? "bx bxs-dock-bottom" : "bx bxs-dock-left"}
|
||||
onClick={() => setSplitEditorOrientation(upcomingOrientation)}
|
||||
@@ -182,13 +187,13 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: N
|
||||
/>;
|
||||
}
|
||||
|
||||
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActionsCustomInnerProps) {
|
||||
export function ToggleReadOnlyButton({ note, 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) || isSavedSqlite)
|
||||
&& note.isContentAvailable() && isDefaultViewMode;
|
||||
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
|
||||
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
|
||||
onClick={() => setReadOnly(!isReadOnly)}
|
||||
@@ -197,7 +202,7 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActions
|
||||
|
||||
function RunActiveNoteButton({ noteMime }: NoteActionsCustomInnerProps) {
|
||||
const isEnabled = noteMime.startsWith("application/javascript") || noteMime === "text/x-sqlite;schema=trilium";
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
icon="bx bx-play"
|
||||
text={t("code_buttons.execute_button_title")}
|
||||
triggerCommand="runActiveNote"
|
||||
@@ -218,7 +223,7 @@ function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
}
|
||||
});
|
||||
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
icon="bx bx-save"
|
||||
text={t("code_buttons.save_to_note_button_title")}
|
||||
onClick={buildSaveSqlToNoteHandler(note)}
|
||||
@@ -227,19 +232,19 @@ function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) {
|
||||
|
||||
function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) {
|
||||
const isEnabled = noteMime.startsWith("application/javascript;env=");
|
||||
return isEnabled && <ActionButton
|
||||
return isEnabled && <NoteAction
|
||||
icon="bx bx-help-circle"
|
||||
text={t("code_buttons.trilium_api_docs_button_title")}
|
||||
onClick={() => openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
|
||||
/>;
|
||||
}
|
||||
|
||||
function InAppHelpButton({ note, noteType }: NoteActionsCustomInnerProps) {
|
||||
function InAppHelpButton({ note }: NoteActionsCustomInnerProps) {
|
||||
const helpUrl = getHelpUrlForNote(note);
|
||||
const isEnabled = !!helpUrl && (noteType !== "book");
|
||||
const isEnabled = !!helpUrl;
|
||||
|
||||
return isEnabled && (
|
||||
<ActionButton
|
||||
<NoteAction
|
||||
icon="bx bx-help-circle"
|
||||
text={t("help-button.title")}
|
||||
onClick={() => helpUrl && openInAppHelpFromUrl(helpUrl)}
|
||||
@@ -247,16 +252,9 @@ function InAppHelpButton({ note, noteType }: NoteActionsCustomInnerProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function AddChildButton({ parentComponent, noteType, viewType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) {
|
||||
if (noteType === "book" && viewType === "geoMap") {
|
||||
return <ActionButton
|
||||
icon="bx bx-plus-circle"
|
||||
text={t("geo-map.create-child-note-title")}
|
||||
onClick={() => parentComponent.triggerEvent("geoMapCreateChildNote", { ntxId })}
|
||||
disabled={isReadOnly}
|
||||
/>;
|
||||
} else if (noteType === "relationMap") {
|
||||
return <ActionButton
|
||||
function AddChildButton({ parentComponent, noteType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) {
|
||||
if (noteType === "relationMap") {
|
||||
return <NoteAction
|
||||
icon="bx bx-folder-plus"
|
||||
text={t("relation_map_buttons.create_child_note_title")}
|
||||
onClick={() => parentComponent.triggerEvent("relationMapCreateChildNote", { ntxId })}
|
||||
@@ -265,3 +263,19 @@ function AddChildButton({ parentComponent, noteType, viewType, ntxId, isReadOnly
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
function NoteAction({ text, ...props }: Pick<ActionButtonProps, "text" | "icon" | "disabled" | "triggerCommand"> & {
|
||||
onClick?: ((e: MouseEvent) => void) | undefined;
|
||||
}) {
|
||||
return (cachedIsMobile
|
||||
? <FormListItem {...props}>{text}</FormListItem>
|
||||
: <ActionButton text={text} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function NoteActionWithFileUpload({ text, ...props }: Pick<ActionButtonProps, "text" | "icon" | "disabled" | "triggerCommand"> & Pick<FormFileUploadProps, "onChange">) {
|
||||
return (cachedIsMobile
|
||||
? <FormFileUploadFormListItem {...props}>{text}</FormFileUploadFormListItem>
|
||||
: <FormFileUploadActionButton text={text} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
63
apps/client/src/widgets/ribbon/NotePathsTab.css
Normal file
63
apps/client/src/widgets/ribbon/NotePathsTab.css
Normal file
@@ -0,0 +1,63 @@
|
||||
body.experimental-feature-new-layout .note-paths-widget {
|
||||
.note-path-intro {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.note-path-list {
|
||||
margin: 12px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
/* Note path card */
|
||||
li {
|
||||
--border-radius: 6px;
|
||||
|
||||
position: relative;
|
||||
background: var(--card-background-color);
|
||||
padding: 8px 20px 8px 25px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
& + li {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Current path arrow */
|
||||
&.path-current::before {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
content: "\ee8f";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
bottom: 0;
|
||||
font-family: "boxicons";
|
||||
font-size: .75em;
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Note path segment */
|
||||
a {
|
||||
margin-inline: 2px;
|
||||
padding-inline: 2px;
|
||||
color: currentColor;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
|
||||
/* The last segment of the note path */
|
||||
&.basename {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import "./NotePathsTab.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||
|
||||
import FNote, { NotePathRecord } from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { NOTE_PATH_TITLE_SEPARATOR } from "../../services/tree";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { joinElements } from "../react/react_utils";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import LinkButton from "../react/LinkButton";
|
||||
import clsx from "clsx";
|
||||
|
||||
import NoteLink from "../react/NoteLink";
|
||||
import { joinElements, ParentComponent } from "../react/react_utils";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
|
||||
export default function NotePathsTab({ note, hoistedNoteId, notePath }: TabContext) {
|
||||
const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId);
|
||||
@@ -20,6 +21,7 @@ export function NotePathsWidget({ sortedNotePaths, currentNotePath }: {
|
||||
sortedNotePaths: NotePathRecord[] | undefined;
|
||||
currentNotePath?: string | null | undefined;
|
||||
}) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
return (
|
||||
<div class="note-paths-widget">
|
||||
<>
|
||||
@@ -39,7 +41,7 @@ export function NotePathsWidget({ sortedNotePaths, currentNotePath }: {
|
||||
|
||||
<LinkButton
|
||||
text={t("note_paths.clone_button")}
|
||||
triggerCommand="cloneNoteIdsTo"
|
||||
onClick={() => parentComponent?.triggerCommand("cloneNoteIdsTo")}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
@@ -112,9 +114,9 @@ function NotePath({ currentNotePath, notePathRecord }: { currentNotePath?: strin
|
||||
<li class={classes}>
|
||||
{joinElements(fullNotePaths.map((notePath, index, arr) => (
|
||||
<NoteLink key={notePath}
|
||||
className={clsx({"basename": (index === arr.length - 1)})}
|
||||
notePath={notePath}
|
||||
noPreview />
|
||||
className={clsx({"basename": (index === arr.length - 1)})}
|
||||
notePath={notePath}
|
||||
noPreview />
|
||||
)), NOTE_PATH_TITLE_SEPARATOR)}
|
||||
|
||||
{icons.map(({ icon, title }) => (
|
||||
|
||||
@@ -2,12 +2,12 @@ import "./SearchDefinitionTab.css";
|
||||
|
||||
import { SaveSearchNoteResponse } from "@triliumnext/commons";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
|
||||
import appContext from "../../components/app_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import bulk_action, { ACTION_GROUPS } from "../../services/bulk_action";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
|
||||
import froca from "../../services/froca";
|
||||
import { t } from "../../services/i18n";
|
||||
import server from "../../services/server";
|
||||
@@ -16,18 +16,16 @@ import tree from "../../services/tree";
|
||||
import { getErrorMessage } from "../../services/utils";
|
||||
import ws from "../../services/ws";
|
||||
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
||||
import CollectionProperties from "../note_bars/CollectionProperties";
|
||||
import Button from "../react/Button";
|
||||
import Button, { SplitButton } from "../react/Button";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import { FormListHeader, FormListItem } from "../react/FormList";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import { ParentComponent } from "../react/react_utils";
|
||||
import ResponsiveContainer from "../react/ResponsiveContainer";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabContext, "note" | "ntxId" | "hidden">) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
|
||||
@@ -88,15 +86,32 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
|
||||
<tr>
|
||||
<td className="title-column">{t("search_definition.add_search_option")}</td>
|
||||
<td colSpan={2} className="add-search-option">
|
||||
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<Button
|
||||
size="small"
|
||||
icon={icon}
|
||||
text={label}
|
||||
title={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
/>
|
||||
))}
|
||||
<ResponsiveContainer
|
||||
desktop={searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<Button
|
||||
key={`${attributeType}-${attributeName}`}
|
||||
size="small" icon={icon} text={label} title={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
/>
|
||||
))}
|
||||
mobile={
|
||||
<Dropdown
|
||||
buttonClassName="action-add-toggle btn btn-sm"
|
||||
text={<><Icon icon="bx bx-plus" />{" "}{t("search_definition.option")}</>}
|
||||
dropdownContainerClassName="mobile-bottom-menu" mobileBackdrop
|
||||
noSelectButtonStyle
|
||||
>
|
||||
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<FormListItem
|
||||
key={`${attributeType}-${attributeName}`}
|
||||
icon={icon}
|
||||
description={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
>{label}</FormListItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
|
||||
<AddBulkActionButton note={note} />
|
||||
</td>
|
||||
@@ -115,55 +130,9 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
|
||||
defaultValue={defaultValue}
|
||||
/>;
|
||||
})}
|
||||
|
||||
{isNewLayout && <tr className="view-options">
|
||||
<td className="title-column">{t("search_definition.view_options")}</td>
|
||||
<td><CollectionProperties note={note} /></td>
|
||||
</tr>}
|
||||
</tbody>
|
||||
<BulkActionsList note={note} />
|
||||
<tbody className="search-actions">
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
<div className="search-actions-container">
|
||||
<Button
|
||||
icon="bx bx-search"
|
||||
text={t("search_definition.search_button")}
|
||||
keyboardShortcut="Enter"
|
||||
onClick={refreshResults}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="bx bxs-zap"
|
||||
text={t("search_definition.search_execute")}
|
||||
onClick={async () => {
|
||||
await server.post(`search-and-execute-note/${note.noteId}`);
|
||||
refreshResults();
|
||||
toast.showMessage(t("search_definition.actions_executed"), 3000);
|
||||
}}
|
||||
/>
|
||||
|
||||
{note.isHiddenCompletely() && <Button
|
||||
icon="bx bx-save"
|
||||
text={t("search_definition.save_to_note")}
|
||||
onClick={async () => {
|
||||
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: note.noteId });
|
||||
if (!notePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
|
||||
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
|
||||
// See https://www.i18next.com/translation-function/interpolation#unescape
|
||||
toast.showMessage(t("search_definition.search_note_saved", { notePathTitle: await tree.getNotePathTitle(notePath) }));
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<SearchButtonBar note={note} refreshResults={refreshResults} />
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
@@ -171,6 +140,56 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
|
||||
);
|
||||
}
|
||||
|
||||
function SearchButtonBar({ note, refreshResults }: {
|
||||
note: FNote;
|
||||
refreshResults(): void;
|
||||
}) {
|
||||
async function searchAndExecuteActions() {
|
||||
await server.post(`search-and-execute-note/${note.noteId}`);
|
||||
refreshResults();
|
||||
toast.showMessage(t("search_definition.actions_executed"), 3000);
|
||||
}
|
||||
|
||||
async function saveSearchNote() {
|
||||
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: note.noteId });
|
||||
if (!notePath) return;
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
|
||||
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
|
||||
// See https://www.i18next.com/translation-function/interpolation#unescape
|
||||
toast.showMessage(t("search_definition.search_note_saved", { notePathTitle: await tree.getNotePathTitle(notePath) }));
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody className="search-actions">
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
<ResponsiveContainer
|
||||
desktop={
|
||||
<div className="search-actions-container">
|
||||
<Button icon="bx bx-search" text={t("search_definition.search_button")} keyboardShortcut="Enter" onClick={refreshResults} />
|
||||
<Button icon="bx bxs-zap" text={t("search_definition.search_execute")} onClick={searchAndExecuteActions} />
|
||||
{note.isHiddenCompletely() && <Button icon="bx bx-save" text={t("search_definition.save_to_note")} onClick={saveSearchNote} />}
|
||||
</div>
|
||||
}
|
||||
mobile={
|
||||
<SplitButton
|
||||
text={t("search_definition.search_button")} icon="bx bx-search"
|
||||
onClick={refreshResults}
|
||||
>
|
||||
<FormListItem icon="bx bxs-zap" onClick={searchAndExecuteActions}>{t("search_definition.search_execute")}</FormListItem>
|
||||
{note.isHiddenCompletely() && <FormListItem icon="bx bx-save" onClick={saveSearchNote}>{t("search_definition.save_to_note")}</FormListItem>}
|
||||
</SplitButton>
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
function BulkActionsList({ note }: { note: FNote }) {
|
||||
const [ bulkActions, setBulkActions ] = useState<RenameNoteBulkAction[]>();
|
||||
|
||||
@@ -203,15 +222,18 @@ function AddBulkActionButton({ note }: { note: FNote }) {
|
||||
buttonClassName="action-add-toggle btn btn-sm"
|
||||
text={<><Icon icon="bx bxs-zap" />{" "}{t("search_definition.action")}</>}
|
||||
noSelectButtonStyle
|
||||
dropdownContainerClassName="mobile-bottom-menu" mobileBackdrop
|
||||
>
|
||||
{ACTION_GROUPS.map(({ actions, title }) => (
|
||||
<>
|
||||
{ACTION_GROUPS.map(({ actions, title }, index) => (
|
||||
<Fragment key={index}>
|
||||
<FormListHeader text={title} />
|
||||
|
||||
{actions.map(({ actionName, actionTitle }) => (
|
||||
<FormListItem onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
|
||||
))}
|
||||
</>
|
||||
<div>
|
||||
{actions.map(({ actionName, actionTitle }) => (
|
||||
<FormListItem key={actionName} onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
@@ -459,7 +459,7 @@ body.experimental-feature-new-layout {
|
||||
gap: var(--button-gap);
|
||||
|
||||
&> button:last-of-type {
|
||||
margin-right: 1em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
|
||||
@media (max-width: 991px) {
|
||||
margin-block: 1em;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
@@ -42,6 +48,12 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
@media (max-width: 991px) {
|
||||
gap: 0.5em;
|
||||
flex-wrap: wrap;
|
||||
.attachment-title { flex-grow: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-details {
|
||||
@@ -127,10 +139,6 @@
|
||||
|
||||
/* #region Attachment actions */
|
||||
|
||||
.attachment-actions .dropdown-menu {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.attachment-actions .dropdown-item .tn-icon {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
|
||||
@@ -239,6 +239,8 @@ function AttachmentActions({ attachment, copyAttachmentLinkToClipboard }: { atta
|
||||
text={<Icon icon="bx bx-dots-vertical-rounded" />}
|
||||
buttonClassName="icon-action-always-border"
|
||||
iconAction
|
||||
dropdownContainerClassName="mobile-bottom-menu"
|
||||
mobileBackdrop
|
||||
>
|
||||
<FormListItem
|
||||
icon="bx bx-file-find"
|
||||
|
||||
@@ -18,6 +18,7 @@ import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
|
||||
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||
import Icon from "../../react/Icon";
|
||||
import OptionsSection from "./components/OptionsSection";
|
||||
import PlatformIndicator from "./components/PlatformIndicator";
|
||||
import RadioWithIllustration from "./components/RadioWithIllustration";
|
||||
import RelatedSettings from "./components/RelatedSettings";
|
||||
|
||||
@@ -174,13 +175,13 @@ function LayoutIllustration({ isNewLayout }: { isNewLayout?: boolean }) {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="title-bar">
|
||||
<Icon icon="bx bx-leaf" />
|
||||
<span className="title">Title</span>
|
||||
<Icon icon="bx bx-dock-right" />
|
||||
<div>
|
||||
<div className="title-bar">
|
||||
<Icon icon="bx bx-leaf" />
|
||||
<span className="title">Title</span>
|
||||
<Icon icon="bx bx-dock-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isNewLayout && <div className="ribbon">
|
||||
@@ -192,7 +193,7 @@ function LayoutIllustration({ isNewLayout }: { isNewLayout?: boolean }) {
|
||||
</div>
|
||||
|
||||
<div className="ribbon-body">
|
||||
<div className="ribbon-body-content"></div>
|
||||
<div className="ribbon-body-content" />
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
@@ -356,7 +357,11 @@ function ElectronIntegration() {
|
||||
|
||||
<FormGroup name="background-effects" description={t("electron_integration.background-effects-description")}>
|
||||
<FormCheckbox
|
||||
label={t("electron_integration.background-effects")}
|
||||
label={<>
|
||||
{t("electron_integration.background-effects")}
|
||||
{" "}
|
||||
<PlatformIndicator windows="11" mac />
|
||||
</>}
|
||||
currentValue={backgroundEffects} onChange={setBackgroundEffects}
|
||||
disabled={nativeTitleBarVisible}
|
||||
/>
|
||||
@@ -383,10 +388,10 @@ function Performance() {
|
||||
currentValue={shadowsEnabled} onChange={setShadowsEnabled}
|
||||
/>
|
||||
|
||||
<FormCheckbox
|
||||
{!isMobile() && <FormCheckbox
|
||||
label={t("ui-performance.enable-backdrop-effects")}
|
||||
currentValue={backdropEffectsEnabled} onChange={setBackdropEffectsEnabled}
|
||||
/>
|
||||
/>}
|
||||
|
||||
{isElectron() && <SmoothScrollEnabledOption />}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.platform-indicator {
|
||||
display: inline-flex;
|
||||
gap: 0.25em;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import "./PlatformIndicator.css";
|
||||
|
||||
import { useRef } from "preact/hooks";
|
||||
|
||||
import { t } from "../../../../services/i18n";
|
||||
import { useStaticTooltip } from "../../../react/hooks";
|
||||
import Icon from "../../../react/Icon";
|
||||
|
||||
interface PlatformIndicatorProps {
|
||||
windows?: boolean | "11";
|
||||
mac: boolean;
|
||||
}
|
||||
|
||||
export default function PlatformIndicator({ windows, mac }: PlatformIndicatorProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
useStaticTooltip(containerRef, {
|
||||
selector: "span",
|
||||
animation: false,
|
||||
title() { return this.title; },
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="platform-indicator">
|
||||
{windows && <Icon
|
||||
icon="bx bxl-windows"
|
||||
title={t("platform_indicator.available_on", { platform: windows === "11" ? "Windows 11" : "Windows" })}
|
||||
/>}
|
||||
{mac && <Icon
|
||||
icon="bx bxl-apple"
|
||||
title={t("platform_indicator.available_on", { platform: "macOS" })}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
|
||||
import { useMemo } from "preact/hooks";
|
||||
import type React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import { t } from "../../../services/i18n";
|
||||
import search from "../../../services/search";
|
||||
import server from "../../../services/server";
|
||||
import toast from "../../../services/toast";
|
||||
import { isElectron } from "../../../services/utils";
|
||||
import Button from "../../react/Button";
|
||||
import FormText from "../../react/FormText";
|
||||
import OptionsSection from "./components/OptionsSection";
|
||||
import TimeSelector from "./components/TimeSelector";
|
||||
import { useMemo } from "preact/hooks";
|
||||
import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks";
|
||||
import { SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons";
|
||||
import FormCheckbox from "../../react/FormCheckbox";
|
||||
import FormGroup from "../../react/FormGroup";
|
||||
import search from "../../../services/search";
|
||||
import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
|
||||
import FormSelect from "../../react/FormSelect";
|
||||
import { isElectron } from "../../../services/utils";
|
||||
import FormText from "../../react/FormText";
|
||||
import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
|
||||
import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks";
|
||||
import OptionsSection from "./components/OptionsSection";
|
||||
import TimeSelector from "./components/TimeSelector";
|
||||
|
||||
export default function OtherSettings() {
|
||||
return (
|
||||
@@ -31,7 +33,7 @@ export default function OtherSettings() {
|
||||
<ShareSettings />
|
||||
<NetworkSettings />
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SearchEngineSettings() {
|
||||
@@ -82,7 +84,7 @@ function SearchEngineSettings() {
|
||||
/>
|
||||
</FormGroup>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function TrayOptionsSettings() {
|
||||
@@ -97,7 +99,7 @@ function TrayOptionsSettings() {
|
||||
onChange={trayEnabled => setDisableTray(!trayEnabled)}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function NoteErasureTimeout() {
|
||||
@@ -105,13 +107,13 @@ function NoteErasureTimeout() {
|
||||
<OptionsSection title={t("note_erasure_timeout.note_erasure_timeout_title")}>
|
||||
<FormText>{t("note_erasure_timeout.note_erasure_description")}</FormText>
|
||||
<FormGroup name="erase-entities-after" label={t("note_erasure_timeout.erase_notes_after")}>
|
||||
<TimeSelector
|
||||
name="erase-entities-after"
|
||||
<TimeSelector
|
||||
name="erase-entities-after"
|
||||
optionValueId="eraseEntitiesAfterTimeInSeconds" optionTimeScaleId="eraseEntitiesAfterTimeScale"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormText>{t("note_erasure_timeout.manual_erasing_description")}</FormText>
|
||||
|
||||
|
||||
<Button
|
||||
text={t("note_erasure_timeout.erase_deleted_notes_now")}
|
||||
onClick={() => {
|
||||
@@ -121,7 +123,7 @@ function NoteErasureTimeout() {
|
||||
}}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function AttachmentErasureTimeout() {
|
||||
@@ -145,7 +147,7 @@ function AttachmentErasureTimeout() {
|
||||
}}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function RevisionSnapshotInterval() {
|
||||
@@ -165,7 +167,7 @@ function RevisionSnapshotInterval() {
|
||||
/>
|
||||
</FormGroup>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function RevisionSnapshotLimit() {
|
||||
@@ -176,7 +178,7 @@ function RevisionSnapshotLimit() {
|
||||
<FormText>{t("revisions_snapshot_limit.note_revisions_snapshot_limit_description")}</FormText>
|
||||
|
||||
<FormGroup name="revision-snapshot-number-limit">
|
||||
<FormTextBoxWithUnit
|
||||
<FormTextBoxWithUnit
|
||||
type="number" min={-1}
|
||||
currentValue={revisionSnapshotNumberLimit}
|
||||
unit={t("revisions_snapshot_limit.snapshot_number_limit_unit")}
|
||||
@@ -197,7 +199,7 @@ function RevisionSnapshotLimit() {
|
||||
}}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function HtmlImportTags() {
|
||||
@@ -236,7 +238,7 @@ function HtmlImportTags() {
|
||||
onClick={() => setAllowedHtmlTags(SANITIZER_DEFAULT_ALLOWED_TAGS)}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ShareSettings() {
|
||||
@@ -246,8 +248,8 @@ function ShareSettings() {
|
||||
return (
|
||||
<OptionsSection title={t("share.title")}>
|
||||
<FormGroup name="redirectBareDomain" description={t("share.redirect_bare_domain_description")}>
|
||||
<FormCheckbox
|
||||
label={t(t("share.redirect_bare_domain"))}
|
||||
<FormCheckbox
|
||||
label={t(t("share.redirect_bare_domain"))}
|
||||
currentValue={redirectBareDomain}
|
||||
onChange={async value => {
|
||||
if (value) {
|
||||
@@ -264,17 +266,17 @@ function ShareSettings() {
|
||||
}
|
||||
setRedirectBareDomain(value);
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup name="showLoginInShareTheme" description={t("share.show_login_link_description")}>
|
||||
<FormCheckbox
|
||||
<FormCheckbox
|
||||
label={t("share.show_login_link")}
|
||||
currentValue={showLogInShareTheme} onChange={setShowLogInShareTheme}
|
||||
/>
|
||||
</FormGroup>
|
||||
</OptionsSection>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function NetworkSettings() {
|
||||
@@ -288,5 +290,5 @@ function NetworkSettings() {
|
||||
currentValue={checkForUpdates} onChange={setCheckForUpdates}
|
||||
/>
|
||||
</OptionsSection>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user