mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 10:02:59 +08:00
chore(prettier): fix code style
This commit is contained in:
parent
67509bc92f
commit
2beaaa95bf
@ -1,6 +1,6 @@
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
import swaggerJsdoc from 'swagger-jsdoc';
|
||||
import swaggerJsdoc from "swagger-jsdoc";
|
||||
import fs from "fs";
|
||||
|
||||
/*
|
||||
@ -12,27 +12,29 @@ import fs from "fs";
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.1.1',
|
||||
openapi: "3.1.1",
|
||||
info: {
|
||||
title: 'Trilium Notes - Sync server API',
|
||||
version: '0.96.6',
|
||||
description: "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).",
|
||||
title: "Trilium Notes - Sync server API",
|
||||
version: "0.96.6",
|
||||
description:
|
||||
"This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).",
|
||||
contact: {
|
||||
name: "TriliumNext issue tracker",
|
||||
url: "https://github.com/TriliumNext/Notes/issues",
|
||||
url: "https://github.com/TriliumNext/Notes/issues"
|
||||
},
|
||||
license: {
|
||||
name: "GNU Free Documentation License 1.3 (or later)",
|
||||
url: "https://www.gnu.org/licenses/fdl-1.3",
|
||||
},
|
||||
},
|
||||
url: "https://www.gnu.org/licenses/fdl-1.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
apis: [
|
||||
// Put individual files here to have them ordered first.
|
||||
'./src/routes/api/setup.ts',
|
||||
"./src/routes/api/setup.ts",
|
||||
// all other files
|
||||
'./src/routes/api/*.ts', './bin/generate-openapi.js'
|
||||
],
|
||||
"./src/routes/api/*.ts",
|
||||
"./bin/generate-openapi.js"
|
||||
]
|
||||
};
|
||||
|
||||
const openapiSpecification = swaggerJsdoc(options);
|
||||
|
@ -11,16 +11,14 @@ test("Displays translation on desktop", async ({ page, context }) => {
|
||||
const app = new App(page, context);
|
||||
await app.goto();
|
||||
|
||||
await expect(page.locator("#left-pane .quick-search input"))
|
||||
.toHaveAttribute("placeholder", "Quick search");
|
||||
await expect(page.locator("#left-pane .quick-search input")).toHaveAttribute("placeholder", "Quick search");
|
||||
});
|
||||
|
||||
test("Displays translation on mobile", async ({ page, context }) => {
|
||||
const app = new App(page, context);
|
||||
await app.goto({ isMobile: true });
|
||||
|
||||
await expect(page.locator("#mobile-sidebar-wrapper .quick-search input"))
|
||||
.toHaveAttribute("placeholder", "Quick search");
|
||||
await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")).toHaveAttribute("placeholder", "Quick search");
|
||||
});
|
||||
|
||||
test("Displays translations in Settings", async ({ page, context }) => {
|
||||
|
@ -39,7 +39,9 @@ test("Displays lint errors for backend script", async ({ page, context }) => {
|
||||
});
|
||||
|
||||
async function expectTooltip(page: Page, tooltip: string) {
|
||||
await expect(page.locator(".CodeMirror-lint-tooltip:visible", {
|
||||
"hasText": tooltip
|
||||
})).toBeVisible();
|
||||
await expect(
|
||||
page.locator(".CodeMirror-lint-tooltip:visible", {
|
||||
hasText: tooltip
|
||||
})
|
||||
).toBeVisible();
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import App from "../support/app";
|
||||
|
||||
test("renders ELK flowchart", async ({ page, context }) => {
|
||||
await testAriaSnapshot({
|
||||
page, context,
|
||||
page,
|
||||
context,
|
||||
noteTitle: "Flowchart ELK on",
|
||||
snapshot: `
|
||||
- document:
|
||||
@ -22,12 +23,13 @@ test("renders ELK flowchart", async ({ page, context }) => {
|
||||
- paragraph: Guarantee
|
||||
- text: Interfaces for B
|
||||
`
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
test("renders standard flowchart", async ({ page, context }) => {
|
||||
await testAriaSnapshot({
|
||||
page, context,
|
||||
page,
|
||||
context,
|
||||
noteTitle: "Flowchart ELK off",
|
||||
snapshot: `
|
||||
- document:
|
||||
@ -46,7 +48,7 @@ test("renders standard flowchart", async ({ page, context }) => {
|
||||
- paragraph: C
|
||||
- text: Interfaces for B
|
||||
`
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
interface AriaTestOpts {
|
||||
|
@ -54,7 +54,7 @@ test("Displays math popup", async ({ page, context }) => {
|
||||
const app = new App(page, context);
|
||||
await app.goto();
|
||||
await app.goToNoteInNewTab("Empty text");
|
||||
const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor")
|
||||
const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor");
|
||||
await noteContent.fill("Hello world");
|
||||
await noteContent.press("ControlOrMeta+M");
|
||||
|
||||
|
@ -25,7 +25,7 @@ export default class App {
|
||||
this.tabBar = page.locator(".tab-row-widget-container");
|
||||
this.noteTree = page.locator(".tree-wrapper");
|
||||
this.launcherBar = page.locator("#launcher-container");
|
||||
this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)")
|
||||
this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)");
|
||||
this.sidebar = page.locator("#right-pane");
|
||||
}
|
||||
|
||||
@ -46,8 +46,7 @@ export default class App {
|
||||
|
||||
// Wait for the page to load.
|
||||
if (url === "/") {
|
||||
await expect(this.page.locator(".tree"))
|
||||
.toContainText("Trilium Integration Test");
|
||||
await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test");
|
||||
await this.closeAllTabs();
|
||||
}
|
||||
}
|
||||
@ -109,11 +108,12 @@ export default class App {
|
||||
});
|
||||
|
||||
expect(csrfToken).toBeTruthy();
|
||||
await expect(await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, {
|
||||
await expect(
|
||||
await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, {
|
||||
headers: {
|
||||
"x-csrf-token": csrfToken
|
||||
}
|
||||
})).toBeOK();
|
||||
})
|
||||
).toBeOK();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const SERVER_URL = 'http://127.0.0.1:8082';
|
||||
const SERVER_URL = "http://127.0.0.1:8082";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
@ -14,7 +14,7 @@ const SERVER_URL = 'http://127.0.0.1:8082';
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
testDir: "./e2e",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
@ -24,22 +24,22 @@ export default defineConfig({
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: SERVER_URL,
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
trace: "on-first-retry"
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] }
|
||||
}
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
@ -73,9 +73,11 @@ export default defineConfig({
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: !process.env.TRILIUM_DOCKER ? {
|
||||
command: 'npm run test:integration-mem-db-dev',
|
||||
webServer: !process.env.TRILIUM_DOCKER
|
||||
? {
|
||||
command: "npm run test:integration-mem-db-dev",
|
||||
url: SERVER_URL,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
} : undefined,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
@ -62,7 +62,7 @@ export interface NoteCommandData extends CommandData {
|
||||
}
|
||||
|
||||
export interface ExecuteCommandData<T> extends CommandData {
|
||||
resolve: (data: T) => void
|
||||
resolve: (data: T) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,7 +70,7 @@ export interface ExecuteCommandData<T> extends CommandData {
|
||||
*/
|
||||
export type CommandMappings = {
|
||||
"api-log-messages": CommandData;
|
||||
focusTree: CommandData,
|
||||
focusTree: CommandData;
|
||||
focusOnTitle: CommandData;
|
||||
focusOnDetail: CommandData;
|
||||
focusOnSearchDefinition: Required<CommandData>;
|
||||
@ -108,7 +108,7 @@ export type CommandMappings = {
|
||||
showInfoDialog: ConfirmWithMessageOptions;
|
||||
showConfirmDialog: ConfirmWithMessageOptions;
|
||||
showRecentChanges: CommandData & { ancestorNoteId: string };
|
||||
showImportDialog: CommandData & { noteId: string; };
|
||||
showImportDialog: CommandData & { noteId: string };
|
||||
openNewNoteSplit: NoteCommandData;
|
||||
openInWindow: NoteCommandData;
|
||||
openNoteInNewTab: CommandData;
|
||||
@ -131,8 +131,10 @@ export type CommandMappings = {
|
||||
editNoteTitle: ContextMenuCommandData;
|
||||
protectSubtree: ContextMenuCommandData;
|
||||
unprotectSubtree: ContextMenuCommandData;
|
||||
openBulkActionsDialog: ContextMenuCommandData | {
|
||||
selectedOrActiveNoteIds?: string[]
|
||||
openBulkActionsDialog:
|
||||
| ContextMenuCommandData
|
||||
| {
|
||||
selectedOrActiveNoteIds?: string[];
|
||||
};
|
||||
editBranchPrefix: ContextMenuCommandData;
|
||||
convertNoteToAttachment: ContextMenuCommandData;
|
||||
@ -221,11 +223,11 @@ export type CommandMappings = {
|
||||
moveTabToNewWindow: CommandData;
|
||||
copyTabToNewWindow: CommandData;
|
||||
closeActiveTab: CommandData & {
|
||||
$el: JQuery<HTMLElement>
|
||||
},
|
||||
$el: JQuery<HTMLElement>;
|
||||
};
|
||||
setZoomFactorAndSave: {
|
||||
zoomFactor: string;
|
||||
}
|
||||
};
|
||||
|
||||
reEvaluateRightPaneVisibility: CommandData;
|
||||
runActiveNote: CommandData;
|
||||
@ -234,18 +236,18 @@ export type CommandMappings = {
|
||||
};
|
||||
scrollToEnd: CommandData;
|
||||
closeThisNoteSplit: CommandData;
|
||||
moveThisNoteSplit: CommandData & { isMovingLeft: boolean; };
|
||||
moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
|
||||
|
||||
// Geomap
|
||||
deleteFromMap: { noteId: string },
|
||||
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
|
||||
deleteFromMap: { noteId: string };
|
||||
openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent };
|
||||
|
||||
toggleZenMode: CommandData;
|
||||
|
||||
updateAttributeList: CommandData & { attributes: Attribute[] };
|
||||
saveAttributes: CommandData;
|
||||
reloadAttributes: CommandData;
|
||||
refreshNoteList: CommandData & { noteId: string; };
|
||||
refreshNoteList: CommandData & { noteId: string };
|
||||
|
||||
refreshResults: {};
|
||||
refreshSearchDefinition: {};
|
||||
@ -348,7 +350,7 @@ type EventMappings = {
|
||||
ntxId: string | null | undefined; // TODO: deduplicate ntxId
|
||||
};
|
||||
tabReorder: {
|
||||
ntxIdsInOrder: string[]
|
||||
ntxIdsInOrder: string[];
|
||||
};
|
||||
refreshNoteList: {
|
||||
noteId: string;
|
||||
|
@ -37,7 +37,25 @@ const NOTE_TYPE_ICONS = {
|
||||
* end user. Those types should be used only for checking against, they are
|
||||
* not for direct use.
|
||||
*/
|
||||
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "geoMap" | "taskList";
|
||||
export type NoteType =
|
||||
| "file"
|
||||
| "image"
|
||||
| "search"
|
||||
| "noteMap"
|
||||
| "launcher"
|
||||
| "doc"
|
||||
| "contentWidget"
|
||||
| "text"
|
||||
| "relationMap"
|
||||
| "render"
|
||||
| "canvas"
|
||||
| "mermaid"
|
||||
| "book"
|
||||
| "webView"
|
||||
| "code"
|
||||
| "mindMap"
|
||||
| "geoMap"
|
||||
| "taskList";
|
||||
|
||||
export interface NotePathRecord {
|
||||
isArchived: boolean;
|
||||
|
@ -264,7 +264,7 @@ export default class DesktopLayout {
|
||||
.child(new InfoDialog())
|
||||
.child(new ConfirmDialog())
|
||||
.child(new PromptDialog())
|
||||
.child(new CloseZenButton())
|
||||
.child(new CloseZenButton());
|
||||
}
|
||||
|
||||
#buildLauncherPane(isHorizontal) {
|
||||
|
@ -4,7 +4,7 @@ import "../stylesheets/bootstrap.scss";
|
||||
// Required for correct loading of scripts in Electron
|
||||
if (typeof module === 'object') {window.module = module; module = undefined;}
|
||||
|
||||
const device = getDeviceType()
|
||||
const device = getDeviceType();
|
||||
console.log("Setting device cookie to:", device);
|
||||
setCookie("trilium-device", device);
|
||||
|
||||
@ -16,17 +16,17 @@ function setCookie(name: string, value?: string) {
|
||||
}
|
||||
|
||||
function getDeviceType() {
|
||||
if (window.location.search === '?desktop') return "desktop";
|
||||
if (window.location.search === '?mobile') return "mobile";
|
||||
if (window.location.search === "?desktop") return "desktop";
|
||||
if (window.location.search === "?mobile") return "mobile";
|
||||
return isMobile() ? "mobile" : "desktop";
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/73731646/944162
|
||||
function isMobile() {
|
||||
const mQ = matchMedia?.('(pointer:coarse)');
|
||||
if (mQ?.media === '(pointer:coarse)') return !!mQ.matches;
|
||||
const mQ = matchMedia?.("(pointer:coarse)");
|
||||
if (mQ?.media === "(pointer:coarse)") return !!mQ.matches;
|
||||
|
||||
if ('orientation' in window) return true;
|
||||
const userAgentsRegEx = /\b(Android|iPhone|iPad|iPod|Windows Phone|BlackBerry|webOS|IEMobile)\b/i
|
||||
return userAgentsRegEx.test(navigator.userAgent)
|
||||
if ("orientation" in window) return true;
|
||||
const userAgentsRegEx = /\b(Android|iPhone|iPad|iPod|Windows Phone|BlackBerry|webOS|IEMobile)\b/i;
|
||||
return userAgentsRegEx.test(navigator.userAgent);
|
||||
}
|
@ -34,8 +34,8 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener<
|
||||
|
||||
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers";
|
||||
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers";
|
||||
const isVisibleItem = (parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers");
|
||||
const isAvailableItem = (parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers");
|
||||
const isVisibleItem = parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers";
|
||||
const isAvailableItem = parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers";
|
||||
const isItem = isVisibleItem || isAvailableItem;
|
||||
const canBeDeleted = !note?.noteId.startsWith("_"); // fixed notes can't be deleted
|
||||
const canBeReset = !canBeDeleted && note?.isLaunchBarConfig();
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import attributeParser from "./attribute_parser.js";
|
||||
|
||||
|
||||
describe("Lexing", () => {
|
||||
it("simple label", () => {
|
||||
expect(attributeParser.lex("#label").map((t: any) => t.text)).toEqual(["#label"]);
|
||||
|
@ -99,8 +99,8 @@ const HIGHLIGHT_JS: Library = {
|
||||
};
|
||||
|
||||
const LEAFLET: Library = {
|
||||
css: [ "node_modules/leaflet/dist/leaflet.css" ],
|
||||
}
|
||||
css: ["node_modules/leaflet/dist/leaflet.css"]
|
||||
};
|
||||
|
||||
async function requireLibrary(library: Library) {
|
||||
if (library.css) {
|
||||
|
@ -274,8 +274,8 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
||||
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
|
||||
|
||||
const ctrlKey = utils.isCtrlKey(evt);
|
||||
const isLeftClick = ("which" in evt && evt.which === 1);
|
||||
const isMiddleClick = ("which" in evt && evt.which === 2);
|
||||
const isLeftClick = "which" in evt && evt.which === 1;
|
||||
const isMiddleClick = "which" in evt && evt.which === 2;
|
||||
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick;
|
||||
|
||||
if (notePath) {
|
||||
|
@ -18,7 +18,7 @@ export default class NoteListRenderer {
|
||||
parentNote,
|
||||
noteIds,
|
||||
showNotePath
|
||||
}
|
||||
};
|
||||
|
||||
if (this.viewType === "list" || this.viewType === "grid") {
|
||||
this.viewMode = new ListOrGridView(this.viewType, args);
|
||||
|
@ -147,7 +147,7 @@ async function renderTooltip(note: FNote | null) {
|
||||
tooltip: true,
|
||||
trim: true
|
||||
});
|
||||
const isContentEmpty = ($renderedContent[0].innerHTML.length === 0);
|
||||
const isContentEmpty = $renderedContent[0].innerHTML.length === 0;
|
||||
|
||||
let content = "";
|
||||
if (noteTitleWithPathAsSuffix) {
|
||||
|
@ -33,8 +33,8 @@ function parseDate(str: string) {
|
||||
|
||||
// Source: https://stackoverflow.com/a/30465299/4898894
|
||||
function getMonthsInDateRange(startDate: string, endDate: string) {
|
||||
const start = startDate.split('-');
|
||||
const end = endDate.split('-');
|
||||
const start = startDate.split("-");
|
||||
const end = endDate.split("-");
|
||||
const startYear = parseInt(start[0]);
|
||||
const endYear = parseInt(end[0]);
|
||||
const dates = [];
|
||||
@ -45,8 +45,8 @@ function getMonthsInDateRange(startDate: string, endDate: string) {
|
||||
|
||||
for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) {
|
||||
const month = j + 1;
|
||||
const displayMonth = month < 10 ? '0'+month : month;
|
||||
dates.push([i, displayMonth].join('-'));
|
||||
const displayMonth = month < 10 ? "0" + month : month;
|
||||
dates.push([i, displayMonth].join("-"));
|
||||
}
|
||||
}
|
||||
return dates;
|
||||
@ -161,7 +161,7 @@ function escapeHtml(str: string) {
|
||||
}
|
||||
|
||||
export function escapeQuotes(value: string) {
|
||||
return value.replaceAll("\"", """);
|
||||
return value.replaceAll('"', """);
|
||||
}
|
||||
|
||||
function formatSize(size: number) {
|
||||
|
6
src/public/app/types-fancytree.d.ts
vendored
6
src/public/app/types-fancytree.d.ts
vendored
@ -11,7 +11,7 @@
|
||||
|
||||
interface JQueryStatic {
|
||||
ui: JQueryUI.UI;
|
||||
};
|
||||
}
|
||||
|
||||
declare namespace JQueryUI {
|
||||
interface UI {
|
||||
@ -679,13 +679,13 @@ declare namespace Fancytree {
|
||||
activate = 1,
|
||||
expand = 2,
|
||||
activate_and_expand = 3,
|
||||
activate_dblclick_expands = 4,
|
||||
activate_dblclick_expands = 4
|
||||
}
|
||||
|
||||
enum FancytreeSelectMode {
|
||||
single = 1,
|
||||
multi = 2,
|
||||
mutlti_hier = 3,
|
||||
mutlti_hier = 3
|
||||
}
|
||||
|
||||
/** Context object passed to events and hook functions. */
|
||||
|
4
src/public/app/types-lib.d.ts
vendored
4
src/public/app/types-lib.d.ts
vendored
@ -13,7 +13,7 @@ declare module "draggabilly" {
|
||||
containment: HTMLElement
|
||||
});
|
||||
element: HTMLElement;
|
||||
on(event: "pointerDown" | "dragStart" | "dragEnd" | "dragMove", callback: Callback)
|
||||
on(event: "pointerDown" | "dragStart" | "dragEnd" | "dragMove", callback: Callback);
|
||||
dragEnd();
|
||||
isDragging: boolean;
|
||||
positionDrag: () => void;
|
||||
@ -21,6 +21,6 @@ declare module "draggabilly" {
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@mind-elixir/node-menu' {
|
||||
declare module "@mind-elixir/node-menu" {
|
||||
export default mindmap;
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export default class BookmarkButtons extends FlexContainer<Component> {
|
||||
: new OpenNoteButtonWidget(note).class("launcher-button");
|
||||
|
||||
if (this.settings.titlePlacement) {
|
||||
if (!('settings' in buttonWidget)) {
|
||||
if (!("settings" in buttonWidget)) {
|
||||
(buttonWidget as any).settings = {};
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import appContext from "../../components/app_context.js";
|
||||
import openService from "../../services/open.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
import type attachmentsApiRoute from "../../../../routes/api/attachments.js"
|
||||
import type attachmentsApiRoute from "../../../../routes/api/attachments.js";
|
||||
import type FAttachment from "../../entities/fattachment.js";
|
||||
import type AttachmentDetailWidget from "../attachment_detail.js";
|
||||
|
||||
@ -105,7 +105,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
|
||||
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
|
||||
this.$uploadNewRevisionInput.on("change", async () => {
|
||||
|
||||
const fileToUpload = this.$uploadNewRevisionInput[0].files?.item(0); // copy to allow reset below
|
||||
this.$uploadNewRevisionInput.val("");
|
||||
if (fileToUpload) {
|
||||
@ -131,7 +130,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
|
||||
$openAttachmentCustomButton.addClass("disabled").append($('<span class="bx bx-info-circle disabled-tooltip" />').attr("title", t("attachments_actions.open_custom_client_only")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async openAttachmentCommand() {
|
||||
@ -170,7 +168,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const { note: newNote } = await server.post<ReturnType<typeof attachmentsApiRoute.convertAttachmentToNote>>(`attachments/${this.attachmentId}/convert-to-note`);
|
||||
toastService.showMessage(t("attachments_actions.convert_success", { title: this.attachment.title }));
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
@ -139,7 +139,8 @@ export default class RibbonContainer extends NoteContextAwareWidget {
|
||||
return super.isEnabled() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
ribbon(widget: NoteContextAwareWidget) { // TODO: Base class
|
||||
ribbon(widget: NoteContextAwareWidget) {
|
||||
// TODO: Base class
|
||||
super.child(widget);
|
||||
|
||||
this.ribbonWidgets.push(widget);
|
||||
@ -236,7 +237,12 @@ export default class RibbonContainer extends NoteContextAwareWidget {
|
||||
const $ribbonTitle = $('<div class="ribbon-tab-title">')
|
||||
.attr("data-ribbon-component-id", ribbonWidget.componentId)
|
||||
.attr("data-ribbon-component-name", (ribbonWidget as any).name as string) // TODO: base class for ribbon widgets
|
||||
.append($('<span class="ribbon-tab-title-icon">').addClass(ret.icon).attr("title", ret.title).attr("data-toggle-command", (ribbonWidget as any).toggleCommand)) // TODO: base class
|
||||
.append(
|
||||
$('<span class="ribbon-tab-title-icon">')
|
||||
.addClass(ret.icon)
|
||||
.attr("title", ret.title)
|
||||
.attr("data-toggle-command", (ribbonWidget as any).toggleCommand)
|
||||
) // TODO: base class
|
||||
.append(" ")
|
||||
.append($('<span class="ribbon-tab-title-label">').text(ret.title));
|
||||
|
||||
|
@ -250,7 +250,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
message: message,
|
||||
icon: "arrow-square-up-right"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
if (message.taskType !== "export") {
|
||||
return;
|
||||
|
@ -320,7 +320,8 @@ export default class RevisionsDialog extends BasicWidget {
|
||||
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
|
||||
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
|
||||
.css("max-width", "100%")
|
||||
.css("max-height", "100%").html()
|
||||
.css("max-height", "100%")
|
||||
.html()
|
||||
);
|
||||
}
|
||||
} else if (revisionItem.type === "file") {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js"
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `\
|
||||
<div class="geo-map-buttons">
|
||||
|
@ -51,7 +51,7 @@ export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
if (this.note && this.note.type !== "book" && byNoteType[this.note.type]) {
|
||||
this.helpNoteIdToOpen = byNoteType[this.note.type];
|
||||
} else if (this.note && this.note.type === "book") {
|
||||
this.helpNoteIdToOpen = byBookType[this.note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
|
||||
this.helpNoteIdToOpen = byBookType[(this.note.getAttributeValue("label", "viewType") as ViewTypeOptions) ?? ""];
|
||||
}
|
||||
|
||||
return !!this.helpNoteIdToOpen;
|
||||
@ -64,7 +64,7 @@ export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
const targetNote = `_help_${this.helpNoteIdToOpen}`;
|
||||
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
viewMode: "contextual-help",
|
||||
viewMode: "contextual-help"
|
||||
};
|
||||
if (!helpSubcontext) {
|
||||
// The help is not already open, open a new split with it.
|
||||
@ -74,7 +74,7 @@ export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
notePath: targetNote,
|
||||
hoistedNoteId: "_help",
|
||||
viewScope
|
||||
})
|
||||
});
|
||||
} else {
|
||||
// There is already a help window open, make sure it opens on the right note.
|
||||
helpSubcontext.setNote(targetNote, { viewScope });
|
||||
|
@ -157,7 +157,7 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
|
||||
if (backlink.relationName) {
|
||||
$item.append($("<p>").text(`${t("zpetne_odkazy.relation")}: ${backlink.relationName}`));
|
||||
} else {
|
||||
$item.append(...backlink.excerpts ?? []);
|
||||
$item.append(...(backlink.excerpts ?? []));
|
||||
}
|
||||
|
||||
this.$items.append($item);
|
||||
|
@ -19,10 +19,10 @@ const TPL = `\
|
||||
</style>
|
||||
|
||||
<div class="geo-map-container"></div>
|
||||
</div>`
|
||||
</div>`;
|
||||
|
||||
export type Leaflet = typeof import("leaflet");
|
||||
export type InitCallback = ((L: Leaflet) => void);
|
||||
export type InitCallback = (L: Leaflet) => void;
|
||||
|
||||
export default class GeoMapWidget extends NoteContextAwareWidget {
|
||||
|
||||
@ -40,8 +40,7 @@ export default class GeoMapWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$container = this.$widget.find(".geo-map-container");
|
||||
|
||||
library_loader.requireLibrary(library_loader.LEAFLET)
|
||||
.then(async () => {
|
||||
library_loader.requireLibrary(library_loader.LEAFLET).then(async () => {
|
||||
const L = (await import("leaflet")).default;
|
||||
|
||||
const map = L.map(this.$container[0], {
|
||||
@ -53,7 +52,7 @@ export default class GeoMapWidget extends NoteContextAwareWidget {
|
||||
this.initCallback(L);
|
||||
}
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
detectRetina: true
|
||||
}).addTo(map);
|
||||
|
@ -66,7 +66,7 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
|
||||
mermaid.mermaidAPI.initialize({
|
||||
startOnLoad: false,
|
||||
...getMermaidConfig() as any
|
||||
...(getMermaidConfig() as any)
|
||||
});
|
||||
|
||||
this.$display.empty();
|
||||
|
@ -81,7 +81,16 @@ const typeWidgetClasses = {
|
||||
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
|
||||
* for protected session or attachment information.
|
||||
*/
|
||||
type ExtendedNoteType = Exclude<NoteType, "mermaid" | "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession";
|
||||
type ExtendedNoteType =
|
||||
| Exclude<NoteType, "mermaid" | "launcher" | "text" | "code">
|
||||
| "empty"
|
||||
| "readOnlyCode"
|
||||
| "readOnlyText"
|
||||
| "editableText"
|
||||
| "editableCode"
|
||||
| "attachmentDetail"
|
||||
| "attachmentList"
|
||||
| "protectedSession";
|
||||
|
||||
export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
@ -329,7 +338,9 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
const label = attrs.find(
|
||||
(attr) =>
|
||||
attr.type === "label" && ["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note)
|
||||
attr.type === "label" &&
|
||||
["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name ?? "") &&
|
||||
attributeService.isAffecting(attr, this.note)
|
||||
);
|
||||
|
||||
const relation = attrs.find((attr) => attr.type === "relation" && ["template", "inherit", "renderNote"].includes(attr.name ?? "") && attributeService.isAffecting(attr, this.note));
|
||||
|
@ -139,7 +139,7 @@ interface NotesAndRelationsData {
|
||||
source: string;
|
||||
target: string;
|
||||
name: string;
|
||||
}[]
|
||||
}[];
|
||||
}
|
||||
|
||||
// Replace
|
||||
@ -152,7 +152,7 @@ interface ResponseLink {
|
||||
|
||||
interface PostNotesMapResponse {
|
||||
notes: string[];
|
||||
links: ResponseLink[],
|
||||
links: ResponseLink[];
|
||||
noteIdToDescendantCountMap: Record<string, number>;
|
||||
}
|
||||
|
||||
@ -160,7 +160,7 @@ interface GroupedLink {
|
||||
id: string;
|
||||
sourceNoteId: string;
|
||||
targetNoteId: string;
|
||||
names: string[]
|
||||
names: string[];
|
||||
}
|
||||
|
||||
interface CssData {
|
||||
@ -313,9 +313,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
if (node.x && node.y) {
|
||||
ctx.arc(node.x, node.y,
|
||||
this.noteIdToSizeMap[node.id], 0,
|
||||
2 * Math.PI, false);
|
||||
ctx.arc(node.x, node.y, this.noteIdToSizeMap[node.id], 0, 2 * Math.PI, false);
|
||||
}
|
||||
ctx.fill();
|
||||
})
|
||||
@ -467,13 +465,13 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
if (source.x && source.y && target.x && target.y) {
|
||||
const x = ((source.x) + (target.x)) / 2;
|
||||
const y = ((source.y) + (target.y)) / 2;
|
||||
const x = (source.x + target.x) / 2;
|
||||
const y = (source.y + target.y) / 2;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
|
||||
const deltaY = (source.y) - (target.y);
|
||||
const deltaX = (source.x) - (target.x);
|
||||
const deltaY = source.y - target.y;
|
||||
const deltaX = source.x - target.x;
|
||||
|
||||
let angle = Math.atan2(deltaY, deltaX);
|
||||
let moveY = 2;
|
||||
|
@ -91,7 +91,7 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
|
||||
async refreshWithNote(note: FNote) {
|
||||
const isReadOnly = (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) || utils.isLaunchBarConfig(note.noteId) || this.noteContext?.viewScope?.viewMode !== "default";
|
||||
|
||||
this.$noteTitle.val(isReadOnly ? await this.noteContext?.getNavigationTitle() || "" : note.title);
|
||||
this.$noteTitle.val(isReadOnly ? (await this.noteContext?.getNavigationTitle()) || "" : note.title);
|
||||
this.$noteTitle.prop("readonly", isReadOnly);
|
||||
|
||||
this.setProtectedStatus(note);
|
||||
|
@ -159,11 +159,11 @@ interface CreateLauncherResponse {
|
||||
message: string;
|
||||
note: {
|
||||
noteId: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface ExpandedSubtreeResponse {
|
||||
branchIds: string[]
|
||||
branchIds: string[];
|
||||
}
|
||||
|
||||
interface Node extends Fancytree.NodeData {
|
||||
@ -180,7 +180,6 @@ interface RefreshContext {
|
||||
}
|
||||
|
||||
export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $tree!: JQuery<HTMLElement>;
|
||||
private $treeActions!: JQuery<HTMLElement>;
|
||||
private $treeSettingsButton!: JQuery<HTMLElement>;
|
||||
@ -571,10 +570,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
clones: {
|
||||
highlightActiveClones: true
|
||||
},
|
||||
enhanceTitle: async function (event: Event, data: {
|
||||
enhanceTitle: async function (
|
||||
event: Event,
|
||||
data: {
|
||||
node: Fancytree.FancytreeNode;
|
||||
noteId: string;
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
const node = data.node;
|
||||
|
||||
if (!node.data.noteId) {
|
||||
|
@ -122,7 +122,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
|
||||
const { top } = this.$widget[0].getBoundingClientRect();
|
||||
|
||||
const height = ($(window).height() ?? 0) - top;
|
||||
const width = (this.$widget.width() ?? 0);
|
||||
const width = this.$widget.width() ?? 0;
|
||||
|
||||
this.$widget.find(".note-map-container")
|
||||
.height(height)
|
||||
|
@ -135,8 +135,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.getBranchRows().find((branch) => branch.noteId === this.noteId) ||
|
||||
(this.noteId != null && loadResults.isNoteReloaded(this.noteId))) {
|
||||
if (loadResults.getBranchRows().find((branch) => branch.noteId === this.noteId) || (this.noteId != null && loadResults.isNoteReloaded(this.noteId))) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,9 @@ export default class NotePropertiesWidget extends NoteContextAwareWidget {
|
||||
async refreshWithNote(note: FNote) {
|
||||
const pageUrl = note.getLabelValue("pageUrl");
|
||||
|
||||
this.$pageUrl.attr("href", pageUrl).attr("title", pageUrl).text(pageUrl ?? "");
|
||||
this.$pageUrl
|
||||
.attr("href", pageUrl)
|
||||
.attr("title", pageUrl)
|
||||
.text(pageUrl ?? "");
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ interface SimilarNote {
|
||||
noteId: string;
|
||||
}
|
||||
|
||||
|
||||
export default class SimilarNotesWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $similarNotesWrapper!: JQuery<HTMLElement>;
|
||||
|
@ -185,7 +185,7 @@ export default class SwitchWidget extends NoteContextAwareWidget {
|
||||
|
||||
/** Gets or sets whether the switch is enabled. */
|
||||
get canToggle() {
|
||||
return (!this.$switchButton.hasClass("disabled"));
|
||||
return !this.$switchButton.hasClass("disabled");
|
||||
}
|
||||
|
||||
set canToggle(isEnabled) {
|
||||
|
@ -79,7 +79,7 @@ export default class SyncStatusWidget extends BasicWidget {
|
||||
settings: {
|
||||
// TriliumNextTODO: narrow types and use TitlePlacement Type
|
||||
titlePlacement: string;
|
||||
}
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -106,7 +106,6 @@ export default class SyncStatusWidget extends BasicWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`)[0], {
|
||||
html: true,
|
||||
placement: this.settings.titlePlacement,
|
||||
|
@ -15,7 +15,7 @@ const TAB_CONTAINER_MIN_WIDTH = 24;
|
||||
const TAB_CONTAINER_MAX_WIDTH = 240;
|
||||
const TAB_CONTAINER_LEFT_PADDING = 5;
|
||||
const NEW_TAB_WIDTH = 32;
|
||||
const MIN_FILLER_WIDTH = (isDesktop ? 50 : 15);
|
||||
const MIN_FILLER_WIDTH = isDesktop ? 50 : 15;
|
||||
const MARGIN_WIDTH = 5;
|
||||
|
||||
const TAB_SIZE_SMALL = 84;
|
||||
|
@ -55,8 +55,8 @@ const TPL = `<div class="toc-widget">
|
||||
</div>`;
|
||||
|
||||
interface Toc {
|
||||
$toc: JQuery<HTMLElement>,
|
||||
headingCount: number
|
||||
$toc: JQuery<HTMLElement>;
|
||||
headingCount: number;
|
||||
}
|
||||
|
||||
export default class TocWidget extends RightPanelWidget {
|
||||
@ -89,8 +89,8 @@ export default class TocWidget extends RightPanelWidget {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isHelpNote = (this.note.type === "doc" && this.note.noteId.startsWith("_help"));
|
||||
const isTextNote = (this.note.type === "text");
|
||||
const isHelpNote = this.note.type === "doc" && this.note.noteId.startsWith("_help");
|
||||
const isTextNote = this.note.type === "text";
|
||||
const isNoteSupported = isTextNote || isHelpNote;
|
||||
|
||||
return isNoteSupported
|
||||
@ -156,7 +156,7 @@ export default class TocWidget extends RightPanelWidget {
|
||||
|
||||
const tocLabelValue = this.tocLabelValue;
|
||||
|
||||
const visible = (tocLabelValue === "" || tocLabelValue === "show") || headingCount >= (options.getInt("minTocHeadings") ?? 0);
|
||||
const visible = tocLabelValue === "" || tocLabelValue === "show" || headingCount >= (options.getInt("minTocHeadings") ?? 0);
|
||||
this.toggleInt(visible);
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.noteContext.viewScope.tocPreviousVisible = visible;
|
||||
|
@ -63,7 +63,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
|
||||
this.$linksWrapper.empty().append(
|
||||
t("attachment_detail.owning_note"),
|
||||
(await linkService.createLink(this.noteId)),
|
||||
await linkService.createLink(this.noteId),
|
||||
t("attachment_detail.you_can_also_open"),
|
||||
await linkService.createLink(this.noteId, {
|
||||
title: t("attachment_detail.list_of_all_attachments"),
|
||||
@ -74,7 +74,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
$helpButton
|
||||
);
|
||||
|
||||
const attachment = (this.attachmentId) ? await froca.getAttachment(this.attachmentId, true) : null;
|
||||
const attachment = this.attachmentId ? await froca.getAttachment(this.attachmentId, true) : null;
|
||||
|
||||
if (!attachment) {
|
||||
this.$wrapper.html("<strong>" + t("attachment_detail.attachment_deleted") + "</strong>");
|
||||
|
@ -66,7 +66,7 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
.text(t("attachment_list.upload_attachments"))
|
||||
.on("click", () => {
|
||||
if (this.noteId) {
|
||||
this.triggerCommand("showUploadAttachmentsDialog", { noteId: this.noteId })
|
||||
this.triggerCommand("showUploadAttachmentsDialog", { noteId: this.noteId });
|
||||
}
|
||||
}),
|
||||
$helpButton
|
||||
|
@ -36,9 +36,7 @@ export default class BookTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
this.$helpNoChildren.toggle(
|
||||
!this.note?.hasChildren()
|
||||
&& this.note?.getAttributeValue("label", "viewType") !== "calendar");
|
||||
this.$helpNoChildren.toggle(!this.note?.hasChildren() && this.note?.getAttributeValue("label", "viewType") !== "calendar");
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
|
@ -59,9 +59,9 @@ const TPL = `
|
||||
`;
|
||||
|
||||
interface CanvasContent {
|
||||
elements: ExcalidrawElement[],
|
||||
files: BinaryFileData[],
|
||||
appState: Partial<AppState>
|
||||
elements: ExcalidrawElement[];
|
||||
files: BinaryFileData[];
|
||||
appState: Partial<AppState>;
|
||||
}
|
||||
|
||||
interface AttachmentMetadata {
|
||||
@ -198,7 +198,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
}
|
||||
(window.process.env as any).PREACT = false;
|
||||
|
||||
const excalidraw = (await import("@excalidraw/excalidraw"));
|
||||
const excalidraw = await import("@excalidraw/excalidraw");
|
||||
this.excalidrawLib = excalidraw;
|
||||
|
||||
const { createRoot } = await import("react-dom/client");
|
||||
@ -476,7 +476,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
createExcalidrawReactApp(react: typeof React, excalidrawComponent: React.MemoExoticComponent<(props: ExcalidrawProps) => JSX.Element>) {
|
||||
const excalidrawWrapperRef = react.useRef<HTMLElement>(null);
|
||||
this.excalidrawWrapperRef = excalidrawWrapperRef;
|
||||
const [dimensions, setDimensions] = react.useState<{ width?: number, height?: number}>({
|
||||
const [dimensions, setDimensions] = react.useState<{ width?: number; height?: number }>({
|
||||
width: undefined,
|
||||
height: undefined
|
||||
});
|
||||
|
@ -7,91 +7,78 @@ export function buildConfig() {
|
||||
image: {
|
||||
styles: {
|
||||
options: [
|
||||
'inline',
|
||||
'alignBlockLeft',
|
||||
'alignCenter',
|
||||
'alignBlockRight',
|
||||
'alignLeft',
|
||||
'alignRight',
|
||||
'full', // full and side are for BC since the old images have been created with these styles
|
||||
'side'
|
||||
"inline",
|
||||
"alignBlockLeft",
|
||||
"alignCenter",
|
||||
"alignBlockRight",
|
||||
"alignLeft",
|
||||
"alignRight",
|
||||
"full", // full and side are for BC since the old images have been created with these styles
|
||||
"side"
|
||||
]
|
||||
},
|
||||
resizeOptions: [
|
||||
{
|
||||
name: 'imageResize:original',
|
||||
name: "imageResize:original",
|
||||
value: null,
|
||||
icon: 'original'
|
||||
icon: "original"
|
||||
},
|
||||
{
|
||||
name: 'imageResize:25',
|
||||
value: '25',
|
||||
icon: 'small'
|
||||
name: "imageResize:25",
|
||||
value: "25",
|
||||
icon: "small"
|
||||
},
|
||||
{
|
||||
name: 'imageResize:50',
|
||||
value: '50',
|
||||
icon: 'medium'
|
||||
name: "imageResize:50",
|
||||
value: "50",
|
||||
icon: "medium"
|
||||
},
|
||||
{
|
||||
name: 'imageResize:75',
|
||||
value: '75',
|
||||
icon: 'medium'
|
||||
name: "imageResize:75",
|
||||
value: "75",
|
||||
icon: "medium"
|
||||
}
|
||||
],
|
||||
toolbar: [
|
||||
// Image styles, see https://ckeditor.com/docs/ckeditor5/latest/features/images/images-styles.html#demo.
|
||||
'imageStyle:inline',
|
||||
'imageStyle:alignCenter',
|
||||
"imageStyle:inline",
|
||||
"imageStyle:alignCenter",
|
||||
{
|
||||
name: "imageStyle:wrapText",
|
||||
title: "Wrap text",
|
||||
items: [
|
||||
'imageStyle:alignLeft',
|
||||
'imageStyle:alignRight',
|
||||
],
|
||||
defaultItem: 'imageStyle:alignRight'
|
||||
items: ["imageStyle:alignLeft", "imageStyle:alignRight"],
|
||||
defaultItem: "imageStyle:alignRight"
|
||||
},
|
||||
{
|
||||
name: "imageStyle:block",
|
||||
title: "Block align",
|
||||
items: [
|
||||
'imageStyle:alignBlockLeft',
|
||||
'imageStyle:alignBlockRight'
|
||||
],
|
||||
defaultItem: "imageStyle:alignBlockLeft",
|
||||
items: ["imageStyle:alignBlockLeft", "imageStyle:alignBlockRight"],
|
||||
defaultItem: "imageStyle:alignBlockLeft"
|
||||
},
|
||||
'|',
|
||||
'imageResize:25',
|
||||
'imageResize:50',
|
||||
'imageResize:original',
|
||||
'|',
|
||||
'toggleImageCaption'
|
||||
"|",
|
||||
"imageResize:25",
|
||||
"imageResize:50",
|
||||
"imageResize:original",
|
||||
"|",
|
||||
"toggleImageCaption"
|
||||
],
|
||||
upload: {
|
||||
types: [ 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff', 'svg', 'svg+xml', 'avif' ]
|
||||
types: ["jpeg", "png", "gif", "bmp", "webp", "tiff", "svg", "svg+xml", "avif"]
|
||||
}
|
||||
},
|
||||
heading: {
|
||||
options: [
|
||||
{ model: 'paragraph' as const, title: 'Paragraph', class: 'ck-heading_paragraph' },
|
||||
{ model: "paragraph" as const, title: "Paragraph", class: "ck-heading_paragraph" },
|
||||
// // heading1 is not used since that should be a note's title
|
||||
{ model: 'heading2' as const, view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
|
||||
{ model: 'heading3' as const, view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' },
|
||||
{ model: 'heading4' as const, view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' },
|
||||
{ model: 'heading5' as const, view: 'h5', title: 'Heading 5', class: 'ck-heading_heading5' },
|
||||
{ model: 'heading6' as const, view: 'h6', title: 'Heading 6', class: 'ck-heading_heading6' }
|
||||
{ model: "heading2" as const, view: "h2", title: "Heading 2", class: "ck-heading_heading2" },
|
||||
{ model: "heading3" as const, view: "h3", title: "Heading 3", class: "ck-heading_heading3" },
|
||||
{ model: "heading4" as const, view: "h4", title: "Heading 4", class: "ck-heading_heading4" },
|
||||
{ model: "heading5" as const, view: "h5", title: "Heading 5", class: "ck-heading_heading5" },
|
||||
{ model: "heading6" as const, view: "h6", title: "Heading 6", class: "ck-heading_heading6" }
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: [
|
||||
'tableColumn',
|
||||
'tableRow',
|
||||
'mergeTableCells',
|
||||
'tableProperties',
|
||||
'tableCellProperties',
|
||||
'toggleTableCaption'
|
||||
]
|
||||
contentToolbar: ["tableColumn", "tableRow", "mergeTableCells", "tableProperties", "tableCellProperties", "toggleTableCaption"]
|
||||
},
|
||||
list: {
|
||||
properties: {
|
||||
@ -101,17 +88,17 @@ export function buildConfig() {
|
||||
}
|
||||
},
|
||||
link: {
|
||||
defaultProtocol: 'https://',
|
||||
defaultProtocol: "https://",
|
||||
allowedProtocols: ALLOWED_PROTOCOLS
|
||||
},
|
||||
// This value must be kept in sync with the language defined in webpack.config.js.
|
||||
language: 'en'
|
||||
}
|
||||
language: "en"
|
||||
};
|
||||
}
|
||||
|
||||
export function buildToolbarConfig(isClassicToolbar: boolean) {
|
||||
if (isClassicToolbar) {
|
||||
const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true"
|
||||
const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true";
|
||||
return buildClassicToolbar(multilineToolbar);
|
||||
} else {
|
||||
return buildFloatingToolbar();
|
||||
@ -123,101 +110,92 @@ function buildClassicToolbar(multilineToolbar: boolean) {
|
||||
return {
|
||||
toolbar: {
|
||||
items: [
|
||||
'heading', 'fontSize',
|
||||
'|',
|
||||
'bold', 'italic',
|
||||
"heading",
|
||||
"fontSize",
|
||||
"|",
|
||||
"bold",
|
||||
"italic",
|
||||
{
|
||||
label: "Text formatting",
|
||||
icon: "text",
|
||||
items: [
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'superscript',
|
||||
'subscript',
|
||||
'code',
|
||||
],
|
||||
items: ["underline", "strikethrough", "superscript", "subscript", "code"]
|
||||
},
|
||||
'|',
|
||||
'fontColor', 'fontBackgroundColor', 'removeFormat',
|
||||
'|',
|
||||
'bulletedList', 'numberedList', 'todoList',
|
||||
'|',
|
||||
'blockQuote', 'insertTable', 'codeBlock', 'footnote',
|
||||
"|",
|
||||
"fontColor",
|
||||
"fontBackgroundColor",
|
||||
"removeFormat",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"todoList",
|
||||
"|",
|
||||
"blockQuote",
|
||||
"insertTable",
|
||||
"codeBlock",
|
||||
"footnote",
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: [
|
||||
'imageUpload',
|
||||
'|',
|
||||
'link',
|
||||
'internallink',
|
||||
'includeNote',
|
||||
'|',
|
||||
'specialCharacters',
|
||||
'math',
|
||||
'mermaid',
|
||||
'horizontalLine',
|
||||
'pageBreak'
|
||||
]
|
||||
items: ["imageUpload", "|", "link", "internallink", "includeNote", "|", "specialCharacters", "math", "mermaid", "horizontalLine", "pageBreak"]
|
||||
},
|
||||
'|',
|
||||
'outdent', 'indent',
|
||||
'|',
|
||||
'markdownImport', 'cuttonote', 'findAndReplace'
|
||||
"|",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
"markdownImport",
|
||||
"cuttonote",
|
||||
"findAndReplace"
|
||||
],
|
||||
shouldNotGroupWhenFull: multilineToolbar
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildFloatingToolbar() {
|
||||
return {
|
||||
toolbar: {
|
||||
items: [
|
||||
'fontSize',
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'superscript',
|
||||
'subscript',
|
||||
'fontColor',
|
||||
'fontBackgroundColor',
|
||||
'code',
|
||||
'link',
|
||||
'removeFormat',
|
||||
'internallink',
|
||||
'cuttonote'
|
||||
"fontSize",
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
"strikethrough",
|
||||
"superscript",
|
||||
"subscript",
|
||||
"fontColor",
|
||||
"fontBackgroundColor",
|
||||
"code",
|
||||
"link",
|
||||
"removeFormat",
|
||||
"internallink",
|
||||
"cuttonote"
|
||||
]
|
||||
},
|
||||
|
||||
blockToolbar: [
|
||||
'heading',
|
||||
'|',
|
||||
'bulletedList', 'numberedList', 'todoList',
|
||||
'|',
|
||||
'blockQuote', 'codeBlock', 'insertTable',
|
||||
'footnote',
|
||||
"heading",
|
||||
"|",
|
||||
"bulletedList",
|
||||
"numberedList",
|
||||
"todoList",
|
||||
"|",
|
||||
"blockQuote",
|
||||
"codeBlock",
|
||||
"insertTable",
|
||||
"footnote",
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: [
|
||||
'internallink',
|
||||
'includeNote',
|
||||
'|',
|
||||
'math',
|
||||
'mermaid',
|
||||
'horizontalLine',
|
||||
'pageBreak'
|
||||
]
|
||||
items: ["internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak"]
|
||||
},
|
||||
'|',
|
||||
'outdent', 'indent',
|
||||
'|',
|
||||
'imageUpload',
|
||||
'markdownImport',
|
||||
'specialCharacters',
|
||||
'findAndReplace'
|
||||
"|",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
"imageUpload",
|
||||
"markdownImport",
|
||||
"specialCharacters",
|
||||
"findAndReplace"
|
||||
]
|
||||
};
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js";
|
||||
import ImageOptions from "./options/images/images.js";
|
||||
import SpellcheckOptions from "./options/spellcheck.js";
|
||||
import PasswordOptions from "./options/password/password.js";
|
||||
import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js"
|
||||
import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js";
|
||||
import EtapiOptions from "./options/etapi.js";
|
||||
import BackupOptions from "./options/backup.js";
|
||||
import SyncOptions from "./options/sync.js";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { GPX, Marker, type LatLng, type LeafletMouseEvent } from "leaflet";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js";
|
||||
import TypeWidget from "./type_widget.js"
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
@ -82,14 +82,14 @@ interface MapData {
|
||||
view?: {
|
||||
center?: LatLng | [number, number];
|
||||
zoom?: number;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface CreateChildResponse {
|
||||
note: {
|
||||
noteId: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
enum State {
|
||||
@ -228,10 +228,10 @@ export default class GeoMapTypeWidget extends TypeWidget {
|
||||
icon,
|
||||
draggable: true,
|
||||
autoPan: true,
|
||||
autoPanSpeed: 5,
|
||||
autoPanSpeed: 5
|
||||
})
|
||||
.addTo(map)
|
||||
.on("moveend", e => {
|
||||
.on("moveend", (e) => {
|
||||
this.moveMarker(note.noteId, (e.target as Marker).getLatLng());
|
||||
});
|
||||
marker.on("mousedown", ({ originalEvent }) => {
|
||||
@ -266,7 +266,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
|
||||
<span class="title-label">${title}</span>`,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#changeState(newState: State) {
|
||||
@ -296,7 +296,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async moveMarker(noteId: string, latLng: LatLng | null) {
|
||||
const value = (latLng ? [latLng.lat, latLng.lng].join(",") : "");
|
||||
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
|
||||
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ interface Theme {
|
||||
val: string;
|
||||
}
|
||||
|
||||
type Response = Record<string, Theme[]>
|
||||
type Response = Record<string, Theme[]>;
|
||||
|
||||
/**
|
||||
* Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter.
|
||||
|
@ -94,10 +94,12 @@ export default class ThemeOptions extends OptionsWidget {
|
||||
this.$themeSelect.empty();
|
||||
|
||||
for (const theme of themes) {
|
||||
this.$themeSelect.append($("<option>")
|
||||
this.$themeSelect.append(
|
||||
$("<option>")
|
||||
.attr("value", theme.val)
|
||||
.attr("data-note-id", theme.noteId || "")
|
||||
.text(theme.title));
|
||||
.text(theme.title)
|
||||
);
|
||||
}
|
||||
|
||||
this.$themeSelect.val(options.theme);
|
||||
|
@ -97,9 +97,8 @@ export default class CodeMimeTypesOptions extends OptionsWidget {
|
||||
const checkbox = $(`<label class="tn-checkbox">`)
|
||||
.append($('<input type="checkbox" class="form-check-input">').attr("id", id).attr("data-mime-type", mimeType.mime).prop("checked", mimeType.enabled))
|
||||
.on("change", () => this.save())
|
||||
.append(mimeType.title)
|
||||
.append(mimeType.title);
|
||||
|
||||
return $("<li>")
|
||||
.append(checkbox);
|
||||
return $("<li>").append(checkbox);
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export default class NoteErasureTimeoutOptions extends TimeSelector {
|
||||
const $timeSelector = this.$widget;
|
||||
// inject TimeSelector widget template
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
|
||||
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector);
|
||||
|
||||
this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button");
|
||||
|
||||
|
@ -26,6 +26,6 @@ export default class RevisionsSnapshotIntervalOptions extends TimeSelector {
|
||||
const $timeSelector = this.$widget;
|
||||
// inject TimeSelector widget template
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
|
||||
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector);
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,8 @@ export default class ShareSettingsOptions extends OptionsWidget {
|
||||
this.$widget = $(TPL);
|
||||
this.contentSized();
|
||||
|
||||
this.$shareRootCheck = this.$widget.find('.share-root-check');
|
||||
this.$shareRootStatus = this.$widget.find('.share-root-status');
|
||||
this.$shareRootCheck = this.$widget.find(".share-root-check");
|
||||
this.$shareRootStatus = this.$widget.find(".share-root-status");
|
||||
|
||||
// Add change handlers for both checkboxes
|
||||
this.$widget.find('input[type="checkbox"]').on("change", (e: JQuery.ChangeEvent) => {
|
||||
@ -38,7 +38,7 @@ export default class ShareSettingsOptions extends OptionsWidget {
|
||||
|
||||
// Show/hide share root status section based on redirectBareDomain checkbox
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.name === 'redirectBareDomain') {
|
||||
if (target.name === "redirectBareDomain") {
|
||||
this.$shareRootCheck.toggle(target.checked);
|
||||
if (target.checked) {
|
||||
this.checkShareRoot();
|
||||
@ -47,7 +47,7 @@ export default class ShareSettingsOptions extends OptionsWidget {
|
||||
});
|
||||
|
||||
// Add click handler for check share root button
|
||||
this.$widget.find('.check-share-root').on("click", () => this.checkShareRoot());
|
||||
this.$widget.find(".check-share-root").on("click", () => this.checkShareRoot());
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
@ -62,28 +62,26 @@ export default class ShareSettingsOptions extends OptionsWidget {
|
||||
}
|
||||
|
||||
async checkShareRoot() {
|
||||
const $button = this.$widget.find('.check-share-root');
|
||||
$button.prop('disabled', true);
|
||||
const $button = this.$widget.find(".check-share-root");
|
||||
$button.prop("disabled", true);
|
||||
|
||||
try {
|
||||
const shareRootNotes = await searchService.searchForNotes("#shareRoot");
|
||||
const sharedShareRootNote = shareRootNotes.find(note => note.isShared());
|
||||
const sharedShareRootNote = shareRootNotes.find((note) => note.isShared());
|
||||
|
||||
if (sharedShareRootNote) {
|
||||
this.$shareRootStatus
|
||||
.removeClass('text-danger')
|
||||
.addClass('text-success')
|
||||
.removeClass("text-danger")
|
||||
.addClass("text-success")
|
||||
.text(t("share.share_root_found", { noteTitle: sharedShareRootNote.title }));
|
||||
} else {
|
||||
this.$shareRootStatus
|
||||
.removeClass('text-success')
|
||||
.addClass('text-danger')
|
||||
.text(shareRootNotes.length > 0
|
||||
? t("share.share_root_not_shared", {noteTitle: shareRootNotes[0].title})
|
||||
: t("share.share_root_not_found"));
|
||||
.removeClass("text-success")
|
||||
.addClass("text-danger")
|
||||
.text(shareRootNotes.length > 0 ? t("share.share_root_not_shared", { noteTitle: shareRootNotes[0].title }) : t("share.share_root_not_found"));
|
||||
}
|
||||
} finally {
|
||||
$button.prop('disabled', false);
|
||||
$button.prop("disabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,6 @@ export default class ProtectedSessionTimeoutOptions extends TimeSelector {
|
||||
const $timeSelector = this.$widget;
|
||||
// inject TimeSelector widget template
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
|
||||
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector);
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export default class TimeSelector extends OptionsWidget {
|
||||
this.optionValueId = options.optionValueId;
|
||||
this.optionTimeScaleId = options.optionTimeScaleId;
|
||||
this.includedTimeScales = options.includedTimeScales || new Set(["seconds", "minutes", "hours", "days"]);
|
||||
this.minimumSeconds = options.minimumSeconds || 0
|
||||
this.minimumSeconds = options.minimumSeconds || 0;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
@ -132,9 +132,9 @@ export default class TimeSelector extends OptionsWidget {
|
||||
private setInternalTimeInSeconds(time: number) {
|
||||
if (time < this.minimumSeconds) {
|
||||
toastService.showError(t("time_selector.minimum_input", { minimumSeconds: this.minimumSeconds }));
|
||||
return this.internalTimeInSeconds = this.minimumSeconds;
|
||||
return (this.internalTimeInSeconds = this.minimumSeconds);
|
||||
}
|
||||
return this.internalTimeInSeconds = time;
|
||||
return (this.internalTimeInSeconds = time);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ const TPL = `
|
||||
`;
|
||||
|
||||
function buildTasks(tasks: FTask[]) {
|
||||
let html = '';
|
||||
let html = "";
|
||||
|
||||
const now = dayjs();
|
||||
const dateFormat = "DD-MM-YYYY";
|
||||
@ -137,7 +137,9 @@ export default class TaskListWidget extends TypeWidget {
|
||||
private $taskContainer!: JQuery<HTMLElement>;
|
||||
private $addNewTask!: JQuery<HTMLElement>;
|
||||
|
||||
static getType() { return "taskList" }
|
||||
static getType() {
|
||||
return "taskList";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
@ -231,8 +233,7 @@ export default class TaskListWidget extends TypeWidget {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (await froca.getTasks(this.noteId))
|
||||
.toSorted((a, b) => {
|
||||
return (await froca.getTasks(this.noteId)).toSorted((a, b) => {
|
||||
// Sort by due date, closest date first.
|
||||
if (!a.dueDate) {
|
||||
return 1;
|
||||
|
@ -68,7 +68,7 @@ const TPL = `
|
||||
interface CreateChildResponse {
|
||||
note: {
|
||||
noteId: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default class CalendarView extends ViewMode {
|
||||
@ -126,7 +126,7 @@ export default class CalendarView extends ViewMode {
|
||||
weekNumbers: this.parentNote.hasAttribute("label", "calendar:weekNumbers"),
|
||||
locale: await CalendarView.#getLocale(),
|
||||
height: "100%",
|
||||
eventContent: (e => {
|
||||
eventContent: (e) => {
|
||||
let html = "";
|
||||
const { iconClass, promotedAttributes } = e.event.extendedProps;
|
||||
|
||||
@ -147,7 +147,7 @@ export default class CalendarView extends ViewMode {
|
||||
}
|
||||
|
||||
return { html };
|
||||
}),
|
||||
},
|
||||
dateClick: async (e) => {
|
||||
if (!this.isCalendarRoot) {
|
||||
return;
|
||||
@ -456,7 +456,7 @@ export default class CalendarView extends ViewMode {
|
||||
|
||||
const offset = date.getTimezoneOffset();
|
||||
const localDate = new Date(date.getTime() - offset * 60 * 1000);
|
||||
return localDate.toISOString().split('T')[0];
|
||||
return localDate.toISOString().split("T")[0];
|
||||
}
|
||||
|
||||
static #offsetDate(date: Date | string | null | undefined, offset: number) {
|
||||
|
@ -75,7 +75,6 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.calendar-dropdown-widget .calendar-week span {
|
||||
flex-direction: column;
|
||||
flex: 0 0 14.28%;
|
||||
|
@ -202,9 +202,8 @@ span[style] {
|
||||
|
||||
@supports selector(.todo-list__label__description:has(*)) and (height: 1lh) {
|
||||
.note-detail-printable .todo-list__label__description {
|
||||
|
||||
/* The percentage of the line height that the check box occupies */
|
||||
--box-ratio: .75;
|
||||
--box-ratio: 0.75;
|
||||
/* The size of the gap between the check box and the caption */
|
||||
--box-text-gap: 0.25em;
|
||||
|
||||
@ -303,7 +302,12 @@ pre > code {
|
||||
orphans: 6;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}
|
@ -32,7 +32,7 @@
|
||||
--cmd-button-icon-color: white;
|
||||
--cmd-button-keyboard-shortcut-background: #0000004d;
|
||||
--cmd-button-keyboard-shortcut-color: white;
|
||||
--cmd-button-disabled-opacity: .5;
|
||||
--cmd-button-disabled-opacity: 0.5;
|
||||
|
||||
--icon-button-color: currentColor;
|
||||
--icon-button-hover-background: var(--hover-item-background-color);
|
||||
|
@ -32,7 +32,7 @@
|
||||
--cmd-button-icon-color: black;
|
||||
--cmd-button-keyboard-shortcut-background: #00000017;
|
||||
--cmd-button-keyboard-shortcut-color: black;
|
||||
--cmd-button-disabled-opacity: .5;
|
||||
--cmd-button-disabled-opacity: 0.5;
|
||||
|
||||
--icon-button-color: currentColor;
|
||||
--icon-button-hover-background: var(--hover-item-background-color);
|
||||
|
@ -61,7 +61,7 @@
|
||||
--help-backdrop-blur: 10px;
|
||||
|
||||
--icon-button-size: 32px;
|
||||
--icon-button-icon-ratio: .65;
|
||||
--icon-button-icon-ratio: 0.65;
|
||||
|
||||
/* Theme capabilities */
|
||||
--tab-note-icons: true;
|
||||
|
@ -30,11 +30,11 @@ button.btn.btn-primary:active,
|
||||
button.btn.btn-secondary:active,
|
||||
button.btn.btn-sm:not(.select-button):active,
|
||||
button.btn.btn-success:active {
|
||||
opacity: .85;
|
||||
opacity: 0.85;
|
||||
box-shadow: unset;
|
||||
background: var(--cmd-button-background-color) !important;
|
||||
color: var(--cmd-button-text-color) !important;
|
||||
transform: scale(.95);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
button.btn.btn-primary:disabled,
|
||||
@ -57,7 +57,7 @@ button.btn.btn-secondary span.bx,
|
||||
button.btn.btn-sm span.bx,
|
||||
button.btn.btn-success span.bx {
|
||||
color: var(--cmd-button-icon-color);
|
||||
padding-right: .35em;
|
||||
padding-right: 0.35em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
@ -66,12 +66,12 @@ button.btn.btn-primary kbd,
|
||||
button.btn.btn-secondary kbd,
|
||||
button.btn.btn-sm kbd,
|
||||
button.btn.btn-success kbd {
|
||||
margin-left: .5em;
|
||||
margin-left: 0.5em;
|
||||
background: var(--cmd-button-keyboard-shortcut-background);
|
||||
color: var(--cmd-button-keyboard-shortcut-color);
|
||||
font-size: .6em;
|
||||
font-size: 0.6em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .5pt;
|
||||
letter-spacing: 0.5pt;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -101,7 +101,7 @@ button.btn.btn-success kbd {
|
||||
--icon-button-hover-background: var(--tab-close-button-hover-background);
|
||||
--icon-button-hover-color: var(--tab-close-button-hover-color);
|
||||
--icon-button-size: 24px;
|
||||
--icon-button-icon-ratio: .8;
|
||||
--icon-button-icon-ratio: 0.8;
|
||||
|
||||
border-radius: 50%;
|
||||
}
|
||||
@ -124,7 +124,7 @@ button.btn.btn-success kbd {
|
||||
|
||||
:root .icon-action:not(.global-menu-button):active::before,
|
||||
:root .tn-tool-button:active::before {
|
||||
transform: scale(.85);
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
:root .icon-action:not(.global-menu-button):focus-visible,
|
||||
@ -137,7 +137,7 @@ button.btn.btn-success kbd {
|
||||
*/
|
||||
|
||||
input:disabled {
|
||||
opacity: .33;
|
||||
opacity: 0.33;
|
||||
}
|
||||
|
||||
/* Text boxes */
|
||||
@ -190,7 +190,8 @@ textarea:focus,
|
||||
outline-offset: 0;
|
||||
background: var(--input-focus-background);
|
||||
color: var(--input-focus-color);
|
||||
transition: outline-color 50ms linear,
|
||||
transition:
|
||||
outline-color 50ms linear,
|
||||
outline-offset 200ms ease-out;
|
||||
}
|
||||
|
||||
@ -229,12 +230,12 @@ input::selection,
|
||||
outline: 3px solid var(--input-focus-outline-color);
|
||||
outline-offset: 0;
|
||||
background: var(--input-focus-background);
|
||||
transition: outline-color 50ms linear,
|
||||
transition:
|
||||
outline-color 50ms linear,
|
||||
outline-offset 200ms ease-out;
|
||||
}
|
||||
|
||||
.input-group input
|
||||
.input-group input:hover,
|
||||
.input-group input .input-group input:hover,
|
||||
.input-group input:focus,
|
||||
.input-group .form-control,
|
||||
.input-group .form-control:hover,
|
||||
@ -277,7 +278,7 @@ input::selection,
|
||||
}
|
||||
|
||||
.input-group a.disabled {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
/* Workaround to set the "background" property. */
|
||||
--button-disabled-background-color: transparent;
|
||||
--button-disabled-text-color: var(--input-action-button-color);
|
||||
@ -319,8 +320,7 @@ select.form-control,
|
||||
outline: 3px solid transparent;
|
||||
outline-offset: 6px;
|
||||
padding-right: calc(15px + 1.5rem);
|
||||
background: var(--input-background-color)
|
||||
var(--dropdown-arrow);
|
||||
background: var(--input-background-color) var(--dropdown-arrow);
|
||||
color: var(--input-text-color);
|
||||
border: unset;
|
||||
border-radius: 0.375rem;
|
||||
@ -330,8 +330,7 @@ select:hover,
|
||||
select.form-select:hover,
|
||||
select.form-control:hover,
|
||||
.select-button.dropdown-toggle.btn:hover {
|
||||
background: var(--input-hover-background)
|
||||
var(--dropdown-arrow);
|
||||
background: var(--input-hover-background) var(--dropdown-arrow);
|
||||
color: var(--input-hover-color);
|
||||
}
|
||||
|
||||
@ -347,10 +346,10 @@ select.form-control:focus,
|
||||
box-shadow: unset;
|
||||
outline: 3px solid var(--input-focus-outline-color);
|
||||
outline-offset: 0;
|
||||
background: var(--select-focus-background)
|
||||
var(--dropdown-arrow);
|
||||
background: var(--select-focus-background) var(--dropdown-arrow);
|
||||
color: var(--select-focus-text-color);
|
||||
transition: outline-color 50ms linear,
|
||||
transition:
|
||||
outline-color 50ms linear,
|
||||
outline-offset 200ms ease-out;
|
||||
}
|
||||
|
||||
@ -360,7 +359,7 @@ option {
|
||||
|
||||
optgroup {
|
||||
color: var(--select-group-heading-text-color);
|
||||
font-size: .75em;
|
||||
font-size: 0.75em;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 40px;
|
||||
@ -376,7 +375,7 @@ optgroup {
|
||||
|
||||
.tn-file-input {
|
||||
position: relative;
|
||||
padding: .375rem 2.25rem .375rem .75rem;
|
||||
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
|
||||
}
|
||||
|
||||
.tn-file-input input[type="file"] {
|
||||
@ -416,19 +415,17 @@ optgroup {
|
||||
/* Check boxes and radio buttons */
|
||||
|
||||
@supports selector(label:has(*)) {
|
||||
|
||||
/* Check box & radio button commons */
|
||||
|
||||
/* The parent label */
|
||||
label.tn-radio,
|
||||
label.tn-checkbox {
|
||||
--box-size: 1em;
|
||||
--box-label-gap: .5em;
|
||||
--box-label-gap: 0.5em;
|
||||
|
||||
position: relative;
|
||||
padding-left: calc(var(--box-size) + var(--box-label-gap)) !important;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
/* The original input */
|
||||
@ -475,7 +472,8 @@ optgroup {
|
||||
@keyframes checkbox-checked {
|
||||
from {
|
||||
transform: scale(2);
|
||||
} to {
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@ -484,11 +482,12 @@ optgroup {
|
||||
label.tn-checkbox::after {
|
||||
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3ctitle%3echeck-bold%3c/title%3e%3cpath d='M9%2c20.42L2.79%2c14.21L5.62%2c11.38L9%2c14.77L18.88%2c4.88L21.71%2c7.71L9%2c20.42Z' /%3e%3c/svg%3e");
|
||||
mask-position: center center;
|
||||
mask-size: .95em;
|
||||
mask-size: 0.95em;
|
||||
background-color: var(--radio-checkbox-indicator-color);
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transition: transform 300ms ease-out,
|
||||
transition:
|
||||
transform 300ms ease-out,
|
||||
opacity 300ms linear;
|
||||
}
|
||||
|
||||
@ -518,8 +517,9 @@ optgroup {
|
||||
@keyframes radio-checked {
|
||||
from {
|
||||
transform: scale(1.5);
|
||||
} to {
|
||||
transform: scale(.5);
|
||||
}
|
||||
to {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,12 +528,13 @@ optgroup {
|
||||
background: var(--radio-checkbox-indicator-color);
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transition: opacity 300ms linear,
|
||||
transition:
|
||||
opacity 300ms linear,
|
||||
transform 300ms ease-in;
|
||||
}
|
||||
|
||||
label.tn-radio:has(> input[type="radio"]:checked)::after {
|
||||
transform: scale(.5);
|
||||
transform: scale(0.5);
|
||||
opacity: 1;
|
||||
transition: opacity 150ms linear;
|
||||
animation: radio-checked 200ms ease-out;
|
||||
@ -545,9 +546,8 @@ optgroup {
|
||||
label.tn-radio:has(> input[type="radio"]:disabled)::after,
|
||||
label.tn-checkbox:has(> input[type="checkbox"]:disabled)::before,
|
||||
label.tn-checkbox:has(> input[type="checkbox"]:disabled)::after {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Switches */
|
||||
@ -579,7 +579,8 @@ body a.tn-link:visited,
|
||||
font-weight: normal;
|
||||
text-decoration: underline;
|
||||
|
||||
transition: background-color 200ms ease-out,
|
||||
transition:
|
||||
background-color 200ms ease-out,
|
||||
box-shadow 200ms ease-out,
|
||||
color 300ms ease-out;
|
||||
}
|
||||
@ -596,7 +597,8 @@ body a.tn-link:hover,
|
||||
--background: var(--link-hover-background);
|
||||
color: var(--link-hover-color);
|
||||
|
||||
transition: background-color 100ms ease-in,
|
||||
transition:
|
||||
background-color 100ms ease-in,
|
||||
box-shadow 100ms ease-in,
|
||||
color 150ms ease-in;
|
||||
}
|
||||
@ -606,16 +608,18 @@ a.tn-link[href^="http://"]:not(.no-arrow)::after,
|
||||
a.tn-link[href^="https://"]:not(.no-arrow)::after,
|
||||
.use-tn-links a.external:not(.no-arrow)::after,
|
||||
.use-tn-links a[href^="http://"]:not(.no-arrow)::after,
|
||||
.use-tn-links a[href^="https://"]:not(.no-arrow)::after {
|
||||
.use-tn-links a[href^="https://"]:not(.no-arrow)::after
|
||||
{
|
||||
display: inline-block;
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes link-arrow-blink {
|
||||
from {
|
||||
opacity: 1;
|
||||
} to {
|
||||
opacity: .5;
|
||||
}
|
||||
to {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@ -624,7 +628,8 @@ a.tn-link:hover[href^="http://"]:not(.no-arrow)::after,
|
||||
a.tn-link:hover[href^="https://"]:not(.no-arrow)::after,
|
||||
.use-tn-links a:hover.external:not(.no-arrow)::after,
|
||||
.use-tn-links a:hover[href^="http://"]:not(.no-arrow)::after,
|
||||
.use-tn-links a:hover[href^="https://"]:not(.no-arrow)::after {
|
||||
.use-tn-links a:hover[href^="https://"]:not(.no-arrow)::after
|
||||
{
|
||||
animation: link-arrow-blink 500ms linear alternate infinite;
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@ div.editability-dropdown a.dropdown-item {
|
||||
}
|
||||
|
||||
.editability-dropdown .dropdown-item .description {
|
||||
opacity: .75;
|
||||
font-size: .85em;
|
||||
opacity: 0.75;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -39,7 +39,7 @@ div.editability-dropdown a.dropdown-item {
|
||||
|
||||
.attribute-list .add-new-attribute-button,
|
||||
.attribute-list .save-attributes-button {
|
||||
bottom: .3em;
|
||||
bottom: 0.3em;
|
||||
}
|
||||
|
||||
.attribute-list .save-attributes-button {
|
||||
@ -70,12 +70,12 @@ div.editability-dropdown a.dropdown-item {
|
||||
*/
|
||||
|
||||
.note-info-widget-table th {
|
||||
opacity: .65;
|
||||
opacity: 0.65;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:root .note-info-widget-table button.calculate-button {
|
||||
min-width: 0;
|
||||
padding: 4px 10px !important;
|
||||
font-size: .8em;
|
||||
font-size: 0.8em;
|
||||
}
|
@ -266,7 +266,9 @@ div.quick-search {
|
||||
padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left);
|
||||
}
|
||||
|
||||
div.quick-search, div.quick-search:hover, div.quick-search:focus-within {
|
||||
div.quick-search,
|
||||
div.quick-search:hover,
|
||||
div.quick-search:focus-within {
|
||||
/* Prevent changes to background and outline when the state of the input group changes */
|
||||
background: transparent;
|
||||
outline: none;
|
||||
@ -1086,7 +1088,7 @@ html body .dropdown-item[disabled] {
|
||||
background: transparent;
|
||||
padding: 1em 8px 14px 8px;
|
||||
text-transform: uppercase;
|
||||
font-size: .8em;
|
||||
font-size: 0.8em;
|
||||
letter-spacing: 1pt;
|
||||
color: var(--menu-item-group-header-color) !important;
|
||||
}
|
||||
@ -1826,13 +1828,13 @@ body.background-effects.zen #root-widget {
|
||||
--root-background: transparent;
|
||||
}
|
||||
|
||||
|
||||
/* Alert bar */
|
||||
|
||||
@keyframes alert-show {
|
||||
from {
|
||||
opacity: 0;
|
||||
} to {
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@ -1844,7 +1846,7 @@ body.background-effects.zen #root-widget {
|
||||
border-radius: 0;
|
||||
padding: 8px 16px;
|
||||
background: var(--alert-bar-background) !important;
|
||||
font-size: .9em;
|
||||
font-size: 0.9em;
|
||||
font-weight: normal;
|
||||
animation: alert-show 300ms ease-in;
|
||||
border-bottom: 2px solid #0000001c !important;
|
||||
@ -1866,7 +1868,7 @@ div.promoted-attributes-container {
|
||||
|
||||
div.promoted-attributes-container,
|
||||
div.promoted-attributes-container input {
|
||||
font-size: .9rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* A promoted attribute card */
|
||||
@ -1905,7 +1907,7 @@ div.promoted-attribute-cell > * {
|
||||
div.promoted-attribute-cell > label {
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
opacity: .75;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
div.promoted-attribute-cell:not(:has(input[type="checkbox"])) > label::after {
|
||||
|
@ -93,7 +93,6 @@
|
||||
"cancel": "Abbrechen",
|
||||
"ok": "OK",
|
||||
"deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert."
|
||||
|
||||
},
|
||||
"export": {
|
||||
"export_note_title": "Notiz exportieren",
|
||||
|
@ -43,7 +43,7 @@ function getDayNotesForMonth(req: Request) {
|
||||
AND attr.value LIKE '${month}%'`;
|
||||
|
||||
if (calendarRoot) {
|
||||
const rows = sql.getRows<{ date: string, noteId: string }>(query);
|
||||
const rows = sql.getRows<{ date: string; noteId: string }>(query);
|
||||
const result: Record<string, string> = {};
|
||||
for (const { date, noteId } of rows) {
|
||||
const note = becca.getNote(noteId);
|
||||
|
@ -7,9 +7,7 @@ import yaml from "js-yaml";
|
||||
import type { JsonObject } from "swagger-ui-express";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const etapiDocument = yaml.load(
|
||||
await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8")
|
||||
) as JsonObject;
|
||||
const etapiDocument = yaml.load(await readFile(join(__dirname, "../etapi/etapi.openapi.yaml"), "utf8")) as JsonObject;
|
||||
const apiDocument = JSON.parse(await readFile(join(__dirname, "api", "openapi.json"), "utf-8"));
|
||||
|
||||
function register(app: Application) {
|
||||
|
@ -35,7 +35,7 @@ export interface TriliumConfig {
|
||||
Session: {
|
||||
cookiePath: string;
|
||||
cookieMaxAge: number;
|
||||
}
|
||||
};
|
||||
Sync: {
|
||||
syncServerHost: string;
|
||||
syncServerTimeout: string;
|
||||
|
@ -408,8 +408,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree
|
||||
value: attr.value,
|
||||
isInheritable: false
|
||||
}).save();
|
||||
} else if (attr.name === "docName"
|
||||
|| (existingAttribute.noteId.startsWith("_help") && attr.name === "iconClass")) {
|
||||
} else if (attr.name === "docName" || (existingAttribute.noteId.startsWith("_help") && attr.name === "iconClass")) {
|
||||
if (existingAttribute.value !== attr.value) {
|
||||
existingAttribute.value = attr.value ?? "";
|
||||
console.log("Updating attribute ", attrId);
|
||||
|
@ -78,7 +78,7 @@ export default function buildLaunchBarConfig() {
|
||||
{ id: "_lbProtectedSession", title: t("hidden-subtree.protected-session-title"), type: "launcher", builtinWidget: "protectedSession", icon: "bx bx bx-shield-quarter" },
|
||||
{ id: "_lbSyncStatus", title: t("hidden-subtree.sync-status-title"), type: "launcher", builtinWidget: "syncStatus", icon: "bx bx-wifi" },
|
||||
{ id: "_lbSettings", title: t("hidden-subtree.settings-title"), type: "launcher", command: "showOptions", icon: "bx bx-cog" }
|
||||
]
|
||||
];
|
||||
|
||||
const mobileAvailableLaunchers: HiddenSubtreeItem[] = [
|
||||
{ id: "_lbMobileNewNote", ...sharedLaunchers.newNote },
|
||||
@ -98,5 +98,5 @@ export default function buildLaunchBarConfig() {
|
||||
desktopVisibleLaunchers,
|
||||
mobileAvailableLaunchers,
|
||||
mobileVisibleLaunchers
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -153,20 +153,22 @@ function sanitize(dirtyHtml: string) {
|
||||
},
|
||||
allowedStyles: {
|
||||
"*": {
|
||||
"color": colorRegex,
|
||||
color: colorRegex,
|
||||
"background-color": colorRegex
|
||||
},
|
||||
"figure": {
|
||||
"float": [ /^\s*(left|right|none)\s*$/ ],
|
||||
"width": sizeRegex,
|
||||
"height": sizeRegex
|
||||
figure: {
|
||||
float: [/^\s*(left|right|none)\s*$/],
|
||||
width: sizeRegex,
|
||||
height: sizeRegex
|
||||
},
|
||||
"table": {
|
||||
table: {
|
||||
"border-color": colorRegex,
|
||||
"border-style": [/^\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*$/]
|
||||
},
|
||||
"td": {
|
||||
"border": [ /^\s*\d+(?:px|em|%)\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*(#(0x)?[0-9a-fA-F]+|rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)|hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\))\s*$/ ]
|
||||
td: {
|
||||
border: [
|
||||
/^\s*\d+(?:px|em|%)\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*(#(0x)?[0-9a-fA-F]+|rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)|hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\))\s*$/
|
||||
]
|
||||
}
|
||||
},
|
||||
allowedSchemes: ALLOWED_PROTOCOLS,
|
||||
|
@ -20,18 +20,22 @@ async function testImport(fileName: string, mimetype: string) {
|
||||
codeImportedAsCode: true
|
||||
});
|
||||
|
||||
return new Promise<{ buffer: Buffer, importedNote: BNote }>((resolve, reject) => {
|
||||
return new Promise<{ buffer: Buffer; importedNote: BNote }>((resolve, reject) => {
|
||||
cls.init(async () => {
|
||||
const rootNote = becca.getNote("root");
|
||||
if (!rootNote) {
|
||||
reject("Missing root note.");
|
||||
}
|
||||
|
||||
const importedNote = single.importSingleFile(taskContext, {
|
||||
const importedNote = single.importSingleFile(
|
||||
taskContext,
|
||||
{
|
||||
originalname: fileName,
|
||||
mimetype,
|
||||
buffer: buffer
|
||||
}, rootNote as BNote);
|
||||
},
|
||||
rootNote as BNote
|
||||
);
|
||||
resolve({
|
||||
buffer,
|
||||
importedNote
|
||||
@ -85,4 +89,4 @@ describe("processNoteContent", () => {
|
||||
expect(importedNote.mime).toBe("text/html");
|
||||
expect(importedNote.getContent().toString()).toBe("<h2>Hello world</h2>\n<p>Plain text goes here.</p>\n");
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@ -61,4 +61,4 @@ describe("processNoteContent", () => {
|
||||
const htmlNote = rootNote.children.find((ch) => ch.title === "IREN Reports Q2 FY25 Results");
|
||||
expect(htmlNote?.getContent().toString().substring(0, 4)).toEqual("<div");
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@ -3,39 +3,34 @@ import { parseNoteMeta } from "./in_app_help.js";
|
||||
import type NoteMeta from "./meta/note_meta.js";
|
||||
|
||||
describe("In-app help", () => {
|
||||
|
||||
it("preserves custom folder icon", () => {
|
||||
const meta: NoteMeta = {
|
||||
"isClone": false,
|
||||
"noteId": "yoAe4jV2yzbd",
|
||||
"notePath": [
|
||||
"OkOZllzB3fqN",
|
||||
"yoAe4jV2yzbd"
|
||||
],
|
||||
"title": "Features",
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
isClone: false,
|
||||
noteId: "yoAe4jV2yzbd",
|
||||
notePath: ["OkOZllzB3fqN", "yoAe4jV2yzbd"],
|
||||
title: "Features",
|
||||
notePosition: 40,
|
||||
prefix: null,
|
||||
isExpanded: false,
|
||||
type: "text",
|
||||
mime: "text/html",
|
||||
attributes: [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-star",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
type: "label",
|
||||
name: "iconClass",
|
||||
value: "bx bx-star",
|
||||
isInheritable: false,
|
||||
position: 10
|
||||
}
|
||||
],
|
||||
"format": "html",
|
||||
"attachments": [],
|
||||
"dirFileName": "Features",
|
||||
"children": []
|
||||
format: "html",
|
||||
attachments: [],
|
||||
dirFileName: "Features",
|
||||
children: []
|
||||
};
|
||||
|
||||
const item = parseNoteMeta(meta, "/");
|
||||
const icon = item.attributes?.find((a) => a.name === "iconClass");
|
||||
expect(icon?.value).toBe("bx bx-star");
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -66,8 +66,7 @@ export function parseNoteMeta(noteMeta: NoteMeta, docNameRoot: string): HiddenSu
|
||||
|
||||
// Handle text notes
|
||||
if (noteMeta.type === "text" && noteMeta.dataFileName) {
|
||||
const docPath = `${docNameRoot}/${path.basename(noteMeta.dataFileName, ".html")}`
|
||||
.substring(1);
|
||||
const docPath = `${docNameRoot}/${path.basename(noteMeta.dataFileName, ".html")}`.substring(1);
|
||||
item.attributes?.push({
|
||||
type: "label",
|
||||
name: "docName",
|
||||
@ -84,7 +83,7 @@ export function parseNoteMeta(noteMeta: NoteMeta, docNameRoot: string): HiddenSu
|
||||
if (noteMeta.children) {
|
||||
const children: HiddenSubtreeItem[] = [];
|
||||
for (const childMeta of noteMeta.children) {
|
||||
let newDocNameRoot = (noteMeta.dirFileName ? `${docNameRoot}/${noteMeta.dirFileName}` : docNameRoot);
|
||||
let newDocNameRoot = noteMeta.dirFileName ? `${docNameRoot}/${noteMeta.dirFileName}` : docNameRoot;
|
||||
children.push(parseNoteMeta(childMeta, newDocNameRoot));
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,6 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
||||
// Share settings
|
||||
redirectBareDomain: boolean;
|
||||
showLoginInShareTheme: boolean;
|
||||
|
||||
}
|
||||
|
||||
export type OptionNames = keyof OptionDefinitions;
|
||||
|
@ -14,11 +14,14 @@ import { default as parseInternal, type ParseOpts } from "./parse.js";
|
||||
|
||||
describe("Parser", () => {
|
||||
it("fulltext parser without content", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
expectExpression(rootExp.subExpressions[0], PropertyComparisonExp);
|
||||
const orExp = expectExpression(rootExp.subExpressions[2], OrExp);
|
||||
@ -27,11 +30,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("fulltext parser with content", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -45,11 +51,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label comparison", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#mylabel", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
const labelComparisonExp = expectExpression(rootExp.subExpressions[2], LabelComparisonExp);
|
||||
@ -59,11 +68,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple attribute negation", () => {
|
||||
let rootExp = parse({
|
||||
let rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#!mylabel"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
let notExp = expectExpression(rootExp.subExpressions[2], NotExp);
|
||||
@ -71,11 +83,14 @@ describe("Parser", () => {
|
||||
expect(attributeExistsExp.attributeType).toEqual("label");
|
||||
expect(attributeExistsExp.attributeName).toEqual("mylabel");
|
||||
|
||||
rootExp = parse({
|
||||
rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["~!myrelation"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
notExp = expectExpression(rootExp.subExpressions[2], NotExp);
|
||||
@ -85,11 +100,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label AND", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "and", "#second", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -101,11 +119,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label AND without explicit AND", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "#second", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -117,11 +138,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("simple label OR", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "or", "#second", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -132,11 +156,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("fulltext and simple label", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: tokens(["hello"]),
|
||||
expressionTokens: tokens(["#mylabel", "=", "text"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
const [firstSub, _, thirdSub, fourth] = expectSubexpressions(rootExp, PropertyComparisonExp, undefined, OrExp, LabelComparisonExp);
|
||||
|
||||
@ -149,11 +176,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("label sub-expression", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", "=", "text", "or", ["#second", "=", "text", "and", "#third", "=", "text"]]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -168,11 +198,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("label sub-expression without explicit operator", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -189,11 +222,14 @@ describe("Parser", () => {
|
||||
});
|
||||
|
||||
it("parses limit without order by", () => {
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext({ limit: 2 })
|
||||
}, OrderByAndLimitExp);
|
||||
},
|
||||
OrderByAndLimitExp
|
||||
);
|
||||
|
||||
expect(rootExp.limit).toBe(2);
|
||||
expect(rootExp.subExpression).toBeInstanceOf(AndExp);
|
||||
@ -236,7 +272,8 @@ describe("Invalid expressions", () => {
|
||||
|
||||
expect(searchContext.error).toEqual(`Error near token "note" in "#first = note.relations.second", it's possible to compare with constant only.`);
|
||||
|
||||
const rootExp = parse({
|
||||
const rootExp = parse(
|
||||
{
|
||||
fulltextTokens: [],
|
||||
expressionTokens: [
|
||||
{ token: "#first", inQuotes: false },
|
||||
@ -244,7 +281,9 @@ describe("Invalid expressions", () => {
|
||||
{ token: "#second", inQuotes: true }
|
||||
],
|
||||
searchContext: new SearchContext()
|
||||
}, AndExp);
|
||||
},
|
||||
AndExp
|
||||
);
|
||||
|
||||
assertIsArchived(rootExp.subExpressions[0]);
|
||||
|
||||
@ -327,16 +366,13 @@ function expectExpression<T extends Expression>(exp: Expression, type: ClassType
|
||||
* @param fourthType the type of the fourth subexpression.
|
||||
* @returns an array of all the subexpressions (in order) typecasted to their expected type.
|
||||
*/
|
||||
function expectSubexpressions<FirstT extends Expression,
|
||||
SecondT extends Expression,
|
||||
ThirdT extends Expression,
|
||||
FourthT extends Expression>(
|
||||
function expectSubexpressions<FirstT extends Expression, SecondT extends Expression, ThirdT extends Expression, FourthT extends Expression>(
|
||||
exp: AndExp,
|
||||
firstType: ClassType<FirstT>,
|
||||
secondType?: ClassType<SecondT>,
|
||||
thirdType?: ClassType<ThirdT>,
|
||||
fourthType?: ClassType<FourthT>): [ FirstT, SecondT, ThirdT, FourthT ]
|
||||
{
|
||||
fourthType?: ClassType<FourthT>
|
||||
): [FirstT, SecondT, ThirdT, FourthT] {
|
||||
expectExpression(exp.subExpressions[0], firstType);
|
||||
if (secondType) {
|
||||
expectExpression(exp.subExpressions[1], secondType);
|
||||
@ -347,10 +383,5 @@ function expectSubexpressions<FirstT extends Expression,
|
||||
if (fourthType) {
|
||||
expectExpression(exp.subExpressions[3], fourthType);
|
||||
}
|
||||
return [
|
||||
exp.subExpressions[0] as FirstT,
|
||||
exp.subExpressions[1] as SecondT,
|
||||
exp.subExpressions[2] as ThirdT,
|
||||
exp.subExpressions[3] as FourthT
|
||||
]
|
||||
return [exp.subExpressions[0] as FirstT, exp.subExpressions[1] as SecondT, exp.subExpressions[2] as ThirdT, exp.subExpressions[3] as FourthT];
|
||||
}
|
||||
|
@ -427,7 +427,7 @@ export interface ParseOpts {
|
||||
fulltextTokens: TokenData[];
|
||||
expressionTokens: TokenStructure;
|
||||
searchContext: SearchContext;
|
||||
originalQuery?: string
|
||||
originalQuery?: string;
|
||||
}
|
||||
|
||||
function parse({ fulltextTokens, expressionTokens, searchContext }: ParseOpts) {
|
||||
|
@ -3,8 +3,7 @@ import BTask from "../becca/entities/btask.js";
|
||||
import type { TaskRow } from "../becca/entities/rows.js";
|
||||
|
||||
export function getTasks(parentNoteId: string) {
|
||||
return becca.getTasks()
|
||||
.filter((task) => task.parentNoteId === parentNoteId && !task.isDone);
|
||||
return becca.getTasks().filter((task) => task.parentNoteId === parentNoteId && !task.isDone);
|
||||
}
|
||||
|
||||
interface CreateTaskParams {
|
||||
@ -19,7 +18,7 @@ export function createNewTask(params: CreateTaskParams) {
|
||||
|
||||
return {
|
||||
task
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleTaskDone(taskId: string) {
|
||||
|
@ -33,7 +33,7 @@ function getTrayIconPath() {
|
||||
}
|
||||
|
||||
function getIconPath(name: string) {
|
||||
const suffix = (!isMac && nativeTheme.shouldUseDarkColors ? "-inverted" : "");
|
||||
const suffix = !isMac && nativeTheme.shouldUseDarkColors ? "-inverted" : "";
|
||||
return path.join(path.dirname(fileURLToPath(import.meta.url)), "../..", "images", "app-icons", "tray", `${name}Template${suffix}.png`);
|
||||
}
|
||||
|
||||
@ -110,7 +110,6 @@ function updateTrayMenu() {
|
||||
click: () => openInSameTab(bookmarkNote)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return menuItems;
|
||||
@ -139,7 +138,7 @@ function updateTrayMenu() {
|
||||
type: "normal",
|
||||
sublabel: formatter.format(date),
|
||||
click: () => openInSameTab(recentNote)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return menuItems;
|
||||
|
@ -12,11 +12,13 @@ describe("Tree", () => {
|
||||
beforeEach(() => {
|
||||
becca.reset();
|
||||
|
||||
rootNote = new NoteBuilder(new BNote({
|
||||
rootNote = new NoteBuilder(
|
||||
new BNote({
|
||||
noteId: "root",
|
||||
title: "root",
|
||||
type: "text"
|
||||
}))
|
||||
})
|
||||
);
|
||||
new BBranch({
|
||||
branchId: "none_root",
|
||||
noteId: "root",
|
||||
@ -34,13 +36,15 @@ describe("Tree", () => {
|
||||
replace: () => {},
|
||||
getMap: () => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./sql_init.js", () => {
|
||||
return {
|
||||
dbReady: () => { console.log("Hello world") }
|
||||
dbReady: () => {
|
||||
console.log("Hello world");
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@ -57,7 +61,7 @@ describe("Tree", () => {
|
||||
rootNote.child(note(String(i)));
|
||||
}
|
||||
|
||||
const expectedOrder = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ];
|
||||
const expectedOrder = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
||||
|
||||
// Sort a few times to ensure that the resulting order is the same.
|
||||
for (let i = 0; i < 5; i++) {
|
||||
|
@ -4,24 +4,20 @@ import utils from "./utils.js";
|
||||
type TestCase<T extends (...args: any) => any> = [desc: string, fnParams: Parameters<T>, expected: ReturnType<T>];
|
||||
|
||||
describe("#newEntityId", () => {
|
||||
|
||||
it("should return a string with a length of 12", () => {
|
||||
const result = utils.newEntityId();
|
||||
expect(result).toBeTypeOf("string");
|
||||
expect(result).toHaveLength(12);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("#randomString", () => {
|
||||
|
||||
it("should return a string with a length as per argument", () => {
|
||||
const stringLength = 5;
|
||||
const result = utils.randomString(stringLength);
|
||||
expect(result).toBeTypeOf("string");
|
||||
expect(result).toHaveLength(stringLength);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// TriliumNextTODO: should use mocks and assert that functions get called
|
||||
@ -64,7 +60,6 @@ describe.todo("#hmac", () => {});
|
||||
describe.todo("#hash", () => {});
|
||||
|
||||
describe("#isEmptyOrWhitespace", () => {
|
||||
|
||||
const testCases: TestCase<typeof utils.isEmptyOrWhitespace>[] = [
|
||||
["w/ 'null' it should return true", [null], true],
|
||||
["w/ 'null' it should return true", [null], true],
|
||||
@ -72,21 +67,19 @@ describe("#isEmptyOrWhitespace", () => {
|
||||
["w/ empty string '' it should return true", [""], true],
|
||||
["w/ single whitespace string ' ' it should return true", [" "], true],
|
||||
["w/ multiple whitespace string ' ' it should return true", [" "], true],
|
||||
["w/ non-empty string ' t ' it should return false", [" t "], false],
|
||||
["w/ non-empty string ' t ' it should return false", [" t "], false]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.isEmptyOrWhitespace(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#sanitizeSqlIdentifier", () => {
|
||||
|
||||
const testCases: TestCase<typeof utils.sanitizeSqlIdentifier>[] = [
|
||||
["w/ 'test' it should not strip anything", ["test"], "test"],
|
||||
["w/ 'test123' it should not strip anything", ["test123"], "test123"],
|
||||
@ -95,17 +88,16 @@ describe("#sanitizeSqlIdentifier", () => {
|
||||
["w/ 'test-' it should strip the '-'", ["test-"], "test"],
|
||||
["w/ 'test-test' it should strip the '-'", ["test-test"], "testtest"],
|
||||
["w/ 'test; --test' it should strip the '; --'", ["test; --test"], "testtest"],
|
||||
["w/ 'test test' it should strip the ' '", ["test test"], "testtest"],
|
||||
["w/ 'test test' it should strip the ' '", ["test test"], "testtest"]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.sanitizeSqlIdentifier(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("#escapeHtml", () => {
|
||||
@ -122,21 +114,23 @@ describe("#unescapeHtml", () => {
|
||||
|
||||
describe("#toObject", () => {
|
||||
it("should return an object with keys and value being set from the supplied Function", () => {
|
||||
type TestListEntry = { testPropA: string, testPropB: string };
|
||||
type TestListEntry = { testPropA: string; testPropB: string };
|
||||
type TestListFn = (testListEntry: TestListEntry) => [string, string];
|
||||
const testList: [TestListEntry, TestListEntry] = [{ testPropA: "keyA", testPropB: "valueA" }, { testPropA: "keyB", testPropB: "valueB" }];
|
||||
const testList: [TestListEntry, TestListEntry] = [
|
||||
{ testPropA: "keyA", testPropB: "valueA" },
|
||||
{ testPropA: "keyB", testPropB: "valueB" }
|
||||
];
|
||||
const fn: TestListFn = (testListEntry: TestListEntry) => [testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn"];
|
||||
|
||||
const result = utils.toObject(testList, fn);
|
||||
expect(result).toStrictEqual({
|
||||
"keyA_fn": "valueA_fn",
|
||||
"keyB_fn": "valueB_fn"
|
||||
keyA_fn: "valueA_fn",
|
||||
keyB_fn: "valueB_fn"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#stripTags", () => {
|
||||
|
||||
//prettier-ignore
|
||||
const htmlWithNewlines =
|
||||
`<p>abc
|
||||
@ -146,15 +140,15 @@ def</p>
|
||||
const testCases: TestCase<typeof utils.stripTags>[] = [
|
||||
["should strip all tags and only return the content, leaving new lines and spaces in tact", [htmlWithNewlines], "abc\ndef\nghi"],
|
||||
//TriliumNextTODO: should this actually insert a space between content to prevent concatenated text?
|
||||
["should strip all tags and only return the content", ["<h1>abc</h1><p>def</p>"], "abcdef"],
|
||||
["should strip all tags and only return the content", ["<h1>abc</h1><p>def</p>"], "abcdef"]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.stripTags(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -233,7 +227,7 @@ describe("#isStringNote", () => {
|
||||
],
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.isStringNote(...fnParams);
|
||||
@ -252,17 +246,16 @@ describe("#removeTextFileExtension", () => {
|
||||
["w/ 'test.markdown' it should strip '.markdown'", ["test.markdown"], "test"],
|
||||
["w/ 'test.html' it should strip '.html'", ["test.html"], "test"],
|
||||
["w/ 'test.htm' it should strip '.htm'", ["test.htm"], "test"],
|
||||
["w/ 'test.zip' it should NOT strip '.zip'", ["test.zip"], "test.zip"],
|
||||
["w/ 'test.zip' it should NOT strip '.zip'", ["test.zip"], "test.zip"]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.removeTextFileExtension(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("#getNoteTitle", () => {
|
||||
@ -330,7 +323,6 @@ describe("#getNoteTitle", () => {
|
||||
});
|
||||
|
||||
describe("#timeLimit", () => {
|
||||
|
||||
it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => {
|
||||
const resolvedValue = `resolved: ${new Date().toISOString()}`;
|
||||
const testPromise = new Promise((res, rej) => {
|
||||
@ -350,7 +342,7 @@ describe("#timeLimit", () => {
|
||||
rej(rejectedValue);
|
||||
}, 100);
|
||||
});
|
||||
await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue)
|
||||
await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue);
|
||||
});
|
||||
|
||||
it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => {
|
||||
@ -360,7 +352,7 @@ describe("#timeLimit", () => {
|
||||
}, 500);
|
||||
//rej("rejected!");
|
||||
});
|
||||
await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`)
|
||||
await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`);
|
||||
});
|
||||
|
||||
it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => {
|
||||
@ -371,42 +363,40 @@ describe("#timeLimit", () => {
|
||||
}, 500);
|
||||
//rej("rejected!");
|
||||
});
|
||||
await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg)
|
||||
await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg);
|
||||
});
|
||||
|
||||
// TriliumNextTODO: since TS avoids this from ever happening – do we need this check?
|
||||
it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => {
|
||||
//@ts-expect-error - passing in illegal type 'undefined'
|
||||
expect(utils.timeLimit(undefined, 200)).toBe(undefined)
|
||||
expect(utils.timeLimit(undefined, 200)).toBe(undefined);
|
||||
});
|
||||
|
||||
// TriliumNextTODO: since TS avoids this from ever happening – do we need this check?
|
||||
it("when the passed promise is not a promise, it should return the passed value", async () => {
|
||||
//@ts-expect-error - passing in illegal type 'object'
|
||||
expect(utils.timeLimit({test: 1}, 200)).toStrictEqual({test: 1})
|
||||
expect(utils.timeLimit({ test: 1 }, 200)).toStrictEqual({ test: 1 });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("#deferred", () => {
|
||||
it("should return a promise", () => {
|
||||
const result = utils.deferred();
|
||||
expect(result).toBeInstanceOf(Promise)
|
||||
})
|
||||
expect(result).toBeInstanceOf(Promise);
|
||||
});
|
||||
// TriliumNextTODO: Add further tests!
|
||||
});
|
||||
|
||||
describe("#removeDiacritic", () => {
|
||||
|
||||
const testCases: TestCase<typeof utils.removeDiacritic>[] = [
|
||||
["w/ 'Äpfel' it should replace the 'Ä'", ["Äpfel"], "Apfel"],
|
||||
["w/ 'Été' it should replace the 'É' and 'é'", ["Été"], "Ete"],
|
||||
["w/ 'Fête' it should replace the 'ê'", ["Fête"], "Fete"],
|
||||
["w/ 'Αλφαβήτα' it should replace the 'ή'", ["Αλφαβήτα"], "Αλφαβητα"],
|
||||
["w/ '' (empty string) it should return empty string", [""], ""],
|
||||
["w/ '' (empty string) it should return empty string", [""], ""]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.removeDiacritic(...fnParams);
|
||||
@ -415,25 +405,22 @@ describe("#removeDiacritic", () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("#normalize", () => {
|
||||
|
||||
const testCases: TestCase<typeof utils.normalize>[] = [
|
||||
["w/ 'Äpfel' it should replace the 'Ä' and return lowercased", ["Äpfel"], "apfel"],
|
||||
["w/ 'Été' it should replace the 'É' and 'é' and return lowercased", ["Été"], "ete"],
|
||||
["w/ 'FêTe' it should replace the 'ê' and return lowercased", ["FêTe"], "fete"],
|
||||
["w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", ["ΑλΦαβήΤα"], "αλφαβητα"],
|
||||
["w/ '' (empty string) it should return empty string", [""], ""],
|
||||
["w/ '' (empty string) it should return empty string", [""], ""]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.normalize(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("#toMap", () => {
|
||||
@ -475,10 +462,10 @@ describe("#envToBoolean", () => {
|
||||
["w/ ' ' (white space string) it should return undefined", [" "], undefined],
|
||||
["w/ undefined it should return undefined", [undefined], undefined],
|
||||
//@ts-expect-error - pass wrong type as param
|
||||
["w/ number 1 it should return undefined", [1], undefined],
|
||||
["w/ number 1 it should return undefined", [1], undefined]
|
||||
];
|
||||
|
||||
testCases.forEach(testCase => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [desc, fnParams, expected] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.envToBoolean(...fnParams);
|
||||
@ -514,7 +501,6 @@ describe("#isDev", () => {
|
||||
});
|
||||
|
||||
describe("#formatDownloadTitle", () => {
|
||||
|
||||
//prettier-ignore
|
||||
const testCases: [fnValue: Parameters<typeof utils.formatDownloadTitle>, expectedValue: ReturnType<typeof utils.formatDownloadTitle>][] = [
|
||||
|
||||
|
@ -269,5 +269,4 @@
|
||||
"new-note": "Neue Notiz",
|
||||
"show-windows": "Fenster anzeigen"
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user