Compare commits

...

43 Commits

Author SHA1 Message Date
contributor
b99cb17bc7
Merge 2069bb28c95cb4f675545a25ee448317b5d8c38e 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
contributor
2069bb28c9 docs: add autoExecuteSearch to html docs 2025-12-04 12:08:25 +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
contributor
d51a0d415b fix auto generated data-list-item-id attributes in docs 2025-11-26 10:25:51 +02:00
contributor
a79c8b4add add autoExecuteSearch user guide docs 2025-11-26 10:25:51 +02:00
contributor
b359b48ec3 add attribute autoExecuteSearch help string 2025-11-26 10:25:51 +02:00
contributor
a0e1cc4154 add autoExecuteSearch label typing 2025-11-26 10:21:10 +02:00
contributor
625c0ed7dc do not activate collections tab for empty search result 2025-11-26 00:45:47 +02:00
contributor
1ac241ad1b tidy up code 2025-11-26 00:45:47 +02:00
contributor
2c447a4293 use module level var instead of sessionStorage 2025-11-26 00:45:47 +02:00
contributor
6c1886c5ca do not activate collections tab for mobile 2025-11-26 00:45:47 +02:00
contributor
9c86145ff8 use session storage for state
f2
2025-11-26 00:45:46 +02:00
contributor
6dcc3a7e81 only exec for search note type 2025-11-26 00:45:46 +02:00
contributor
921b56a89e use search results for state 2025-11-26 00:45:46 +02:00
contributor
9d0b532aeb fixing first tab cannot be open 2025-11-26 00:45:46 +02:00
contributor
cc468d964f add ability to auto execute search note 2025-11-26 00:45:46 +02:00
54 changed files with 1065 additions and 556 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

@ -386,6 +386,7 @@
"workspace_template": "This note will appear in the selection of available template when creating new note, but only when hoisted into a workspace containing this template",
"search_home": "new search notes will be created as children of this note",
"workspace_search_home": "new search notes will be created as children of this note when hoisted to some ancestor of this workspace note",
"auto_execute_search": "Automatically executes the search defined in a saved search note and switches to the Collection Properties tab if any notes match the query",
"inbox": "default inbox location for new notes - when you create a note using \"new note\" button in the sidebar, notes will be created as child notes in the note marked as with <code>#inbox</code> label.",
"workspace_inbox": "default inbox location for new notes when hoisted to some ancestor of this workspace note",
"sql_console_home": "default location of SQL console notes",

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

@ -236,6 +236,7 @@ const ATTR_HELP: Record<string, Record<string, string>> = {
workspaceTemplate: t("attribute_detail.workspace_template"),
searchHome: t("attribute_detail.search_home"),
workspaceSearchHome: t("attribute_detail.workspace_search_home"),
autoExecuteSearch: t("attribute_detail.auto_execute_search"),
inbox: t("attribute_detail.inbox"),
workspaceInbox: t("attribute_detail.workspace_inbox"),
sqlConsoleHome: t("attribute_detail.sql_console_home"),

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

@ -22,7 +22,7 @@ import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
import { getErrorMessage } from "../../services/utils";
import "./SearchDefinitionTab.css";
export default function SearchDefinitionTab({ note, ntxId, hidden }: TabContext) {
export default function SearchDefinitionTab({ note, ntxId, hidden, noteContext }: TabContext) {
const parentComponent = useContext(ParentComponent);
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
const [ error, setError ] = useState<{ message: string }>();
@ -73,6 +73,27 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: TabContext)
}
});
useEffect(() => {
async function autoExecute() {
if (!note || note.type !== "search" || !note.hasLabel("autoExecuteSearch")) {
executionState.save("");
return;
}
const lastExecutedNoteId = executionState.load();
if (lastExecutedNoteId !== note.noteId) {
executionState.save(note.noteId);
await refreshResults();
if (noteContext?.viewScope?.viewMode === "default" && note.children.length > 0) {
parentComponent?.triggerCommand("toggleRibbonTabBookProperties", {});
}
}
}
autoExecute();
}, [note]);
return (
<div className="search-definition-widget">
<div className="search-settings">
@ -160,6 +181,14 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: TabContext)
)
}
const executionState = function() {
let lastAutoExecutedSearchNoteId = "";
return {
load: () => lastAutoExecutedSearchNoteId,
save: (noteId: string) => lastAutoExecutedSearchNoteId = noteId,
};
}();
function BulkActionsList({ note }: { note: FNote }) {
const [ bulkActions, setBulkActions ] = useState<RenameNoteBulkAction[]>();

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

@ -222,6 +222,14 @@
<a
class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a>).</td>
</tr>
<tr>
<td><code>autoExecuteSearch</code>
</td>
<td>Automatically executes the search defined in a saved search note and switches
to the <em>Collection Properties</em> tab if any notes match the query. A
search note with this attribute functions as a dynamic collection of notes
(see&nbsp;<a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>)</td>
</tr>
<tr>
<td><code>workspace</code> and related attributes</td>
<td>See&nbsp;<a class="reference-link" href="#root/_help_9sRHySam5fXb">Workspaces</a>.</td>

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>
<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
<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,24 +6,27 @@
a start date and optionally an end date, as an event.</p>
<p>The Calendar view has multiple display modes:</p>
<ul>
<li>Week view, where all the 7 days of the week (or 5 if the weekends are
<li data-list-item-id="e304b5ae296d7f47b813dcd8cf2dbba42">Week view, where all the 7 days of the week (or 5 if the weekends are
hidden) are displayed in columns. This mode allows entering and displaying
time-specific events, not just all-day events.</li>
<li>Month view, where the entire month is displayed and all-day events can
<li data-list-item-id="ea8d70da10fa78ede209782b485e2de49">Month view, where the entire month is displayed and all-day events can
be inserted. Both time-specific events and all-day events are listed.</li>
<li>Year view, which displays the entire year for quick reference.</li>
<li>List view, which displays all the events of a given month in sequence.</li>
<li
data-list-item-id="e057acab031bcf780ce3055e534ab2d61">Year view, which displays the entire year for quick reference.</li>
<li
data-list-item-id="e5528ab7e56ada969592f5d35896a4808">List view, which displays all the events of a given month in sequence.</li>
</ul>
<p>Unlike other Collection view types, the Calendar view also allows some
kind of interaction, such as moving events around as well as creating new
ones.</p>
<h2>Creating a calendar</h2>
<figure class="table">
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
@ -46,52 +49,82 @@
</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>
<figure
class="table">
<table>
<thead>
<tr>
@ -124,10 +157,10 @@
<td>
<p>Which view to display in the calendar:</p>
<ul>
<li><code>timeGridWeek</code> for the <em>week</em> view;</li>
<li><code>dayGridMonth</code> for the <em>month</em> view;</li>
<li><code>multiMonthYear</code> for the <em>year</em> view;</li>
<li><code>listMonth</code> for the <em>list</em> view.</li>
<li data-list-item-id="e2cd230dc41f41fe91ee74d7d1fa87372"><code>timeGridWeek</code> for the <em>week</em> view;</li>
<li data-list-item-id="eee1dba4c6cc51ebd53d0a0dd52044cd6"><code>dayGridMonth</code> for the <em>month</em> view;</li>
<li data-list-item-id="ed8721a76a1865dac882415f662ed45b9"><code>multiMonthYear</code> for the <em>year</em> view;</li>
<li data-list-item-id="edf09a13759102d98dac34c33eb690c05"><code>listMonth</code> for the <em>list</em> view.</li>
</ul>
<p>Any other value will be dismissed and the default view (month) will be
used instead.</p>
@ -143,10 +176,13 @@
</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>
@ -192,8 +228,12 @@
<tr>
<td><code>#calendar:color</code>
</td>
<td>Similar to <code>#color</code>, but applies the color only for the event
in the calendar and not for other places such as the note tree. (<em>Deprecated</em>)</td>
<td>
<p><strong>❌️ Removed since v0.100.0. Use </strong><code><strong>#color</strong></code><strong> instead.</strong>
</p>
<p>Similar to <code>#color</code>, but applies the color only for the event
in the calendar and not for other places such as the note tree.</p>
</td>
</tr>
<tr>
<td><code>#iconClass</code>
@ -212,15 +252,15 @@
<td><code>#calendar:displayedAttributes</code>
</td>
<td>Allows displaying the value of one or more attributes in the calendar
like this:&nbsp;&nbsp;&nbsp;&nbsp;
like this:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>
<img src="9_Calendar_image.png">&nbsp;&nbsp;
<img src="9_Calendar_image.png">&nbsp;&nbsp;&nbsp;
<br>
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>&nbsp;&nbsp;
<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;
title of the target note:&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br><code>~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
</td>
@ -252,7 +292,7 @@
</tr>
</tbody>
</table>
</figure>
<h2>How the calendar works</h2>
<p>
<img src="11_Calendar_image.png">
@ -279,19 +319,20 @@
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
<li data-list-item-id="ef8dd9e289abf6cb973bd379d219170ae">The calendar events are now rendered based on their <code>dateNote</code> attribute
rather than <code>startDate</code>.</li>
<li>Interactive editing such as dragging over an empty era or resizing an
<li data-list-item-id="e35671ad2eee19bba69bb31f3f33cce37">Interactive editing such as dragging over an empty era or resizing an
event is no longer possible.</li>
<li>Clicking on the empty space on a date will automatically open that day's
<li data-list-item-id="e8930686b36fb7fa36d123f9f202cbc75">Clicking on the empty space on a date will automatically open that day's
note or create it if it does not exist.</li>
<li>Direct children of a day note will be displayed on the calendar despite
<li data-list-item-id="edc838e74ed3479fe2f0fbfaef462dbbb">Direct children of a day note will be displayed on the calendar despite
not having a <code>dateNote</code> attribute. Children of the child notes
will not be displayed.</li>
</ul>
<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
@ -301,6 +342,8 @@ height="724">
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>
@ -322,13 +365,14 @@ height="724">
</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>
@ -347,11 +391,13 @@ height="724">
</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>
@ -372,3 +418,4 @@ height="724">
</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": []
}
]
},

File diff suppressed because one or more lines are too long

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

@ -23,6 +23,7 @@ type Labels = {
ancestorDepth: string;
orderBy: string;
orderDirection: string;
autoExecuteSearch: boolean;
// Collection-specific
viewType: string;

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

24
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:
@ -15150,6 +15148,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies:
@ -15214,8 +15214,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-watchdog': 47.2.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@ -15719,6 +15717,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-widget': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-mention@47.2.0(patch_hash=5981fb59ba35829e4dff1d39cf771000f8a8fdfa7a34b51d8af9549541f2d62d)':
dependencies: