mirror of
https://github.com/zadam/trilium.git
synced 2026-03-01 18:13:39 +01:00
Merge remote-tracking branch 'origin/main' into renovate/express-serve-static-core-5.x
Some checks are pending
Checks / main (push) Waiting to run
Some checks are pending
Checks / main (push) Waiting to run
This commit is contained in:
commit
34ca7912fc
@ -14,7 +14,7 @@
|
||||
"keywords": [],
|
||||
"author": "Elian Doran <contact@eliandoran.me>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.30.2",
|
||||
"packageManager": "pnpm@10.30.3",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.19.2",
|
||||
"archiver": "7.0.1",
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"@fullcalendar/interaction": "6.1.20",
|
||||
"@fullcalendar/list": "6.1.20",
|
||||
"@fullcalendar/multimonth": "6.1.20",
|
||||
"@fullcalendar/rrule": "6.1.20",
|
||||
"@fullcalendar/timegrid": "6.1.20",
|
||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
@ -63,6 +64,7 @@
|
||||
"react-i18next": "16.5.4",
|
||||
"react-window": "2.2.7",
|
||||
"reveal.js": "5.2.1",
|
||||
"rrule": "2.8.1",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
"vanilla-js-wheel-zoom": "9.0.4"
|
||||
|
||||
@ -3,6 +3,8 @@ import options from "../services/options.js";
|
||||
import zoomService from "../components/zoom.js";
|
||||
import contextMenu, { type MenuItem } from "./context_menu.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import server from "../services/server.js";
|
||||
import * as clipboardExt from "../services/clipboard_ext.js";
|
||||
import type { BrowserWindow } from "electron";
|
||||
import type { CommandNames, AppContext } from "../components/app_context.js";
|
||||
|
||||
@ -60,6 +62,33 @@ function setupContextMenu() {
|
||||
uiIcon: "bx bx-copy",
|
||||
handler: () => webContents.copy()
|
||||
});
|
||||
|
||||
items.push({
|
||||
enabled: hasText,
|
||||
title: t("electron_context_menu.copy-as-markdown"),
|
||||
uiIcon: "bx bx-copy-alt",
|
||||
handler: async () => {
|
||||
const selection = window.getSelection();
|
||||
if (!selection || !selection.rangeCount) return '';
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(range.cloneContents());
|
||||
|
||||
const htmlContent = div.innerHTML;
|
||||
if (htmlContent) {
|
||||
try {
|
||||
const { markdownContent } = await server.post<{ markdownContent: string }>(
|
||||
"other/to-markdown",
|
||||
{ htmlContent }
|
||||
);
|
||||
await clipboardExt.copyTextWithToast(markdownContent);
|
||||
} catch (error) {
|
||||
console.error("Failed to copy as markdown:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === "none") {
|
||||
|
||||
@ -16,6 +16,8 @@ import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import renderService from "./render.js";
|
||||
import { applySingleBlockSyntaxHighlight } from "./syntax_highlight.js";
|
||||
import utils, { getErrorMessage } from "./utils.js";
|
||||
import PdfViewer from "../widgets/type_widgets/file/PdfViewer";
|
||||
import { h, render } from "preact";
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
@ -195,10 +197,13 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
|
||||
const $content = $('<div style="display: flex; flex-direction: column; height: 100%; justify-content: end;">');
|
||||
|
||||
if (type === "pdf") {
|
||||
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
|
||||
$pdfPreview.attr("src", openService.getUrlForDownload(`pdfjs/web/viewer.html?file=../../api/${entityType}/${entityId}/open`));
|
||||
const url = `../../api/${entityType}/${entityId}/open`;
|
||||
const $viewer = $(`<div style="height: 100%">`);
|
||||
render(h(PdfViewer, {pdfUrl: url, editable: false}), $viewer.get(0)!);
|
||||
|
||||
$content.append($viewer);
|
||||
|
||||
|
||||
$content.append($pdfPreview);
|
||||
} else if (type === "audio") {
|
||||
const $audioPreview = $("<audio controls></audio>")
|
||||
.attr("src", openService.getUrlForDownload(`api/${entityType}/${entityId}/open-partial`))
|
||||
|
||||
@ -647,10 +647,10 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ck-content hr {
|
||||
margin: 5px 0;
|
||||
height: 1px;
|
||||
background-color: var(--main-border-color);
|
||||
:root .ck-content hr {
|
||||
margin-block: 5px;
|
||||
height: 0;
|
||||
border: thin solid var(--main-border-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
@ -372,10 +372,6 @@ body[dir=ltr] #launcher-container {
|
||||
.calendar-dropdown-widget .calendar-header [data-calendar-input="month"] {
|
||||
--input-background-color: transparent;
|
||||
--menu-background-color: transparent;
|
||||
|
||||
text-align: center;
|
||||
font-size: 1.4em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-header input:not(:focus) {
|
||||
@ -425,8 +421,6 @@ body[dir=ltr] #launcher-container {
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-week span {
|
||||
font-size: 0.85em;
|
||||
font-weight: 500;
|
||||
color: var(--calendar-weekday-labels-color);
|
||||
}
|
||||
|
||||
@ -689,9 +683,10 @@ body.layout-vertical.background-effects div.quick-search .dropdown-menu {
|
||||
padding-inline-start: 12px;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-active {
|
||||
#left-pane span.fancytree-node.fancytree-active,
|
||||
#left-pane span.fancytree-node.fancytree-active:hover {
|
||||
position: relative;
|
||||
background: transparent !important;
|
||||
background: transparent;
|
||||
color: var(--custom-color, var(--left-pane-item-selected-color));
|
||||
}
|
||||
|
||||
@ -704,6 +699,14 @@ body.layout-vertical.background-effects div.quick-search .dropdown-menu {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* .fancytree-node pseudo-elements:
|
||||
*
|
||||
* - ::before: the active tree item decorator.
|
||||
* - ::after: the selected tree item background. A pseudo-element is used instead of the
|
||||
* element's background color, to allow alpha compositing for the hover state.
|
||||
*/
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-active::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
@ -718,6 +721,24 @@ body.layout-vertical.background-effects div.quick-search .dropdown-menu {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-selected {
|
||||
--left-pane-item-selected-shadow-size: 4px;
|
||||
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-selected::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -2;
|
||||
content: "";
|
||||
inset: 0;
|
||||
background: var(--selection-background-color);
|
||||
animation: left-pane-item-select 100ms ease-out;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.protected > span.fancytree-custom-icon {
|
||||
position: relative;
|
||||
filter: unset !important;
|
||||
@ -780,7 +801,8 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#left-pane .tree-item-button {
|
||||
#left-pane .tree-item-button,
|
||||
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon {
|
||||
margin-inline-end: 6px;
|
||||
border: unset;
|
||||
border-radius: 50%;
|
||||
@ -791,7 +813,8 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
|
||||
box-shadow 200ms ease-out;
|
||||
}
|
||||
|
||||
#left-pane .tree-item-button:hover {
|
||||
#left-pane .tree-item-button:hover,
|
||||
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon:hover {
|
||||
background: var(--left-pane-item-action-button-hover-background);
|
||||
box-shadow: var(--left-pane-item-action-button-hover-shadow);
|
||||
transition:
|
||||
@ -799,10 +822,41 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
|
||||
box-shadow 100ms ease-in;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-active .tree-item-button:hover {
|
||||
#left-pane span.fancytree-node.fancytree-active .tree-item-button:hover,
|
||||
#left-pane span.fancytree-node.fancytree-active.fancytree-selected .fancytree-custom-icon:hover {
|
||||
box-shadow: var(--left-pane-item-selected-action-button-hover-shadow);
|
||||
}
|
||||
|
||||
/* Selected item bulk action button */
|
||||
|
||||
@keyframes bulk-action-button-blink {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon::before {
|
||||
border: 0;
|
||||
font-size: .65em;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-selected:hover .fancytree-custom-icon:not(:hover)::before {
|
||||
animation: bulk-action-button-blink 500ms linear infinite alternate;
|
||||
}
|
||||
|
||||
#left-pane span.fancytree-node.fancytree-selected.protected .fancytree-custom-icon::after {
|
||||
/* Protected note indicator */
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
#context-menu-container {
|
||||
/* The context menu of the tree */
|
||||
--menu-item-icon-vert-offset: -1px;
|
||||
@ -1033,7 +1087,7 @@ body.layout-vertical.electron.platform-darwin .tab-row-container {
|
||||
height: var(--tab-height) !important;
|
||||
}
|
||||
|
||||
.tab-row-widget > * {
|
||||
body.layout-vertical .tab-row-widget > * {
|
||||
margin-top: calc((var(--tab-bar-height) - var(--tab-height)) / 2);
|
||||
}
|
||||
|
||||
|
||||
@ -140,10 +140,22 @@ ul.fancytree-container {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
|
||||
.fancytree-custom-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
/* Fallback icon */
|
||||
:where(.fancytree-custom-icon)::before {
|
||||
content: "?";
|
||||
}
|
||||
|
||||
/* Protected note icon badge */
|
||||
span.fancytree-node.protected > span.fancytree-custom-icon {
|
||||
filter: drop-shadow(2px 2px 2px var(--main-text-color));
|
||||
}
|
||||
@ -185,7 +197,7 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
|
||||
|
||||
span.fancytree-active {
|
||||
color: var(--active-item-text-color);
|
||||
background-color: var(--active-item-background-color) !important;
|
||||
background-color: var(--active-item-background-color);
|
||||
border-color: transparent; /* invisible border */
|
||||
border-radius: 5px;
|
||||
}
|
||||
@ -195,20 +207,15 @@ span.fancytree-active .fancytree-title {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
span.fancytree-selected {
|
||||
border-color: var(--main-border-color) !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span.fancytree-selected .fancytree-title {
|
||||
text-decoration: underline;
|
||||
font-style: italic;
|
||||
span.fancytree-node.fancytree-selected {
|
||||
background-color: var(--selection-background-color);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
span.fancytree-selected .fancytree-custom-icon::before {
|
||||
font-family: "boxicons";
|
||||
content: "\eb43";
|
||||
border: 1px solid var(--main-border-color);
|
||||
content: "\ef05";
|
||||
border: 1px solid var(--main-text-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
@ -882,6 +882,7 @@
|
||||
"electron_context_menu": {
|
||||
"cut": "قص",
|
||||
"copy": "نسخ",
|
||||
"copy-as-markdown": "نسخ كـ Markdown",
|
||||
"paste": "لصق",
|
||||
"copy-link": "نسخ الرابط",
|
||||
"add-term-to-dictionary": "اضافة \"{{term}}\" الى القاموس",
|
||||
|
||||
@ -1760,6 +1760,7 @@
|
||||
"add-term-to-dictionary": "将 \"{{term}}\" 添加到字典",
|
||||
"cut": "剪切",
|
||||
"copy": "复制",
|
||||
"copy-as-markdown": "复制为 Markdown",
|
||||
"copy-link": "复制链接",
|
||||
"paste": "粘贴",
|
||||
"paste-as-plain-text": "以纯文本粘贴",
|
||||
|
||||
@ -1729,6 +1729,7 @@
|
||||
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
|
||||
"cut": "Ausschneiden",
|
||||
"copy": "Kopieren",
|
||||
"copy-as-markdown": "Als Markdown kopieren",
|
||||
"copy-link": "Link kopieren",
|
||||
"paste": "Einfügen",
|
||||
"paste-as-plain-text": "Als unformatierten Text einfügen",
|
||||
|
||||
@ -1810,6 +1810,7 @@
|
||||
"add-term-to-dictionary": "Add \"{{term}}\" to dictionary",
|
||||
"cut": "Cut",
|
||||
"copy": "Copy",
|
||||
"copy-as-markdown": "Copy as Markdown",
|
||||
"copy-link": "Copy link",
|
||||
"paste": "Paste",
|
||||
"paste-as-plain-text": "Paste as plain text",
|
||||
|
||||
@ -1778,6 +1778,7 @@
|
||||
"add-term-to-dictionary": "Agregar \"{{term}}\" al diccionario",
|
||||
"cut": "Cortar",
|
||||
"copy": "Copiar",
|
||||
"copy-as-markdown": "Copiar como markdown",
|
||||
"copy-link": "Copiar enlace",
|
||||
"paste": "Pegar",
|
||||
"paste-as-plain-text": "Pegar como texto plano",
|
||||
|
||||
@ -1689,6 +1689,7 @@
|
||||
"add-term-to-dictionary": "Ajouter «{{term}}» au dictionnaire",
|
||||
"cut": "Couper",
|
||||
"copy": "Copier",
|
||||
"copy-as-markdown": "Copier en markdown",
|
||||
"copy-link": "Copier le lien",
|
||||
"paste": "Coller",
|
||||
"paste-as-plain-text": "Coller comme texte brut",
|
||||
|
||||
@ -1808,6 +1808,7 @@
|
||||
"add-term-to-dictionary": "Cuir \"{{term}}\" leis an bhfoclóir",
|
||||
"cut": "Gearr",
|
||||
"copy": "Cóipeáil",
|
||||
"copy-as-markdown": "Cóipeáil mar markdown",
|
||||
"copy-link": "Cóipeáil nasc",
|
||||
"paste": "Greamaigh",
|
||||
"paste-as-plain-text": "Greamaigh mar théacs simplí",
|
||||
|
||||
@ -1810,6 +1810,7 @@
|
||||
"add-term-to-dictionary": "\"{{term}}\" को डिक्शनरी में जोड़ें",
|
||||
"cut": "कट (Cut)",
|
||||
"copy": "कॉपी (Copy)",
|
||||
"copy-as-markdown": "Markdown के रूप में कॉपी करें",
|
||||
"copy-link": "लिंक कॉपी करें",
|
||||
"paste": "पेस्ट (Paste)",
|
||||
"paste-as-plain-text": "प्लेन टेक्स्ट की तरह पेस्ट करें",
|
||||
|
||||
@ -334,6 +334,7 @@
|
||||
"electron_context_menu": {
|
||||
"cut": "Taglia",
|
||||
"copy": "Copia",
|
||||
"copy-as-markdown": "Copia come markdown",
|
||||
"paste": "Incolla",
|
||||
"copy-link": "Copia collegamento",
|
||||
"paste-as-plain-text": "Incolla come testo semplice",
|
||||
@ -519,7 +520,7 @@
|
||||
"custom_name_label": "Nome del motore di ricerca personalizzato",
|
||||
"custom_name_placeholder": "Personalizza il nome del motore di ricerca",
|
||||
"custom_url_label": "L'URL del motore di ricerca personalizzato deve includere {keyword} come segnaposto per il termine di ricerca.",
|
||||
"custom_url_placeholder": "Personalizza l'URL del motore di ricerca"
|
||||
"custom_url_placeholder": "Personalizza l'URL del motore di ricerca"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabelle"
|
||||
@ -592,7 +593,7 @@
|
||||
"collapseExpand": "collassa/espande il nodo",
|
||||
"notSet": "non impostato",
|
||||
"goBackForwards": "indietro/avanti nella cronologia",
|
||||
"showJumpToNoteDialog": "mostra <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Vai a\"</a>",
|
||||
"showJumpToNoteDialog": "mostra <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">finestra \"Vai a\"</a>",
|
||||
"title": "Scheda riassuntiva",
|
||||
"noteNavigation": "Nota navigazione",
|
||||
"scrollToActiveNote": "scorri fino alla nota attiva",
|
||||
|
||||
@ -1353,6 +1353,7 @@
|
||||
"add-term-to-dictionary": "辞書に \"{{term}}\" を追加",
|
||||
"cut": "切り取り",
|
||||
"copy": "コピー",
|
||||
"copy-as-markdown": "Markdownとしてコピー",
|
||||
"copy-link": "リンクをコピー",
|
||||
"paste": "貼り付け",
|
||||
"paste-as-plain-text": "プレーンテキストで貼り付け",
|
||||
|
||||
@ -261,7 +261,9 @@
|
||||
"percentage": "%"
|
||||
},
|
||||
"pagination": {
|
||||
"total_notes": "{{count}} notatek"
|
||||
"total_notes": "{{count}} notatek",
|
||||
"prev_page": "Poprzednia strona",
|
||||
"next_page": "Następna strona"
|
||||
},
|
||||
"collections": {
|
||||
"rendering_error": "Nie można wyświetlić zawartości z powodu błędu."
|
||||
@ -612,6 +614,7 @@
|
||||
"electron_context_menu": {
|
||||
"cut": "Wytnij",
|
||||
"copy": "Kopiuj",
|
||||
"copy-as-markdown": "Kopiuj jako markdown",
|
||||
"copy-link": "Kopiuj link",
|
||||
"paste": "Wklej",
|
||||
"paste-as-plain-text": "Wklej jako zwykły tekst",
|
||||
@ -737,7 +740,8 @@
|
||||
"raster": "Raster",
|
||||
"vector_light": "Wektor (Jasny)",
|
||||
"vector_dark": "Wektor (Ciemny)",
|
||||
"show-scale": "Pokaż skalę"
|
||||
"show-scale": "Pokaż skalę",
|
||||
"show-labels": "Pokaż nazwy znaczników"
|
||||
},
|
||||
"table_context_menu": {
|
||||
"delete_row": "Usuń wiersz"
|
||||
@ -1232,7 +1236,7 @@
|
||||
"no_attachments": "Ta notatka nie ma załączników."
|
||||
},
|
||||
"book": {
|
||||
"no_children_help": "Ta kolekcja nie posiada żadnych notatek podrzędnych, więc nie ma nic do wyświetlenia. Zobacz <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> po szczegóły.",
|
||||
"no_children_help": "Ta kolekcja nie zawiera notatek podrzędnych, więc nie ma nic do wyświetlenia.",
|
||||
"drag_locked_title": "Zablokowane do edycji",
|
||||
"drag_locked_message": "Przeciąganie niedozwolone, ponieważ kolekcja jest zablokowana do edycji."
|
||||
},
|
||||
@ -1653,7 +1657,8 @@
|
||||
"description": "Opis",
|
||||
"reload_app": "Przeładuj aplikację, aby zastosować zmiany",
|
||||
"set_all_to_default": "Ustaw wszystkie skróty na domyślne",
|
||||
"confirm_reset": "Czy na pewno chcesz zresetować wszystkie skróty klawiszowe do domyślnych?"
|
||||
"confirm_reset": "Czy na pewno chcesz zresetować wszystkie skróty klawiszowe do domyślnych?",
|
||||
"no_results": "Nie znaleziono skrótów pasujących do '{{filter}}'"
|
||||
},
|
||||
"spellcheck": {
|
||||
"title": "Sprawdzanie pisowni",
|
||||
@ -1860,7 +1865,9 @@
|
||||
"print_report_collection_content_few": "Nie można wydrukować {{count}} notatek w kolekcji, ponieważ nie są one obsługiwane lub są chronione.",
|
||||
"print_report_collection_content_many": "Nie można wydrukować {{count}} notatek w kolekcji, ponieważ nie są one obsługiwane lub są chronione.",
|
||||
"print_report_collection_details_button": "Zobacz szczegóły",
|
||||
"print_report_collection_details_ignored_notes": "Zignorowane notatki"
|
||||
"print_report_collection_details_ignored_notes": "Zignorowane notatki",
|
||||
"print_report_error_title": "Nie udało się wydrukować",
|
||||
"print_report_stack_trace": "Ślad stosu"
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "wpisz tytuł notatki tutaj...",
|
||||
@ -1875,7 +1882,8 @@
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "Nie znaleziono notatek dla podanych parametrów wyszukiwania.",
|
||||
"search_not_executed": "Wyszukiwanie nie zostało jeszcze wykonane. Kliknij przycisk \"Szukaj\" powyżej, aby zobaczyć wyniki."
|
||||
"search_not_executed": "Nie przeprowadzono jeszcze wyszukiwania.",
|
||||
"search_now": "Szukaj teraz"
|
||||
},
|
||||
"spacer": {
|
||||
"configure_launchbar": "Konfiguruj pasek szybkiego dostępu"
|
||||
@ -2145,5 +2153,49 @@
|
||||
},
|
||||
"bookmark_buttons": {
|
||||
"bookmarks": "Zakładki"
|
||||
},
|
||||
"render": {
|
||||
"setup_title": "Wyświetl własny kod HTML lub Preact JSX w tej notatce",
|
||||
"setup_create_sample_preact": "Stwórz przykładową notatkę z użyciem Preact",
|
||||
"setup_create_sample_html": "Stwórz przykładową notatkę z użyciem HTML",
|
||||
"setup_sample_created": "Utworzono przykładową notatkę jako notatkę podrzędną.",
|
||||
"disabled_description": "Ta notatka pochodzi z zewnętrznego źródła. Ze względów bezpieczeństwa funkcja ta nie jest domyślnie włączona. Upewnij się, że ufasz źródłu, zanim ją aktywujesz.",
|
||||
"disabled_button_enable": "Włącz renderowanie notatki"
|
||||
},
|
||||
"web_view_setup": {
|
||||
"title": "Utwórz podgląd strony na żywo bezpośrednio w Trilium",
|
||||
"url_placeholder": "Wpisz lub wklej adres strony internetowej, na przykład https://triliumnotes.org",
|
||||
"create_button": "Utwórz widok strony",
|
||||
"invalid_url_title": "Nieprawidłowy adres",
|
||||
"invalid_url_message": "Wprowadź prawidłowy adres strony, na przykład https://triliumnotes.org.",
|
||||
"disabled_description": "Ten widok strony został zaimportowany z zewnętrznego źródła. Aby chronić Cię przed phishingiem lub szkodliwą zawartością, nie jest on ładowany automatycznie. Możesz go włączyć, jeśli ufasz źródłu.",
|
||||
"disabled_button_enable": "Włącz widok strony"
|
||||
},
|
||||
"active_content_badges": {
|
||||
"type_icon_pack": "Pakiet ikon",
|
||||
"type_backend_script": "Skrypt po stronie serwera",
|
||||
"type_frontend_script": "Skrypt po stronie klienta",
|
||||
"type_widget": "Widżet",
|
||||
"type_app_css": "Niestandardowy CSS",
|
||||
"type_render_note": "Renderuj notatkę",
|
||||
"type_web_view": "Widok strony",
|
||||
"type_app_theme": "Własny motyw",
|
||||
"toggle_tooltip_enable_tooltip": "Kliknij, aby włączyć {{type}}.",
|
||||
"toggle_tooltip_disable_tooltip": "Kliknij, aby wyłączyć {{type}}.",
|
||||
"menu_docs": "Otwórz dokumentację",
|
||||
"menu_execute_now": "Uruchom skrypt teraz",
|
||||
"menu_run": "Uruchamiaj automatycznie",
|
||||
"menu_run_disabled": "Ręcznie",
|
||||
"menu_run_backend_startup": "Podczas uruchamiania backendu",
|
||||
"menu_run_hourly": "Co godzinę",
|
||||
"menu_run_daily": "Codziennie",
|
||||
"menu_run_frontend_startup": "Podczas uruchamiania desktopowego frontendu",
|
||||
"menu_run_mobile_startup": "Podczas uruchamiania mobilnego frontendu",
|
||||
"menu_change_to_widget": "Zmień na widżet",
|
||||
"menu_change_to_frontend_script": "Zmień na skrypt frontendowy",
|
||||
"menu_theme_base": "Baza motywu"
|
||||
},
|
||||
"setup_form": {
|
||||
"more_info": "Dowiedz się więcej"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1774,6 +1774,7 @@
|
||||
"add-term-to-dictionary": "Adicionar \"{{term}}\" ao dicionário",
|
||||
"cut": "Cortar",
|
||||
"copy": "Copiar",
|
||||
"copy-as-markdown": "Copiar como markdown",
|
||||
"copy-link": "Copiar ligação",
|
||||
"paste": "Colar",
|
||||
"paste-as-plain-text": "Colar como texto sem formatação",
|
||||
|
||||
@ -1637,6 +1637,7 @@
|
||||
"add-term-to-dictionary": "Adicionar \"{{term}}\" ao dicionário",
|
||||
"cut": "Cortar",
|
||||
"copy": "Copiar",
|
||||
"copy-as-markdown": "Copiar como markdown",
|
||||
"copy-link": "Copiar link",
|
||||
"paste": "Colar",
|
||||
"paste-as-plain-text": "Colar como texto sem formatação",
|
||||
|
||||
@ -1724,6 +1724,7 @@
|
||||
"electron_context_menu": {
|
||||
"add-term-to-dictionary": "Adaugă „{{term}}” în dicționar",
|
||||
"copy": "Copiază",
|
||||
"copy-as-markdown": "Copiază ca markdown",
|
||||
"copy-link": "Copiază legătura",
|
||||
"cut": "Decupează",
|
||||
"paste": "Lipește",
|
||||
|
||||
@ -708,6 +708,7 @@
|
||||
"paste": "Вставить",
|
||||
"copy-link": "Скопировать ссылку",
|
||||
"copy": "Скопировать",
|
||||
"copy-as-markdown": "Копировать как Markdown",
|
||||
"cut": "Вырезать",
|
||||
"search_online": "Поиск \"{{term}}\" в {{searchEngine}}",
|
||||
"add-term-to-dictionary": "Добавить \"{{term}}\" в словарь",
|
||||
|
||||
@ -1722,6 +1722,7 @@
|
||||
"add-term-to-dictionary": "將 \"{{term}}\" 新增至字典",
|
||||
"cut": "剪下",
|
||||
"copy": "複製",
|
||||
"copy-as-markdown": "複製為 Markdown",
|
||||
"copy-link": "複製連結",
|
||||
"paste": "貼上",
|
||||
"paste-as-plain-text": "以純文字貼上",
|
||||
|
||||
@ -1533,6 +1533,7 @@
|
||||
"add-term-to-dictionary": "Додати \"{{term}}\" до словника",
|
||||
"cut": "Вирізати",
|
||||
"copy": "Копіювати",
|
||||
"copy-as-markdown": "Копіювати як Markdown",
|
||||
"copy-link": "Копіювати посилання",
|
||||
"paste": "Вставити",
|
||||
"paste-as-plain-text": "Вставити як звичайний текст",
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
padding-inline: 12px;
|
||||
margin-inline: var(--content-margin-inline);
|
||||
padding-block: 4px;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildNote, buildNotes } from "../../../test/easy-froca.js";
|
||||
import { buildEvent, buildEvents } from "./event_builder.js";
|
||||
import { LOCALE_MAPPINGS } from "./index.js";
|
||||
@ -148,7 +148,7 @@ describe("Promoted attributes", () => {
|
||||
expect(event).toHaveLength(1);
|
||||
expect(event[0]?.promotedAttributes).toMatchObject([
|
||||
[ "assignee", "Target note" ]
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
it("supports start time and end time", async () => {
|
||||
@ -177,6 +177,86 @@ describe("Promoted attributes", () => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe("Recurrence", () => {
|
||||
it("supports valid recurrence without end date", async () => {
|
||||
const noteIds = buildNotes([
|
||||
{
|
||||
title: "Recurring Event",
|
||||
"#startDate": "2025-05-05",
|
||||
"#recurrence": "FREQ=DAILY;COUNT=5"
|
||||
}
|
||||
]);
|
||||
const events = await buildEvents(noteIds);
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toMatchObject({
|
||||
title: "Recurring Event",
|
||||
start: "2025-05-05",
|
||||
});
|
||||
expect(events[0].rrule).toContain("DTSTART:20250505");
|
||||
expect(events[0].rrule).toContain("FREQ=DAILY;COUNT=5");
|
||||
expect(events[0].end).toBeUndefined();
|
||||
});
|
||||
|
||||
it("supports recurrence with start and end time (duration calculated)", async () => {
|
||||
const noteIds = buildNotes([
|
||||
{
|
||||
title: "Timed Recurring Event",
|
||||
"#startDate": "2025-05-05",
|
||||
"#startTime": "13:00",
|
||||
"#endTime": "15:30",
|
||||
"#recurrence": "FREQ=WEEKLY;COUNT=3"
|
||||
}
|
||||
]);
|
||||
const events = await buildEvents(noteIds);
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0]).toMatchObject({
|
||||
title: "Timed Recurring Event",
|
||||
start: "2025-05-05T13:00:00",
|
||||
duration: "02:30"
|
||||
});
|
||||
expect(events[0].rrule).toContain("DTSTART:20250505T130000");
|
||||
expect(events[0].end).toBeUndefined();
|
||||
});
|
||||
|
||||
it("removes end date when recurrence is valid", async () => {
|
||||
const noteIds = buildNotes([
|
||||
{
|
||||
title: "Recurring With End",
|
||||
"#startDate": "2025-05-05",
|
||||
"#endDate": "2025-05-07",
|
||||
"#recurrence": "FREQ=DAILY;COUNT=2"
|
||||
}
|
||||
]);
|
||||
const events = await buildEvents(noteIds);
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].rrule).toBeDefined();
|
||||
expect(events[0].end).toBeUndefined();
|
||||
});
|
||||
|
||||
it("writes to console on invalid recurrence rule", async () => {
|
||||
const noteIds = buildNotes([
|
||||
{
|
||||
title: "Invalid Recurrence",
|
||||
"#startDate": "2025-05-05",
|
||||
"#recurrence": "RRULE:FREQ=INVALID"
|
||||
}
|
||||
]);
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
await buildEvents(noteIds);
|
||||
const calledWithInvalid = consoleSpy.mock.calls.some(call =>
|
||||
call[0].includes("has an invalid #recurrence string")
|
||||
);
|
||||
expect(calledWithInvalid).toBe(true);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("Building locales", () => {
|
||||
it("every language has a locale defined", async () => {
|
||||
for (const { id, contentOnly } of LOCALES) {
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import clsx from "clsx";
|
||||
import { start } from "repl";
|
||||
import * as rruleLib from 'rrule';
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import froca from "../../../services/froca";
|
||||
import server from "../../../services/server";
|
||||
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
|
||||
import toastService from "../../../services/toast";
|
||||
import { getCustomisableLabel, getMonthsInDateRange } from "./utils";
|
||||
|
||||
interface Event {
|
||||
startDate: string,
|
||||
endDate?: string | null,
|
||||
startTime?: string | null,
|
||||
endTime?: string | null,
|
||||
isArchived?: boolean;
|
||||
isArchived?: boolean,
|
||||
recurrence?: string | null;
|
||||
}
|
||||
|
||||
export async function buildEvents(noteIds: string[]) {
|
||||
@ -28,8 +33,17 @@ export async function buildEvents(noteIds: string[]) {
|
||||
const endDate = getCustomisableLabel(note, "endDate", "calendar:endDate");
|
||||
const startTime = getCustomisableLabel(note, "startTime", "calendar:startTime");
|
||||
const endTime = getCustomisableLabel(note, "endTime", "calendar:endTime");
|
||||
const recurrence = getCustomisableLabel(note, "recurrence", "calendar:recurrence");
|
||||
const isArchived = note.hasLabel("archived");
|
||||
events.push(await buildEvent(note, { startDate, endDate, startTime, endTime, isArchived }));
|
||||
try {
|
||||
events.push(await buildEvent(note, { startDate, endDate, startTime, endTime, recurrence, isArchived }));
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
const errorMessage = error.message;
|
||||
toastService.showError(errorMessage);
|
||||
console.error(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return events.flat();
|
||||
@ -59,6 +73,7 @@ export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg)
|
||||
|
||||
events.push(await buildEvent(dateNote, { startDate }));
|
||||
|
||||
|
||||
if (dateNote.hasChildren()) {
|
||||
const childNoteIds = await dateNote.getSubtreeNoteIds();
|
||||
for (const childNoteId of childNoteIds) {
|
||||
@ -79,7 +94,7 @@ export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg)
|
||||
return events.flat();
|
||||
}
|
||||
|
||||
export async function buildEvent(note: FNote, { startDate, endDate, startTime, endTime, isArchived }: Event) {
|
||||
export async function buildEvent(note: FNote, { startDate, endDate, startTime, endTime, recurrence, isArchived }: Event) {
|
||||
const customTitleAttributeName = note.getLabelValue("calendar:title");
|
||||
const titles = await parseCustomTitle(customTitleAttributeName, note);
|
||||
const colorClass = note.getColorClass();
|
||||
@ -98,9 +113,10 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
|
||||
|
||||
startDate = (startTime ? `${startDate}T${startTime}:00` : startDate);
|
||||
if (!startTime) {
|
||||
const endDateOffset = offsetDate(endDate ?? startDate, 1);
|
||||
if (endDateOffset) {
|
||||
endDate = formatDateToLocalISO(endDateOffset);
|
||||
if (endDate) {
|
||||
endDate = dayjs(endDate).add(1, "day").format("YYYY-MM-DD");
|
||||
} else if (startDate) {
|
||||
endDate = dayjs(startDate).add(1, "day").format("YYYY-MM-DD");
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +134,30 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
|
||||
if (endDate) {
|
||||
eventData.end = endDate;
|
||||
}
|
||||
|
||||
if (recurrence) {
|
||||
// Generate rrule string
|
||||
const rruleString = `DTSTART:${dayjs(startDate).format("YYYYMMDD[T]HHmmss")}\n${recurrence}`;
|
||||
|
||||
// Validate rrule string
|
||||
let rruleValid = true;
|
||||
try {
|
||||
rruleLib.rrulestr(rruleString, { forceset: true }) as rruleLib.RRuleSet;
|
||||
} catch {
|
||||
rruleValid = false;
|
||||
}
|
||||
|
||||
if (rruleValid) {
|
||||
delete eventData.end;
|
||||
eventData.rrule = rruleString;
|
||||
if (endDate){
|
||||
const duration = dayjs.duration(dayjs(endDate).diff(dayjs(startDate)));
|
||||
eventData.duration = duration.format("HH:mm");
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Note "${note.noteId} ${note.title}" has an invalid #recurrence string ${recurrence}. Excluding...`);
|
||||
}
|
||||
}
|
||||
events.push(eventData);
|
||||
}
|
||||
return events;
|
||||
|
||||
@ -52,7 +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;
|
||||
padding: 0 var(--content-margin-inline);
|
||||
}
|
||||
|
||||
.calendar-container .fc-list-sticky .fc-list-day > * {
|
||||
|
||||
@ -252,6 +252,7 @@ function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
|
||||
plugins.push((await import("@fullcalendar/timegrid")).default);
|
||||
plugins.push((await import("@fullcalendar/list")).default);
|
||||
plugins.push((await import("@fullcalendar/multimonth")).default);
|
||||
plugins.push((await import("@fullcalendar/rrule")).default);
|
||||
if (isEditable || isCalendarRoot) {
|
||||
plugins.push((await import("@fullcalendar/interaction")).default);
|
||||
}
|
||||
|
||||
@ -234,7 +234,7 @@
|
||||
}
|
||||
|
||||
&.type-pdf {
|
||||
iframe {
|
||||
div {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
.table-view-container {
|
||||
height: 100%;
|
||||
margin-inline-start: var(--content-margin-inline);
|
||||
}
|
||||
|
||||
.search-result-widget-content .table-view {
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
.calendar-dropdown-widget .calendar-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 0 0.5rem 0.5rem 0.5rem;
|
||||
padding: 0 0.5rem 1rem 0.5rem;
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-header>div {
|
||||
@ -65,8 +65,13 @@
|
||||
border: 0;
|
||||
border-inline-start: unset;
|
||||
background-color: var(--menu-background-color);
|
||||
font-weight: bold;
|
||||
outline: 0;
|
||||
font-weight: 300;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-header .dropdown-toggle {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-header .dropdown-toggle::after {
|
||||
@ -82,18 +87,20 @@
|
||||
.calendar-dropdown-widget .calendar-week span {
|
||||
flex-direction: column;
|
||||
flex: 0 0 12.5%;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
max-width: 12.5%;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: .85em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-height: 250px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.calendar-dropdown-widget .calendar-week-number {
|
||||
|
||||
@ -4,10 +4,11 @@ body.experimental-feature-new-layout {
|
||||
}
|
||||
|
||||
.title-actions {
|
||||
--title-actions-padding-start: 12px;
|
||||
--title-actions-padding-end: 8px;
|
||||
--title-actions-padding-start: var(--content-margin-inline);
|
||||
--title-actions-padding-end: var(--content-margin-inline);
|
||||
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: var(--max-content-width);
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
|
||||
position: relative;
|
||||
top: 5px;
|
||||
padding: .25em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
|
||||
@ -17,11 +17,13 @@ body.experimental-feature-new-layout .note-paths-widget {
|
||||
padding: 8px 20px 8px 25px;
|
||||
|
||||
&:first-child {
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-top-right-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
& + li {
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
contain: none !important;
|
||||
}
|
||||
|
||||
.search-result-widget .note-list {
|
||||
padding: 10px;
|
||||
.search-result-widget .note-list-wrapper {
|
||||
margin-inline: var(--content-margin-inline);
|
||||
}
|
||||
|
||||
.note-split.type-search .scrolling-container {
|
||||
|
||||
@ -3,4 +3,8 @@
|
||||
margin-inline: 40px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,16 @@
|
||||
import type { HTMLAttributes, RefObject } from "preact";
|
||||
import { useCallback, useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import Inter from "./../../../fonts/Inter/Inter-VariableFont_opsz,wght.ttf";
|
||||
import { useSyncedRef, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
||||
|
||||
const VARIABLE_WHITELIST = new Set([
|
||||
"root-background",
|
||||
"main-background-color",
|
||||
"main-border-color",
|
||||
"main-text-color"
|
||||
]);
|
||||
interface FontDefinition {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const FONTS: FontDefinition[] = [
|
||||
{name: "Inter", url: Inter},
|
||||
]
|
||||
|
||||
interface PdfViewerProps extends Pick<HTMLAttributes<HTMLIFrameElement>, "tabIndex"> {
|
||||
iframeRef?: RefObject<HTMLIFrameElement>;
|
||||
@ -34,6 +36,7 @@ export default function PdfViewer({ iframeRef: externalIframeRef, pdfUrl, onLoad
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
class="pdf-preview"
|
||||
style={{width: "100%", height: "100%"}}
|
||||
src={`pdfjs/web/viewer.html?file=${pdfUrl}&lang=${locale}&sidebar=${newLayout ? "0" : "1"}&editable=${editable ? "1" : "0"}`}
|
||||
onLoad={() => {
|
||||
injectStyles();
|
||||
@ -55,8 +58,12 @@ function useStyleInjection(iframeRef: RefObject<HTMLIFrameElement>) {
|
||||
style.id = 'client-root-vars';
|
||||
style.textContent = cssVarsToString(getRootCssVariables());
|
||||
styleRef.current = style;
|
||||
|
||||
doc.head.appendChild(style);
|
||||
|
||||
const fontStyles = doc.createElement("style");
|
||||
fontStyles.textContent = FONTS.map(injectFont).join("\n");
|
||||
doc.head.appendChild(fontStyles);
|
||||
|
||||
}, [ iframeRef ]);
|
||||
|
||||
// React to changes.
|
||||
@ -79,7 +86,7 @@ function getRootCssVariables() {
|
||||
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
const prop = styles[i];
|
||||
if (prop.startsWith('--') && VARIABLE_WHITELIST.has(prop.substring(2))) {
|
||||
if (prop.startsWith('--')) {
|
||||
vars[`--tn-${prop.substring(2)}`] = styles.getPropertyValue(prop).trim();
|
||||
}
|
||||
}
|
||||
@ -92,3 +99,12 @@ function cssVarsToString(vars: Record<string, string>) {
|
||||
.map(([k, v]) => ` ${k}: ${v};`)
|
||||
.join('\n')}\n}`;
|
||||
}
|
||||
|
||||
function injectFont(font: FontDefinition) {
|
||||
return `
|
||||
@font-face {
|
||||
font-family: '${font.name}';
|
||||
src: url('${font.url}');
|
||||
}
|
||||
`;
|
||||
}
|
||||
@ -19,4 +19,12 @@
|
||||
.tn-link {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
DIR=`dirname "$0"`
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
|
||||
"$DIR/trilium"
|
||||
exec "$DIR/trilium"
|
||||
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
DIR=`dirname "$0"`
|
||||
export TRILIUM_DATA_DIR="$DIR/trilium-data"
|
||||
|
||||
"$DIR/trilium"
|
||||
exec "$DIR/trilium"
|
||||
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
DIR=`dirname "$0"`
|
||||
export TRILIUM_SAFE_MODE=1
|
||||
|
||||
"$DIR/trilium" --disable-gpu
|
||||
exec "$DIR/trilium" --disable-gpu
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
"@types/xml2js": "0.4.14",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"axios": "1.13.5",
|
||||
"axios": "1.13.6",
|
||||
"bindings": "1.5.0",
|
||||
"bootstrap": "5.3.8",
|
||||
"chardet": "2.1.1",
|
||||
@ -108,7 +108,7 @@
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"marked": "17.0.3",
|
||||
"mime-types": "3.0.2",
|
||||
"multer": "2.0.2",
|
||||
"multer": "2.1.0",
|
||||
"normalize-strings": "1.1.1",
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
|
||||
@ -43,7 +43,7 @@ rm -rf $BUILD_DIR/node/lib/node_modules/{npm,corepack} \
|
||||
$BUILD_DIR/node_modules/electron* \
|
||||
$BUILD_DIR/electron*.{js,map}
|
||||
|
||||
printf "#!/bin/sh\n./node/bin/node main.cjs\n" > $BUILD_DIR/trilium.sh
|
||||
printf "#!/bin/sh\nexec ./node/bin/node main.cjs\n" > $BUILD_DIR/trilium.sh
|
||||
chmod 755 $BUILD_DIR/trilium.sh
|
||||
|
||||
VERSION=`jq -r ".version" package.json`
|
||||
|
||||
71
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
71
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
@ -185,6 +185,18 @@
|
||||
at which the event ends (in relation with <code spellcheck="false">endDate</code> if
|
||||
present, or <code spellcheck="false">startDate</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code spellcheck="false">#recurrence</code>
|
||||
</td>
|
||||
<td>This is an optional CalDAV <code spellcheck="false">RRULE</code> string
|
||||
that if present, determines whether a task should repeat or not. Note that
|
||||
it does not include the <code spellcheck="false">DTSTART</code> attribute,
|
||||
which is derived from the <code spellcheck="false">#startDate</code> and
|
||||
<code
|
||||
spellcheck="false">#startTime</code>directly. For examples of valid <code spellcheck="false">RRULE</code> strings
|
||||
see <a href="https://icalendar.org/rrule-tool.html">https://icalendar.org/rrule-tool.html</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code spellcheck="false">#color</code>
|
||||
</td>
|
||||
@ -282,6 +294,65 @@
|
||||
<p>When not used in a Journal, the calendar is recursive. That is, it will
|
||||
look for events not just in its child notes but also in the children of
|
||||
these child notes.</p>
|
||||
<p> </p>
|
||||
<h2>Recurrence</h2>
|
||||
<p>The built in calendar view also supports repeating tasks. If a child note
|
||||
of the calendar has a #recurrence label with a valid recurrence, that event
|
||||
will repeat on the calendar according to the recurrence string. </p>
|
||||
<p>For example, to make a note repeat on the calendar:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Every Day - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=1"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 3 days - <code spellcheck="false">#recurrence="FREQ=DAILY;INTERVAL=3"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every week - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=1"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 2 weeks on Monday, Wednesday and Friday - <code spellcheck="false">#recurrence="FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 3 months - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=3"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every 2 months on the First Sunday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Every month on the Last Friday - <code spellcheck="false">#recurrence="FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR"</code>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>And so on.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>For other examples of valid <code spellcheck="false">RRULE</code> strings
|
||||
see <a href="https://icalendar.org/rrule-tool.html">https://icalendar.org/rrule-tool.html</a>
|
||||
</p>
|
||||
<p>Note that the recurrence string does not include the <code spellcheck="false">DTSTART</code> attribute
|
||||
as defined in the iCAL specifications. This is derived directly from the
|
||||
<code
|
||||
spellcheck="false">startDate</code>and <code spellcheck="false">startTime</code> attributes</p>
|
||||
<p>If you want to override the label the calendar uses to fetch the recurrence
|
||||
string, you can use the <code spellcheck="false">#calendar:recurrence</code> attribute.
|
||||
For example, you can set <code spellcheck="false">#calendar:recurrence=taskRepeats</code>.
|
||||
Then you can set your recurrence string like <code spellcheck="false">#taskRepeats="FREQ=DAILY;INTERVAL=1"</code>
|
||||
</p>
|
||||
<p>Also note that the recurrence label can be made promoted as with the start
|
||||
and end dates. </p>
|
||||
<aside class="admonition warning">
|
||||
<p>If the recurrence string is not valid, a toast will be shown with the
|
||||
note ID and title of the note with the erroneous recurrence message. This
|
||||
note will not be added to the calendar</p>
|
||||
</aside>
|
||||
<p> </p>
|
||||
<h2>Use-cases</h2>
|
||||
<h3>Using with the Journal / calendar</h3>
|
||||
<p>It is possible to integrate the calendar view into the Journal with day
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"keywords": [],
|
||||
"packageManager": "pnpm@10.30.2",
|
||||
"packageManager": "pnpm@10.30.3",
|
||||
"devDependencies": {
|
||||
"@wxt-dev/auto-icons": "1.1.1",
|
||||
"wxt": "0.20.18"
|
||||
|
||||
@ -72,11 +72,12 @@ For each note of the calendar, the following attributes can be used:
|
||||
| `#endDate` | Similar to `startDate`, mentions the end date if the event spans across multiple days. The date is inclusive, so the end day is also considered. The attribute can be missing for single-day events. |
|
||||
| `#startTime` | The time the event starts at. If this value is missing, then the event is considered a full-day event. The format is `HH:MM` (hours in 24-hour format and minutes). |
|
||||
| `#endTime` | Similar to `startTime`, it mentions the time at which the event ends (in relation with `endDate` if present, or `startDate`). |
|
||||
| `#recurrence` | This is an optional CalDAV `RRULE` string that if present, determines whether a task should repeat or not. Note that it does not include the `DTSTART` attribute, which is derived from the `#startDate` and `#startTime` directly. For examples of valid `RRULE` strings see [https://icalendar.org/rrule-tool.html](https://icalendar.org/rrule-tool.html) |
|
||||
| `#color` | Displays the event with a specified color (named such as `red`, `gray` or hex such as `#FF0000`). This will also change the color of the note in other places such as the note tree. |
|
||||
| `#calendar:color` | **❌️ Removed since v0.100.0. Use** `**#color**` **instead.** <br> <br>Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. |
|
||||
| `#calendar:color` | **❌️ Removed since v0.100.0. Use** `**#color**` **instead.** <br> <br>Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. |
|
||||
| `#iconClass` | If present, the icon of the note will be displayed to the left of the event title. |
|
||||
| `#calendar:title` | Changes the title of an event to point to an attribute of the note other than the title, can either a label or a relation (without the `#` or `~` symbol). See _Use-cases_ for more information. |
|
||||
| `#calendar:displayedAttributes` | Allows displaying the value of one or more attributes in the calendar like this: <br> <br> <br> <br>`#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"` <br> <br>It can also be used with relations, case in which it will display the title of the target note: <br> <br>`~assignee=@My assignee #calendar:displayedAttributes="assignee"` |
|
||||
| `#calendar:displayedAttributes` | Allows displaying the value of one or more attributes in the calendar like this: <br> <br> <br> <br>`#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"` <br> <br>It can also be used with relations, case in which it will display the title of the target note: <br> <br>`~assignee=@My assignee #calendar:displayedAttributes="assignee"` |
|
||||
| `#calendar:startDate` | Allows using a different label to represent the start date, other than `startDate` (e.g. `expiryDate`). The label name **must not be** prefixed with `#`. If the label is not defined for a note, the default will be used instead. |
|
||||
| `#calendar:endDate` | Similar to `#calendar:startDate`, allows changing the attribute which is being used to read the end date. |
|
||||
| `#calendar:startTime` | Similar to `#calendar:startDate`, allows changing the attribute which is being used to read the start time. |
|
||||
@ -102,6 +103,32 @@ This will result in:
|
||||
|
||||
When not used in a Journal, the calendar is recursive. That is, it will look for events not just in its child notes but also in the children of these child notes.
|
||||
|
||||
## Recurrence
|
||||
|
||||
The built in calendar view also supports repeating tasks. If a child note of the calendar has a #recurrence label with a valid recurrence, that event will repeat on the calendar according to the recurrence string.
|
||||
|
||||
For example, to make a note repeat on the calendar:
|
||||
|
||||
* Every Day - `#recurrence="FREQ=DAILY;INTERVAL=1"`
|
||||
* Every 3 days - `#recurrence="FREQ=DAILY;INTERVAL=3"`
|
||||
* Every week - `#recurrence="FREQ=WEEKLY;INTERVAL=1"`
|
||||
* Every 2 weeks on Monday, Wednesday and Friday - `#recurrence="FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR"`
|
||||
* Every 3 months - `#recurrence="FREQ=MONTHLY;INTERVAL=3"`
|
||||
* Every 2 months on the First Sunday - `#recurrence="FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU"`
|
||||
* Every month on the Last Friday - `#recurrence="FREQ=MONTHLY;INTERVAL=1;BYDAY=-1FR"`
|
||||
* And so on.
|
||||
|
||||
For other examples of valid `RRULE` strings see [https://icalendar.org/rrule-tool.html](https://icalendar.org/rrule-tool.html)
|
||||
|
||||
Note that the recurrence string does not include the `DTSTART` attribute as defined in the iCAL specifications. This is derived directly from the `startDate` and `startTime` attributes
|
||||
|
||||
If you want to override the label the calendar uses to fetch the recurrence string, you can use the `#calendar:recurrence` attribute. For example, you can set `#calendar:recurrence=taskRepeats`. Then you can set your recurrence string like `#taskRepeats="FREQ=DAILY;INTERVAL=1"`
|
||||
|
||||
Also note that the recurrence label can be made promoted as with the start and end dates.
|
||||
|
||||
> [!WARNING]
|
||||
> If the recurrence string is not valid, a toast will be shown with the note ID and title of the note with the erroneous recurrence message. This note will not be added to the calendar
|
||||
|
||||
## Use-cases
|
||||
|
||||
### Using with the Journal / calendar
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"@types/express": "5.0.6",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.10.14",
|
||||
"@types/node": "24.11.0",
|
||||
"@vitest/browser-webdriverio": "4.0.18",
|
||||
"@vitest/coverage-v8": "4.0.18",
|
||||
"@vitest/ui": "4.0.18",
|
||||
@ -61,7 +61,7 @@
|
||||
"eslint": "10.0.2",
|
||||
"eslint-config-preact": "2.0.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-playwright": "2.7.1",
|
||||
"eslint-plugin-playwright": "2.8.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"happy-dom": "20.7.0",
|
||||
"http-server": "14.1.1",
|
||||
@ -93,7 +93,7 @@
|
||||
"url": "https://github.com/TriliumNext/Trilium/issues"
|
||||
},
|
||||
"homepage": "https://triliumnotes.org",
|
||||
"packageManager": "pnpm@10.30.2",
|
||||
"packageManager": "pnpm@10.30.3",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
"@ssddanbrown/codemirror-lang-smarty": "1.0.0",
|
||||
"@ssddanbrown/codemirror-lang-twig": "1.0.0",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"codemirror-lang-elixir": "4.0.0",
|
||||
"codemirror-lang-elixir": "4.0.1",
|
||||
"codemirror-lang-hcl": "0.1.0",
|
||||
"codemirror-lang-mermaid": "0.5.0",
|
||||
"eslint-linter-browserify": "10.0.2"
|
||||
|
||||
@ -1,31 +1,332 @@
|
||||
:root {
|
||||
color-scheme: var(--tn-theme-style);
|
||||
/* #region General */
|
||||
|
||||
--body-bg-color: transparent;
|
||||
--toolbar-bg-color: transparent;
|
||||
:root {
|
||||
--main-color: var(--tn-main-text-color);
|
||||
--body-bg-color: transparent;
|
||||
--outline-color: gray;
|
||||
--focus-ring-color: var(--tn-input-focus-outline-color);
|
||||
--toolbar-border-color: var(--tn-main-border-color);
|
||||
--toolbar-icon-bg-color: var(--tn-main-text-color);
|
||||
--toolbar-bg-color: transparent;
|
||||
--toolbar-icon-opacity: 1;
|
||||
--toggled-btn-bg-color: var(--tn-hover-item-background-color);
|
||||
--doorhanger-bg-color: var(--tn-menu-background-color);
|
||||
--doorhanger-separator-color: var(--tn-main-border-color);
|
||||
|
||||
--page-margin: 12px auto;
|
||||
--spreadHorizontalWrapped-margin-LR: 4px;
|
||||
|
||||
color-scheme: var(--tn-theme-style);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.pdfViewer {
|
||||
.page,
|
||||
.page > .canvasWrapper,
|
||||
.page > .canvasWrapper > canvas {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.page {
|
||||
border: 1px solid var(--tn-main-border-color);
|
||||
box-shadow: 7px 7px 15px #00000010;
|
||||
body.read-only-document {
|
||||
/* TODO: find a more elegant way to display a PDF in a read only view */
|
||||
--toolbar-height: 0;
|
||||
.toolbar,
|
||||
.editToolbar {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
:root button,
|
||||
:root dialog,
|
||||
:root #toolbarContainer,
|
||||
:root .toolbarButton,
|
||||
:root #scaleSelect,
|
||||
:root .toolbarButtonWithContainer .editorParamsToolbar .editorParamsLabel,
|
||||
:root #toolbarContainer #toolbarViewer input,
|
||||
:root #editorUndoBar,
|
||||
:root .dialogButton {
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
#secondaryToolbar,
|
||||
#documentPropertiesDialog,
|
||||
#findbar.doorHanger,
|
||||
.doorHangerRight,
|
||||
#printServiceDialog,
|
||||
:root :is(.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor, .signatureEditor), .textLayer) .editToolbar,
|
||||
#viewerContainer .editToolbar .colorPicker .dropdown,
|
||||
#editorUndoBar {
|
||||
border: 1px solid var(--tn-dropdown-border-color);
|
||||
border-radius: var(--tn-dropdown-border-radius);
|
||||
background-color: var(--tn-menu-background-color);
|
||||
padding: var(--tn-menu-padding-size);
|
||||
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--tn-dropdown-shadow-opacity));
|
||||
backdrop-filter: var(--tn-dropdown-backdrop-filter);
|
||||
}
|
||||
|
||||
.doorHangerRight,
|
||||
.doorHangerLeft,
|
||||
.doorHanger {
|
||||
&::after, &::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:root .toggle-button {
|
||||
--toggle-border-color: transparent;
|
||||
--toggle-background-color: var(--tn-input-background-color);
|
||||
--toggle-background-color-hover: var(--toggle-background-color);
|
||||
--toggle-dot-background-color: var(--tn-input-text-color);
|
||||
--toggle-background-color-pressed: var(--tn-input-text-color);
|
||||
--toggle-background-color-pressed-hover: var(--toggle-background-color-pressed);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:root .colorPicker {
|
||||
--hover-outline-color: var(--tn-input-focus-outline-color);
|
||||
--selected-outline-color: var(--tn-main-text-color);
|
||||
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* Text boxes */
|
||||
input:not([type]),
|
||||
input[type="number"] {
|
||||
--field-border-color: transparent;
|
||||
--field-bg-color: var(--tn-input-background-color);
|
||||
--field-color: var(--tn-input-text-color);
|
||||
--input-horizontal-padding: 8px;
|
||||
|
||||
border-radius: 4px !important;
|
||||
font-size: .85rem !important;
|
||||
|
||||
&:hover {
|
||||
--field-bg-color: var(--tn-input-hover-background);
|
||||
--field-color: var(--tn-input-hover-color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: transparent !important;
|
||||
outline: 2px solid var(--tn-input-focus-outline-color);
|
||||
--field-bg-color: var(--tn-input-focus-background);
|
||||
--field-color: var(--tn-input-focus-color);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--tn-input-placeholder-color)
|
||||
}
|
||||
|
||||
&::selection {
|
||||
background-color: var(--tn-input-selection-background);
|
||||
color: var(--tn-input-selection-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--tn-input-focus-outline-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region Toolbar */
|
||||
|
||||
#toolbarContainer select.scaleSelect,
|
||||
#toolbarContainer input.pageNumber {
|
||||
height: calc(var(--toolbar-height) - 8px);
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
#toolbarContainer {
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
border-radius: 6px;
|
||||
|
||||
&:not(.labeled):active::before {
|
||||
transform: scale(.85) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--tn-hover-item-background-color);
|
||||
color: var(--tn-hover-item-text-color);
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
/* Icon-only button */
|
||||
&:not(.labeled) {
|
||||
background: var(--tn-ck-editor-toolbar-button-on-background);
|
||||
|
||||
&&::before {
|
||||
background: var(--tn-ck-editor-toolbar-button-on-color);
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
color: var(--tn-menu-item-icon-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.verticalToolbarSeparator,
|
||||
.splitToolbarButtonSeparator {
|
||||
--separator-color: transparent;
|
||||
}
|
||||
|
||||
.verticalToolbarSeparator {
|
||||
margin-inline: 4px;
|
||||
}
|
||||
|
||||
:root #findbar {
|
||||
--toolbar-height: 40px;
|
||||
|
||||
padding: 0 4px;
|
||||
|
||||
/* Search input */
|
||||
.loadingInput {
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
/* Search input - no results */
|
||||
#findInputContainer #findInput[data-status="notFound"] {
|
||||
--tn-input-focus-outline-color: var(--tn-dropdown-item-icon-destructive-color);
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
/* Option buttons */
|
||||
.toggleButton.toolbarLabel,
|
||||
.toolbarButton {
|
||||
height: calc(var(--toolbar-height) - 12px);
|
||||
padding-inline: 10px;
|
||||
border-radius: 6px;
|
||||
aspect-ratio: unset;
|
||||
}
|
||||
|
||||
/* Toggable option buttons */
|
||||
.toggleButton.toolbarLabel {
|
||||
--main-color: var(--tn-main-text-color);
|
||||
--button-hover-color: var(--tn-hover-item-background-color);
|
||||
--toggled-btn-bg-color: var(--tn-ck-editor-toolbar-button-on-background);
|
||||
--toggled-btn-color: var(--tn-ck-editor-toolbar-button-on-color);
|
||||
}
|
||||
|
||||
/* Search status text */
|
||||
#findbarMessageContainer #findResultsCount,
|
||||
#findMsg {
|
||||
background-color: transparent;
|
||||
color: var(--tn-main-text-color);
|
||||
opacity: .5;
|
||||
};
|
||||
|
||||
/* Not found message */
|
||||
#findMsg[data-status="notFound"] {
|
||||
color: var(--tn-dropdown-item-icon-destructive-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#toolbarContainer #toolbarViewer #pageNumber {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#scaleSelectContainer {
|
||||
--dropdown-btn-bg-color: transparent;
|
||||
--button-hover-color: transparent;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover,
|
||||
&:focus-within{
|
||||
background-color: var(--tn-hover-item-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Toolbar editor dropdowns */
|
||||
:root .editorParamsToolbar:not(.menu),
|
||||
:root #highlightParamsToolbarContainer {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
/* Toolbar dropdowns */
|
||||
:root .editorParamsToolbar {
|
||||
.menu {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.editorParamsToolbarContainer {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overflow menu */
|
||||
:root #secondaryToolbar {
|
||||
--toolbar-icon-bg-color: var(--tn-menu-item-icon-color);
|
||||
--toolbar-icon-hover-bg-color: var(--tn-menu-item-icon-color);
|
||||
--toggled-btn-bg-color: transparent;
|
||||
--toggled-btn-color: currentColor;
|
||||
--doorhanger-icon-opacity: 1;
|
||||
padding: var(--tn-menu-padding-size);
|
||||
width: auto;
|
||||
min-width: 220px;
|
||||
max-width: 400px;
|
||||
|
||||
.toolbarButton.labeled {
|
||||
color: var(--tn-menu-text-color);
|
||||
padding-inline-end: 12px;
|
||||
padding-block: 6px;
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Horizontal menu dividers */
|
||||
:root #highlightParamsToolbarContainer #editorHighlightVisibility .divider,
|
||||
:root .horizontalToolbarSeparator {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
background: unset;
|
||||
border: none;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-top: 1px solid var(--tn-main-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Radio menu items */
|
||||
#cursorToolButtons .toolbarButton,
|
||||
#scrollModeButtons .toolbarButton,
|
||||
#spreadModeButtons .toolbarButton {
|
||||
--toggled-hover-active-btn-color: var(--tn-hover-item-background-color);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 2em;
|
||||
height: 100%;
|
||||
/* https://pictogrammers.com/library/mdi/icon/radiobox-blank/ */
|
||||
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3ctitle%3eradiobox-blank%3c/title%3e%3cpath d='M12%2c20A8%2c8 0 0%2c1 4%2c12A8%2c8 0 0%2c1 12%2c4A8%2c8 0 0%2c1 20%2c12A8%2c8 0 0%2c1 12%2c20M12%2c2A10%2c10 0 0%2c0 2%2c12A10%2c10 0 0%2c0 12%2c22A10%2c10 0 0%2c0 22%2c12A10%2c10 0 0%2c0 12%2c2Z' /%3e%3c/svg%3e");
|
||||
mask-size: 16px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center center;
|
||||
background-color: var(--tn-main-text-color);
|
||||
}
|
||||
|
||||
&.toggled::after {
|
||||
/* https://pictogrammers.com/library/mdi/icon/radiobox-marked/ */
|
||||
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3ctitle%3eradiobox-marked%3c/title%3e%3cpath d='M12%2c20A8%2c8 0 0%2c1 4%2c12A8%2c8 0 0%2c1 12%2c4A8%2c8 0 0%2c1 20%2c12A8%2c8 0 0%2c1 12%2c20M12%2c2A10%2c10 0 0%2c0 2%2c12A10%2c10 0 0%2c0 12%2c22A10%2c10 0 0%2c0 22%2c12A10%2c10 0 0%2c0 12%2c2M12%2c7A5%2c5 0 0%2c0 7%2c12A5%2c5 0 0%2c0 12%2c17A5%2c5 0 0%2c0 17%2c12A5%2c5 0 0%2c0 12%2c7Z' /%3e%3c/svg%3e");
|
||||
}
|
||||
}
|
||||
|
||||
/* Permanently removed buttons */
|
||||
#viewsManagerToggleButton,
|
||||
#downloadButton,
|
||||
@ -43,10 +344,103 @@
|
||||
|
||||
/* #region Properties Dialog */
|
||||
|
||||
/* Hide irrelevant properties */
|
||||
#documentPropertiesDialog > .row:has(#fileNameField),
|
||||
#documentPropertiesDialog > .row:has(#linearizedField) {
|
||||
display: none;
|
||||
#documentPropertiesDialog {
|
||||
--separator-color: transparent;
|
||||
|
||||
user-select: none;
|
||||
padding: 1em;
|
||||
|
||||
.row {
|
||||
line-height: 1.5;
|
||||
|
||||
> span {
|
||||
font-weight: bold;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
> p {
|
||||
user-select: all;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide irrelevant properties */
|
||||
> .row:has(#fileNameField),
|
||||
> .row:has(#linearizedField) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#documentPropertiesClose {
|
||||
/* TODO: restyle */
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region Viewer Area */
|
||||
|
||||
.pdfViewer {
|
||||
.page,
|
||||
.page > .canvasWrapper,
|
||||
.page > .canvasWrapper > canvas {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.page {
|
||||
border: 1px solid var(--tn-main-border-color);
|
||||
box-shadow: 7px 7px 15px #00000010;
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManager {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root :is(.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor, .signatureEditor), .textLayer) .editToolbar {
|
||||
--editor-toolbar-hover-bg-color: var(--tn-hover-item-background-color);
|
||||
--editor-toolbar-hover-fg-color: var(--tn-hover-item-text-color);
|
||||
--editor-toolbar-hover-outline: transparent;
|
||||
|
||||
padding: 4px;
|
||||
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
gap: 2px;
|
||||
|
||||
> * {
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
.deleteButton::before {
|
||||
background-color: var(--tn-dropdown-item-icon-destructive-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewerContainer .editToolbar .colorPicker .dropdown {
|
||||
padding: 12px 6px;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: "";
|
||||
inset: 0;
|
||||
border: 1px solid var(--tn-dropdown-border-color);
|
||||
border-radius: var(--tn-dropdown-border-radius);
|
||||
backdrop-filter: var(--tn-dropdown-backdrop-filter);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
#editorUndoBar {
|
||||
--message-bar-fg-color: var(--tn-main-text-color);
|
||||
--message-bar-icon-color: var(--tn-menu-item-icon-color);
|
||||
--undo-button-border: transparent;
|
||||
padding-inline: 20px;
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
@ -7,6 +7,9 @@ import { setupPdfLayers } from "./layers";
|
||||
async function main() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const isEditable = urlParams.get("editable") === "1";
|
||||
|
||||
document.body.classList.toggle("read-only-document", !isEditable);
|
||||
|
||||
if (urlParams.get("sidebar") === "0") {
|
||||
hideSidebar();
|
||||
}
|
||||
|
||||
388
pnpm-lock.yaml
generated
388
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user