Merge branch 'develop' into ai-llm-integration

This commit is contained in:
perf3ct 2025-03-13 19:10:46 +00:00
commit f8d4088dfe
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
14 changed files with 180 additions and 87 deletions

View File

@ -26,7 +26,7 @@ test("Displays translations in Settings", async ({ page, context }) => {
await app.goto();
await app.closeAllTabs();
await app.goToSettings();
await app.noteTree.getByText("Appearance").click();
await app.noteTree.getByText("Language & Region").click();
await expect(app.currentNoteSplit).toContainText("Localization");
await expect(app.currentNoteSplit).toContainText("Language");
@ -38,16 +38,16 @@ test("User can change language from settings", async ({ page, context }) => {
await app.closeAllTabs();
await app.goToSettings();
await app.noteTree.getByText("Appearance").click();
await app.noteTree.getByText("Language & Region").click();
// Check that the default value (English) is set.
await expect(app.currentNoteSplit).toContainText("Theme");
await expect(app.currentNoteSplit).toContainText("First day of the week");
const languageCombobox = app.currentNoteSplit.getByRole("combobox").first();
await expect(languageCombobox).toHaveValue("en");
// Select Chinese and ensure the translation is set.
await languageCombobox.selectOption("cn");
await expect(app.currentNoteSplit).toContainText("主题", { timeout: 15000 });
await expect(app.currentNoteSplit).toContainText("一周的第一天", { timeout: 15000 });
await expect(languageCombobox).toHaveValue("cn");
// Select English again.

View File

@ -105,7 +105,7 @@ export function buildConfig() {
}
function buildStyleDefinitions() {
const element = "blockquote";
const element = "p";
return [
{
name: "Note",

View File

@ -8,8 +8,7 @@ import HeadingStyleOptions from "./options/text_notes/heading_style.js";
import TableOfContentsOptions from "./options/text_notes/table_of_contents.js";
import HighlightsListOptions from "./options/text_notes/highlights_list.js";
import TextAutoReadOnlySizeOptions from "./options/text_notes/text_auto_read_only_size.js";
import VimKeyBindingsOptions from "./options/code_notes/vim_key_bindings.js";
import WrapLinesOptions from "./options/code_notes/wrap_lines.js";
import CodeEditorOptions from "./options/code_notes/code_editor.js";
import CodeAutoReadOnlySizeOptions from "./options/code_notes/code_auto_read_only_size.js";
import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js";
import ImageOptions from "./options/images/images.js";
@ -33,7 +32,7 @@ import DatabaseAnonymizationOptions from "./options/advanced/database_anonymizat
import BackendLogWidget from "./content/backend_log.js";
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
import RibbonOptions from "./options/appearance/ribbon.js";
import LocalizationOptions from "./options/appearance/i18n.js";
import LocalizationOptions from "./options/i18n/i18n.js";
import CodeBlockOptions from "./options/appearance/code_block.js";
import EditorOptions from "./options/text_notes/editor.js";
import ShareSettingsOptions from "./options/other/share_settings.js";
@ -63,16 +62,48 @@ const TPL = `<div class="note-detail-content-widget note-detail-printable">
</div>`;
const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
_optionsAppearance: [LocalizationOptions, ThemeOptions, FontsOptions, CodeBlockOptions, ElectronIntegrationOptions, MaxContentWidthOptions, RibbonOptions],
_optionsShortcuts: [KeyboardShortcutsOptions],
_optionsTextNotes: [EditorOptions, HeadingStyleOptions, TableOfContentsOptions, HighlightsListOptions, TextAutoReadOnlySizeOptions],
_optionsCodeNotes: [VimKeyBindingsOptions, WrapLinesOptions, CodeAutoReadOnlySizeOptions, CodeMimeTypesOptions],
_optionsImages: [ImageOptions],
_optionsSpellcheck: [SpellcheckOptions],
_optionsPassword: [PasswordOptions, ProtectedSessionTimeoutOptions],
_optionsEtapi: [EtapiOptions],
_optionsBackup: [BackupOptions],
_optionsSync: [SyncOptions],
_optionsAppearance: [
ThemeOptions,
FontsOptions,
CodeBlockOptions,
ElectronIntegrationOptions,
MaxContentWidthOptions,
RibbonOptions
],
_optionsShortcuts: [
KeyboardShortcutsOptions
],
_optionsTextNotes: [
EditorOptions,
HeadingStyleOptions,
TableOfContentsOptions,
HighlightsListOptions,
TextAutoReadOnlySizeOptions
],
_optionsCodeNotes: [
CodeEditorOptions,
CodeMimeTypesOptions,
CodeAutoReadOnlySizeOptions
],
_optionsImages: [
ImageOptions
],
_optionsSpellcheck: [
SpellcheckOptions
],
_optionsPassword: [
PasswordOptions,
ProtectedSessionTimeoutOptions
],
_optionsEtapi: [
EtapiOptions
],
_optionsBackup: [
BackupOptions
],
_optionsSync: [
SyncOptions
],
_optionsAi: [AiSettingsOptions],
_optionsOther: [
SearchEngineOptions,
@ -81,13 +112,23 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
AttachmentErasureTimeoutOptions,
RevisionsSnapshotIntervalOptions,
RevisionSnapshotsLimitOptions,
NetworkConnectionsOptions,
HtmlImportTagsOptions,
ShareSettingsOptions
ShareSettingsOptions,
NetworkConnectionsOptions
],
_optionsLocalization: [ LanguageOptions ],
_optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions],
_backendLog: [BackendLogWidget]
_optionsLocalization: [
LocalizationOptions,
LanguageOptions
],
_optionsAdvanced: [
AdvancedSyncOptions,
DatabaseIntegrityCheckOptions,
DatabaseAnonymizationOptions,
VacuumDatabaseOptions
],
_backendLog: [
BackendLogWidget
]
};
export default class ContentWidgetTypeWidget extends TypeWidget {

View File

@ -0,0 +1,39 @@
import OptionsWidget from "../options_widget.js";
import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = `
<div class="options-section">
<h4>${t("code-editor-options.title")}</h4>
<label class="tn-checkbox">
<input type="checkbox" class="vim-keymap-enabled form-check-input">
${t("vim_key_bindings.use_vim_keybindings_in_code_notes")}
</label>
<p class="form-text">${t("vim_key_bindings.enable_vim_keybindings")}</p>
<label class="tn-checkbox">
<input type="checkbox" class="line-wrap-enabled form-check-input">
${t("wrap_lines.wrap_lines_in_code_notes")}
</label>
<p class="form-text">${t("wrap_lines.enable_line_wrap")}</p>
</div>`;
export default class CodeEditorOptions extends OptionsWidget {
private $vimKeymapEnabled!: JQuery<HTMLElement>;
private $codeLineWrapEnabled!: JQuery<HTMLElement>;
doRender() {
this.$widget = $(TPL);
this.$vimKeymapEnabled = this.$widget.find(".vim-keymap-enabled");
this.$vimKeymapEnabled.on("change", () => this.updateCheckboxOption("vimKeymapEnabled", this.$vimKeymapEnabled));
this.$codeLineWrapEnabled = this.$widget.find(".line-wrap-enabled");
this.$codeLineWrapEnabled.on("change", () => this.updateCheckboxOption("codeLineWrapEnabled", this.$codeLineWrapEnabled));
}
async optionsLoaded(options: OptionMap) {
this.setCheckboxState(this.$vimKeymapEnabled, options.vimKeymapEnabled);
this.setCheckboxState(this.$codeLineWrapEnabled, options.codeLineWrapEnabled);
}
}

View File

@ -1,27 +0,0 @@
import OptionsWidget from "../options_widget.js";
import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = `
<div class="options-section">
<h4>${t("vim_key_bindings.use_vim_keybindings_in_code_notes")}</h4>
<label class="tn-checkbox">
<input type="checkbox" class="vim-keymap-enabled form-check-input">
${t("vim_key_bindings.enable_vim_keybindings")}
</label>
</div>`;
export default class VimKeyBindingsOptions extends OptionsWidget {
private $vimKeymapEnabled!: JQuery<HTMLElement>;
doRender() {
this.$widget = $(TPL);
this.$vimKeymapEnabled = this.$widget.find(".vim-keymap-enabled");
this.$vimKeymapEnabled.on("change", () => this.updateCheckboxOption("vimKeymapEnabled", this.$vimKeymapEnabled));
}
async optionsLoaded(options: OptionMap) {
this.setCheckboxState(this.$vimKeymapEnabled, options.vimKeymapEnabled);
}
}

View File

@ -1,27 +0,0 @@
import OptionsWidget from "../options_widget.js";
import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = `
<div class="options-section">
<h4>${t("wrap_lines.wrap_lines_in_code_notes")}</h4>
<label class="tn-checkbox">
<input type="checkbox" class="line-wrap-enabled form-check-input">
${t("wrap_lines.enable_line_wrap")}
</label>
</div>`;
export default class WrapLinesOptions extends OptionsWidget {
private $codeLineWrapEnabled!: JQuery<HTMLElement>;
doRender() {
this.$widget = $(TPL);
this.$codeLineWrapEnabled = this.$widget.find(".line-wrap-enabled");
this.$codeLineWrapEnabled.on("change", () => this.updateCheckboxOption("codeLineWrapEnabled", this.$codeLineWrapEnabled));
}
async optionsLoaded(options: OptionMap) {
this.setCheckboxState(this.$codeLineWrapEnabled, options.codeLineWrapEnabled);
}
}

View File

@ -18,7 +18,10 @@ const TPL = `
})}
</p>
<button type="button" class="create-etapi-token btn btn-sm">${t("etapi.create_token")}</button>
<button type="button" class="create-etapi-token btn btn-sm">
<span class="bx bx-plus"></span>
${t("etapi.create_token")}
</button>
<hr />
@ -118,7 +121,7 @@ export default class EtapiOptions extends OptionsWidget {
.append($("<td>").text(formatDateTime(token.utcDateCreated)))
.append(
$("<td>").append(
$(`<span class="bx bx-pen token-table-button icon-action" title="${t("etapi.rename_token")}"></span>`).on("click", () => this.renameToken(token.etapiTokenId, token.name)),
$(`<span class="bx bx-edit-alt token-table-button icon-action" title="${t("etapi.rename_token")}"></span>`).on("click", () => this.renameToken(token.etapiTokenId, token.name)),
$(`<span class="bx bx-trash token-table-button icon-action" title="${t("etapi.delete_token")}"></span>`).on("click", () => this.deleteToken(token.etapiTokenId, token.name))
)
)

View File

@ -1688,6 +1688,7 @@ body.zen .title-row {
height: unset !important;
-webkit-app-region: drag;
padding-left: env(titlebar-area-x);
padding-right: 2.5em;
}
body.zen .note-title-widget,
@ -1888,6 +1889,35 @@ footer.file-footer button {
font-size: 1.1em;
}
.ck-content .admonition {
body.desktop .ck-content .admonition {
--accent-color: var(--card-border-color);
border: 1px solid var(--accent-color);
box-shadow: var(--card-box-shadow);
border-radius: 0.5em;
padding: 1em;
}
margin: 1.25em 0;
margin-right: 14px;
position: relative;
padding-left: 2.5em;
}
body.desktop .ck-content .admonition::before {
color: var(--accent-color);
font-family: boxicons !important;
position: absolute;
top: 1em;
left: 1em;
}
body.desktop .ck-content .admonition.note { --accent-color: #69c7ff; }
body.desktop .ck-content .admonition.tip { --accent-color: #40c025; }
body.desktop .ck-content .admonition.important { --accent-color: #9839f7; }
body.desktop .ck-content .admonition.caution { --accent-color: #ff2e2e; }
body.desktop .ck-content .admonition.warning { --accent-color: #e2aa03; }
body.desktop .ck-content .admonition.note::before { content: "\eb21"; }
body.desktop .ck-content .admonition.tip::before { content: "\ea0d"; }
body.desktop .ck-content .admonition.important::before { content: "\ea7c"; }
body.desktop .ck-content .admonition.caution::before { content: "\eac7"; }
body.desktop .ck-content .admonition.warning::before { content: "\eac5"; }

View File

@ -138,6 +138,8 @@
--protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871;
--right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color);
--gutter-color: transparent;

View File

@ -132,6 +132,8 @@
--protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528;
--right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color);
--gutter-color: transparent;

View File

@ -37,19 +37,17 @@ div.promoted-attributes-container {
}
/*
* File Properties and Note Info
* File/Image Properties and Note Info
*/
/* The property label */
.note-info-widget-table th,
.file-properties-widget .file-table th {
.file-properties-widget .file-table th,
.image-properties > div:first-child > span > strong {
opacity: 0.65;
font-weight: 500;
}
/*
* File Properties
*/
.file-properties-widget {
container: info-section / inline-size;
}
@ -63,9 +61,12 @@ div.promoted-attributes-container {
padding: 8px 0 0 0;
}
.file-properties-widget .file-buttons {
/* The button bar */
.file-properties-widget .file-buttons,
.image-properties > div:last-of-type {
padding: 0;
justify-content: flex-start;
margin: 0;
justify-content: flex-start !important;
flex-wrap: wrap;
gap: 12px;
}
@ -74,6 +75,13 @@ div.promoted-attributes-container {
white-space: nowrap;
}
/* The properties row for images */
.image-properties > div:first-child {
justify-content: flex-start !important;
flex-wrap: wrap;
gap: 10px 40px;
}
/*
* Basic properties
*/

View File

@ -958,8 +958,27 @@ body.mobile .note-title {
background: var(--main-background-color);
}
#right-pane div.card-header {
align-items: center;
border: 0;
}
#right-pane .card-header-title {
margin-top: 2px;
font-weight: 600;
font-size: .85em;
letter-spacing: .3pt;
color: var(--right-pane-heading-color);
}
#right-pane .card-header-buttons {
align-items: center;
top: 0;
}
#right-pane .toc-widget,
#right-pane .highlights-list-widget {
padding: 0 0 0 10px;
}
#right-pane .toc li,

View File

@ -1224,6 +1224,9 @@
"description": "Automatic read-only note size is the size after which notes will be displayed in a read-only mode (for performance reasons).",
"label": "Automatic read-only size (code notes)"
},
"code-editor-options": {
"title": "Editor"
},
"code_mime_types": {
"title": "Available MIME types in the dropdown"
},