mirror of
https://github.com/zadam/trilium.git
synced 2026-02-17 20:07:01 +01:00
Merge branch 'main' into siriusbcd_close_split
This commit is contained in:
@@ -91,6 +91,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends");
|
||||
const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers");
|
||||
const [ calendarView, setCalendarView ] = useNoteLabel(note, "calendar:view");
|
||||
const [ initialDate ] = useNoteLabel(note, "calendar:initialDate");
|
||||
const initialView = useRef(calendarView);
|
||||
const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current));
|
||||
useResizeObserver(containerRef, () => calendarRef.current?.updateSize());
|
||||
@@ -134,6 +135,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
height="90%"
|
||||
nowIndicator
|
||||
handleWindowResize={false}
|
||||
initialDate={initialDate || undefined}
|
||||
locale={locale}
|
||||
{...editingProps}
|
||||
eventDidMount={eventDidMount}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useCallback, useEffect, useRef } from "preact/hooks";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
import { MindElixirData, MindElixirInstance, Operation, default as VanillaMindElixir } from "mind-elixir";
|
||||
import { MindElixirData, MindElixirInstance, Operation, Options, default as VanillaMindElixir } from "mind-elixir";
|
||||
import { HTMLAttributes, RefObject } from "preact";
|
||||
// allow node-menu plugin css to be bundled by webpack
|
||||
import nodeMenu from "@mind-elixir/node-menu";
|
||||
import "mind-elixir/style";
|
||||
import "@mind-elixir/node-menu/dist/style.css";
|
||||
import "./MindMap.css";
|
||||
import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents } from "../react/hooks";
|
||||
import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks";
|
||||
import { refToJQuerySelector } from "../react/react_utils";
|
||||
import utils from "../../services/utils";
|
||||
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
const NEW_TOPIC_NAME = "";
|
||||
|
||||
@@ -21,6 +22,24 @@ interface MindElixirProps {
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, Options["locale"] | null> = {
|
||||
ar: null,
|
||||
cn: "zh_CN",
|
||||
de: null,
|
||||
en: "en",
|
||||
en_rtl: "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
it: "it",
|
||||
ja: "ja",
|
||||
pt: "pt",
|
||||
pt_br: "pt",
|
||||
ro: null,
|
||||
ru: "ru",
|
||||
tw: "zh_TW",
|
||||
uk: null
|
||||
};
|
||||
|
||||
export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
const apiRef = useRef<MindElixirInstance>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -110,12 +129,14 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef: externalApiRef, onChange, editable }: MindElixirProps) {
|
||||
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
|
||||
const apiRef = useRef<MindElixirInstance>(null);
|
||||
const [ locale ] = useTriliumOption("locale");
|
||||
|
||||
function reinitialize() {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const mind = new VanillaMindElixir({
|
||||
el: containerRef.current,
|
||||
locale: LOCALE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined,
|
||||
editable
|
||||
});
|
||||
|
||||
@@ -143,7 +164,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
|
||||
if (data) {
|
||||
apiRef.current?.init(data);
|
||||
}
|
||||
}, [ editable ]);
|
||||
}, [ editable, locale ]);
|
||||
|
||||
// On change listener.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||
import { TypeWidgetProps } from "../type_widget";
|
||||
import "@excalidraw/excalidraw/index.css";
|
||||
import { useNoteLabelBoolean } from "../../react/hooks";
|
||||
import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
|
||||
import { useCallback, useMemo, useRef } from "preact/hooks";
|
||||
import { type ExcalidrawImperativeAPI, type AppState } from "@excalidraw/excalidraw/types";
|
||||
import options from "../../../services/options";
|
||||
@@ -9,6 +9,8 @@ import "./Canvas.css";
|
||||
import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
|
||||
import { goToLinkExt } from "../../../services/link";
|
||||
import useCanvasPersistence from "./persistence";
|
||||
import { LANGUAGE_MAPPINGS } from "./i18n";
|
||||
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
// currently required by excalidraw, in order to allows self-hosting fonts locally.
|
||||
// this avoids making excalidraw load the fonts from an external CDN.
|
||||
@@ -21,6 +23,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
|
||||
const documentStyle = window.getComputedStyle(document.documentElement);
|
||||
return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"];
|
||||
}, []);
|
||||
const [ locale ] = useTriliumOption("locale");
|
||||
const persistence = useCanvasPersistence(note, noteContext, apiRef, themeStyle, isReadOnly);
|
||||
|
||||
/** Use excalidraw's native zoom instead of the global zoom. */
|
||||
@@ -58,6 +61,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
|
||||
detectScroll={false}
|
||||
handleKeyboardGlobally={false}
|
||||
autoFocus={false}
|
||||
langCode={LANGUAGE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined}
|
||||
UIOptions={{
|
||||
canvasActions: {
|
||||
saveToActiveFile: false,
|
||||
|
||||
29
apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
Normal file
29
apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { LOCALES } from "@triliumnext/commons";
|
||||
import { readdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { LANGUAGE_MAPPINGS } from "./i18n.js";
|
||||
|
||||
const localeDir = join(__dirname, "../../../../../../node_modules/@excalidraw/excalidraw/dist/prod/locales");
|
||||
|
||||
describe("Canvas i18n", () => {
|
||||
it("all languages are mapped correctly", () => {
|
||||
// Read the node_modules dir to obtain all the supported locales.
|
||||
const supportedLanguageCodes = new Set<string>();
|
||||
for (const file of readdirSync(localeDir)) {
|
||||
if (file.startsWith("percentages")) continue;
|
||||
const match = file.match("^[a-z]{2,3}(?:-[A-Z]{2,3})?");
|
||||
if (!match) continue;
|
||||
supportedLanguageCodes.add(match[0]);
|
||||
}
|
||||
|
||||
// Cross-check the locales.
|
||||
for (const locale of LOCALES) {
|
||||
if (locale.contentOnly || locale.devOnly) continue;
|
||||
const languageCode = LANGUAGE_MAPPINGS[locale.id];
|
||||
if (!supportedLanguageCodes.has(languageCode)) {
|
||||
expect.fail(`Unable to find locale for ${locale.id} -> ${languageCode}.`)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
19
apps/client/src/widgets/type_widgets/canvas/i18n.ts
Normal file
19
apps/client/src/widgets/type_widgets/canvas/i18n.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
export const LANGUAGE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, string | null> = {
|
||||
ar: "ar-SA",
|
||||
cn: "zh-CN",
|
||||
de: "de-DE",
|
||||
en: "en",
|
||||
en_rtl: "en",
|
||||
es: "es-ES",
|
||||
fr: "fr-FR",
|
||||
it: "it-IT",
|
||||
ja: "ja-JP",
|
||||
pt: "pt-PT",
|
||||
pt_br: "pt-BR",
|
||||
ro: "ro-RO",
|
||||
ru: "ru-RU",
|
||||
tw: "zh-TW",
|
||||
uk: "uk-UA"
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat";
|
||||
import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor, TemplateDefinition } from "@triliumnext/ckeditor5";
|
||||
import { buildConfig, BuildEditorOptions } from "./config";
|
||||
import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef } from "../../react/hooks";
|
||||
import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef, useTriliumOption } from "../../react/hooks";
|
||||
import link from "../../../services/link";
|
||||
import froca from "../../../services/froca";
|
||||
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
export type BoxSize = "small" | "medium" | "full";
|
||||
|
||||
@@ -37,6 +38,7 @@ interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "cla
|
||||
export default function CKEditorWithWatchdog({ containerRef: externalContainerRef, content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi, templates }: CKEditorWithWatchdogProps) {
|
||||
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
|
||||
const watchdogRef = useRef<EditorWatchdog>(null);
|
||||
const [ uiLanguage ] = useTriliumOption("locale");
|
||||
const [ editor, setEditor ] = useState<CKTextEditor>();
|
||||
const { parentComponent } = useNoteContext();
|
||||
|
||||
@@ -156,6 +158,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
|
||||
const editor = await buildEditor(container, !!isClassicEditor, {
|
||||
forceGplLicense: false,
|
||||
isClassicEditor: !!isClassicEditor,
|
||||
uiLanguage: uiLanguage as DISPLAYABLE_LOCALE_IDS,
|
||||
contentLanguage: contentLanguage ?? null,
|
||||
templates
|
||||
});
|
||||
@@ -180,7 +183,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
|
||||
watchdog.create(container);
|
||||
|
||||
return () => watchdog.destroy();
|
||||
}, [ contentLanguage, templates ]);
|
||||
}, [ contentLanguage, templates, uiLanguage ]);
|
||||
|
||||
// React to content changes.
|
||||
useEffect(() => editor?.setData(content ?? ""), [ editor, content ]);
|
||||
|
||||
39
apps/client/src/widgets/type_widgets/text/config.spec.ts
Normal file
39
apps/client/src/widgets/type_widgets/text/config.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { DISPLAYABLE_LOCALE_IDS, LOCALES } from "@triliumnext/commons";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock('../../../services/options.js', () => ({
|
||||
default: {
|
||||
get(name: string) {
|
||||
if (name === "allowedHtmlTags") return "[]";
|
||||
return undefined;
|
||||
},
|
||||
getJson: () => []
|
||||
}
|
||||
}));
|
||||
|
||||
describe("CK config", () => {
|
||||
it("maps all languages correctly", async () => {
|
||||
const { buildConfig } = await import("./config.js");
|
||||
for (const locale of LOCALES) {
|
||||
if (locale.contentOnly || locale.devOnly) continue;
|
||||
|
||||
const config = await buildConfig({
|
||||
uiLanguage: locale.id as DISPLAYABLE_LOCALE_IDS,
|
||||
contentLanguage: locale.id,
|
||||
forceGplLicense: false,
|
||||
isClassicEditor: false,
|
||||
templates: []
|
||||
});
|
||||
|
||||
let expectedLocale = locale.id.substring(0, 2);
|
||||
if (expectedLocale === "cn") expectedLocale = "zh";
|
||||
if (expectedLocale === "tw") expectedLocale = "zh-tw";
|
||||
|
||||
if (locale.id !== "en") {
|
||||
expect((config.language as any).ui).toMatch(new RegExp(`^${expectedLocale}`));
|
||||
expect(config.translations, locale.id).toBeDefined();
|
||||
expect(config.translations, locale.id).toHaveLength(2);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ALLOWED_PROTOCOLS, MIME_TYPE_AUTO } from "@triliumnext/commons";
|
||||
import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
|
||||
import { ALLOWED_PROTOCOLS, DISPLAYABLE_LOCALE_IDS, MIME_TYPE_AUTO } from "@triliumnext/commons";
|
||||
import { buildExtraCommands, type EditorConfig, getCkLocale, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
|
||||
import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
|
||||
import options from "../../../services/options.js";
|
||||
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
|
||||
@@ -17,6 +17,7 @@ export const OPEN_SOURCE_LICENSE_KEY = "GPL";
|
||||
export interface BuildEditorOptions {
|
||||
forceGplLicense: boolean;
|
||||
isClassicEditor: boolean;
|
||||
uiLanguage: DISPLAYABLE_LOCALE_IDS;
|
||||
contentLanguage: string | null;
|
||||
templates: TemplateDefinition[];
|
||||
}
|
||||
@@ -161,9 +162,8 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
|
||||
htmlSupport: {
|
||||
allow: JSON.parse(options.get("allowedHtmlTags"))
|
||||
},
|
||||
// This value must be kept in sync with the language defined in webpack.config.js.
|
||||
language: "en",
|
||||
removePlugins: getDisabledPlugins()
|
||||
removePlugins: getDisabledPlugins(),
|
||||
...await getCkLocale(opts.uiLanguage)
|
||||
};
|
||||
|
||||
// Set up content language.
|
||||
|
||||
Reference in New Issue
Block a user