mirror of
https://github.com/zadam/trilium.git
synced 2025-12-04 14:34:24 +01:00
Compare commits
31 Commits
780cf26e40
...
8938b3fb9e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8938b3fb9e | ||
|
|
b8585594cd | ||
|
|
d173cc982c | ||
|
|
471c57b3ed | ||
|
|
093d7d783b | ||
|
|
7cc20600e7 | ||
|
|
559c654fbb | ||
|
|
01a03e3e97 | ||
|
|
dd3233a556 | ||
|
|
c4a426566f | ||
|
|
c081a596df | ||
|
|
6b07908cf7 | ||
|
|
2985bd0a1c | ||
|
|
975e8487fc | ||
|
|
54408d3ec8 | ||
|
|
e15bc5a232 | ||
|
|
8fdda59440 | ||
|
|
6f85d3370c | ||
|
|
8c324cd185 | ||
|
|
f7f7fda040 | ||
|
|
74c11f4d4e | ||
|
|
0525cfab79 | ||
|
|
2d73627908 | ||
|
|
94d015789d | ||
|
|
af2f6246e8 | ||
|
|
ebbdf0294a | ||
|
|
5df539f0a4 | ||
|
|
4f6dfeb773 | ||
|
|
52bb83e878 | ||
|
|
1f21c65a99 | ||
|
|
5d5fd2079a |
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,6 +8,7 @@ out-tsc
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnpm-store
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
@ -18,6 +19,7 @@ node_modules
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
.devcontainer
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
|
||||
19
README.md
19
README.md
@ -146,6 +146,21 @@ Here's the language coverage we have so far:
|
||||
|
||||
### Code
|
||||
|
||||
General (OS / docker / podman, etc.) dependencies:
|
||||
|
||||
Debian
|
||||
```
|
||||
apt update
|
||||
apt install -y build-essential python3 make g++ libsqlite3-dev
|
||||
corepack enable
|
||||
```
|
||||
|
||||
Alpine
|
||||
```
|
||||
apk add --no-cache build-base python3 python3-dev sqlite-dev
|
||||
corepack enable
|
||||
```
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
@ -154,6 +169,10 @@ pnpm install
|
||||
pnpm run server:start
|
||||
```
|
||||
|
||||
> If you faced with some problems, try to delete all `node_modules` and `.pnpm-store` folders, not only from the root, from every directory, like `apps/{app_name}/node_modules`and `/packages/{package_name}/node_modules` and then reinstall it by the `pnpm install`.
|
||||
|
||||
Share styles not compiling by default, if you see share page without styles, make `pnpm run server:build` and then run development server.
|
||||
|
||||
### Documentation
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.99.5",
|
||||
"version": "0.100.0",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
@ -38,8 +38,6 @@
|
||||
"boxicons": "2.1.4",
|
||||
"clsx": "2.1.1",
|
||||
"color": "5.0.3",
|
||||
"dayjs": "1.11.19",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "3.0.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
|
||||
@ -126,9 +126,7 @@ function isAffecting(attrRow: AttributeRow, affectedNote: FNote | null | undefin
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This doesn't seem right.
|
||||
//@ts-ignore
|
||||
if (this.isInheritable) {
|
||||
if (attrRow.isInheritable) {
|
||||
for (const owningNote of owningNotes) {
|
||||
if (owningNote.hasAncestor(attrNote.noteId, true)) {
|
||||
return true;
|
||||
|
||||
44
apps/client/src/services/bundle.spec.ts
Normal file
44
apps/client/src/services/bundle.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { Bundle, executeBundle } from "./bundle";
|
||||
import { buildNote } from "../test/easy-froca";
|
||||
|
||||
describe("Script bundle", () => {
|
||||
it("dayjs is available", async () => {
|
||||
const script = /* js */`return api.dayjs().format("YYYY-MM-DD");`;
|
||||
const bundle = getBundle(script);
|
||||
const result = await executeBundle(bundle, null, $());
|
||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
||||
});
|
||||
|
||||
it("dayjs is-same-or-before plugin exists", async () => {
|
||||
const script = /* js */`return api.dayjs("2023-10-01").isSameOrBefore(api.dayjs("2023-10-02"));`;
|
||||
const bundle = getBundle(script);
|
||||
const result = await executeBundle(bundle, null, $());
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function getBundle(script: string) {
|
||||
const id = buildNote({
|
||||
title: "Script note"
|
||||
}).noteId;
|
||||
const bundle: Bundle = {
|
||||
script: [
|
||||
'',
|
||||
`apiContext.modules['${id}'] = { exports: {} };`,
|
||||
`return await ((async function(exports, module, require, api) {`,
|
||||
`try {`,
|
||||
`${script}`,
|
||||
`;`,
|
||||
`} catch (e) { throw new Error(\"Load of script note \\\"Client\\\" (${id}) failed with: \" + e.message); }`,
|
||||
`for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];`,
|
||||
`return module.exports;`,
|
||||
`}).call({}, {}, apiContext.modules['${id}'], apiContext.require([]), apiContext.apis['${id}']));`,
|
||||
''
|
||||
].join('\n'),
|
||||
html: "",
|
||||
noteId: id,
|
||||
allNoteIds: [ id ]
|
||||
};
|
||||
return bundle;
|
||||
}
|
||||
@ -27,7 +27,7 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
|
||||
return await executeBundle(bundle, originEntity);
|
||||
}
|
||||
|
||||
async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
|
||||
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
|
||||
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
|
||||
|
||||
try {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type { FNoteRow } from "../entities/fnote.js";
|
||||
import froca from "./froca.js";
|
||||
import server from "./server.js";
|
||||
|
||||
@ -17,7 +17,7 @@ import shortcutService from "./shortcuts.js";
|
||||
import dialogService from "./dialog.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import { t } from "./i18n.js";
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type NoteContext from "../components/note_context.js";
|
||||
import type Component from "../components/component.js";
|
||||
import { formatLogMessage } from "@triliumnext/commons";
|
||||
|
||||
@ -2,7 +2,7 @@ import options from "./options.js";
|
||||
import i18next from "i18next";
|
||||
import i18nextHttpBackend from "i18next-http-backend";
|
||||
import server from "./server.js";
|
||||
import type { Locale } from "@triliumnext/commons";
|
||||
import { LOCALE_IDS, setDayjsLocale, type Locale } from "@triliumnext/commons";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
let locales: Locale[] | null;
|
||||
@ -13,7 +13,7 @@ let locales: Locale[] | null;
|
||||
export let translationsInitializedPromise = $.Deferred();
|
||||
|
||||
export async function initLocale() {
|
||||
const locale = (options.get("locale") as string) || "en";
|
||||
const locale = ((options.get("locale") as string) || "en") as LOCALE_IDS;
|
||||
|
||||
locales = await server.get<Locale[]>("options/locales");
|
||||
|
||||
@ -27,6 +27,7 @@ export async function initLocale() {
|
||||
returnEmptyString: false
|
||||
});
|
||||
|
||||
await setDayjsLocale(locale);
|
||||
translationsInitializedPromise.resolve();
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type { ViewScope } from "./link.js";
|
||||
import FNote from "../entities/fnote";
|
||||
|
||||
|
||||
@ -46,6 +46,8 @@ function mockServer() {
|
||||
attributes: []
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(`Unsupported GET to mocked server: ${url}`);
|
||||
},
|
||||
|
||||
async post(url: string, data: object) {
|
||||
|
||||
@ -14,7 +14,6 @@ import ws from "../services/ws";
|
||||
import { UpdateAttributeResponse } from "@triliumnext/commons";
|
||||
import attributes from "../services/attributes";
|
||||
import debounce from "../services/debounce";
|
||||
import { randomString } from "../services/utils";
|
||||
|
||||
interface Cell {
|
||||
uniqueId: string;
|
||||
@ -30,7 +29,7 @@ interface CellProps {
|
||||
cell: Cell,
|
||||
cells: Cell[],
|
||||
shouldFocus: boolean;
|
||||
setCells(cells: Cell[]): void;
|
||||
setCells: Dispatch<StateUpdater<Cell[] | undefined>>;
|
||||
setCellToFocus(cell: Cell): void;
|
||||
}
|
||||
|
||||
@ -98,7 +97,7 @@ function usePromotedAttributeData(note: FNote | null | undefined, componentId: s
|
||||
valueAttrs = valueAttrs.slice(0, 1);
|
||||
}
|
||||
|
||||
for (const valueAttr of valueAttrs) {
|
||||
for (const [ i, valueAttr ] of valueAttrs.entries()) {
|
||||
const definition = definitionAttr.getDefinition();
|
||||
|
||||
// if not owned, we'll force creation of a new attribute instead of updating the inherited one
|
||||
@ -106,7 +105,7 @@ function usePromotedAttributeData(note: FNote | null | undefined, componentId: s
|
||||
valueAttr.attributeId = "";
|
||||
}
|
||||
|
||||
const uniqueId = randomString(10);
|
||||
const uniqueId = `${note.noteId}-${valueAttr.name}-${i}`;
|
||||
cells.push({ definitionAttr, definition, valueAttr, valueName, uniqueId });
|
||||
}
|
||||
}
|
||||
@ -292,8 +291,8 @@ function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
||||
id={inputId}
|
||||
noteId={props.cell.valueAttr.value}
|
||||
noteIdChanged={async (value) => {
|
||||
const { note, cell, componentId } = props;
|
||||
cell.valueAttr.attributeId = (await updateAttribute(note, cell, componentId, value)).attributeId;
|
||||
const { note, cell, componentId, setCells } = props;
|
||||
await updateAttribute(note, cell, componentId, value, setCells);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@ -423,7 +422,7 @@ function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute,
|
||||
});
|
||||
}
|
||||
|
||||
function buildPromotedAttributeLabelChangedListener({ note, cell, componentId, ...props }: CellProps): OnChangeListener {
|
||||
function buildPromotedAttributeLabelChangedListener({ note, cell, componentId, setCells }: CellProps): OnChangeListener {
|
||||
async function onChange(e: OnChangeEventData) {
|
||||
const inputEl = e.target as HTMLInputElement;
|
||||
let value: string;
|
||||
@ -434,14 +433,14 @@ function buildPromotedAttributeLabelChangedListener({ note, cell, componentId, .
|
||||
value = inputEl.value;
|
||||
}
|
||||
|
||||
cell.valueAttr.attributeId = (await updateAttribute(note, cell, componentId, value)).attributeId;
|
||||
await updateAttribute(note, cell, componentId, value, setCells);
|
||||
}
|
||||
|
||||
return debounce(onChange, 250);
|
||||
}
|
||||
|
||||
function updateAttribute(note: FNote, cell: Cell, componentId: string, value: string | undefined) {
|
||||
return server.put<UpdateAttributeResponse>(
|
||||
async function updateAttribute(note: FNote, cell: Cell, componentId: string, value: string | undefined, setCells: Dispatch<StateUpdater<Cell[] | undefined>>) {
|
||||
const { attributeId } = await server.put<UpdateAttributeResponse>(
|
||||
`notes/${note.noteId}/attribute`,
|
||||
{
|
||||
attributeId: cell.valueAttr.attributeId,
|
||||
@ -451,4 +450,15 @@ function updateAttribute(note: FNote, cell: Cell, componentId: string, value: st
|
||||
},
|
||||
componentId
|
||||
);
|
||||
setCells(prev =>
|
||||
prev?.map(c =>
|
||||
c.uniqueId === cell.uniqueId
|
||||
? { ...c, valueAttr: {
|
||||
...c.valueAttr,
|
||||
attributeId,
|
||||
value
|
||||
} }
|
||||
: c
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,17 +7,10 @@ import toastService from "../../services/toast.js";
|
||||
import options from "../../services/options.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import isoWeek from "dayjs/plugin/isoWeek.js";
|
||||
import utc from "dayjs/plugin/utc.js";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||
import { dayjs, type Dayjs } from "@triliumnext/commons";
|
||||
import "../../stylesheets/calendar.css";
|
||||
import type { AttributeRow, OptionDefinitions } from "@triliumnext/commons";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isoWeek);
|
||||
|
||||
const MONTHS = [
|
||||
t("calendar.january"),
|
||||
t("calendar.february"),
|
||||
|
||||
@ -21,6 +21,7 @@ import ActionButton from "../../react/ActionButton";
|
||||
import { RefObject } from "preact";
|
||||
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
|
||||
import { openCalendarContextMenu } from "./context_menu";
|
||||
import { isMobile } from "../../../services/utils";
|
||||
|
||||
interface CalendarViewData {
|
||||
|
||||
@ -266,7 +267,7 @@ function useEventDisplayCustomization(parentNote: FNote) {
|
||||
|
||||
// Prepend the icon to the title, if any.
|
||||
if (iconClass) {
|
||||
let titleContainer;
|
||||
let titleContainer: HTMLElement | null = null;
|
||||
switch (e.view.type) {
|
||||
case "timeGridWeek":
|
||||
case "dayGridMonth":
|
||||
@ -285,6 +286,9 @@ function useEventDisplayCustomization(parentNote: FNote) {
|
||||
}
|
||||
}
|
||||
|
||||
// Disable the default context menu.
|
||||
e.el.dataset.noContextMenu = "true";
|
||||
|
||||
// Append promoted attributes to the end of the event container.
|
||||
if (promotedAttributes) {
|
||||
let promotedAttributesHtml = "";
|
||||
@ -310,12 +314,18 @@ function useEventDisplayCustomization(parentNote: FNote) {
|
||||
$(mainContainer ?? e.el).append($(promotedAttributesHtml));
|
||||
}
|
||||
|
||||
e.el.addEventListener("contextmenu", async (contextMenuEvent) => {
|
||||
async function onContextMenu(contextMenuEvent: PointerEvent) {
|
||||
const note = await froca.getNote(e.event.extendedProps.noteId);
|
||||
if (!note) return;
|
||||
|
||||
openCalendarContextMenu(contextMenuEvent, note, parentNote);
|
||||
});
|
||||
}
|
||||
|
||||
if (isMobile()) {
|
||||
e.el.addEventListener("click", onContextMenu);
|
||||
} else {
|
||||
e.el.addEventListener("contextmenu", onContextMenu);
|
||||
}
|
||||
}, []);
|
||||
return { eventDidMount };
|
||||
}
|
||||
|
||||
@ -85,8 +85,7 @@ export function buildClassicToolbar(multilineToolbar: boolean) {
|
||||
"|",
|
||||
"insertTemplate",
|
||||
"markdownImport",
|
||||
"cuttonote",
|
||||
"findAndReplace"
|
||||
"cuttonote"
|
||||
],
|
||||
shouldNotGroupWhenFull: multilineToolbar
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { t } from "../services/i18n.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import server from "../services/server.js";
|
||||
import fileWatcher from "../services/file_watcher.js";
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop",
|
||||
"version": "0.99.5",
|
||||
"version": "0.100.0",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "src/main.ts",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/server",
|
||||
"version": "0.99.5",
|
||||
"version": "0.100.0",
|
||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||
"private": true,
|
||||
"main": "./src/main.ts",
|
||||
@ -76,7 +76,6 @@
|
||||
"compression": "1.8.1",
|
||||
"cookie-parser": "1.4.7",
|
||||
"csrf-csrf": "3.2.2",
|
||||
"dayjs": "1.11.19",
|
||||
"debounce": "3.0.0",
|
||||
"debug": "4.4.3",
|
||||
"ejs": "3.1.10",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { beforeAll } from "vitest";
|
||||
import i18next from "i18next";
|
||||
import { join } from "path";
|
||||
import dayjs from "dayjs";
|
||||
import { setDayjsLocale } from "@triliumnext/commons";
|
||||
|
||||
// Initialize environment variables.
|
||||
process.env.TRILIUM_DATA_DIR = join(__dirname, "db");
|
||||
@ -25,6 +25,5 @@ beforeAll(async () => {
|
||||
});
|
||||
|
||||
// Initialize dayjs
|
||||
await import("dayjs/locale/en.js");
|
||||
dayjs.locale("en");
|
||||
await setDayjsLocale("en");
|
||||
});
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
File diff suppressed because one or more lines are too long
@ -4,28 +4,29 @@ class="image image-style-align-center">
|
||||
<img style="aspect-ratio:1398/1015;" src="Split View_2_Split View_im.png"
|
||||
width="1398" height="1015">
|
||||
</figure>
|
||||
<h2><strong>Interactions</strong></h2>
|
||||
|
||||
<h2><strong>Interactions</strong></h2>
|
||||
<ul>
|
||||
<li data-list-item-id="eb22263532280510ca0efeb2c2e757629">Press the
|
||||
<li>Press the
|
||||
<img src="Split View_Split View_imag.png">button to the right of a note's title to open a new split to the right
|
||||
of it.
|
||||
<ul>
|
||||
<li data-list-item-id="eda17492ea2d8da7c4bf2fb3e2f7bfbe9">It is possible to have as many splits as desired, simply press again the
|
||||
<li>It is possible to have as many splits as desired, simply press again the
|
||||
button.</li>
|
||||
<li data-list-item-id="ea0223c947ea17534d577c9cfef4d5c6e">Only horizontal splits are possible, vertical or drag & dropping is
|
||||
<li>Only horizontal splits are possible, vertical or drag & dropping is
|
||||
not supported.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="e77d99fdc9a0846903de57d1b710fdd56">When at least one split is open, press the
|
||||
<li>When at least one split is open, press the
|
||||
<img src="Split View_3_Split View_im.png">button next to it to close it.</li>
|
||||
<li data-list-item-id="ec9d11f5bcfd10795f282e275938b2f4a">Use the
|
||||
<li>Use the
|
||||
<img src="Split View_4_Split View_im.png">or the
|
||||
<img src="Split View_1_Split View_im.png">button to move around the splits.</li>
|
||||
<li data-list-item-id="e8384a579c3d6ee8df4d7dbf9b07c3436">Each <a href="#root/_help_3seOhtN8uLIY">tab</a> has its own split view configuration
|
||||
<li>Each <a href="#root/_help_3seOhtN8uLIY">tab</a> has its own split view configuration
|
||||
(e.g. one tab can have two notes in a split view, whereas the others are
|
||||
one-note views).
|
||||
<ul>
|
||||
<li data-list-item-id="e298299f6b2f1b9d8b5f26a5f8a0c9092">The tab will indicate only the title of the main note (the first one in
|
||||
<li>The tab will indicate only the title of the main note (the first one in
|
||||
the list).</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -48,35 +49,34 @@ class="image image-style-align-center">
|
||||
as well, with the following differences from the desktop version of the
|
||||
split:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="efc0bb8eb81ea1617b73188613f2ede5d">On smartphones, the split views are laid out vertically (one on the top
|
||||
<li>On smartphones, the split views are laid out vertically (one on the top
|
||||
and one on the bottom), instead of horizontally as on the desktop.</li>
|
||||
<li
|
||||
data-list-item-id="e7659da22c36db39ae8e3dc5424afba1e">There can be only one split open per tab.</li>
|
||||
<li data-list-item-id="eb848af9837a484a9de9117922c6d7186">It's not possible to resize the two split panes.</li>
|
||||
<li data-list-item-id="e869c240066f602fbc1c0e55259ba62e5">When the keyboard is opened, the active note will be “maximized”, thus
|
||||
<li>There can be only one split open per tab.</li>
|
||||
<li>It's not possible to resize the two split panes.</li>
|
||||
<li>When the keyboard is opened, the active note will be “maximized”, thus
|
||||
allowing for more space even when a split is open. When the keyboard is
|
||||
closed, the splits become equal in size again.</li>
|
||||
</ul>
|
||||
<p>Interaction:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="edbf5c644758db5aca9867c516f97542b">To create a new split, click the three dots button on the right of the
|
||||
<li>To create a new split, click the three dots button on the right of the
|
||||
note title and select <em>Create new split</em>.
|
||||
<ul>
|
||||
<li data-list-item-id="eed272873b629f70418c3e7074a829369">This option will only be available if there is no split already open in
|
||||
<li>This option will only be available if there is no split already open in
|
||||
the current tab.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="e733863e6058336ebfcf27042f56be312">To close a split, click the three dots button on the right of the note
|
||||
<li>To close a split, click the three dots button on the right of the note
|
||||
title and select <em>Close this pane</em>.
|
||||
<ul>
|
||||
<li data-list-item-id="e9e6c191873dcd658242c64553343e0c7">Note that this option will only be available on the second note in the
|
||||
<li>Note that this option will only be available on the second note in the
|
||||
split (the one at the bottom on smartphones, the one on the right on tablets).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="e780e5e7736b4d26705102545c5626a6a">When long-pressing a link, a contextual menu will show up with an option
|
||||
<li>When long-pressing a link, a contextual menu will show up with an option
|
||||
to <em>Open note in a new split</em>.
|
||||
<ul>
|
||||
<li data-list-item-id="e264277ee51f6d266a4088e2545f6648d">If there's already a split, the option will replace the existing split
|
||||
<li>If there's already a split, the option will replace the existing split
|
||||
instead.</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
199
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
199
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
@ -6,24 +6,27 @@
|
||||
a start date and optionally an end date, as an event.</p>
|
||||
<p>The Calendar view has multiple display modes:</p>
|
||||
<ul>
|
||||
<li>Week view, where all the 7 days of the week (or 5 if the weekends are
|
||||
<li data-list-item-id="e304b5ae296d7f47b813dcd8cf2dbba42">Week view, where all the 7 days of the week (or 5 if the weekends are
|
||||
hidden) are displayed in columns. This mode allows entering and displaying
|
||||
time-specific events, not just all-day events.</li>
|
||||
<li>Month view, where the entire month is displayed and all-day events can
|
||||
<li data-list-item-id="ea8d70da10fa78ede209782b485e2de49">Month view, where the entire month is displayed and all-day events can
|
||||
be inserted. Both time-specific events and all-day events are listed.</li>
|
||||
<li>Year view, which displays the entire year for quick reference.</li>
|
||||
<li>List view, which displays all the events of a given month in sequence.</li>
|
||||
<li
|
||||
data-list-item-id="e057acab031bcf780ce3055e534ab2d61">Year view, which displays the entire year for quick reference.</li>
|
||||
<li
|
||||
data-list-item-id="e5528ab7e56ada969592f5d35896a4808">List view, which displays all the events of a given month in sequence.</li>
|
||||
</ul>
|
||||
<p>Unlike other Collection view types, the Calendar view also allows some
|
||||
kind of interaction, such as moving events around as well as creating new
|
||||
ones.</p>
|
||||
<h2>Creating a calendar</h2>
|
||||
<table>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -45,54 +48,84 @@
|
||||
by selecting the “Collection Properties” tab in the ribbon.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Creating a new event/note</h2>
|
||||
<ul>
|
||||
<li>Clicking on a day will create a new child note and assign it to that particular
|
||||
<li data-list-item-id="ed78c7591e6504413d89bf60a172ba7fa">Clicking on a day will create a new child note and assign it to that particular
|
||||
day.
|
||||
<ul>
|
||||
<li>You will be asked for the name of the new note. If the popup is dismissed
|
||||
<li data-list-item-id="e9313832f770bbe02f6dbfdce57cb040c">You will be asked for the name of the new note. If the popup is dismissed
|
||||
by pressing the close button or escape, then the note will not be created.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>It's possible to drag across multiple days to set both the start and end
|
||||
<li data-list-item-id="e154d333887462e26c059d7b3218ffd10">It's possible to drag across multiple days to set both the start and end
|
||||
date of a particular note.
|
||||
<br>
|
||||
<img src="Calendar_image.png">
|
||||
</li>
|
||||
<li>Creating new notes from the calendar will respect the <code>~child:template</code> relation
|
||||
<li data-list-item-id="e8685250a40c8a75bf4a44ba3c4218495">Creating new notes from the calendar will respect the <code>~child:template</code> relation
|
||||
if set on the Collection note.</li>
|
||||
</ul>
|
||||
<h2>Interacting with events</h2>
|
||||
<ul>
|
||||
<li>Hovering the mouse over an event will display information about the note.
|
||||
<li data-list-item-id="ef3868587f3133abbb6a43f1e7ba73df4">Hovering the mouse over an event will display information about the note.
|
||||
<br>
|
||||
<img src="7_Calendar_image.png">
|
||||
</li>
|
||||
<li>Left clicking the event will open a <a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> to
|
||||
<li data-list-item-id="e0349fc14f198e77629a20fce7b910cbd">Left clicking the event will open a <a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a> to
|
||||
edit the note in a popup while allowing easy return to the calendar by
|
||||
just dismissing the popup.
|
||||
<ul>
|
||||
<li>Middle clicking will open the note in a new tab.</li>
|
||||
<li>Right click will offer more options including opening the note in a new
|
||||
<li data-list-item-id="e40934ee8189ac129ce9a62f1a34f90fd">Middle clicking will open the note in a new tab.</li>
|
||||
<li data-list-item-id="ed673e7b92f8713cfa950d6030deeb658">Right click will offer more options including opening the note in a new
|
||||
split or window.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Drag and drop an event on the calendar to move it to another day.</li>
|
||||
<li>The length of an event can be changed by placing the mouse to the right
|
||||
<li data-list-item-id="e42434d681c96273852be7111b2464e66">Drag and drop an event on the calendar to move it to another day.</li>
|
||||
<li
|
||||
data-list-item-id="ec6982b2bdebadd048f01b2c9d5204375">The length of an event can be changed by placing the mouse to the right
|
||||
edge of the event and dragging the mouse around.</li>
|
||||
</ul>
|
||||
<h2>Interaction on mobile</h2>
|
||||
<p>When Trilium is on mobile, the interaction with the calendar is slightly
|
||||
different:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e9eccdc329cafb680914a2fea2bf3b967">Clicking on an event triggers the contextual menu, including the option
|
||||
to open in <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_ZjLYv08Rp3qC">Quick edit</a>.</li>
|
||||
<li
|
||||
data-list-item-id="e095306ea6783bd216f3848b3d5b6bcba">To insert a new event, touch and hold the empty space. When successful,
|
||||
the empty space will become colored to indicate the selection.
|
||||
<ul>
|
||||
<li data-list-item-id="e678eb6949bb0dcb609880b66f4c218b8">Before releasing, drag across multiple spaces to create multi-day events.</li>
|
||||
<li
|
||||
data-list-item-id="eb77c1f5e90700a75c40fd0dab32a9266">When released, a prompt will appear to enter the note title.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="e824cd65431fa336433aa1e4186c1578d">To move an existing event, touch and hold the event until the empty space
|
||||
near it will become colored.
|
||||
<ul>
|
||||
<li data-list-item-id="e13dbb47246890b5f93404cf3e34feb3f">At this point the event can be dragged across other days on the calendar.</li>
|
||||
<li
|
||||
data-list-item-id="e011cdfec5a11fc783c5cc0acb2b704e1">Or the event can be resized by tapping on the small circle to the right
|
||||
end of the event.</li>
|
||||
<li data-list-item-id="ea38d2059d0736ad6d172bbb9f1cde4aa">To exit out of editing mode, simply tap the empty space anywhere on the
|
||||
calendar.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Configuring the calendar view</h2>
|
||||
<p>In the <em>Collections</em> tab in the <a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>,
|
||||
it's possible to adjust the following:</p>
|
||||
<ul>
|
||||
<li>Hide weekends from the week view.</li>
|
||||
<li>Display week numbers on the calendar.</li>
|
||||
<li data-list-item-id="e5226aa5761f9b2fd47d0c38c252e7f74">Hide weekends from the week view.</li>
|
||||
<li data-list-item-id="e62c9e62cfbc2d6e2e4587cfce90143a0">Display week numbers on the calendar.</li>
|
||||
</ul>
|
||||
<h2>Configuring the calendar using attributes</h2>
|
||||
<p>The following attributes can be added to the Collection type:</p>
|
||||
<table>
|
||||
<figure
|
||||
class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@ -124,10 +157,10 @@
|
||||
<td>
|
||||
<p>Which view to display in the calendar:</p>
|
||||
<ul>
|
||||
<li><code>timeGridWeek</code> for the <em>week</em> view;</li>
|
||||
<li><code>dayGridMonth</code> for the <em>month</em> view;</li>
|
||||
<li><code>multiMonthYear</code> for the <em>year</em> view;</li>
|
||||
<li><code>listMonth</code> for the <em>list</em> view.</li>
|
||||
<li data-list-item-id="e2cd230dc41f41fe91ee74d7d1fa87372"><code>timeGridWeek</code> for the <em>week</em> view;</li>
|
||||
<li data-list-item-id="eee1dba4c6cc51ebd53d0a0dd52044cd6"><code>dayGridMonth</code> for the <em>month</em> view;</li>
|
||||
<li data-list-item-id="ed8721a76a1865dac882415f662ed45b9"><code>multiMonthYear</code> for the <em>year</em> view;</li>
|
||||
<li data-list-item-id="edf09a13759102d98dac34c33eb690c05"><code>listMonth</code> for the <em>list</em> view.</li>
|
||||
</ul>
|
||||
<p>Any other value will be dismissed and the default view (month) will be
|
||||
used instead.</p>
|
||||
@ -142,12 +175,15 @@
|
||||
or clicking).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>In addition, the first day of the week can be either Sunday or Monday
|
||||
</table>
|
||||
</figure>
|
||||
<p>In addition, the first day of the week can be either Sunday or Monday
|
||||
and can be adjusted from the application settings.</p>
|
||||
<h2>Configuring the calendar events using attributes</h2>
|
||||
<p>For each note of the calendar, the following attributes can be used:</p>
|
||||
<table>
|
||||
<h2>Configuring the calendar events using attributes</h2>
|
||||
<p>For each note of the calendar, the following attributes can be used:</p>
|
||||
<figure
|
||||
class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@ -192,8 +228,12 @@
|
||||
<tr>
|
||||
<td><code>#calendar:color</code>
|
||||
</td>
|
||||
<td>Similar to <code>#color</code>, but applies the color only for the event
|
||||
in the calendar and not for other places such as the note tree. (<em>Deprecated</em>)</td>
|
||||
<td>
|
||||
<p><strong>❌️ Removed since v0.100.0. Use </strong><code><strong>#color</strong></code><strong> instead.</strong>
|
||||
</p>
|
||||
<p>Similar to <code>#color</code>, but applies the color only for the event
|
||||
in the calendar and not for other places such as the note tree.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#iconClass</code>
|
||||
@ -212,15 +252,15 @@
|
||||
<td><code>#calendar:displayedAttributes</code>
|
||||
</td>
|
||||
<td>Allows displaying the value of one or more attributes in the calendar
|
||||
like this:
|
||||
like this:
|
||||
<br>
|
||||
<br>
|
||||
<img src="9_Calendar_image.png">
|
||||
<img src="9_Calendar_image.png">
|
||||
<br>
|
||||
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>
|
||||
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>
|
||||
<br>
|
||||
<br>It can also be used with relations, case in which it will display the
|
||||
title of the target note:
|
||||
title of the target note:
|
||||
<br>
|
||||
<br><code>~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
|
||||
</td>
|
||||
@ -251,57 +291,60 @@
|
||||
which is being used to read the end time.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>How the calendar works</h2>
|
||||
<p>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>How the calendar works</h2>
|
||||
<p>
|
||||
<img src="11_Calendar_image.png">
|
||||
</p>
|
||||
<p>The calendar displays all the child notes of the Collection that have
|
||||
</p>
|
||||
<p>The calendar displays all the child notes of the Collection that have
|
||||
a <code>#startDate</code>. An <code>#endDate</code> can optionally be added.</p>
|
||||
<p>If editing the start date and end date from the note itself is desirable,
|
||||
<p>If editing the start date and end date from the note itself is desirable,
|
||||
the following attributes can be added to the Collection note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date"
|
||||
#label:endDate(inheritable)="promoted,alias=End Date,single,date"
|
||||
#hidePromotedAttributes </code></pre>
|
||||
<p>This will result in:</p>
|
||||
<p>
|
||||
<p>This will result in:</p>
|
||||
<p>
|
||||
<img src="10_Calendar_image.png">
|
||||
</p>
|
||||
<p>When not used in a Journal, the calendar is recursive. That is, it will
|
||||
</p>
|
||||
<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>
|
||||
<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
|
||||
<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
|
||||
notes. In order to do so change the note type of the Journal note (calendar
|
||||
root) to Collection and then select the Calendar View.</p>
|
||||
<p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
|
||||
<p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
|
||||
attribute, the calendar will know that it's in a calendar and apply the
|
||||
following:</p>
|
||||
<ul>
|
||||
<li>The calendar events are now rendered based on their <code>dateNote</code> attribute
|
||||
<ul>
|
||||
<li data-list-item-id="ef8dd9e289abf6cb973bd379d219170ae">The calendar events are now rendered based on their <code>dateNote</code> attribute
|
||||
rather than <code>startDate</code>.</li>
|
||||
<li>Interactive editing such as dragging over an empty era or resizing an
|
||||
<li data-list-item-id="e35671ad2eee19bba69bb31f3f33cce37">Interactive editing such as dragging over an empty era or resizing an
|
||||
event is no longer possible.</li>
|
||||
<li>Clicking on the empty space on a date will automatically open that day's
|
||||
<li data-list-item-id="e8930686b36fb7fa36d123f9f202cbc75">Clicking on the empty space on a date will automatically open that day's
|
||||
note or create it if it does not exist.</li>
|
||||
<li>Direct children of a day note will be displayed on the calendar despite
|
||||
<li data-list-item-id="edc838e74ed3479fe2f0fbfaef462dbbb">Direct children of a day note will be displayed on the calendar despite
|
||||
not having a <code>dateNote</code> attribute. Children of the child notes
|
||||
will not be displayed.</li>
|
||||
</ul>
|
||||
<img src="8_Calendar_image.png" width="1217"
|
||||
height="724">
|
||||
|
||||
<h3>Using a different attribute as event title</h3>
|
||||
<p>By default, events are displayed on the calendar by their note title.
|
||||
</ul>
|
||||
<p>
|
||||
<img src="8_Calendar_image.png" width="1217"
|
||||
height="724">
|
||||
</p>
|
||||
<h3>Using a different attribute as event title</h3>
|
||||
<p>By default, events are displayed on the calendar by their note title.
|
||||
However, it is possible to configure a different attribute to be displayed
|
||||
instead.</p>
|
||||
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
|
||||
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
|
||||
calendar/Collection note), with the value being <code>name</code> where <code>name</code> can
|
||||
be any label (make not to add the <code>#</code> prefix). The attribute can
|
||||
also come through inheritance such as a template attribute. If the note
|
||||
does not have the requested label, the title of the note will be used instead.</p>
|
||||
<table>
|
||||
<figure
|
||||
class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
@ -321,15 +364,16 @@ height="724">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Using a relation attribute as event title</h3>
|
||||
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set
|
||||
</table>
|
||||
</figure>
|
||||
<h3>Using a relation attribute as event title</h3>
|
||||
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set
|
||||
it to <code>name</code> where <code>name</code> is the name of the relation
|
||||
to use.</p>
|
||||
<p>Moreover, if there are more relations of the same name, they will be displayed
|
||||
<p>Moreover, if there are more relations of the same name, they will be displayed
|
||||
as multiple events coming from the same note.</p>
|
||||
<table>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
@ -346,13 +390,15 @@ height="724">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Note that it's even possible to have a <code>#calendar:title</code> on the
|
||||
</table>
|
||||
</figure>
|
||||
<p>Note that it's even possible to have a <code>#calendar:title</code> on the
|
||||
target note (e.g. “John Smith”) which will try to render an attribute of
|
||||
it. Note that it's not possible to use a relation here as well for safety
|
||||
reasons (an accidental recursion of attributes could cause the application
|
||||
to loop infinitely).</p>
|
||||
<table>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
@ -371,4 +417,5 @@ height="724">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
</figure>
|
||||
37
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting/Script API/Day.js.html
generated
vendored
Normal file
37
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Scripting/Script API/Day.js.html
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<p>Day.js is a date manipulation library that's used by Trilium, but it's
|
||||
also shared with both front-end and back-end scripts. For more information
|
||||
about the library itself, consult the <a href="https://day.js.org/en/">official documentation</a>.</p>
|
||||
<h2>How to use</h2>
|
||||
<p>The <code>dayjs</code> method is provided directly in the <code>api</code> global:</p><pre><code class="language-application-javascript-env-backend">const date = api.dayjs();
|
||||
api.log(date.format("YYYY-MM-DD"));</code></pre>
|
||||
<h2>Plugins</h2>
|
||||
<p>Day.js uses a modular, plugin-based architecture. Generally these plugins
|
||||
must be imported, but this process doesn't work inside Trilium scripts
|
||||
due to the use of a bundler.</p>
|
||||
<p>Since v0.100.0, the same set of plugins is available for both front-end
|
||||
and back-end scripts.</p>
|
||||
<p>The following Day.js plugins are directly integrated into Trilium:</p>
|
||||
<ul>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/advanced-format">AdvancedFormat</a>
|
||||
</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/duration">Duration</a>, since
|
||||
v0.100.0.</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/is-between">IsBetween</a>
|
||||
</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/iso-week">IsoWeek</a>
|
||||
</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/is-same-or-after">IsSameOrAfter</a>
|
||||
</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/is-same-or-before">IsSameOrBefore</a>
|
||||
</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/quarter-of-year">QuarterOfYear</a>
|
||||
</li>
|
||||
<li><a href="https://day.js.org/docs/en/plugin/utc">UTC</a>
|
||||
</li>
|
||||
</ul>
|
||||
<aside class="admonition note">
|
||||
<p>If another Day.js plugin might be needed for scripting purposes, feel
|
||||
free to open a feature request for it. Depending on the size of the plugin
|
||||
and the potential use of it inside the Trilium code base, it has a chance
|
||||
of being integrated.</p>
|
||||
</aside>
|
||||
@ -11,8 +11,7 @@ import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
import BRevision from "./brevision.js";
|
||||
import BAttachment from "./battachment.js";
|
||||
import TaskContext from "../../services/task_context.js";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc.js";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import eventService from "../../services/events.js";
|
||||
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons";
|
||||
import type BBranch from "./bbranch.js";
|
||||
@ -22,7 +21,6 @@ import searchService from "../../services/search/services/search.js";
|
||||
import cloningService from "../../services/cloning.js";
|
||||
import noteService from "../../services/notes.js";
|
||||
import handlers from "../../services/handlers.js";
|
||||
dayjs.extend(utc);
|
||||
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import supertest, { type Response } from "supertest";
|
||||
import type { Application } from "express";
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import { type SQLiteSessionStore } from "./session_parser.js";
|
||||
import { SessionData } from "express-session";
|
||||
import cls from "../services/cls.js";
|
||||
|
||||
@ -7,7 +7,7 @@ import dateNoteService from "./date_notes.js";
|
||||
import treeService from "./tree.js";
|
||||
import config from "./config.js";
|
||||
import axios from "axios";
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import xml2js from "xml2js";
|
||||
import * as cheerio from "cheerio";
|
||||
import cloningService from "./cloning.js";
|
||||
@ -37,17 +37,6 @@ import type Becca from "../becca/becca-interface.js";
|
||||
import type { NoteParams } from "./note-interface.js";
|
||||
import type { ApiParams } from "./backend_script_api_interface.js";
|
||||
|
||||
// Dayjs plugins
|
||||
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import advancedFormat from "dayjs/plugin/advancedFormat.js";
|
||||
|
||||
dayjs.extend(isSameOrBefore);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(advancedFormat);
|
||||
|
||||
/**
|
||||
* A whole number
|
||||
* @typedef {number} int
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type BNote from "../becca/entities/bnote.js";
|
||||
|
||||
@ -1,27 +1,17 @@
|
||||
import type BNote from "../becca/entities/bnote.js";
|
||||
import type { Dayjs } from "dayjs";
|
||||
|
||||
import advancedFormat from "dayjs/plugin/advancedFormat.js";
|
||||
import attributeService from "./attributes.js";
|
||||
import cloningService from "./cloning.js";
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs, Dayjs } from "@triliumnext/commons";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||
import noteService from "./notes.js";
|
||||
import optionService from "./options.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import quarterOfYear from "dayjs/plugin/quarterOfYear.js";
|
||||
import searchContext from "../services/search/search_context.js";
|
||||
import searchService from "../services/search/services/search.js";
|
||||
import sql from "./sql.js";
|
||||
import { t } from "i18next";
|
||||
import { ordinal } from "./i18n.js";
|
||||
import isoWeek from "dayjs/plugin/isoWeek.js";
|
||||
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(quarterOfYear);
|
||||
dayjs.extend(advancedFormat);
|
||||
dayjs.extend(isoWeek);
|
||||
|
||||
const CALENDAR_ROOT_LABEL = "calendarRoot";
|
||||
const YEAR_LABEL = "yearNote";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import cls from "./cls.js";
|
||||
|
||||
const LOCAL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss.SSSZZ";
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { LOCALES } from "@triliumnext/commons";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { DAYJS_LOADER } from "./i18n";
|
||||
|
||||
describe("i18n", () => {
|
||||
it("translations are valid JSON", () => {
|
||||
@ -16,13 +15,4 @@ describe("i18n", () => {
|
||||
.not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it("all dayjs locales are valid", async () => {
|
||||
for (const locale of LOCALES) {
|
||||
const dayjsLoader = DAYJS_LOADER[locale.id];
|
||||
expect(dayjsLoader, `Locale ${locale.id} missing.`).toBeDefined();
|
||||
|
||||
await dayjsLoader();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,31 +4,7 @@ import sql_init from "./sql_init.js";
|
||||
import { join } from "path";
|
||||
import { getResourceDir } from "./utils.js";
|
||||
import hidden_subtree from "./hidden_subtree.js";
|
||||
import { LOCALES, type Locale, type LOCALE_IDS } from "@triliumnext/commons";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
|
||||
// When adding a new locale, prefer the version with hyphen instead of underscore.
|
||||
export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs/locale/en.js")>> = {
|
||||
"ar": () => import("dayjs/locale/ar.js"),
|
||||
"cn": () => import("dayjs/locale/zh-cn.js"),
|
||||
"de": () => import("dayjs/locale/de.js"),
|
||||
"en": () => import("dayjs/locale/en.js"),
|
||||
"en-GB": () => import("dayjs/locale/en-gb.js"),
|
||||
"en_rtl": () => import("dayjs/locale/en.js"),
|
||||
"es": () => import("dayjs/locale/es.js"),
|
||||
"fa": () => import("dayjs/locale/fa.js"),
|
||||
"fr": () => import("dayjs/locale/fr.js"),
|
||||
"it": () => import("dayjs/locale/it.js"),
|
||||
"he": () => import("dayjs/locale/he.js"),
|
||||
"ja": () => import("dayjs/locale/ja.js"),
|
||||
"ku": () => import("dayjs/locale/ku.js"),
|
||||
"pt_br": () => import("dayjs/locale/pt-br.js"),
|
||||
"pt": () => import("dayjs/locale/pt.js"),
|
||||
"ro": () => import("dayjs/locale/ro.js"),
|
||||
"ru": () => import("dayjs/locale/ru.js"),
|
||||
"tw": () => import("dayjs/locale/zh-tw.js"),
|
||||
"uk": () => import("dayjs/locale/uk.js"),
|
||||
}
|
||||
import { dayjs, LOCALES, setDayjsLocale, type Dayjs, type Locale, type LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
export async function initializeTranslations() {
|
||||
const resourceDir = getResourceDir();
|
||||
@ -46,10 +22,7 @@ export async function initializeTranslations() {
|
||||
});
|
||||
|
||||
// Initialize dayjs locale.
|
||||
const dayjsLocale = DAYJS_LOADER[locale];
|
||||
if (dayjsLocale) {
|
||||
dayjs.locale(await dayjsLocale());
|
||||
}
|
||||
await setDayjsLocale(locale);
|
||||
}
|
||||
|
||||
export function ordinal(date: Dayjs) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import sax from "sax";
|
||||
import stream from "stream";
|
||||
import { Throttle } from "stream-throttle";
|
||||
|
||||
@ -16,7 +16,7 @@ import BBranch from "../becca/entities/bbranch.js";
|
||||
import BNote from "../becca/entities/bnote.js";
|
||||
import BAttribute from "../becca/entities/battribute.js";
|
||||
import BAttachment from "../becca/entities/battachment.js";
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import htmlSanitizer from "./html_sanitizer.js";
|
||||
import ValidationError from "../errors/validation_error.js";
|
||||
import noteTypesService from "./note_types.js";
|
||||
|
||||
@ -59,7 +59,7 @@ describe("Script", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("dayjs", () => {
|
||||
describe("dayjs in backend scripts", () => {
|
||||
const scriptNote = note("dayjs", {
|
||||
type: "code",
|
||||
mime: "application/javascript;env=backend",
|
||||
@ -74,7 +74,7 @@ describe("Script", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("dayjs is-same-or-before", () => {
|
||||
it("dayjs is-same-or-before plugin exists", () => {
|
||||
cls.init(() => {
|
||||
const bundle = getScriptBundle(scriptNote.note, true, "backend", [], `return api.dayjs("2023-10-01").isSameOrBefore(api.dayjs("2023-10-02"));`);
|
||||
expect(bundle).toBeDefined();
|
||||
@ -82,33 +82,5 @@ describe("Script", () => {
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("dayjs is-same-or-after", () => {
|
||||
cls.init(() => {
|
||||
const bundle = getScriptBundle(scriptNote.note, true, "backend", [], `return api.dayjs("2023-10-02").isSameOrAfter(api.dayjs("2023-10-01"));`);
|
||||
expect(bundle).toBeDefined();
|
||||
const result = executeBundle(bundle!);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("dayjs is-between", () => {
|
||||
cls.init(() => {
|
||||
const bundle = getScriptBundle(scriptNote.note, true, "backend", [], `return api.dayjs("2023-10-02").isBetween(api.dayjs("2023-10-01"), api.dayjs("2023-10-03"));`);
|
||||
expect(bundle).toBeDefined();
|
||||
const result = executeBundle(bundle!);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// advanced format
|
||||
it("dayjs advanced format", () => {
|
||||
cls.init(() => {
|
||||
const bundle = getScriptBundle(scriptNote.note, true, "backend", [], `return api.dayjs("2023-10-01").format("Q");`);
|
||||
expect(bundle).toBeDefined();
|
||||
const result = executeBundle(bundle!);
|
||||
expect(result).not.toBe("Q");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import AndExp from "../expressions/and.js";
|
||||
import OrExp from "../expressions/or.js";
|
||||
import NotExp from "../expressions/not.js";
|
||||
|
||||
@ -40,15 +40,21 @@ interface Subroot {
|
||||
|
||||
type GetNoteFunction = (id: string) => SNote | BNote | null;
|
||||
|
||||
function getSharedSubTreeRoot(note: SNote | BNote | undefined): Subroot {
|
||||
function addContentAccessQuery(note: SNote | BNote, secondEl?:boolean) {
|
||||
if (!(note instanceof BNote) && note.contentAccessor && note.contentAccessor?.type === "query") {
|
||||
return secondEl ? `&cat=${note.contentAccessor.getToken()}` : `?cat=${note.contentAccessor.getToken()}`;
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
export function getSharedSubTreeRoot(note: SNote | BNote | undefined, parentId: string | undefined = undefined): Subroot {
|
||||
if (!note || note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
||||
// share root itself is not shared
|
||||
return {};
|
||||
}
|
||||
|
||||
// every path leads to share root, but which one to choose?
|
||||
// for the sake of simplicity, URLs are not note paths
|
||||
const parentBranch = note.getParentBranches()[0];
|
||||
const parentBranches = note.getParentBranches()
|
||||
const parentBranch = (parentId ? parentBranches.find((pb: SBranch | BBranch) => pb.parentNoteId === parentId) : undefined) || parentBranches[0];
|
||||
|
||||
if (note instanceof BNote) {
|
||||
return {
|
||||
@ -64,7 +70,7 @@ function getSharedSubTreeRoot(note: SNote | BNote | undefined): Subroot {
|
||||
};
|
||||
}
|
||||
|
||||
return getSharedSubTreeRoot(parentBranch.getParentNote());
|
||||
return getSharedSubTreeRoot(parentBranch.getParentNote(), parentId);
|
||||
}
|
||||
|
||||
export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath: string, ancestors: string[]) {
|
||||
@ -91,7 +97,7 @@ export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath
|
||||
}
|
||||
|
||||
export function renderNoteContent(note: SNote) {
|
||||
const subRoot = getSharedSubTreeRoot(note);
|
||||
const subRoot = getSharedSubTreeRoot(note, note.parentId);
|
||||
|
||||
const ancestors: string[] = [];
|
||||
let notePointer = note;
|
||||
@ -107,23 +113,23 @@ export function renderNoteContent(note: SNote) {
|
||||
// Determine CSS to load.
|
||||
const cssToLoad: string[] = [];
|
||||
if (!note.isLabelTruthy("shareOmitDefaultCss")) {
|
||||
cssToLoad.push(`assets/styles.css`);
|
||||
cssToLoad.push(`assets/scripts.css`);
|
||||
cssToLoad.push(`../assets/styles.css`);
|
||||
cssToLoad.push(`../assets/scripts.css`);
|
||||
}
|
||||
for (const cssRelation of note.getRelations("shareCss")) {
|
||||
cssToLoad.push(`api/notes/${cssRelation.value}/download`);
|
||||
cssToLoad.push(`../api/notes/${cssRelation.value}/download${addContentAccessQuery(note)}`);
|
||||
}
|
||||
|
||||
// Determine JS to load.
|
||||
const jsToLoad: string[] = [
|
||||
"assets/scripts.js"
|
||||
"../assets/scripts.js"
|
||||
];
|
||||
for (const jsRelation of note.getRelations("shareJs")) {
|
||||
jsToLoad.push(`api/notes/${jsRelation.value}/download`);
|
||||
jsToLoad.push(`../api/notes/${jsRelation.value}/download${addContentAccessQuery(note)}`);
|
||||
}
|
||||
|
||||
const customLogoId = note.getRelation("shareLogo")?.value;
|
||||
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `../${assetUrlFragment}/images/icon-color.svg`;
|
||||
const logoUrl = customLogoId ? `../api/images/${customLogoId}/image.png${addContentAccessQuery(note)}` : `../../${assetUrlFragment}/images/icon-color.svg`;
|
||||
|
||||
return renderNoteContentInternal(note, {
|
||||
subRoot,
|
||||
@ -133,7 +139,7 @@ export function renderNoteContent(note: SNote) {
|
||||
logoUrl,
|
||||
ancestors,
|
||||
isStatic: false,
|
||||
faviconUrl: note.hasRelation("shareFavicon") ? `api/notes/${note.getRelationValue("shareFavicon")}/download` : `../favicon.ico`
|
||||
faviconUrl: note.hasRelation("shareFavicon") ? `../api/notes/${note.getRelationValue("shareFavicon")}/download${addContentAccessQuery(note)}` : `../../favicon.ico`
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,6 +164,7 @@ function renderNoteContentInternal(note: SNote | BNote, renderArgs: RenderArgs)
|
||||
isEmpty,
|
||||
assetPath: shareAdjustedAssetPath,
|
||||
assetUrlFragment,
|
||||
addContentAccessQuery: (second: boolean | undefined) => addContentAccessQuery(note, second),
|
||||
showLoginInShareTheme,
|
||||
t,
|
||||
isDev,
|
||||
@ -321,7 +328,7 @@ function renderText(result: Result, note: SNote | BNote) {
|
||||
}
|
||||
|
||||
if (href?.startsWith("#")) {
|
||||
handleAttachmentLink(linkEl, href, getNote, getAttachment);
|
||||
handleAttachmentLink(linkEl, href, getNote, getAttachment, note);
|
||||
}
|
||||
|
||||
if (linkEl.classList.contains("reference-link")) {
|
||||
@ -349,7 +356,7 @@ function renderText(result: Result, note: SNote | BNote) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNoteFunction, getAttachment: (id: string) => BAttachment | SAttachment | null) {
|
||||
function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNoteFunction, getAttachment: (id: string) => BAttachment | SAttachment | null, note: SNote | BNote) {
|
||||
const linkRegExp = /attachmentId=([a-zA-Z0-9_]+)/g;
|
||||
let attachmentMatch;
|
||||
if ((attachmentMatch = linkRegExp.exec(href))) {
|
||||
@ -357,7 +364,7 @@ function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNot
|
||||
const attachment = getAttachment(attachmentId);
|
||||
|
||||
if (attachment) {
|
||||
linkEl.setAttribute("href", `api/attachments/${attachmentId}/download`);
|
||||
linkEl.setAttribute("href", `../api/attachments/${attachmentId}/download${addContentAccessQuery(note)}`);
|
||||
linkEl.classList.add(`attachment-link`);
|
||||
linkEl.classList.add(`role-${attachment.role}`);
|
||||
linkEl.childNodes.length = 0;
|
||||
@ -373,7 +380,7 @@ function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNot
|
||||
const linkedNote = getNote(noteId);
|
||||
if (linkedNote) {
|
||||
const isExternalLink = linkedNote.hasLabel("shareExternalLink");
|
||||
const href = isExternalLink ? linkedNote.getLabelValue("shareExternalLink") : `./${linkedNote.shareId}`;
|
||||
const href = isExternalLink ? linkedNote.getLabelValue("shareExternalLink") : `../${linkedNote.shareId}`;
|
||||
if (href) {
|
||||
linkEl.setAttribute("href", href);
|
||||
}
|
||||
@ -430,7 +437,7 @@ function renderMermaid(result: Result, note: SNote | BNote) {
|
||||
}
|
||||
|
||||
result.content = `
|
||||
<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">
|
||||
<img src="../api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}${addContentAccessQuery(note, true)}">
|
||||
<hr>
|
||||
<details>
|
||||
<summary>Chart source</summary>
|
||||
@ -439,14 +446,14 @@ function renderMermaid(result: Result, note: SNote | BNote) {
|
||||
}
|
||||
|
||||
function renderImage(result: Result, note: SNote | BNote) {
|
||||
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`;
|
||||
result.content = `<img src="../api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}${addContentAccessQuery(note, true)}">`;
|
||||
}
|
||||
|
||||
function renderFile(note: SNote | BNote, result: Result) {
|
||||
if (note.mime === "application/pdf") {
|
||||
result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`;
|
||||
result.content = `<iframe class="pdf-view" src="../api/notes/${note.noteId}/view${addContentAccessQuery(note)}"></iframe>`;
|
||||
} else {
|
||||
result.content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download'">Download file</button>`;
|
||||
result.content = `<button type="button" onclick="location.href='../api/notes/${note.noteId}/download${addContentAccessQuery(note)}'">Download file</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import searchService from "../services/search/services/search.js";
|
||||
import SearchContext from "../services/search/search_context.js";
|
||||
import type SNote from "./shaca/entities/snote.js";
|
||||
import type SAttachment from "./shaca/entities/sattachment.js";
|
||||
import { getDefaultTemplatePath, renderNoteContent } from "./content_renderer.js";
|
||||
import { getDefaultTemplatePath, getSharedSubTreeRoot, renderNoteContent } from "./content_renderer.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
function addNoIndexHeader(note: SNote, res: Response) {
|
||||
@ -60,6 +60,20 @@ function checkNoteAccess(noteId: string, req: Request, res: Response) {
|
||||
const header = req.header("Authorization");
|
||||
|
||||
if (!header?.startsWith("Basic ")) {
|
||||
if (req.path.startsWith("/share/api") && note.contentAccessor) {
|
||||
let contentAccessToken = ""
|
||||
if (note.contentAccessor.type === "cookie") contentAccessToken += req.cookies["trilium.cat"] || ""
|
||||
else if (note.contentAccessor.type === "query") contentAccessToken += req.query['cat'] || ""
|
||||
|
||||
if (contentAccessToken){
|
||||
if (note.contentAccessor.isTokenValid(contentAccessToken)){
|
||||
return note
|
||||
}
|
||||
res.status(401).send("Access is expired. Return back and update the page.");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -124,9 +138,14 @@ function register(router: Router) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (note.isLabelTruthy("shareExclude")) {
|
||||
res.status(404);
|
||||
render404(res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkNoteAccess(note.noteId, req, res)) {
|
||||
requestCredentials(res);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -138,6 +157,10 @@ function register(router: Router) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (note.contentAccessor && note.contentAccessor.type === "cookie") {
|
||||
res.cookie('trilium.cat', note.contentAccessor.getToken(), { maxAge: note.contentAccessor.getTokenExpiration() * 1000, httpOnly: true })
|
||||
}
|
||||
|
||||
res.send(renderNoteContent(note));
|
||||
}
|
||||
|
||||
@ -157,14 +180,29 @@ function register(router: Router) {
|
||||
renderNote(shaca.shareRootNote, req, res);
|
||||
});
|
||||
|
||||
router.get("/share/:parentShareId/:shareId", (req, res) => {
|
||||
shacaLoader.ensureLoad();
|
||||
|
||||
const { parentShareId, shareId } = req.params;
|
||||
|
||||
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
|
||||
if (note){
|
||||
note.parentId = parentShareId
|
||||
note.initContentAccessor()
|
||||
}
|
||||
|
||||
renderNote(note, req, res);
|
||||
});
|
||||
|
||||
router.get("/share/:shareId", (req, res) => {
|
||||
shacaLoader.ensureLoad();
|
||||
|
||||
const { shareId } = req.params;
|
||||
|
||||
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
|
||||
const parent = getSharedSubTreeRoot(note)
|
||||
|
||||
renderNote(note, req, res);
|
||||
res.redirect(`${parent?.note?.noteId}/${shareId}`)
|
||||
});
|
||||
|
||||
router.get("/share/api/notes/:noteId", (req, res) => {
|
||||
|
||||
81
apps/server/src/share/shaca/entities/content_accessor.ts
Normal file
81
apps/server/src/share/shaca/entities/content_accessor.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import crypto from "crypto";
|
||||
import SNote from "./snote";
|
||||
import utils from "../../../services/utils";
|
||||
|
||||
const DefaultAccessTimeoutSec = 10 * 60; // 10 minutes
|
||||
|
||||
export class ContentAccessor {
|
||||
note: SNote;
|
||||
token: string;
|
||||
timestamp: number;
|
||||
type: string;
|
||||
timeout: number;
|
||||
key: Buffer;
|
||||
|
||||
constructor(note: SNote) {
|
||||
this.note = note;
|
||||
this.key = crypto.randomBytes(32);
|
||||
this.token = "";
|
||||
this.timestamp = 0;
|
||||
this.timeout = Number(this.note.getAttributeValue("label", "shareAccessTokenTimeout") || DefaultAccessTimeoutSec)
|
||||
|
||||
switch (this.note.getAttributeValue("label", "shareContentAccess")) {
|
||||
case "basic": this.type = "basic"; break
|
||||
case "query": this.type = "query"; break
|
||||
default: this.type = "cookie"; break
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
__encrypt(text: string) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv);
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
return iv.toString('hex') + encrypted;
|
||||
}
|
||||
|
||||
__decrypt(encryptedText: string) {
|
||||
try {
|
||||
const iv = Buffer.from(encryptedText.slice(0, 32), 'hex');
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv);
|
||||
let decrypted = decipher.update(encryptedText.slice(32), 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
__compare(originalText: string, encryptedText: string) {
|
||||
return originalText === this.__decrypt(encryptedText)
|
||||
}
|
||||
|
||||
update() {
|
||||
if (new Date().getTime() < this.timestamp + this.getTimeout() * 1000) return
|
||||
this.token = utils.randomString(36);
|
||||
this.key = crypto.randomBytes(32);
|
||||
this.timestamp = new Date().getTime();
|
||||
}
|
||||
|
||||
isTokenValid(encToken: string) {
|
||||
return this.__compare(this.token, encToken) && new Date().getTime() < this.timestamp + this.getTimeout() * 1000;
|
||||
}
|
||||
|
||||
getToken() {
|
||||
return this.__encrypt(this.token);
|
||||
}
|
||||
|
||||
getTokenExpiration() {
|
||||
return (this.timestamp + (this.timeout * 1000) - new Date().getTime()) /1000;
|
||||
}
|
||||
|
||||
getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
getContentAccessType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
}
|
||||
@ -10,6 +10,7 @@ import type SAttribute from "./sattribute.js";
|
||||
import type SBranch from "./sbranch.js";
|
||||
import type { SNoteRow } from "./rows.js";
|
||||
import { NOTE_TYPE_ICONS } from "../../../becca/entities/bnote.js";
|
||||
import { ContentAccessor } from "./content_accessor.js";
|
||||
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
@ -19,6 +20,7 @@ const isCredentials = (attr: SAttribute) => attr.type === "label" && attr.name =
|
||||
|
||||
class SNote extends AbstractShacaEntity {
|
||||
noteId: string;
|
||||
parentId?: string | undefined;
|
||||
title: string;
|
||||
type: string;
|
||||
mime: string;
|
||||
@ -33,11 +35,13 @@ class SNote extends AbstractShacaEntity {
|
||||
private __inheritableAttributeCache: SAttribute[] | null;
|
||||
targetRelations: SAttribute[];
|
||||
attachments: SAttachment[];
|
||||
contentAccessor: ContentAccessor | undefined;
|
||||
|
||||
constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: SNoteRow) {
|
||||
super();
|
||||
|
||||
this.noteId = noteId;
|
||||
this.parentId = undefined;
|
||||
this.title = isProtected ? "[protected]" : title;
|
||||
this.type = type;
|
||||
this.mime = mime;
|
||||
@ -59,6 +63,19 @@ class SNote extends AbstractShacaEntity {
|
||||
this.shaca.notes[this.noteId] = this;
|
||||
}
|
||||
|
||||
initContentAccessor(){
|
||||
if (!this.contentAccessor && this.getCredentials().length > 0) {
|
||||
this.contentAccessor = new ContentAccessor(this);
|
||||
}
|
||||
if (this.contentAccessor) {
|
||||
this.contentAccessor.update()
|
||||
}
|
||||
}
|
||||
|
||||
getParentId() {
|
||||
return this.parentId;
|
||||
}
|
||||
|
||||
getParentBranches() {
|
||||
return this.parentBranches;
|
||||
}
|
||||
@ -72,7 +89,7 @@ class SNote extends AbstractShacaEntity {
|
||||
}
|
||||
|
||||
getVisibleChildBranches() {
|
||||
return this.getChildBranches().filter((branch) => !branch.isHidden && !branch.getNote().isLabelTruthy("shareHiddenFromTree"));
|
||||
return this.getChildBranches().filter((branch) => !branch.isHidden && !branch.getNote().isLabelTruthy("shareHiddenFromTree") && !branch.getNote().isLabelTruthy("shareExclude"));
|
||||
}
|
||||
|
||||
getParentNotes() {
|
||||
@ -80,7 +97,7 @@ class SNote extends AbstractShacaEntity {
|
||||
}
|
||||
|
||||
getChildNotes() {
|
||||
return this.children;
|
||||
return this.children.filter((note) => !note.isLabelTruthy("shareExclude"));
|
||||
}
|
||||
|
||||
getVisibleChildNotes() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/KJUp1g3csedB/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/MKeODWYVO3J3/Documentation_image.png" width="205" height="162">
|
||||
|
||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
116
docs/Release Notes/!!!meta.json
vendored
116
docs/Release Notes/!!!meta.json
vendored
@ -61,6 +61,32 @@
|
||||
"attachments": [],
|
||||
"dirFileName": "Release Notes",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "iPGKEk7pwJXK",
|
||||
"notePath": [
|
||||
"hD3V4hiu2VW4",
|
||||
"iPGKEk7pwJXK"
|
||||
],
|
||||
"title": "v0.100.0",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "template",
|
||||
"value": "wyurrlcDl416",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "v0.100.0.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "7HKMTjmopLcM",
|
||||
@ -69,7 +95,7 @@
|
||||
"7HKMTjmopLcM"
|
||||
],
|
||||
"title": "v0.99.5",
|
||||
"notePosition": 10,
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -95,7 +121,7 @@
|
||||
"RMBaNYPsRpIr"
|
||||
],
|
||||
"title": "v0.99.4",
|
||||
"notePosition": 20,
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -121,7 +147,7 @@
|
||||
"yuroLztFfpu5"
|
||||
],
|
||||
"title": "v0.99.3",
|
||||
"notePosition": 30,
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -147,7 +173,7 @@
|
||||
"z207sehwMJ6C"
|
||||
],
|
||||
"title": "v0.99.2",
|
||||
"notePosition": 40,
|
||||
"notePosition": 50,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -173,7 +199,7 @@
|
||||
"WGQsXq2jNyTi"
|
||||
],
|
||||
"title": "v0.99.1",
|
||||
"notePosition": 50,
|
||||
"notePosition": 60,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -199,7 +225,7 @@
|
||||
"cyw2Yue9vXf3"
|
||||
],
|
||||
"title": "v0.99.0",
|
||||
"notePosition": 60,
|
||||
"notePosition": 70,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -225,7 +251,7 @@
|
||||
"QOJwjruOUr4k"
|
||||
],
|
||||
"title": "v0.98.1",
|
||||
"notePosition": 70,
|
||||
"notePosition": 80,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -251,7 +277,7 @@
|
||||
"PLUoryywi0BC"
|
||||
],
|
||||
"title": "v0.98.0",
|
||||
"notePosition": 80,
|
||||
"notePosition": 90,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -277,7 +303,7 @@
|
||||
"lvOuiWsLDv8F"
|
||||
],
|
||||
"title": "v0.97.2",
|
||||
"notePosition": 90,
|
||||
"notePosition": 100,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -303,7 +329,7 @@
|
||||
"OtFZ6Nd9vM3n"
|
||||
],
|
||||
"title": "v0.97.1",
|
||||
"notePosition": 100,
|
||||
"notePosition": 110,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -329,7 +355,7 @@
|
||||
"SJZ5PwfzHSQ1"
|
||||
],
|
||||
"title": "v0.97.0",
|
||||
"notePosition": 110,
|
||||
"notePosition": 120,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -355,7 +381,7 @@
|
||||
"mYXFde3LuNR7"
|
||||
],
|
||||
"title": "v0.96.0",
|
||||
"notePosition": 120,
|
||||
"notePosition": 130,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -381,7 +407,7 @@
|
||||
"jthwbL0FdaeU"
|
||||
],
|
||||
"title": "v0.95.0",
|
||||
"notePosition": 130,
|
||||
"notePosition": 140,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -407,7 +433,7 @@
|
||||
"7HGYsJbLuhnv"
|
||||
],
|
||||
"title": "v0.94.1",
|
||||
"notePosition": 140,
|
||||
"notePosition": 150,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -433,7 +459,7 @@
|
||||
"Neq53ujRGBqv"
|
||||
],
|
||||
"title": "v0.94.0",
|
||||
"notePosition": 150,
|
||||
"notePosition": 160,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -459,7 +485,7 @@
|
||||
"VN3xnce1vLkX"
|
||||
],
|
||||
"title": "v0.93.0",
|
||||
"notePosition": 160,
|
||||
"notePosition": 170,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -477,7 +503,7 @@
|
||||
"WRaBfQqPr6qo"
|
||||
],
|
||||
"title": "v0.92.7",
|
||||
"notePosition": 170,
|
||||
"notePosition": 180,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -503,7 +529,7 @@
|
||||
"a2rwfKNmUFU1"
|
||||
],
|
||||
"title": "v0.92.6",
|
||||
"notePosition": 180,
|
||||
"notePosition": 190,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -521,7 +547,7 @@
|
||||
"fEJ8qErr0BKL"
|
||||
],
|
||||
"title": "v0.92.5-beta",
|
||||
"notePosition": 190,
|
||||
"notePosition": 200,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -539,7 +565,7 @@
|
||||
"kkkZQQGSXjwy"
|
||||
],
|
||||
"title": "v0.92.4",
|
||||
"notePosition": 200,
|
||||
"notePosition": 210,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -557,7 +583,7 @@
|
||||
"vAroNixiezaH"
|
||||
],
|
||||
"title": "v0.92.3-beta",
|
||||
"notePosition": 210,
|
||||
"notePosition": 220,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -575,7 +601,7 @@
|
||||
"mHEq1wxAKNZd"
|
||||
],
|
||||
"title": "v0.92.2-beta",
|
||||
"notePosition": 220,
|
||||
"notePosition": 230,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -593,7 +619,7 @@
|
||||
"IykjoAmBpc61"
|
||||
],
|
||||
"title": "v0.92.1-beta",
|
||||
"notePosition": 230,
|
||||
"notePosition": 240,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -611,7 +637,7 @@
|
||||
"dq2AJ9vSBX4Y"
|
||||
],
|
||||
"title": "v0.92.0-beta",
|
||||
"notePosition": 240,
|
||||
"notePosition": 250,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -629,7 +655,7 @@
|
||||
"3a8aMe4jz4yM"
|
||||
],
|
||||
"title": "v0.91.6",
|
||||
"notePosition": 250,
|
||||
"notePosition": 260,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -647,7 +673,7 @@
|
||||
"8djQjkiDGESe"
|
||||
],
|
||||
"title": "v0.91.5",
|
||||
"notePosition": 260,
|
||||
"notePosition": 270,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -665,7 +691,7 @@
|
||||
"OylxVoVJqNmr"
|
||||
],
|
||||
"title": "v0.91.4-beta",
|
||||
"notePosition": 270,
|
||||
"notePosition": 280,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -683,7 +709,7 @@
|
||||
"tANGQDvnyhrj"
|
||||
],
|
||||
"title": "v0.91.3-beta",
|
||||
"notePosition": 280,
|
||||
"notePosition": 290,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -701,7 +727,7 @@
|
||||
"hMoBfwSoj1SC"
|
||||
],
|
||||
"title": "v0.91.2-beta",
|
||||
"notePosition": 290,
|
||||
"notePosition": 300,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -719,7 +745,7 @@
|
||||
"a2XMSKROCl9z"
|
||||
],
|
||||
"title": "v0.91.1-beta",
|
||||
"notePosition": 300,
|
||||
"notePosition": 310,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -737,7 +763,7 @@
|
||||
"yqXFvWbLkuMD"
|
||||
],
|
||||
"title": "v0.90.12",
|
||||
"notePosition": 310,
|
||||
"notePosition": 320,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -755,7 +781,7 @@
|
||||
"veS7pg311yJP"
|
||||
],
|
||||
"title": "v0.90.11-beta",
|
||||
"notePosition": 320,
|
||||
"notePosition": 330,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -773,7 +799,7 @@
|
||||
"sq5W9TQxRqMq"
|
||||
],
|
||||
"title": "v0.90.10-beta",
|
||||
"notePosition": 330,
|
||||
"notePosition": 340,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -791,7 +817,7 @@
|
||||
"yFEGVCUM9tPx"
|
||||
],
|
||||
"title": "v0.90.9-beta",
|
||||
"notePosition": 340,
|
||||
"notePosition": 350,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -809,7 +835,7 @@
|
||||
"o4wAGqOQuJtV"
|
||||
],
|
||||
"title": "v0.90.8",
|
||||
"notePosition": 350,
|
||||
"notePosition": 360,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -842,7 +868,7 @@
|
||||
"i4A5g9iOg9I0"
|
||||
],
|
||||
"title": "v0.90.7-beta",
|
||||
"notePosition": 360,
|
||||
"notePosition": 370,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -860,7 +886,7 @@
|
||||
"ThNf2GaKgXUs"
|
||||
],
|
||||
"title": "v0.90.6-beta",
|
||||
"notePosition": 370,
|
||||
"notePosition": 380,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -878,7 +904,7 @@
|
||||
"G4PAi554kQUr"
|
||||
],
|
||||
"title": "v0.90.5-beta",
|
||||
"notePosition": 380,
|
||||
"notePosition": 390,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -905,7 +931,7 @@
|
||||
"zATRobGRCmBn"
|
||||
],
|
||||
"title": "v0.90.4",
|
||||
"notePosition": 390,
|
||||
"notePosition": 400,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -923,7 +949,7 @@
|
||||
"sCDLf8IKn3Iz"
|
||||
],
|
||||
"title": "v0.90.3",
|
||||
"notePosition": 400,
|
||||
"notePosition": 410,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -941,7 +967,7 @@
|
||||
"VqqyBu4AuTjC"
|
||||
],
|
||||
"title": "v0.90.2-beta",
|
||||
"notePosition": 410,
|
||||
"notePosition": 420,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -959,7 +985,7 @@
|
||||
"RX3Nl7wInLsA"
|
||||
],
|
||||
"title": "v0.90.1-beta",
|
||||
"notePosition": 420,
|
||||
"notePosition": 430,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -977,7 +1003,7 @@
|
||||
"GyueACukPWjk"
|
||||
],
|
||||
"title": "v0.90.0-beta",
|
||||
"notePosition": 430,
|
||||
"notePosition": 440,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -995,7 +1021,7 @@
|
||||
"kzjHexDTTeVB"
|
||||
],
|
||||
"title": "v0.48",
|
||||
"notePosition": 440,
|
||||
"notePosition": 450,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@ -1062,7 +1088,7 @@
|
||||
"wyurrlcDl416"
|
||||
],
|
||||
"title": "Release Template",
|
||||
"notePosition": 450,
|
||||
"notePosition": 460,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
|
||||
160
docs/Release Notes/Release Notes/v0.100.0.md
vendored
Normal file
160
docs/Release Notes/Release Notes/v0.100.0.md
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
# v0.100.0
|
||||
> [!NOTE]
|
||||
> If you are interested in an [official mobile application](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/7447) ([#7447](https://github.com/TriliumNext/Trilium/issues/7447)) or [multi-user support](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/4956) ([#4956](https://github.com/TriliumNext/Trilium/issues/4956)), consider offering financial support via IssueHunt (see links).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
>
|
||||
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Trilium) (top-right).
|
||||
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
|
||||
|
||||
## 💡 Key highlights
|
||||
|
||||
* [Allow changing note color from tree](https://github.com/TriliumNext/Trilium/issues/4137) by @adoriandoran
|
||||
* More Collection types can now be printed or exported to PDF:
|
||||
* List collections which will render in a book-like fashion with all its children and preserving the note hierarchy, with functional table of contents and links (for PDFs).
|
||||
* Table collections will now render a printable table.
|
||||
* See [documentation](https://docs.triliumnotes.org/user-guide/concepts/notes/printing-and-pdf-export#printing-multiple-notes) for more info.
|
||||
* Board view improvements
|
||||
* The number of items in each column is now displayed.
|
||||
* Attributes (labels & relations) can now be displayed for each note using promoted attributes, consult the documentation for information on how to do so.
|
||||
* Mobile now has basic support for splits (maximum of two), laid out vertically for smartphones.
|
||||
* **Breaking change**: `#calendar:color` was completely removed in favor of the global `#color` attribute.
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
* [Advanced search may return more results](https://github.com/TriliumNext/Trilium/issues/7706) by @perfectra1n
|
||||
* [Fix enex import stores wrong format in database for dateCreated, dateModified](https://github.com/TriliumNext/Trilium/pull/7718) by @contributor
|
||||
* [Custom Global Shortcut for Toggle System Tray Icon Fails to Show Window](https://github.com/TriliumNext/Trilium/issues/7730) by @contributor
|
||||
* [Fix NoteLink component is unable to display path for root note](https://github.com/TriliumNext/Trilium/pull/7736) by @contributor
|
||||
* [file: protocol not working on Linux](https://github.com/TriliumNext/Trilium/issues/6696) by @laundmo
|
||||
* [Can't type proper value in font size inputbox in Appearance tab in Configuration](https://github.com/TriliumNext/Trilium/issues/7740)
|
||||
* [#top/#bottom reversed when #sortDirection=desc](https://github.com/TriliumNext/Trilium/issues/7716)
|
||||
* [Deleting note from journal not working](https://github.com/TriliumNext/Trilium/issues/7702)
|
||||
* Fixes in the share functionality:
|
||||
* [Inline mermaid diagrams not rendering](https://github.com/TriliumNext/Trilium/issues/7765)
|
||||
* [Share url can be broken because of extra slash](https://github.com/TriliumNext/Trilium/pull/7779) by @contributor
|
||||
* [Invalid code formatting caused by unescaped HTML](https://github.com/TriliumNext/Trilium/issues/7783)
|
||||
* [`#shareHiddenFromTree` does not hide link in the footer](https://github.com/TriliumNext/Trilium/issues/7781)
|
||||
* [Title of protected note is visible when linked into shared note](https://github.com/TriliumNext/Trilium/issues/4801)
|
||||
* [Scripting: Right Pane Widget example produces error](https://github.com/TriliumNext/Trilium/issues/7778)
|
||||
* [The WebView note type is not passed through template notes](https://github.com/TriliumNext/Trilium/issues/7557)
|
||||
* [\[MIME type error\] Browser blocks loading print.css (application/json)](https://github.com/TriliumNext/Trilium/issues/7772)
|
||||
* [Overly narrow empty tab layout on mobile](https://github.com/TriliumNext/Trilium/pull/7824) by @SiriusXT
|
||||
* Printing:
|
||||
* “Print job canceled" error message when dismissing.
|
||||
* Included notes not rendered.
|
||||
* Quick edit:
|
||||
* Keyboard shortcuts not always working.
|
||||
* [Quick view of dateNote in calendar doesn't respect readOnly status](https://github.com/TriliumNext/Trilium/issues/7715)
|
||||
* Promoted attributes: some labels would redirect to the wrong input box.
|
||||
* Attachments:
|
||||
* [PDF viewer displays abnormally](https://github.com/TriliumNext/Trilium/issues/7847)
|
||||
* Newly uploaded attachments don't display their size.
|
||||
* [Search selected text in Trilium](https://github.com/TriliumNext/Trilium/pull/7859) by @contributor
|
||||
* Collections
|
||||
* [Collection/Table Breaks with Colon within Attribute Name](https://github.com/TriliumNext/Trilium/issues/7860)
|
||||
* All descendant notes displayed in List/Grid when switching views.
|
||||
* Geomap:
|
||||
* Map not loading at the lowest zoom.
|
||||
* [Markers are not colored](https://github.com/TriliumNext/Trilium/issues/7920).
|
||||
* Infinite scroll is now disabled to avoid issues with markers disappearing.
|
||||
* Board:
|
||||
* Columns leaking when switching between two different collections of the same type.
|
||||
* Unable to press Escape to dismiss adding a new column.
|
||||
* Changing `board:groupBy` doesn't refresh the board.
|
||||
* Calendar: events sometimes not refreshing.
|
||||
* MacOS:
|
||||
* [ForwardInNoteHistory and backInNoteHistory shortcuts clobber system functionality](https://github.com/TriliumNext/Trilium/issues/3708)
|
||||
* [Unable to use CMD+UpArrow / CMD+DownArrow hotkeys within the note](https://github.com/TriliumNext/Trilium/issues/6964)
|
||||
* Mobile:
|
||||
* Some context menu would hide when tapping on submenus.
|
||||
* Classic toolbar in Quick Edit would not open dropdowns properly.
|
||||
* Keyboard shortcut + symbol shown in global menu
|
||||
* Calendar in launch bar:
|
||||
* The month popup would still be shown after changing the year.
|
||||
* Clicking on the edges of the calendar would dismiss the popup.
|
||||
* Tooltip showing over the calendar drop-down
|
||||
* [Focus issues within split pane](https://github.com/TriliumNext/Trilium/pull/7877) by @SiriusXT
|
||||
* Read-only bar displayed when accessing note attachments.
|
||||
* [Edited Notes tab hidden by fixed formatting toolbar](https://github.com/TriliumNext/Trilium/issues/7900)
|
||||
* Backlinks not refreshed after inserting a new link
|
||||
* [Text color unreadable in the Calendar](https://github.com/TriliumNext/Trilium/issues/7569) by @adoriandoran
|
||||
* [Math duplicates itself in code blocks](https://github.com/TriliumNext/Trilium/issues/7913)
|
||||
* [Show 'import successful' when canceling markdown import](https://github.com/TriliumNext/Trilium/pull/7899) by @SiriusXT
|
||||
* Switching an existing note (with children) to mindmap type will still display preview cards for its children.
|
||||
* Clarify converting notes to attachments in tree context menu (better message, disable option when not supported).
|
||||
* Note actions: some menu items disabled when switching note type.
|
||||
|
||||
## ✨ Improvements
|
||||
|
||||
* Additional floating buttons
|
||||
* Image notes now have a “Copy reference to image” but⁸ton
|
||||
* Render notes have a “Refresh” button.
|
||||
* Render notes are now searchable.
|
||||
* Mind map & canvas notes now support read-only mode.
|
||||
* View source prettifies all JSON content.
|
||||
* [Note info ribbon flex layout](https://github.com/TriliumNext/Trilium/pull/7678) by @contributor
|
||||
* ["Open note on server" menu item](https://github.com/TriliumNext/Trilium/pull/7477) by @contributor
|
||||
* Board view: warn if trying to add a column that already exists.
|
||||
* [Hide archived notes in classic collections](https://github.com/TriliumNext/Trilium/issues/7705)
|
||||
* Next theme: a different background color on mobile (white on light theme).
|
||||
* In-app-help: render reference links with icon.
|
||||
* [Calendar: Ability to lock the calendar to a specific date](https://github.com/TriliumNext/Trilium/issues/7691) by @dherrerace
|
||||
* [Option to close any of the panes from 2 open panes](https://github.com/TriliumNext/Trilium/issues/4511)
|
||||
* Presentation: display no children warning when empty.
|
||||
* [Tree: Keep moved notes always visible](https://github.com/TriliumNext/Trilium/pull/7776) by @SiriusXT
|
||||
* Share: display note icon in reference links.
|
||||
* Printing improvements:
|
||||
* Render inline Mermaid in Text notes.
|
||||
* Display progress of printing and exporting to PDF when working with Presentation or List collections.
|
||||
* [Strike Through Font on Completed To-do Items](https://github.com/TriliumNext/Trilium/issues/4269)
|
||||
* Promoted attributes:
|
||||
* Label auto-complete now works on mobile as well.
|
||||
* Debouncing changes to avoid updating the server too many times while typing.
|
||||
* [Configurable CORP (resource policy)](https://github.com/TriliumNext/Trilium/issues/7826) by @lzinga
|
||||
* Code notes options: display a tooltip with the syntax highlighting support for each language.
|
||||
* Quick edit:
|
||||
* Improve the layout of code notes and Mermaid notes.
|
||||
* Floating buttons are now displayed inside the popup.
|
||||
* Smoothly reacts to changes in color.
|
||||
* Button to open the note in full view (new tab).
|
||||
* New MIME types supported (including syntax highlighting):
|
||||
* [KDL](https://github.com/TriliumNext/Trilium/issues/7848)
|
||||
* [SAP ABAP](https://github.com/TriliumNext/Trilium/issues/7851)
|
||||
* [Option to expand n-level subnotes in list view](https://github.com/TriliumNext/Trilium/issues/7669)
|
||||
* macOS: Tabs no longer leave a gap when full screen is enabled.
|
||||
* [Format brush](https://github.com/TriliumNext/Trilium/issues/4848) by @SiriusXT
|
||||
* Mobile:
|
||||
* Context menus are bigger and easier to use.
|
||||
* Improved the layout of the note revisions dialog.
|
||||
* Calendar has a new design for the events that better supports colors by @adoriandoran
|
||||
* Minor improvements to the list of attachments.
|
||||
* Various UI improvements by @adoriandoran
|
||||
* Scripting improvements:
|
||||
* [Day.js `duration` plugin is now included](https://github.com/TriliumNext/Trilium/issues/4456)
|
||||
* Both back-end and front-end plugins now share the same Day.js configuration, including plugins and locale.
|
||||
* Promoted attributes not reacting to changes in inheritable attributes.
|
||||
* [Disable spell-check on code-snippets](https://github.com/TriliumNext/Trilium/issues/7894) by @lzinga
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
* [Add Traefik configuration documentation](https://github.com/TriliumNext/Trilium/pull/7769) by @andreasntr
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
* Text editor (CKEditor) now respects the user language.
|
||||
* Canvas (Excalidraw) also respects user language.
|
||||
* Mindmap notes also respect user notes.
|
||||
* Two dialog messages not translated.
|
||||
* Added English (United Kingdom). Mostly for the formatting, but translations are welcome (handling the differences between the base US translation and UK).
|
||||
* Proper plural support for Backlinks.
|
||||
* `dayjs` locale is now set on the client.
|
||||
|
||||
## 🛠️ Technical updates
|
||||
|
||||
* Type widgets (the code behind note types such as text, code) was rewritten in React. **Feel free to report any issues you may find**.
|
||||
* [Add class name to content-header](https://github.com/TriliumNext/Trilium/pull/7713) by @SiriusXT
|
||||
* Upgrade flake to Node 24 by @FliegendeWurst
|
||||
* [better-sqlite3 error on nightly build](https://github.com/TriliumNext/Trilium/issues/7839)
|
||||
* [Add comprehensive AI coding agent instructions for copilot-instructions](https://github.com/TriliumNext/Trilium/pull/7917) by @lzinga
|
||||
35
docs/User Guide/!!!meta.json
vendored
35
docs/User Guide/!!!meta.json
vendored
@ -15928,6 +15928,41 @@
|
||||
],
|
||||
"dataFileName": "Backend API.dat",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "ApVHZ8JY5ofC",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"CdNpE2pqjmI6",
|
||||
"GLks18SNjxmC",
|
||||
"ApVHZ8JY5ofC"
|
||||
],
|
||||
"title": "Day.js",
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
"value": "day.js",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-calendar",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Day.js.md",
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -131,7 +131,7 @@ To do so, create a shared text note and apply the `shareIndex` label. When viewe
|
||||
|
||||
## Attribute reference
|
||||
|
||||
<table class="ck-table-resized"><colgroup><col style="width:18.38%;"><col style="width:81.62%;"></colgroup><thead><tr><th>Attribute</th><th>Description</th></tr></thead><tbody><tr><td><code>#shareHiddenFromTree</code></td><td>this note is hidden from left navigation tree, but still accessible with its URL</td></tr><tr><td><code>#shareExternalLink</code></td><td>note will act as a link to an external website in the share tree</td></tr><tr><td><code>#shareAlias</code></td><td>define an alias using which the note will be available under <code>https://your_trilium_host/share/[your_alias]</code></td></tr><tr><td><code>#shareOmitDefaultCss</code></td><td>default share page CSS will be omitted. Use when you make extensive styling changes.</td></tr><tr><td><code>#shareRoot</code></td><td>marks note which is served on /share root.</td></tr><tr><td><code>#shareDescription</code></td><td>define text to be added to the HTML meta tag for description</td></tr><tr><td><code>#shareRaw</code></td><td>Note will be served in its raw format, without HTML wrapper. See also <a class="reference-link" href="Sharing/Serving%20directly%20the%20content%20o.md">Serving directly the content of a note</a> for an alternative method without setting an attribute.</td></tr><tr><td><code>#shareDisallowRobotIndexing</code></td><td><p>Indicates to web crawlers that the page should not be indexed of this note by:</p><ul><li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li><li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li></ul></td></tr><tr><td><code>#shareCredentials</code></td><td>require credentials to access this shared note. Value is expected to be in format <code>username:password</code>. Don't forget to make this inheritable to apply to child-notes/images.</td></tr><tr><td><code>#shareIndex</code></td><td>Note with this label will list all roots of shared notes.</td></tr><tr><td><code>#shareHtmlLocation</code></td><td>defines where custom HTML injected via <code>~shareHtml</code> relation should be placed. Applied to the HTML snippet note itself. Format: <code>location:position</code> where location is <code>head</code>, <code>body</code>, or <code>content</code> and position is <code>start</code> or <code>end</code>. Defaults to <code>content:end</code>.</td></tr></tbody></table>
|
||||
<table class="ck-table-resized"><colgroup><col style="width:18.38%;"><col style="width:81.62%;"></colgroup><thead><tr><th>Attribute</th><th>Description</th></tr></thead><tbody><tr><td><code>#shareHiddenFromTree</code></td><td>this note is hidden from left navigation tree, but still accessible with its URL</td></tr><tr><td><code>#shareTemplateNoPrevNext</code></td><td>hide bottom page navigation prev and next page.</td></tr><tr><td><code>#shareTemplateNoLeftPanel</code></td><td>hide left panel fully.</td></tr><tr><td><code>#shareExclude</code></td><td>this note will be excluded from share, not accessible via direct URL (implemented to hide scripts from share)</td></tr><tr><td><code>#shareContentAccess</code></td><td>method for attachments authorization in case when note protected with login and password (#shareCredentials). Could be cookie (the cookie will be provided when page loads) / query (every url will be updated with token) / basic (only basic header authorization)). By default for browser used cookie.</td></tr><tr><td><code>#shareAccessTokenTimeout</code></td><td>token expiration timeout in seconds, by default 10 minutes. While token not expired user could download attachment, after that he will get message `Access is expired. Return back and update the page.`</td></tr><tr><td><code>#shareExternalLink</code></td><td>note will act as a link to an external website in the share tree</td></tr><tr><td><code>#shareAlias</code></td><td>define an alias using which the note will be available under <code>https://your_trilium_host/share/[your_alias]</code></td></tr><tr><td><code>#shareOmitDefaultCss</code></td><td>default share page CSS will be omitted. Use when you make extensive styling changes.</td></tr><tr><td><code>#shareRoot</code></td><td>marks note which is served on /share root.</td></tr><tr><td><code>#shareDescription</code></td><td>define text to be added to the HTML meta tag for description</td></tr><tr><td><code>#shareRaw</code></td><td>Note will be served in its raw format, without HTML wrapper. See also <a class="reference-link" href="Sharing/Serving%20directly%20the%20content%20o.md">Serving directly the content of a note</a> for an alternative method without setting an attribute.</td></tr><tr><td><code>#shareDisallowRobotIndexing</code></td><td><p>Indicates to web crawlers that the page should not be indexed of this note by:</p><ul><li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li><li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li></ul></td></tr><tr><td><code>#shareCredentials</code></td><td>require credentials to access this shared note. Value is expected to be in format <code>username:password</code>. Don't forget to make this inheritable to apply to child-notes/images.</td></tr><tr><td><code>#shareIndex</code></td><td>Note with this label will list all roots of shared notes.</td></tr><tr><td><code>#shareHtmlLocation</code></td><td>defines where custom HTML injected via <code>~shareHtml</code> relation should be placed. Applied to the HTML snippet note itself. Format: <code>location:position</code> where location is <code>head</code>, <code>body</code>, or <code>content</code> and position is <code>start</code> or <code>end</code>. Defaults to <code>content:end</code>.</td></tr></tbody></table>
|
||||
|
||||
### Customizing logo
|
||||
|
||||
|
||||
@ -37,6 +37,19 @@ Unlike other Collection view types, the Calendar view also allows some kind of i
|
||||
* Drag and drop an event on the calendar to move it to another day.
|
||||
* The length of an event can be changed by placing the mouse to the right edge of the event and dragging the mouse around.
|
||||
|
||||
## Interaction on mobile
|
||||
|
||||
When Trilium is on mobile, the interaction with the calendar is slightly different:
|
||||
|
||||
* Clicking on an event triggers the contextual menu, including the option to open in <a class="reference-link" href="../Basic%20Concepts%20and%20Features/Navigation/Quick%20edit.md">Quick edit</a>.
|
||||
* To insert a new event, touch and hold the empty space. When successful, the empty space will become colored to indicate the selection.
|
||||
* Before releasing, drag across multiple spaces to create multi-day events.
|
||||
* When released, a prompt will appear to enter the note title.
|
||||
* To move an existing event, touch and hold the event until the empty space near it will become colored.
|
||||
* At this point the event can be dragged across other days on the calendar.
|
||||
* Or the event can be resized by tapping on the small circle to the right end of the event.
|
||||
* To exit out of editing mode, simply tap the empty space anywhere on the calendar.
|
||||
|
||||
## Configuring the calendar view
|
||||
|
||||
In the _Collections_ tab in the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Ribbon.md">Ribbon</a>, it's possible to adjust the following:
|
||||
@ -48,7 +61,7 @@ In the _Collections_ tab in the <a class="reference-link" href="../Basic%20Conc
|
||||
|
||||
The following attributes can be added to the Collection type:
|
||||
|
||||
<table><thead><tr><th>Name</th><th>Description</th></tr></thead><tbody><tr><td><code>#calendar:hideWeekends</code></td><td>When present (regardless of value), it will hide Saturday and Sundays from the calendar.</td></tr><tr><td><code>#calendar:weekNumbers</code></td><td>When present (regardless of value), it will show the number of the week on the calendar.</td></tr><tr><td><code>#calendar:initialDate</code></td><td>Change the date the calendar opens on. When not present, the calendar opens on the current date.</td></tr><tr><td><code>#calendar:view</code></td><td><p>Which view to display in the calendar:</p><ul><li><code>timeGridWeek</code> for the <em>week</em> view;</li><li><code>dayGridMonth</code> for the <em>month</em> view;</li><li><code>multiMonthYear</code> for the <em>year</em> view;</li><li><code>listMonth</code> for the <em>list</em> view.</li></ul><p>Any other value will be dismissed and the default view (month) will be used instead.</p><p>The value of this label is automatically updated when changing the view using the UI buttons.</p></td></tr><tr><td><code>~child:template</code></td><td>Defines the template for newly created notes in the calendar (via dragging or clicking).</td></tr></tbody></table>
|
||||
<table><thead><tr><th>Name</th><th>Description</th></tr></thead><tbody><tr><td><code>#calendar:hideWeekends</code></td><td>When present (regardless of value), it will hide Saturday and Sundays from the calendar.</td></tr><tr><td><code>#calendar:weekNumbers</code></td><td>When present (regardless of value), it will show the number of the week on the calendar.</td></tr><tr><td><code>#calendar:initialDate</code></td><td>Change the date the calendar opens on. When not present, the calendar opens on the current date.</td></tr><tr><td><code>#calendar:view</code></td><td><p>Which view to display in the calendar:</p><ul><li data-list-item-id="e2cd230dc41f41fe91ee74d7d1fa87372"><code>timeGridWeek</code> for the <em>week</em> view;</li><li data-list-item-id="eee1dba4c6cc51ebd53d0a0dd52044cd6"><code>dayGridMonth</code> for the <em>month</em> view;</li><li data-list-item-id="ed8721a76a1865dac882415f662ed45b9"><code>multiMonthYear</code> for the <em>year</em> view;</li><li data-list-item-id="edf09a13759102d98dac34c33eb690c05"><code>listMonth</code> for the <em>list</em> view.</li></ul><p>Any other value will be dismissed and the default view (month) will be used instead.</p><p>The value of this label is automatically updated when changing the view using the UI buttons.</p></td></tr><tr><td><code>~child:template</code></td><td>Defines the template for newly created notes in the calendar (via dragging or clicking).</td></tr></tbody></table>
|
||||
|
||||
In addition, the first day of the week can be either Sunday or Monday and can be adjusted from the application settings.
|
||||
|
||||
@ -63,10 +76,10 @@ For each note of the calendar, the following attributes can be used:
|
||||
| `#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`). |
|
||||
| `#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` | Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. (_Deprecated_) |
|
||||
| `#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. |
|
||||
|
||||
31
docs/User Guide/User Guide/Scripting/Script API/Day.js.md
vendored
Normal file
31
docs/User Guide/User Guide/Scripting/Script API/Day.js.md
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Day.js
|
||||
Day.js is a date manipulation library that's used by Trilium, but it's also shared with both front-end and back-end scripts. For more information about the library itself, consult the [official documentation](https://day.js.org/en/).
|
||||
|
||||
## How to use
|
||||
|
||||
The `dayjs` method is provided directly in the `api` global:
|
||||
|
||||
```javascript
|
||||
const date = api.dayjs();
|
||||
api.log(date.format("YYYY-MM-DD"));
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
Day.js uses a modular, plugin-based architecture. Generally these plugins must be imported, but this process doesn't work inside Trilium scripts due to the use of a bundler.
|
||||
|
||||
Since v0.100.0, the same set of plugins is available for both front-end and back-end scripts.
|
||||
|
||||
The following Day.js plugins are directly integrated into Trilium:
|
||||
|
||||
* [AdvancedFormat](https://day.js.org/docs/en/plugin/advanced-format)
|
||||
* [Duration](https://day.js.org/docs/en/plugin/duration), since v0.100.0.
|
||||
* [IsBetween](https://day.js.org/docs/en/plugin/is-between)
|
||||
* [IsoWeek](https://day.js.org/docs/en/plugin/iso-week)
|
||||
* [IsSameOrAfter](https://day.js.org/docs/en/plugin/is-same-or-after)
|
||||
* [IsSameOrBefore](https://day.js.org/docs/en/plugin/is-same-or-before)
|
||||
* [QuarterOfYear](https://day.js.org/docs/en/plugin/quarter-of-year)
|
||||
* [UTC](https://day.js.org/docs/en/plugin/utc)
|
||||
|
||||
> [!NOTE]
|
||||
> If another Day.js plugin might be needed for scripting purposes, feel free to open a feature request for it. Depending on the size of the plugin and the potential use of it inside the Trilium code base, it has a chance of being integrated.
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/source",
|
||||
"version": "0.99.5",
|
||||
"version": "0.100.0",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Alignment, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo, Bookmark, Emoji, Notification, EmojiMention, EmojiPicker } from "ckeditor5";
|
||||
import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Alignment, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo, Bookmark, Emoji, Notification, EmojiMention, EmojiPicker } from "ckeditor5";
|
||||
import { SlashCommand, Template, FormatPainter } from "ckeditor5-premium-features";
|
||||
import type { Plugin } from "ckeditor5";
|
||||
import CutToNotePlugin from "./plugins/cuttonote.js";
|
||||
@ -29,6 +29,7 @@ import CodeBlockToolbar from "./plugins/code_block_toolbar.js";
|
||||
import CodeBlockLanguageDropdown from "./plugins/code_block_language_dropdown.js";
|
||||
import MoveBlockUpDownPlugin from "./plugins/move_block_updown.js";
|
||||
import ScrollOnUndoRedoPlugin from "./plugins/scroll_on_undo_redo.js"
|
||||
import InlineCodeNoSpellcheck from "./plugins/inline_code_no_spellcheck.js";
|
||||
|
||||
/**
|
||||
* Plugins that are specific to Trilium and not part of the CKEditor 5 core, included in both text editors but not in the attribute editor.
|
||||
@ -49,7 +50,8 @@ const TRILIUM_PLUGINS: typeof Plugin[] = [
|
||||
CodeBlockLanguageDropdown,
|
||||
CodeBlockToolbar,
|
||||
MoveBlockUpDownPlugin,
|
||||
ScrollOnUndoRedoPlugin
|
||||
ScrollOnUndoRedoPlugin,
|
||||
InlineCodeNoSpellcheck,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -140,7 +142,6 @@ export const COMMON_PLUGINS: typeof Plugin[] = [
|
||||
RemoveFormat,
|
||||
SpecialCharacters,
|
||||
SpecialCharactersEssentials,
|
||||
FindAndReplace,
|
||||
PageBreak,
|
||||
GeneralHtmlSupport,
|
||||
TextPartLanguage,
|
||||
|
||||
18
packages/ckeditor5/src/plugins/inline_code_no_spellcheck.ts
Normal file
18
packages/ckeditor5/src/plugins/inline_code_no_spellcheck.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Plugin } from "ckeditor5";
|
||||
|
||||
export default class InlineCodeNoSpellcheck extends Plugin {
|
||||
|
||||
init() {
|
||||
this.editor.conversion.for('downcast').attributeToElement({
|
||||
model: 'code',
|
||||
view: (modelAttributeValue, conversionApi) => {
|
||||
const { writer } = conversionApi;
|
||||
return writer.createAttributeElement('code', {
|
||||
spellcheck: 'false'
|
||||
});
|
||||
},
|
||||
converterPriority: 'high'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/commons",
|
||||
"version": "0.99.5",
|
||||
"version": "0.100.0",
|
||||
"description": "Shared library between the clients (e.g. browser, Electron) and the server, mostly for type definitions and utility methods.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@ -10,5 +10,12 @@
|
||||
"name": "Trilium Notes Team",
|
||||
"email": "contact@eliandoran.me",
|
||||
"url": "https://triliumnotes.org"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "1.11.19",
|
||||
"dayjs-plugin-utc": "0.1.2"
|
||||
}
|
||||
}
|
||||
@ -11,3 +11,4 @@ export * from "./lib/shared_constants.js";
|
||||
export * from "./lib/ws_api.js";
|
||||
export * from "./lib/attribute_names.js";
|
||||
export * from "./lib/utils.js";
|
||||
export * from "./lib/dayjs.js";
|
||||
|
||||
59
packages/commons/src/lib/dayjs.spec.ts
Normal file
59
packages/commons/src/lib/dayjs.spec.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/advancedFormat.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/duration.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/isBetween.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/isoWeek.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/isSameOrAfter.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/isSameOrBefore.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/quarterOfYear.d.ts" />
|
||||
/// <reference types="../../../../node_modules/dayjs/plugin/utc.d.ts" />
|
||||
|
||||
import { LOCALES } from "./i18n.js";
|
||||
import { DAYJS_LOADER, dayjs } from "./dayjs.js";
|
||||
|
||||
describe("dayjs", () => {
|
||||
it("all dayjs locales are valid", async () => {
|
||||
for (const locale of LOCALES) {
|
||||
const dayjsLoader = DAYJS_LOADER[locale.id];
|
||||
expect(dayjsLoader, `Locale ${locale.id} missing.`).toBeDefined();
|
||||
|
||||
await dayjsLoader();
|
||||
}
|
||||
});
|
||||
|
||||
describe("Plugins", () => {
|
||||
it("advanced format is available", () => {
|
||||
expect(dayjs("2023-10-01").format("Q")).not.toBe("Q");
|
||||
});
|
||||
|
||||
it("duration plugin is available", () => {
|
||||
const d = dayjs.duration({ hours: 2, minutes: 30 });
|
||||
expect(d.asMinutes()).toBe(150);
|
||||
});
|
||||
|
||||
it("is-between is available", () => {
|
||||
expect(dayjs("2023-10-02").isBetween(dayjs("2023-10-01"), dayjs("2023-10-03"))).toBe(true);
|
||||
});
|
||||
|
||||
it("iso-week is available", () => {
|
||||
// ISO week number: 2023-01-01 is ISO week 52 of previous year
|
||||
expect(dayjs("2023-01-01").isoWeek()).toBe(52);
|
||||
});
|
||||
|
||||
it("is-same-or-before is available", () => {
|
||||
expect(dayjs("2023-10-01").isSameOrBefore(dayjs("2023-10-02"))).toBe(true);
|
||||
});
|
||||
|
||||
it("is-same-or-after is available", () => {
|
||||
expect(dayjs("2023-10-02").isSameOrAfter(dayjs("2023-10-01"))).toBe(true);
|
||||
});
|
||||
|
||||
it("quarter-year is available", () => {
|
||||
expect(dayjs("2023-05-15").quarter()).toBe(2);
|
||||
});
|
||||
|
||||
it("utc is available", () => {
|
||||
const utcDate = dayjs("2023-10-01T12:00:00").utc();
|
||||
expect(utcDate.utcOffset()).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
68
packages/commons/src/lib/dayjs.ts
Normal file
68
packages/commons/src/lib/dayjs.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { default as dayjs, type Dayjs } from "dayjs";
|
||||
|
||||
import "dayjs/plugin/advancedFormat";
|
||||
import "dayjs/plugin/duration";
|
||||
import "dayjs/plugin/isBetween";
|
||||
import "dayjs/plugin/isoWeek";
|
||||
import "dayjs/plugin/isSameOrAfter";
|
||||
import "dayjs/plugin/isSameOrBefore";
|
||||
import "dayjs/plugin/quarterOfYear";
|
||||
import "dayjs/plugin/utc";
|
||||
|
||||
//#region Plugins
|
||||
import advancedFormat from "dayjs/plugin/advancedFormat.js";
|
||||
import duration from "dayjs/plugin/duration.js";
|
||||
import isBetween from "dayjs/plugin/isBetween.js";
|
||||
import isoWeek from "dayjs/plugin/isoWeek.js";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
|
||||
import quarterOfYear from "dayjs/plugin/quarterOfYear.js";
|
||||
import utc from "dayjs/plugin/utc.js";
|
||||
import { LOCALE_IDS } from "./i18n.js";
|
||||
|
||||
dayjs.extend(advancedFormat);
|
||||
dayjs.extend(duration);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(isoWeek);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isSameOrBefore);
|
||||
dayjs.extend(quarterOfYear);
|
||||
dayjs.extend(utc);
|
||||
//#endregion
|
||||
|
||||
//#region Locales
|
||||
export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs/locale/en.js")>> = {
|
||||
"ar": () => import("dayjs/locale/ar.js"),
|
||||
"cn": () => import("dayjs/locale/zh-cn.js"),
|
||||
"de": () => import("dayjs/locale/de.js"),
|
||||
"en": () => import("dayjs/locale/en.js"),
|
||||
"en-GB": () => import("dayjs/locale/en-gb.js"),
|
||||
"en_rtl": () => import("dayjs/locale/en.js"),
|
||||
"es": () => import("dayjs/locale/es.js"),
|
||||
"fa": () => import("dayjs/locale/fa.js"),
|
||||
"fr": () => import("dayjs/locale/fr.js"),
|
||||
"it": () => import("dayjs/locale/it.js"),
|
||||
"he": () => import("dayjs/locale/he.js"),
|
||||
"ja": () => import("dayjs/locale/ja.js"),
|
||||
"ku": () => import("dayjs/locale/ku.js"),
|
||||
"pt_br": () => import("dayjs/locale/pt-br.js"),
|
||||
"pt": () => import("dayjs/locale/pt.js"),
|
||||
"ro": () => import("dayjs/locale/ro.js"),
|
||||
"ru": () => import("dayjs/locale/ru.js"),
|
||||
"tw": () => import("dayjs/locale/zh-tw.js"),
|
||||
"uk": () => import("dayjs/locale/uk.js"),
|
||||
}
|
||||
|
||||
async function setDayjsLocale(locale: LOCALE_IDS) {
|
||||
const dayjsLocale = DAYJS_LOADER[locale];
|
||||
if (dayjsLocale) {
|
||||
dayjs.locale(await dayjsLocale());
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export {
|
||||
dayjs,
|
||||
Dayjs,
|
||||
setDayjsLocale
|
||||
};
|
||||
@ -11,6 +11,7 @@ export interface Locale {
|
||||
electronLocale?: "en" | "de" | "es" | "fr" | "zh_CN" | "zh_TW" | "ro" | "af" | "am" | "ar" | "bg" | "bn" | "ca" | "cs" | "da" | "el" | "en_GB" | "es_419" | "et" | "fa" | "fi" | "fil" | "gu" | "he" | "hi" | "hr" | "hu" | "id" | "it" | "ja" | "kn" | "ko" | "lt" | "lv" | "ml" | "mr" | "ms" | "nb" | "nl" | "pl" | "pt_BR" | "pt_PT" | "ru" | "sk" | "sl" | "sr" | "sv" | "sw" | "ta" | "te" | "th" | "tr" | "uk" | "ur" | "vi";
|
||||
}
|
||||
|
||||
// When adding a new locale, prefer the version with hyphen instead of underscore.
|
||||
const UNSORTED_LOCALES = [
|
||||
{ id: "cn", name: "简体中文", electronLocale: "zh_CN" },
|
||||
{ id: "de", name: "Deutsch", electronLocale: "de" },
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
let openGraphImage = subRoot.note.getLabelValue("shareOpenGraphImage");
|
||||
// Relation takes priority and requires some altering
|
||||
if (subRoot.note.hasRelation("shareOpenGraphImage")) {
|
||||
openGraphImage = `api/images/${subRoot.note.getRelation("shareOpenGraphImage").value}/image.png`;
|
||||
openGraphImage = `api/images/${subRoot.note.getRelation("shareOpenGraphImage").value}/image.png${addContentAccessQuery()}`;
|
||||
}
|
||||
%>
|
||||
<title><%= pageTitle %></title>
|
||||
@ -109,6 +109,7 @@ content = content.replaceAll(headingRe, (...match) => {
|
||||
<button aria-label="Show Mobile Menu" id="show-menu-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
|
||||
</div>
|
||||
<div id="split-pane">
|
||||
<% if (!note.isLabelTruthy("shareTemplateNoLeftPanel")) { %>
|
||||
<div id="left-pane">
|
||||
<div id="navigation">
|
||||
<div id="site-header">
|
||||
@ -143,6 +144,8 @@ content = content.replaceAll(headingRe, (...match) => {
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div id="right-pane">
|
||||
<div id="main">
|
||||
<div id="content" class="type-<%= note.type %><% if (note.type === "text") { %> ck-content<% } %><% if (isEmpty) { %> no-content<% } %>">
|
||||
@ -152,7 +155,9 @@ content = content.replaceAll(headingRe, (...match) => {
|
||||
<p>This note has no content.</p>
|
||||
<% } else { %>
|
||||
<%
|
||||
content = content.replace(/<img /g, `<img alt="${t("share_theme.image_alt")}" loading="lazy" `);
|
||||
content = content
|
||||
.replace(/<img /g, `<img alt="${t("share_theme.image_alt")}" loading="lazy" `)
|
||||
.replace(/src="(api\/[^"]+)"/g, (m, url) => `src="../${url}${addContentAccessQuery(url.includes('?'))}"`);
|
||||
%>
|
||||
<%- content %>
|
||||
<% } %>
|
||||
@ -189,7 +194,7 @@ content = content.replaceAll(headingRe, (...match) => {
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (hasTree) { %>
|
||||
<% if (hasTree && !note.isLabelTruthy("shareTemplateNoPrevNext")) { %>
|
||||
<%- include("prev_next", { note: note, subRoot: subRoot }) %>
|
||||
<% } %>
|
||||
</footer>
|
||||
|
||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -226,12 +226,6 @@ importers:
|
||||
color:
|
||||
specifier: 5.0.3
|
||||
version: 5.0.3
|
||||
dayjs:
|
||||
specifier: 1.11.19
|
||||
version: 1.11.19
|
||||
dayjs-plugin-utc:
|
||||
specifier: 0.1.2
|
||||
version: 0.1.2
|
||||
debounce:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
@ -636,9 +630,6 @@ importers:
|
||||
csrf-csrf:
|
||||
specifier: 3.2.2
|
||||
version: 3.2.2
|
||||
dayjs:
|
||||
specifier: 1.11.19
|
||||
version: 1.11.19
|
||||
debounce:
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0
|
||||
@ -1328,7 +1319,14 @@ importers:
|
||||
specifier: 9.39.1
|
||||
version: 9.39.1
|
||||
|
||||
packages/commons: {}
|
||||
packages/commons:
|
||||
dependencies:
|
||||
dayjs:
|
||||
specifier: 1.11.19
|
||||
version: 1.11.19
|
||||
dayjs-plugin-utc:
|
||||
specifier: 0.1.2
|
||||
version: 0.1.2
|
||||
|
||||
packages/express-partial-content:
|
||||
dependencies:
|
||||
@ -15150,6 +15148,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.2.0
|
||||
'@ckeditor/ckeditor5-utils': 47.2.0
|
||||
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@ -15214,8 +15214,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.2.0
|
||||
'@ckeditor/ckeditor5-watchdog': 47.2.0
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
@ -15719,6 +15717,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.2.0
|
||||
'@ckeditor/ckeditor5-widget': 47.2.0
|
||||
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-mention@47.2.0(patch_hash=5981fb59ba35829e4dff1d39cf771000f8a8fdfa7a34b51d8af9549541f2d62d)':
|
||||
dependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user