mirror of
https://github.com/zadam/trilium.git
synced 2026-03-22 20:01:42 +01:00
Compare commits
11 Commits
feat/add-m
...
totp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f123d50631 | ||
|
|
4f2fbc754d | ||
|
|
16083dbb85 | ||
|
|
929e5ee590 | ||
|
|
178a3b4318 | ||
|
|
d4810be0ab | ||
|
|
cf7ac03a70 | ||
|
|
446cc7ab5c | ||
|
|
d595a1d019 | ||
|
|
6004f35f66 | ||
|
|
aac5127e1f |
@@ -16,7 +16,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.24.1",
|
||||
"@redocly/cli": "2.24.0",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.4",
|
||||
"js-yaml": "4.1.1",
|
||||
|
||||
@@ -53,22 +53,22 @@
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.2",
|
||||
"globals": "17.4.0",
|
||||
"i18next": "25.10.3",
|
||||
"i18next": "25.8.18",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "4.0.0",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.40",
|
||||
"katex": "0.16.38",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.5",
|
||||
"marked": "17.0.4",
|
||||
"mermaid": "11.13.0",
|
||||
"mind-elixir": "5.9.3",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.29.0",
|
||||
"react-i18next": "16.6.0",
|
||||
"react-i18next": "16.5.8",
|
||||
"react-window": "2.2.7",
|
||||
"reveal.js": "6.0.0",
|
||||
"rrule": "2.8.1",
|
||||
|
||||
@@ -17,6 +17,9 @@ class SetupController {
|
||||
private syncServerHostInput: HTMLInputElement;
|
||||
private syncProxyInput: HTMLInputElement;
|
||||
private passwordInput: HTMLInputElement;
|
||||
private totpTokenInput: HTMLInputElement;
|
||||
private totpSection: HTMLElement;
|
||||
private totpEnabled = false;
|
||||
private sections: Record<SetupStep, HTMLElement>;
|
||||
|
||||
constructor(rootNode: HTMLElement, syncInProgress: boolean) {
|
||||
@@ -29,6 +32,8 @@ class SetupController {
|
||||
this.syncServerHostInput = mustGetElement("sync-server-host", HTMLInputElement);
|
||||
this.syncProxyInput = mustGetElement("sync-proxy", HTMLInputElement);
|
||||
this.passwordInput = mustGetElement("password", HTMLInputElement);
|
||||
this.totpTokenInput = mustGetElement("totp-token", HTMLInputElement);
|
||||
this.totpSection = mustGetElement("totp-section", HTMLElement);
|
||||
this.sections = {
|
||||
"setup-type": mustGetElement("setup-type-section", HTMLElement),
|
||||
"new-document-in-progress": mustGetElement("new-document-in-progress-section", HTMLElement),
|
||||
@@ -56,6 +61,10 @@ class SetupController {
|
||||
});
|
||||
}
|
||||
|
||||
this.syncServerHostInput.addEventListener("blur", () => {
|
||||
void this.checkTotpStatus();
|
||||
});
|
||||
|
||||
for (const backButton of document.querySelectorAll<HTMLElement>("[data-action='back']")) {
|
||||
backButton.addEventListener("click", () => {
|
||||
this.back();
|
||||
@@ -87,9 +96,40 @@ class SetupController {
|
||||
}
|
||||
}
|
||||
|
||||
private async checkTotpStatus() {
|
||||
const syncServerHost = this.syncServerHostInput.value.trim();
|
||||
|
||||
if (!syncServerHost) {
|
||||
this.setTotpEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await $.post("api/setup/check-server-totp", {
|
||||
syncServerHost
|
||||
});
|
||||
|
||||
this.setTotpEnabled(!!resp.totpEnabled);
|
||||
} catch {
|
||||
// If we can't reach the server, don't show the TOTP field yet.
|
||||
this.setTotpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private setTotpEnabled(enabled: boolean) {
|
||||
this.totpEnabled = enabled;
|
||||
|
||||
if (!enabled) {
|
||||
this.totpTokenInput.value = "";
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
private back() {
|
||||
this.setStep("setup-type");
|
||||
this.setupType = "";
|
||||
this.setTotpEnabled(false);
|
||||
|
||||
for (const input of this.setupTypeInputs) {
|
||||
input.checked = false;
|
||||
@@ -113,11 +153,21 @@ class SetupController {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.checkTotpStatus();
|
||||
|
||||
const totpToken = this.totpTokenInput.value.trim();
|
||||
|
||||
if (this.totpEnabled && !totpToken) {
|
||||
showAlert("TOTP token can't be empty when two-factor authentication is enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// not using server.js because it loads too many dependencies
|
||||
const resp = await $.post("api/setup/sync-from-server", {
|
||||
syncServerHost,
|
||||
syncProxy,
|
||||
password
|
||||
password,
|
||||
totpToken
|
||||
});
|
||||
|
||||
if (resp.result === "success") {
|
||||
@@ -139,13 +189,10 @@ class SetupController {
|
||||
section.style.display = step === this.step ? "" : "none";
|
||||
}
|
||||
|
||||
this.totpSection.style.display = this.totpEnabled ? "" : "none";
|
||||
this.setupTypeNextButton.disabled = !this.setupType;
|
||||
}
|
||||
|
||||
private getSelectedSetupType(): SetupType {
|
||||
return (this.setupTypeInputs.find((input) => input.checked)?.value ?? "") as SetupType;
|
||||
}
|
||||
|
||||
private startSyncPolling() {
|
||||
if (this.syncPollIntervalId !== null) {
|
||||
return;
|
||||
@@ -196,7 +243,7 @@ function mustGetElement<T extends typeof HTMLElement>(id: string, ctor: T): Inst
|
||||
return element as InstanceType<T>;
|
||||
}
|
||||
|
||||
addEventListener("DOMContentLoaded", (event) => {
|
||||
addEventListener("DOMContentLoaded", () => {
|
||||
const rootNode = document.getElementById("setup-dialog");
|
||||
if (!rootNode || !(rootNode instanceof HTMLElement)) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Σχετικά με το Trilium Notes",
|
||||
"title": "Πληροφορίες για το Trilium Notes",
|
||||
"homepage": "Αρχική Σελίδα:",
|
||||
"app_version": "Έκδοση εφαρμογής:",
|
||||
"db_version": "Έκδοση βάσης δεδομένων:",
|
||||
|
||||
@@ -1180,8 +1180,7 @@
|
||||
"is_owned_by_note": "ノートによって所有されています",
|
||||
"and_more": "...その他 {{count}} 件。",
|
||||
"print_landscape": "PDF にエクスポートするときに、ページの向きを縦向きではなく横向きに変更します。",
|
||||
"print_page_size": "PDF にエクスポートするときに、ページのサイズを変更します。サポートされる値: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>。",
|
||||
"textarea": "複数行テキスト"
|
||||
"print_page_size": "PDF にエクスポートするときに、ページのサイズを変更します。サポートされる値: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>。"
|
||||
},
|
||||
"link_context_menu": {
|
||||
"open_note_in_popup": "クイック編集",
|
||||
|
||||
@@ -446,8 +446,7 @@
|
||||
"app_theme_base": "設定為 \"next\"、\"next-light \" 或 \"next-dark\",以使用相應的 TriliumNext 主題(自動、淺色或深色)作為自訂主題的基礎,而非傳統主題。",
|
||||
"print_landscape": "匯出為 PDF 時,將頁面方向更改為橫向而非縱向。",
|
||||
"print_page_size": "在匯出 PDF 時更改頁面大小。支援的值:<code>A0</code>、<code>A1</code>、<code>A2</code>、<code>A3</code>、<code>A4</code>、<code>A5</code>、<code>A6</code>、<code>Legal</code>、<code>Letter</code>、<code>Tabloid</code>、<code>Ledger</code>。",
|
||||
"color_type": "顏色",
|
||||
"textarea": "多行文字"
|
||||
"color_type": "顏色"
|
||||
},
|
||||
"attribute_editor": {
|
||||
"help_text_body1": "要新增標籤,只需輸入例如 <code>#rock</code> 或者如果您還想新增值,則例如 <code>#year = 2020</code>",
|
||||
@@ -2183,52 +2182,5 @@
|
||||
},
|
||||
"setup_form": {
|
||||
"more_info": "了解更多"
|
||||
},
|
||||
"media": {
|
||||
"play": "播放 (空白鍵)",
|
||||
"pause": "暫停 (空白鍵)",
|
||||
"back-10s": "往前 10 秒 (左方向鍵)",
|
||||
"forward-30s": "往後 30 秒",
|
||||
"mute": "靜音 (M)",
|
||||
"unmute": "解除靜音 (M)",
|
||||
"playback-speed": "播放速度",
|
||||
"loop": "循環",
|
||||
"disable-loop": "解除循環",
|
||||
"rotate": "旋轉",
|
||||
"picture-in-picture": "畫中畫",
|
||||
"exit-picture-in-picture": "退出畫中畫",
|
||||
"fullscreen": "全螢幕 (F)",
|
||||
"exit-fullscreen": "退出全螢幕",
|
||||
"unsupported-format": "此檔案格式不支援媒體預覽:\n{{mime}}",
|
||||
"zoom-to-fit": "放大至填滿畫面",
|
||||
"zoom-reset": "重設放大至填滿畫面"
|
||||
},
|
||||
"mermaid": {
|
||||
"placeholder": "請輸入您的美人魚圖表內容,或選用下方其中一個範例圖表。",
|
||||
"sample_diagrams": "範例圖表:",
|
||||
"sample_flowchart": "流程圖",
|
||||
"sample_class": "階層圖",
|
||||
"sample_sequence": "時序圖",
|
||||
"sample_entity_relationship": "實體關係圖",
|
||||
"sample_state": "狀態圖",
|
||||
"sample_mindmap": "心智圖",
|
||||
"sample_architecture": "架構圖",
|
||||
"sample_block": "區塊圖",
|
||||
"sample_c4": "C4 圖",
|
||||
"sample_gantt": "甘特圖",
|
||||
"sample_git": "Git 分支圖",
|
||||
"sample_kanban": "看板圖",
|
||||
"sample_packet": "數據包圖",
|
||||
"sample_pie": "圓餅圖",
|
||||
"sample_quadrant": "象限圖",
|
||||
"sample_radar": "雷達圖",
|
||||
"sample_requirement": "需求圖",
|
||||
"sample_sankey": "桑基圖",
|
||||
"sample_timeline": "時間軸",
|
||||
"sample_treemap": "樹狀圖",
|
||||
"sample_user_journey": "用戶旅程",
|
||||
"sample_xy": "XY 圖表",
|
||||
"sample_venn": "韋恩圖",
|
||||
"sample_ishikawa": "魚骨圖"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
|
||||
import type { Attribute } from "../../services/attribute_parser.js";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features.js";
|
||||
import { focusSavedElement, saveFocusedElement } from "../../services/focus.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import promotedAttributeDefinitionParser from "../../services/promoted_attribute_definition_parser.js";
|
||||
import server from "../../services/server.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import SpacedUpdate from "../../services/spaced_update.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import type { Attribute } from "../../services/attribute_parser.js";
|
||||
import { focusSavedElement, saveFocusedElement } from "../../services/focus.js";
|
||||
import { isExperimentalFeatureEnabled } from "../../services/experimental_features.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="attr-detail tn-tool-dialog">
|
||||
@@ -29,7 +29,6 @@ const TPL = /*html*/`
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
box-shadow: 10px 10px 93px -25px black;
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.attr-help td {
|
||||
@@ -344,7 +343,6 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
private $relatedNotesList!: JQuery<HTMLElement>;
|
||||
private $relatedNotesMoreNotes!: JQuery<HTMLElement>;
|
||||
private $attrHelp!: JQuery<HTMLElement>;
|
||||
private $statusBar?: JQuery<HTMLElement>;
|
||||
|
||||
private relatedNotesSpacedUpdate!: SpacedUpdate;
|
||||
private attribute!: Attribute;
|
||||
@@ -579,24 +577,17 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNewLayout) {
|
||||
if (!this.$statusBar) {
|
||||
this.$statusBar = $(document.body).find(".component.status-bar");
|
||||
}
|
||||
this.$widget
|
||||
.css("left", detPosition.left)
|
||||
.css("right", detPosition.right)
|
||||
.css("top", y - offset.top + 70)
|
||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||
|
||||
const statusBarHeight = this.$statusBar.outerHeight() ?? 0;
|
||||
const maxHeight = document.body.clientHeight - statusBarHeight;
|
||||
if (isNewLayout) {
|
||||
this.$widget
|
||||
.css("left", offset.left + (typeof detPosition.left === "number" ? detPosition.left : 0))
|
||||
.css("top", "unset")
|
||||
.css("bottom", statusBarHeight ?? 0)
|
||||
.css("max-height", maxHeight);
|
||||
} else {
|
||||
this.$widget
|
||||
.css("left", detPosition.left)
|
||||
.css("right", detPosition.right)
|
||||
.css("top", y - offset.top + 70)
|
||||
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
|
||||
.css("bottom", 70)
|
||||
.css("max-height", "80vh");
|
||||
}
|
||||
|
||||
if (focus === "name") {
|
||||
@@ -704,14 +695,14 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
return "label-definition";
|
||||
} else if (attribute.name.startsWith("relation:")) {
|
||||
return "relation-definition";
|
||||
} else {
|
||||
return "label";
|
||||
}
|
||||
return "label";
|
||||
|
||||
} else if (attribute.type === "relation") {
|
||||
return "relation";
|
||||
} else {
|
||||
this.$title.text("");
|
||||
}
|
||||
this.$title.text("");
|
||||
|
||||
}
|
||||
|
||||
updateAttributeInEditor() {
|
||||
|
||||
@@ -364,19 +364,23 @@
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
|
||||
.ck-content p {
|
||||
margin-bottom: 0.5em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.ck-content figure.image {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.ck-content .table {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
overflow-x: scroll;
|
||||
--scrollbar-thickness: 0;
|
||||
scrollbar-width: none;
|
||||
|
||||
|
||||
table {
|
||||
width: max-content;
|
||||
table-layout: auto;
|
||||
@@ -431,4 +435,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
/* #endregion */
|
||||
@@ -1,4 +1,3 @@
|
||||
import { createPortal } from "preact/compat";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import FAttribute from "../../entities/fattribute";
|
||||
@@ -75,7 +74,7 @@ export default function InheritedAttributesTab({ note, componentId, emptyListStr
|
||||
)}
|
||||
</div>
|
||||
|
||||
{createPortal(attributeDetailWidgetEl, document.body)}
|
||||
{attributeDetailWidgetEl}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, ModelNode, ModelPosition } from "@triliumnext/ckeditor5";
|
||||
import { AttributeType } from "@triliumnext/commons";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { MutableRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
|
||||
@@ -337,8 +336,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
|
||||
let matchedAttr: Attribute | null = null;
|
||||
|
||||
for (const attr of parsedAttrs) {
|
||||
if (attr.startIndex !== undefined && clickIndex > attr.startIndex &&
|
||||
attr.endIndex !== undefined && clickIndex <= attr.endIndex) {
|
||||
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
|
||||
matchedAttr = attr;
|
||||
break;
|
||||
}
|
||||
@@ -409,7 +407,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
|
||||
)}
|
||||
</div>}
|
||||
|
||||
{createPortal(attributeDetailWidgetEl, document.body)}
|
||||
{attributeDetailWidgetEl}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.8.0",
|
||||
"mime-types": "3.0.2",
|
||||
"sanitize-filename": "1.6.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"tsx": "4.21.0",
|
||||
"yargs": "18.0.0"
|
||||
},
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "8.0.0",
|
||||
"https-proxy-agent": "8.0.0",
|
||||
"i18next": "25.10.3",
|
||||
"i18next": "25.8.18",
|
||||
"i18next-fs-backend": "2.6.1",
|
||||
"image-type": "6.0.0",
|
||||
"ini": "6.0.0",
|
||||
@@ -107,13 +107,13 @@
|
||||
"is-svg": "6.1.0",
|
||||
"jimp": "1.6.0",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"marked": "17.0.5",
|
||||
"marked": "17.0.4",
|
||||
"mime-types": "3.0.2",
|
||||
"multer": "2.1.1",
|
||||
"normalize-strings": "1.1.1",
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.17.2",
|
||||
"sax": "1.6.0",
|
||||
"serve-favicon": "2.5.1",
|
||||
@@ -126,8 +126,8 @@
|
||||
"tmp": "0.2.5",
|
||||
"turnish": "1.8.0",
|
||||
"unescape": "1.0.1",
|
||||
"vite": "8.0.1",
|
||||
"ws": "8.20.0",
|
||||
"vite": "8.0.0",
|
||||
"ws": "8.19.0",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "3.2.1"
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ CREATE UNIQUE INDEX `IDX_entityChanges_entityName_entityId` ON "entity_changes"
|
||||
`entityId`
|
||||
);
|
||||
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
|
||||
CREATE INDEX IDX_branches_parentNoteId_isDeleted_notePosition ON branches (parentNoteId, isDeleted, notePosition);
|
||||
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
|
||||
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
|
||||
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
|
||||
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
|
||||
@@ -146,13 +146,6 @@ CREATE INDEX IDX_notes_blobId on notes (blobId);
|
||||
CREATE INDEX IDX_revisions_blobId on revisions (blobId);
|
||||
CREATE INDEX IDX_attachments_blobId on attachments (blobId);
|
||||
|
||||
CREATE INDEX IDX_entity_changes_isSynced_id ON entity_changes (isSynced, id);
|
||||
CREATE INDEX IDX_entity_changes_isErased_entityName ON entity_changes (isErased, entityName);
|
||||
CREATE INDEX IDX_notes_isDeleted_utcDateModified ON notes (isDeleted, utcDateModified);
|
||||
CREATE INDEX IDX_branches_isDeleted_utcDateModified ON branches (isDeleted, utcDateModified);
|
||||
CREATE INDEX IDX_attributes_isDeleted_utcDateModified ON attributes (isDeleted, utcDateModified);
|
||||
CREATE INDEX IDX_attachments_isDeleted_utcDateModified ON attachments (isDeleted, utcDateModified);
|
||||
CREATE INDEX IDX_attachments_utcDateScheduledForErasureSince ON attachments (utcDateScheduledForErasureSince);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
|
||||
@@ -155,6 +155,8 @@
|
||||
"proxy-instruction": "如果您将代理设置留空,将使用系统代理(仅适用于桌面应用)",
|
||||
"password": "密码",
|
||||
"password-placeholder": "密码",
|
||||
"totp-token": "TOTP 验证码",
|
||||
"totp-token-placeholder": "请输入 TOTP 验证码",
|
||||
"back": "返回",
|
||||
"finish-setup": "完成设置"
|
||||
},
|
||||
|
||||
@@ -252,6 +252,8 @@
|
||||
"proxy-instruction": "If you leave proxy setting blank, system proxy will be used (applies to the desktop application only)",
|
||||
"password": "Password",
|
||||
"password-placeholder": "Password",
|
||||
"totp-token": "TOTP Token",
|
||||
"totp-token-placeholder": "Enter your TOTP code",
|
||||
"back": "Back",
|
||||
"finish-setup": "Finish setup"
|
||||
},
|
||||
|
||||
@@ -155,6 +155,8 @@
|
||||
"proxy-instruction": "如果您將代理設定留空,將使用系統代理(僅適用於桌面版)",
|
||||
"password": "密碼",
|
||||
"password-placeholder": "密碼",
|
||||
"totp-token": "TOTP 驗證碼",
|
||||
"totp-token-placeholder": "請輸入 TOTP 驗證碼",
|
||||
"back": "返回",
|
||||
"finish-setup": "完成設定"
|
||||
},
|
||||
|
||||
@@ -141,6 +141,10 @@
|
||||
<label for="password"><%= t("setup_sync-from-server.password") %></label>
|
||||
<input type="password" id="password" class="form-control" placeholder="<%= t("setup_sync-from-server.password-placeholder") %>">
|
||||
</div>
|
||||
<div id="totp-section" class="form-group" style="margin-bottom: 8px; display: none;">
|
||||
<label for="totp-token"><%= t("setup_sync-from-server.totp-token") %></label>
|
||||
<input type="text" id="totp-token" class="form-control" placeholder="<%= t("setup_sync-from-server.totp-token-placeholder") %>" autocomplete="one-time-code">
|
||||
</div>
|
||||
|
||||
<button type="button" data-action="back" class="btn btn-secondary"><%= t("setup_sync-from-server.back") %></button>
|
||||
|
||||
|
||||
1
apps/server/src/express.d.ts
vendored
1
apps/server/src/express.d.ts
vendored
@@ -8,6 +8,7 @@ export declare module "express-serve-static-core" {
|
||||
|
||||
authorization?: string;
|
||||
"trilium-cred"?: string;
|
||||
"trilium-totp"?: string;
|
||||
"x-csrf-token"?: string;
|
||||
|
||||
"trilium-component-id"?: string;
|
||||
|
||||
@@ -6,27 +6,6 @@
|
||||
|
||||
// Migrations should be kept in descending order, so the latest migration is first.
|
||||
const MIGRATIONS: (SqlMigration | JsMigration)[] = [
|
||||
// Add missing database indices for query performance
|
||||
{
|
||||
version: 235,
|
||||
sql: /*sql*/`
|
||||
CREATE INDEX IF NOT EXISTS IDX_entity_changes_isSynced_id
|
||||
ON entity_changes (isSynced, id);
|
||||
CREATE INDEX IF NOT EXISTS IDX_entity_changes_isErased_entityName
|
||||
ON entity_changes (isErased, entityName);
|
||||
CREATE INDEX IF NOT EXISTS IDX_notes_isDeleted_utcDateModified
|
||||
ON notes (isDeleted, utcDateModified);
|
||||
CREATE INDEX IF NOT EXISTS IDX_branches_isDeleted_utcDateModified
|
||||
ON branches (isDeleted, utcDateModified);
|
||||
CREATE INDEX IF NOT EXISTS IDX_attributes_isDeleted_utcDateModified
|
||||
ON attributes (isDeleted, utcDateModified);
|
||||
CREATE INDEX IF NOT EXISTS IDX_attachments_isDeleted_utcDateModified
|
||||
ON attachments (isDeleted, utcDateModified);
|
||||
DROP INDEX IF EXISTS IDX_branches_parentNoteId;
|
||||
CREATE INDEX IF NOT EXISTS IDX_branches_parentNoteId_isDeleted_notePosition
|
||||
ON branches (parentNoteId, isDeleted, notePosition);
|
||||
`
|
||||
},
|
||||
// Migrate aiChat notes to code notes since LLM integration has been removed
|
||||
{
|
||||
version: 234,
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
"use strict";
|
||||
|
||||
import sqlInit from "../../services/sql_init.js";
|
||||
import setupService from "../../services/setup.js";
|
||||
import log from "../../services/log.js";
|
||||
import appInfo from "../../services/app_info.js";
|
||||
|
||||
import type { Request } from "express";
|
||||
|
||||
import appInfo from "../../services/app_info.js";
|
||||
import log from "../../services/log.js";
|
||||
import setupService from "../../services/setup.js";
|
||||
import sqlInit from "../../services/sql_init.js";
|
||||
import totp from "../../services/totp.js";
|
||||
|
||||
function getStatus() {
|
||||
return {
|
||||
isInitialized: sqlInit.isDbInitialized(),
|
||||
schemaExists: sqlInit.schemaExists(),
|
||||
syncVersion: appInfo.syncVersion
|
||||
syncVersion: appInfo.syncVersion,
|
||||
totpEnabled: totp.isTotpEnabled()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,9 +22,9 @@ async function setupNewDocument() {
|
||||
}
|
||||
|
||||
function setupSyncFromServer(req: Request) {
|
||||
const { syncServerHost, syncProxy, password } = req.body;
|
||||
const { syncServerHost, syncProxy, password, totpToken } = req.body;
|
||||
|
||||
return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, password);
|
||||
return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, password, totpToken);
|
||||
}
|
||||
|
||||
function saveSyncSeed(req: Request) {
|
||||
@@ -82,10 +85,26 @@ function getSyncSeed() {
|
||||
};
|
||||
}
|
||||
|
||||
async function checkServerTotpStatus(req: Request) {
|
||||
const { syncServerHost } = req.body;
|
||||
|
||||
if (!syncServerHost) {
|
||||
return { totpEnabled: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await setupService.checkRemoteTotpStatus(syncServerHost);
|
||||
return { totpEnabled: !!resp.totpEnabled };
|
||||
} catch {
|
||||
return { totpEnabled: false };
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getStatus,
|
||||
setupNewDocument,
|
||||
setupSyncFromServer,
|
||||
getSyncSeed,
|
||||
saveSyncSeed
|
||||
saveSyncSeed,
|
||||
checkServerTotpStatus
|
||||
};
|
||||
|
||||
@@ -244,6 +244,7 @@ function register(app: express.Application) {
|
||||
asyncRoute(PST, "/api/setup/sync-from-server", [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler);
|
||||
route(GET, "/api/setup/sync-seed", [loginRateLimiter, auth.checkCredentials], setupApiRoute.getSyncSeed, apiResultHandler);
|
||||
asyncRoute(PST, "/api/setup/sync-seed", [auth.checkAppNotInitialized], setupApiRoute.saveSyncSeed, apiResultHandler);
|
||||
asyncRoute(PST, "/api/setup/check-server-totp", [auth.checkAppNotInitialized], setupApiRoute.checkServerTotpStatus, apiResultHandler);
|
||||
|
||||
apiRoute(GET, "/api/autocomplete", autocompleteApiRoute.getAutocomplete);
|
||||
apiRoute(GET, "/api/autocomplete/notesCount", autocompleteApiRoute.getNotesCount);
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { OptionRow } from "@triliumnext/commons";
|
||||
export interface SetupStatusResponse {
|
||||
syncVersion: number;
|
||||
schemaExists: boolean;
|
||||
totpEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import packageJson from "../../package.json" with { type: "json" };
|
||||
import build from "./build.js";
|
||||
import dataDir from "./data_dir.js";
|
||||
|
||||
const APP_DB_VERSION = 235;
|
||||
const APP_DB_VERSION = 234;
|
||||
const SYNC_VERSION = 37;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ import options from "./options";
|
||||
|
||||
let app: Application;
|
||||
|
||||
function encodeCred(password: string): string {
|
||||
return Buffer.from(`dummy:${password}`).toString("base64");
|
||||
}
|
||||
|
||||
describe("Auth", () => {
|
||||
beforeAll(async () => {
|
||||
const buildApp = (await (import("../../src/app.js"))).default;
|
||||
@@ -72,4 +76,49 @@ describe("Auth", () => {
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Setup status endpoint", () => {
|
||||
it("returns totpEnabled: true when TOTP is enabled", async () => {
|
||||
cls.init(() => {
|
||||
options.setOption("mfaEnabled", "true");
|
||||
options.setOption("mfaMethod", "totp");
|
||||
options.setOption("totpVerificationHash", "hi");
|
||||
});
|
||||
const response = await supertest(app)
|
||||
.get("/api/setup/status")
|
||||
.expect(200);
|
||||
expect(response.body.totpEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it("returns totpEnabled: false when TOTP is disabled", async () => {
|
||||
cls.init(() => {
|
||||
options.setOption("mfaEnabled", "false");
|
||||
});
|
||||
const response = await supertest(app)
|
||||
.get("/api/setup/status")
|
||||
.expect(200);
|
||||
expect(response.body.totpEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkCredentials TOTP enforcement", () => {
|
||||
beforeAll(() => {
|
||||
config.General.noAuthentication = false;
|
||||
refreshAuth();
|
||||
});
|
||||
|
||||
it("does not require TOTP token when TOTP is disabled", async () => {
|
||||
cls.init(() => {
|
||||
options.setOption("mfaEnabled", "false");
|
||||
});
|
||||
// Will still fail with 401 due to wrong password, but NOT because of missing TOTP
|
||||
const response = await supertest(app)
|
||||
.get("/api/setup/sync-seed")
|
||||
.set("trilium-cred", encodeCred("wrongpassword"))
|
||||
.expect(401);
|
||||
// The error should be about password, not TOTP
|
||||
expect(response.text).toContain("Incorrect password");
|
||||
});
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import etapiTokenService from "./etapi_tokens.js";
|
||||
import log from "./log.js";
|
||||
import sqlInit from "./sql_init.js";
|
||||
import { isElectron } from "./utils.js";
|
||||
import passwordEncryptionService from "./encryption/password_encryption.js";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
|
||||
import attributes from "./attributes.js";
|
||||
import config from "./config.js";
|
||||
import passwordService from "./encryption/password.js";
|
||||
import totp from "./totp.js";
|
||||
import passwordEncryptionService from "./encryption/password_encryption.js";
|
||||
import recoveryCodeService from "./encryption/recovery_codes.js";
|
||||
import etapiTokenService from "./etapi_tokens.js";
|
||||
import log from "./log.js";
|
||||
import openID from "./open_id.js";
|
||||
import options from "./options.js";
|
||||
import attributes from "./attributes.js";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import sqlInit from "./sql_init.js";
|
||||
import totp from "./totp.js";
|
||||
import { isElectron } from "./utils.js";
|
||||
|
||||
let noAuthentication = false;
|
||||
refreshAuth();
|
||||
@@ -161,9 +163,28 @@ function checkCredentials(req: Request, res: Response, next: NextFunction) {
|
||||
if (!passwordEncryptionService.verifyPassword(password)) {
|
||||
res.setHeader("Content-Type", "text/plain").status(401).send("Incorrect password");
|
||||
log.info(`WARNING: Wrong password from ${req.ip}, rejecting.`);
|
||||
} else {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify TOTP if enabled
|
||||
if (totp.isTotpEnabled()) {
|
||||
const totpHeader = req.headers["trilium-totp"];
|
||||
const totpToken = Array.isArray(totpHeader) ? totpHeader[0] : totpHeader;
|
||||
if (typeof totpToken !== "string" || !totpToken) {
|
||||
res.setHeader("Content-Type", "text/plain").status(401).send("TOTP token is required");
|
||||
log.info(`WARNING: Missing or invalid TOTP token from ${req.ip}, rejecting.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Accept TOTP code or recovery code
|
||||
if (!totp.validateTOTP(totpToken) && !recoveryCodeService.verifyRecoveryCode(totpToken)) {
|
||||
res.setHeader("Content-Type", "text/plain").status(401).send("Incorrect TOTP token");
|
||||
log.info(`WARNING: Wrong TOTP token from ${req.ip}, rejecting.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import optionService from "../options.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
import { randomSecureToken, toBase64, constantTimeCompare } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
import type { OptionNames } from "@triliumnext/commons";
|
||||
|
||||
import optionService from "../options.js";
|
||||
import { constantTimeCompare,randomSecureToken, toBase64 } from "../utils.js";
|
||||
import dataEncryptionService from "./data_encryption.js";
|
||||
import myScryptService from "./my_scrypt.js";
|
||||
|
||||
const TOTP_OPTIONS: Record<string, OptionNames> = {
|
||||
SALT: "totpEncryptionSalt",
|
||||
ENCRYPTED_SECRET: "totpEncryptedSecret",
|
||||
|
||||
@@ -62,6 +62,9 @@ async function exec<T>(opts: ExecOpts): Promise<T> {
|
||||
|
||||
if (opts.auth) {
|
||||
headers["trilium-cred"] = Buffer.from(`dummy:${opts.auth.password}`).toString("base64");
|
||||
if (opts.auth.totpToken) {
|
||||
headers["trilium-totp"] = opts.auth.totpToken;
|
||||
}
|
||||
}
|
||||
|
||||
const request = (await client).request({
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface ExecOpts {
|
||||
cookieJar?: CookieJar;
|
||||
auth?: {
|
||||
password?: string;
|
||||
totpToken?: string;
|
||||
};
|
||||
timeout: number;
|
||||
body?: string | {};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import syncService from "./sync.js";
|
||||
import log from "./log.js";
|
||||
import sqlInit from "./sql_init.js";
|
||||
import optionService from "./options.js";
|
||||
import syncOptions from "./sync_options.js";
|
||||
import request from "./request.js";
|
||||
import appInfo from "./app_info.js";
|
||||
import { timeLimit } from "./utils.js";
|
||||
import becca from "../becca/becca.js";
|
||||
import type { SetupStatusResponse, SetupSyncSeedResponse } from "./api-interface.js";
|
||||
import appInfo from "./app_info.js";
|
||||
import log from "./log.js";
|
||||
import optionService from "./options.js";
|
||||
import request from "./request.js";
|
||||
import sqlInit from "./sql_init.js";
|
||||
import syncService from "./sync.js";
|
||||
import syncOptions from "./sync_options.js";
|
||||
import { timeLimit } from "./utils.js";
|
||||
|
||||
async function hasSyncServerSchemaAndSeed() {
|
||||
const response = await requestToSyncServer<SetupStatusResponse>("GET", "/api/setup/status");
|
||||
@@ -55,13 +55,13 @@ async function requestToSyncServer<T>(method: string, path: string, body?: strin
|
||||
url: syncOptions.getSyncServerHost() + path,
|
||||
body,
|
||||
proxy: syncOptions.getSyncProxy(),
|
||||
timeout: timeout
|
||||
timeout
|
||||
}),
|
||||
timeout
|
||||
)) as T;
|
||||
}
|
||||
|
||||
async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string, password: string) {
|
||||
async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string, password: string, totpToken?: string) {
|
||||
if (sqlInit.isDbInitialized()) {
|
||||
return {
|
||||
result: "failure",
|
||||
@@ -76,7 +76,7 @@ async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string
|
||||
const resp = await request.exec<SetupSyncSeedResponse>({
|
||||
method: "get",
|
||||
url: `${syncServerHost}/api/setup/sync-seed`,
|
||||
auth: { password },
|
||||
auth: { password, totpToken },
|
||||
proxy: syncProxy,
|
||||
timeout: 30000 // seed request should not take long
|
||||
});
|
||||
@@ -111,10 +111,30 @@ function getSyncSeedOptions() {
|
||||
return [becca.getOption("documentId"), becca.getOption("documentSecret")];
|
||||
}
|
||||
|
||||
async function checkRemoteTotpStatus(syncServerHost: string): Promise<{ totpEnabled: boolean }> {
|
||||
// Validate URL scheme to mitigate SSRF
|
||||
if (!syncServerHost.startsWith("http://") && !syncServerHost.startsWith("https://")) {
|
||||
return { totpEnabled: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await request.exec<{ totpEnabled?: boolean }>({
|
||||
method: "get",
|
||||
url: `${syncServerHost}/api/setup/status`,
|
||||
proxy: null,
|
||||
timeout: 10000
|
||||
});
|
||||
return { totpEnabled: !!resp?.totpEnabled };
|
||||
} catch {
|
||||
return { totpEnabled: false };
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
hasSyncServerSchemaAndSeed,
|
||||
triggerSync,
|
||||
sendSeedToSyncServer,
|
||||
setupSyncFromSyncServer,
|
||||
getSyncSeedOptions
|
||||
getSyncSeedOptions,
|
||||
checkRemoteTotpStatus
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Totp, generateSecret } from 'time2fa';
|
||||
import options from './options.js';
|
||||
import { generateSecret,Totp } from 'time2fa';
|
||||
|
||||
import totpEncryptionService from './encryption/totp_encryption.js';
|
||||
import options from './options.js';
|
||||
|
||||
function isTotpEnabled(): boolean {
|
||||
return options.getOptionOrNull('mfaEnabled') === "true" &&
|
||||
@@ -10,7 +11,7 @@ function isTotpEnabled(): boolean {
|
||||
|
||||
function createSecret(): { success: boolean; message?: string } {
|
||||
try {
|
||||
const secret = generateSecret();
|
||||
const secret = generateSecret(20);
|
||||
|
||||
totpEncryptionService.setTotpSecret(secret);
|
||||
|
||||
@@ -43,6 +44,8 @@ function validateTOTP(submittedPasscode: string): boolean {
|
||||
return Totp.validate({
|
||||
passcode: submittedPasscode,
|
||||
secret: secret.trim()
|
||||
}, {
|
||||
secretSize: secret.trim().length === 32 ? 20 : 10
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to validate TOTP:', e);
|
||||
|
||||
@@ -9,20 +9,20 @@
|
||||
"preview": "pnpm build && vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18next": "25.10.3",
|
||||
"i18next": "25.8.18",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"preact": "10.29.0",
|
||||
"preact-iso": "2.11.1",
|
||||
"preact-render-to-string": "6.6.6",
|
||||
"react-i18next": "16.6.0"
|
||||
"react-i18next": "16.5.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "2.10.5",
|
||||
"eslint": "10.1.0",
|
||||
"@preact/preset-vite": "2.10.4",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-preact": "2.0.0",
|
||||
"typescript": "5.9.3",
|
||||
"user-agent-data-types": "0.4.2",
|
||||
"vite": "8.0.1",
|
||||
"vite": "8.0.0",
|
||||
"vitest": "4.1.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
17
flake.lock
generated
17
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1774171785,
|
||||
"narHash": "sha256-upDSNdH1WEL2Z0ISvRXTWk7rEndTxUcaTOLY9imJYa8=",
|
||||
"lastModified": 1769184885,
|
||||
"narHash": "sha256-wVX5Cqpz66SINNsmt3Bv/Ijzzfl8EPUISq5rKK129K0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f8a13215c766347f3da9beef4cfc952eb23fa46e",
|
||||
"rev": "12689597ba7a6d776c3c979f393896be095269d4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -43,16 +43,15 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1774171918,
|
||||
"narHash": "sha256-0OBrtBnowvYP/YMKh7GB1GX22ORK+2X771EVgT+1tsk=",
|
||||
"owner": "TriliumNext",
|
||||
"lastModified": 1749022118,
|
||||
"narHash": "sha256-7Qzmy1snKbxFBKoqUrfyxxmEB8rPxDdV7PQwRiAR01o=",
|
||||
"owner": "FliegendeWurst",
|
||||
"repo": "pnpm2nix-nzbr",
|
||||
"rev": "536d67261ffe7c91cb286c8581cc799a1b61e969",
|
||||
"rev": "35f88a41d29839b3989f31871263451c8e092cb1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "TriliumNext",
|
||||
"ref": "fix/optional_dependencies_filtering",
|
||||
"owner": "FliegendeWurst",
|
||||
"repo": "pnpm2nix-nzbr",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
pnpm2nix = {
|
||||
url = "github:TriliumNext/pnpm2nix-nzbr/fix/optional_dependencies_filtering";
|
||||
url = "github:FliegendeWurst/pnpm2nix-nzbr";
|
||||
inputs = {
|
||||
flake-utils.follows = "flake-utils";
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
@@ -325,8 +325,6 @@
|
||||
buildInputs = [
|
||||
nodejs
|
||||
pnpm
|
||||
electron
|
||||
nodejs.python
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"cross-env": "10.1.0",
|
||||
"dpdm": "4.0.1",
|
||||
"esbuild": "0.27.4",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-preact": "2.0.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-playwright": "2.10.1",
|
||||
@@ -76,7 +76,7 @@
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.57.1",
|
||||
"upath": "2.0.1",
|
||||
"vite": "8.0.1",
|
||||
"vite": "8.0.0",
|
||||
"vite-plugin-dts": "4.5.4",
|
||||
"vitest": "4.1.0"
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.0.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.1",
|
||||
@@ -29,11 +29,11 @@
|
||||
"@vitest/browser": "4.1.0",
|
||||
"@vitest/coverage-istanbul": "4.1.0",
|
||||
"ckeditor5": "47.6.1",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.4.0",
|
||||
"stylelint": "17.5.0",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.0.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.1",
|
||||
@@ -30,11 +30,11 @@
|
||||
"@vitest/browser": "4.1.0",
|
||||
"@vitest/coverage-istanbul": "4.1.0",
|
||||
"ckeditor5": "47.6.1",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.4.0",
|
||||
"stylelint": "17.5.0",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.0.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.1",
|
||||
@@ -32,11 +32,11 @@
|
||||
"@vitest/browser": "4.1.0",
|
||||
"@vitest/coverage-istanbul": "4.1.0",
|
||||
"ckeditor5": "47.6.1",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.4.0",
|
||||
"stylelint": "17.5.0",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.0.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.1",
|
||||
@@ -32,11 +32,11 @@
|
||||
"@vitest/browser": "4.1.0",
|
||||
"@vitest/coverage-istanbul": "4.1.0",
|
||||
"ckeditor5": "47.6.1",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.4.0",
|
||||
"stylelint": "17.5.0",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@@ -22,9 +22,8 @@
|
||||
flex-direction: column;
|
||||
padding: var(--ck-spacing-standard);
|
||||
box-sizing: border-box;
|
||||
min-width: 400px;
|
||||
max-width: 60vw;
|
||||
max-height: 350px;
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
overflow: visible;
|
||||
user-select: text;
|
||||
}
|
||||
@@ -64,8 +63,8 @@
|
||||
border-radius: var(--ck-border-radius);
|
||||
background: var(--ck-color-input-background) !important;
|
||||
transition: border-color 120ms ease;
|
||||
overflow: auto;
|
||||
clip-path: none;
|
||||
overflow: visible !important;
|
||||
clip-path: none !important;
|
||||
}
|
||||
.ck.ck-math-input .ck-mathlive-container:focus-within {
|
||||
border-color: var(--ck-color-focus-border);
|
||||
@@ -160,12 +159,16 @@
|
||||
.ck.ck-math-preview {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
max-height: none !important;
|
||||
height: auto !important;
|
||||
padding: var(--ck-spacing-small);
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
display: block;
|
||||
text-align: left;
|
||||
overflow: auto;
|
||||
overflow-x: auto !important;
|
||||
overflow-y: visible !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Center equation when in display mode */
|
||||
@@ -210,7 +213,8 @@
|
||||
.ck.ck-balloon-panel .ck-balloon-panel__content,
|
||||
.ck.ck-math-form,
|
||||
.ck-math-view,
|
||||
.ck.ck-math-input {
|
||||
.ck.ck-math-input,
|
||||
.ck.ck-math-input .ck-mathlive-container {
|
||||
overflow: visible !important;
|
||||
clip-path: none !important;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.0.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.1",
|
||||
@@ -32,11 +32,11 @@
|
||||
"@vitest/browser": "4.1.0",
|
||||
"@vitest/coverage-istanbul": "4.1.0",
|
||||
"ckeditor5": "47.6.1",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.4.0",
|
||||
"stylelint": "17.5.0",
|
||||
"stylelint": "17.4.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"ckeditor5-premium-features": "47.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@smithy/middleware-retry": "4.4.44",
|
||||
"@smithy/middleware-retry": "4.4.43",
|
||||
"@types/jquery": "4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,6 @@
|
||||
"codemirror-lang-elixir": "4.0.1",
|
||||
"codemirror-lang-hcl": "0.1.0",
|
||||
"codemirror-lang-mermaid": "0.5.0",
|
||||
"eslint-linter-browserify": "10.1.0"
|
||||
"eslint-linter-browserify": "10.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fuse.js": "7.1.0",
|
||||
"katex": "0.16.40",
|
||||
"katex": "0.16.38",
|
||||
"mermaid": "11.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,7 +35,7 @@
|
||||
"@typescript-eslint/parser": "8.57.1",
|
||||
"dotenv": "17.3.1",
|
||||
"esbuild": "0.27.4",
|
||||
"eslint": "10.1.0",
|
||||
"eslint": "10.0.3",
|
||||
"highlight.js": "11.11.1",
|
||||
"typescript": "5.9.3"
|
||||
}
|
||||
|
||||
1118
pnpm-lock.yaml
generated
1118
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user