Compare commits

..

29 Commits

Author SHA1 Message Date
renovate[bot]
2b4256f4a2
Merge 69d5ab8b91fa3441d9a1b8f04a5d92491e188579 into b8585594cd138783588a7ac4d6a3260de779427d 2025-12-04 11:03:26 +00:00
Elian Doran
b8585594cd
fix(text): duplicate search dialogs (closes #5735)
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Documentation / Build and Deploy Documentation (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
2025-12-04 13:03:08 +02:00
Elian Doran
d173cc982c
chore(release): prepare for v0.100.0 2025-12-04 12:21:40 +02:00
Elian Doran
471c57b3ed
docs(release): changelog for v0.100.0 2025-12-04 10:43:34 +02:00
Elian Doran
093d7d783b
fix(promoted_attributes): value sometimes empty when reopening note 2025-12-04 10:38:07 +02:00
Elian Doran
7cc20600e7
Disable spell-check on code-snippets (#7929) 2025-12-04 07:47:02 +00:00
Elian Doran
559c654fbb
fix(promoted_attributes): not reacting to inheritable changes 2025-12-04 09:44:54 +02:00
Elian Doran
01a03e3e97
docs(user): mark #calendar:color as completely removed 2025-12-04 09:27:17 +02:00
Elian Doran
dd3233a556
docs(user): document calendar interaction on mobile 2025-12-04 09:22:25 +02:00
Elian Doran
c4a426566f
feat(collections/calendar): change click behaviour on mobile 2025-12-04 09:09:23 +02:00
Elian Doran
c081a596df
Unify Dayjs between client and server (#7930) 2025-12-04 07:08:11 +00:00
Elian Doran
6b07908cf7
chore(server): fix some more dependencies to JSON 2025-12-04 08:45:28 +02:00
Elian Doran
2985bd0a1c
chore(dayjs): fix typecheck
Some checks are pending
Checks / main (push) Waiting to run
2025-12-03 23:29:38 +02:00
Lucas
975e8487fc
Merge branch 'TriliumNext:main' into main 2025-12-03 12:59:47 -08:00
Elian Doran
54408d3ec8
chore(dayjs): address requested changes 2025-12-03 22:24:30 +02:00
Elian Doran
e15bc5a232
Merge branch 'main' into main 2025-12-03 20:05:00 +00:00
Elian Doran
8fdda59440
Merge branch 'main' into feature/unify_dayjs 2025-12-03 20:04:48 +00:00
Elian Doran
6f85d3370c
docs(technical): dayjs intro & supported plugins 2025-12-03 22:01:06 +02:00
Elian Doran
8c324cd185
test(client): running script bundle with dayjs 2025-12-03 21:44:34 +02:00
Elian Doran
f7f7fda040
feat(dayjs): enable duration plugin (closes #4456) 2025-12-03 21:26:10 +02:00
Elian Doran
74c11f4d4e
chore(test): warning of unsupported requests 2025-12-03 21:15:16 +02:00
Elian Doran
0525cfab79
feat(client): set dayjs locale 2025-12-03 21:10:54 +02:00
Elian Doran
2d73627908
test(dayjs): add a test for all plugins 2025-12-03 20:58:52 +02:00
Elian Doran
94d015789d
test(dayjs): relocate dayjs tests into commons 2025-12-03 20:54:35 +02:00
Elian Doran
af2f6246e8
chore(commons): tests not run 2025-12-03 20:49:57 +02:00
Elian Doran
ebbdf0294a
refactor(dayjs): relocate locale loading in commons 2025-12-03 20:49:15 +02:00
Elian Doran
5df539f0a4
refactor(dayjs): relocate all plugins and imports to commons 2025-12-03 20:44:48 +02:00
lzinga
4f6dfeb773 fixed whitespace and redundant lines. 2025-12-03 10:12:05 -08:00
lzinga
52bb83e878 feat(plugins): add InlineCodeNoSpellcheck plugin to disable spellcheck for inline code 2025-12-03 10:02:35 -08:00
48 changed files with 1021 additions and 554 deletions

View File

@ -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",

View File

@ -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;

View 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;
}

View File

@ -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 {

View File

@ -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";

View File

@ -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";

View File

@ -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();
}

View File

@ -1,4 +1,4 @@
import dayjs from "dayjs";
import { dayjs } from "@triliumnext/commons";
import type { ViewScope } from "./link.js";
import FNote from "../entities/fnote";

View File

@ -46,6 +46,8 @@ function mockServer() {
attributes: []
}
}
console.warn(`Unsupported GET to mocked server: ${url}`);
},
async post(url: string, data: object) {

View File

@ -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
)
);
}

View File

@ -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"),

View File

@ -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 };
}

View File

@ -85,8 +85,7 @@ export function buildClassicToolbar(multilineToolbar: boolean) {
"|",
"insertTemplate",
"markdownImport",
"cuttonote",
"findAndReplace"
"cuttonote"
],
shouldNotGroupWhenFull: multilineToolbar
}

View File

@ -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";

View File

@ -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",

View File

@ -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",

View File

@ -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");
});

File diff suppressed because one or more lines are too long

View File

@ -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 &amp; dropping is
<li>Only horizontal splits are possible, vertical or drag &amp; 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
allowing for more space even when a split is open. When the keyboard is
closed, the splits become equal in size again.</li>
<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>

View File

@ -6,369 +6,416 @@
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>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<img src="2_Calendar_image.png">
</td>
<td>The Calendar View works only for Collection note types. To create a new
note, right click on the note tree on the left and select Insert note after,
or Insert child note and then select <em>Collection</em>.</td>
</tr>
<tr>
<td>2</td>
<td>
<img src="3_Calendar_image.png">
</td>
<td>Once created, the “View type” of the Collection needs changed to “Calendar”,
by selecting the “Collection Properties” tab in the ribbon.</td>
</tr>
</tbody>
</table>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<img src="2_Calendar_image.png">
</td>
<td>The Calendar View works only for Collection note types. To create a new
note, right click on the note tree on the left and select Insert note after,
or Insert child note and then select <em>Collection</em>.</td>
</tr>
<tr>
<td>2</td>
<td>
<img src="3_Calendar_image.png">
</td>
<td>Once created, the “View type” of the Collection needs changed to “Calendar”,
by selecting the “Collection Properties” tab in the ribbon.</td>
</tr>
</tbody>
</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&nbsp;<a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a>&nbsp;to
<li data-list-item-id="e0349fc14f198e77629a20fce7b910cbd">Left clicking the event will open a&nbsp;<a class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a>&nbsp;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&nbsp;<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&nbsp;<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>
<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>
<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>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#startDate</code>
</td>
<td>The date the event starts, which will display it in the calendar. The
format is <code>YYYY-MM-DD</code> (year, month and day separated by a minus
sign).</td>
</tr>
<tr>
<td><code>#endDate</code>
</td>
<td>Similar to <code>startDate</code>, mentions the end date if the event spans
across multiple days. The date is inclusive, so the end day is also considered.
The attribute can be missing for single-day events.</td>
</tr>
<tr>
<td><code>#startTime</code>
</td>
<td>The time the event starts at. If this value is missing, then the event
is considered a full-day event. The format is <code>HH:MM</code> (hours in
24-hour format and minutes).</td>
</tr>
<tr>
<td><code>#endTime</code>
</td>
<td>Similar to <code>startTime</code>, it mentions the time at which the event
ends (in relation with <code>endDate</code> if present, or <code>startDate</code>).</td>
</tr>
<tr>
<td><code>#color</code>
</td>
<td>Displays the event with a specified color (named such as <code>red</code>, <code>gray</code> or
hex such as <code>#FF0000</code>). This will also change the color of the
note in other places such as the note tree.</td>
</tr>
<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>
</tr>
<tr>
<td><code>#iconClass</code>
</td>
<td>If present, the icon of the note will be displayed to the left of the
event title.</td>
</tr>
<tr>
<td><code>#calendar:title</code>
</td>
<td>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 <code>#</code> or <code>~</code> symbol).
See <em>Use-cases</em> for more information.</td>
</tr>
<tr>
<td><code>#calendar:displayedAttributes</code>
</td>
<td>Allows displaying the value of one or more attributes in the calendar
like this:&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>
<img src="9_Calendar_image.png">&nbsp;&nbsp;
<br>
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>&nbsp;&nbsp;
<br>
<br>It can also be used with relations, case in which it will display the
title of the target note:&nbsp;&nbsp;&nbsp;
<br>
<br><code>~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
</td>
</tr>
<tr>
<td><code>#calendar:startDate</code>
</td>
<td>Allows using a different label to represent the start date, other than <code>startDate</code> (e.g. <code>expiryDate</code>).
The label name <strong>must not be</strong> prefixed with <code>#</code>.
If the label is not defined for a note, the default will be used instead.</td>
</tr>
<tr>
<td><code>#calendar:endDate</code>
</td>
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
which is being used to read the end date.</td>
</tr>
<tr>
<td><code>#calendar:startTime</code>
</td>
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
which is being used to read the start time.</td>
</tr>
<tr>
<td><code>#calendar:endTime</code>
</td>
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
which is being used to read the end time.</td>
</tr>
</tbody>
</table>
<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
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,
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"
<figure
class="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>
</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>
<figure
class="table">
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#startDate</code>
</td>
<td>The date the event starts, which will display it in the calendar. The
format is <code>YYYY-MM-DD</code> (year, month and day separated by a minus
sign).</td>
</tr>
<tr>
<td><code>#endDate</code>
</td>
<td>Similar to <code>startDate</code>, mentions the end date if the event spans
across multiple days. The date is inclusive, so the end day is also considered.
The attribute can be missing for single-day events.</td>
</tr>
<tr>
<td><code>#startTime</code>
</td>
<td>The time the event starts at. If this value is missing, then the event
is considered a full-day event. The format is <code>HH:MM</code> (hours in
24-hour format and minutes).</td>
</tr>
<tr>
<td><code>#endTime</code>
</td>
<td>Similar to <code>startTime</code>, it mentions the time at which the event
ends (in relation with <code>endDate</code> if present, or <code>startDate</code>).</td>
</tr>
<tr>
<td><code>#color</code>
</td>
<td>Displays the event with a specified color (named such as <code>red</code>, <code>gray</code> or
hex such as <code>#FF0000</code>). This will also change the color of the
note in other places such as the note tree.</td>
</tr>
<tr>
<td><code>#calendar:color</code>
</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>
</td>
<td>If present, the icon of the note will be displayed to the left of the
event title.</td>
</tr>
<tr>
<td><code>#calendar:title</code>
</td>
<td>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 <code>#</code> or <code>~</code> symbol).
See <em>Use-cases</em> for more information.</td>
</tr>
<tr>
<td><code>#calendar:displayedAttributes</code>
</td>
<td>Allows displaying the value of one or more attributes in the calendar
like this:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>
<img src="9_Calendar_image.png">&nbsp;&nbsp;&nbsp;
<br>
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>&nbsp;&nbsp;&nbsp;
<br>
<br>It can also be used with relations, case in which it will display the
title of the target note:&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br><code>~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
</td>
</tr>
<tr>
<td><code>#calendar:startDate</code>
</td>
<td>Allows using a different label to represent the start date, other than <code>startDate</code> (e.g. <code>expiryDate</code>).
The label name <strong>must not be</strong> prefixed with <code>#</code>.
If the label is not defined for a note, the default will be used instead.</td>
</tr>
<tr>
<td><code>#calendar:endDate</code>
</td>
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
which is being used to read the end date.</td>
</tr>
<tr>
<td><code>#calendar:startTime</code>
</td>
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
which is being used to read the start time.</td>
</tr>
<tr>
<td><code>#calendar:endTime</code>
</td>
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
which is being used to read the end time.</td>
</tr>
</tbody>
</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
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,
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>
<img src="10_Calendar_image.png">
</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
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>)
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
rather than <code>startDate</code>.</li>
<li>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
note or create it if it does not exist.</li>
<li>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.
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
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>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">#startDate=2025-02-11 #endDate=2025-02-13 #name="My vacation" #calendar:title="name"</code></pre>
</td>
<td>
<p>&nbsp;</p>
<figure class="image image-style-align-center">
<img style="aspect-ratio:445/124;" src="5_Calendar_image.png"
width="445" height="124">
</figure>
</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
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
as multiple events coming from the same note.</p>
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">#startDate=2025-02-14 #endDate=2025-02-15 ~for=@John Smith ~for=@Jane Doe #calendar:title="for"</code></pre>
</td>
<td>
<img src="6_Calendar_image.png" width="294"
height="151">
</td>
</tr>
</tbody>
</table>
<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 &nbsp;of attributes could cause the application
to loop infinitely).</p>
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">#calendar:title="shortName" #shortName="John S."</code></pre>
</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:296/150;" src="1_Calendar_image.png"
width="296" height="150">
</figure>
</td>
</tr>
</tbody>
</table>
<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
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
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>)
attribute, the calendar will know that it's in a calendar and apply the
following:</p>
<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 data-list-item-id="e35671ad2eee19bba69bb31f3f33cce37">Interactive editing such as dragging over an empty era or resizing an
event is no longer possible.</li>
<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 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>
<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
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>
<figure
class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">#startDate=2025-02-11 #endDate=2025-02-13 #name="My vacation" #calendar:title="name"</code></pre>
</td>
<td>
<p>&nbsp;</p>
<figure class="image image-style-align-center">
<img style="aspect-ratio:445/124;" src="5_Calendar_image.png"
width="445" height="124">
</figure>
</td>
</tr>
</tbody>
</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
as multiple events coming from the same note.</p>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">#startDate=2025-02-14 #endDate=2025-02-15 ~for=@John Smith ~for=@Jane Doe #calendar:title="for"</code></pre>
</td>
<td>
<img src="6_Calendar_image.png" width="294"
height="151">
</td>
</tr>
</tbody>
</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 &nbsp;of attributes could cause the application
to loop infinitely).</p>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre><code class="language-text-x-trilium-auto">#calendar:title="shortName" #shortName="John S."</code></pre>
</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:296/150;" src="1_Calendar_image.png"
width="296" height="150">
</figure>
</td>
</tr>
</tbody>
</table>
</figure>

View 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>

View File

@ -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";

View File

@ -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";

View File

@ -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

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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();
}
});
});

View File

@ -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) {

View File

@ -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";

View File

@ -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";

View File

@ -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");
});
});
});
});

View File

@ -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";

View File

@ -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.

View File

@ -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",

View 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

View File

@ -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": []
}
]
},

View File

@ -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>![](9_Calendar_image.png)    <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>![](9_Calendar_image.png)     <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. |

View 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.

View File

@ -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"

View File

@ -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,

View 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'
});
}
}

View File

@ -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"
}
}

View File

@ -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";

View 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);
});
});
});

View 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
};

View File

@ -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" },

22
pnpm-lock.yaml generated
View File

@ -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:
@ -15147,6 +15145,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:
@ -15211,8 +15211,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: