diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index 74d3dbfa9..6886e0845 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -17,7 +17,8 @@ function register(router) { 'type': [v.mandatory, v.notNull, v.isAttributeType], 'name': [v.mandatory, v.notNull, v.isString], 'value': [v.notNull, v.isString], - 'isInheritable': [v.notNull, v.isBoolean] + 'isInheritable': [v.notNull, v.isBoolean], + 'position': [v.notNull, v.isInteger] }; eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => { @@ -40,7 +41,8 @@ function register(router) { }); const ALLOWED_PROPERTIES_FOR_PATCH = { - 'value': [v.notNull, v.isString] + 'value': [v.notNull, v.isString], + 'position': [v.notNull, v.isInteger] }; eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => { diff --git a/src/etapi/etapi.openapi.yaml b/src/etapi/etapi.openapi.yaml index b8f0b5382..974acbdfe 100644 --- a/src/etapi/etapi.openapi.yaml +++ b/src/etapi/etapi.openapi.yaml @@ -31,7 +31,7 @@ paths: '201': description: note created content: - application/json: + application/json; charset=utf-8: schema: properties: note: @@ -43,7 +43,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /notes: @@ -163,13 +163,13 @@ paths: '200': description: search response content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/SearchResponse' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /notes/{noteId}: @@ -186,13 +186,13 @@ paths: '200': description: note response content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' patch: @@ -208,13 +208,13 @@ paths: '200': description: note updated content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' delete: @@ -226,7 +226,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /notes/{noteId}/content: @@ -288,7 +288,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /notes/{noteId}/note-revision: @@ -315,32 +315,10 @@ paths: default: description: unexpected error content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /branches/{branchId}: - parameters: - - name: branchId - in: path - required: true - schema: - $ref: '#/components/schemas/EntityId' - get: - description: Returns a branch identified by its ID - operationId: getBranchById - responses: - '200': - description: branch response - content: - application/json: - schema: - $ref: '#/components/schemas/Branch' - default: - description: unexpected error - content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' + /branches: post: description: > Create a branch (clone a note to a different location in the tree). @@ -357,19 +335,42 @@ paths: '200': description: branch updated (branch between parent note and child note already existed) content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Branch' '201': description: branch created content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Branch' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/Error' + /branches/{branchId}: + parameters: + - name: branchId + in: path + required: true + schema: + $ref: '#/components/schemas/EntityId' + get: + description: Returns a branch identified by its ID + operationId: getBranchById + responses: + '200': + description: branch response + content: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/Branch' + default: + description: unexpected error + content: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' patch: @@ -385,13 +386,13 @@ paths: '200': description: branch updated content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Branch' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' delete: @@ -405,7 +406,30 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/Error' + /attributes: + post: + description: create an attribute for a given note + operationId: postAttribute + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Attribute' + responses: + '201': + description: attribute created + content: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/Attribute' + default: + description: unexpected error + content: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /attributes/{attributeId}: @@ -422,35 +446,13 @@ paths: '200': description: attribute response content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Attribute' default: description: unexpected error content: - application/json: - schema: - $ref: '#/components/schemas/Error' - post: - description: create an attribute for a given note - operationId: postAttribute - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Attribute' - responses: - '201': - description: attribute created - content: - application/json: - schema: - $ref: '#/components/schemas/Attribute' - default: - description: unexpected error - content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' patch: @@ -466,13 +468,13 @@ paths: '200': description: attribute updated content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Attribute' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' delete: @@ -484,7 +486,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /refresh-note-ordering/{parentNoteId}: @@ -506,7 +508,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /inbox/{date}: @@ -527,13 +529,13 @@ paths: '200': description: inbox note content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /calendar/days/{date}: @@ -552,13 +554,13 @@ paths: '200': description: day note content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /calendar/weeks/{date}: @@ -577,13 +579,13 @@ paths: '200': description: week note content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /calendar/months/{month}: @@ -602,13 +604,13 @@ paths: '200': description: month note content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /calendar/years/{year}: @@ -627,13 +629,13 @@ paths: '200': description: year note content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Note' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /auth/login: @@ -654,7 +656,7 @@ paths: '201': description: auth token content: - application/json: + application/json; charset=utf-8: schema: properties: authToken: @@ -665,7 +667,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /auth/logout: @@ -678,7 +680,7 @@ paths: default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' /app-info: @@ -689,13 +691,13 @@ paths: '200': description: app info content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/AppInfo' default: description: unexpected error content: - application/json: + application/json; charset=utf-8: schema: $ref: '#/components/schemas/Error' @@ -774,7 +776,7 @@ components: type: string type: type: string - enum: [text, code, render, file, image, search, relationMap, book, noteMap, mermaid, webView, shortcut] + enum: [text, code, render, file, image, search, relationMap, book, noteMap, mermaid, webView, shortcut, doc, contentWidget, launcher] mime: type: string isProtected: @@ -810,9 +812,6 @@ components: Branch: type: object description: Branch places the note into the tree, it represents the relationship between a parent note and child note - required: - - noteId - - parentNoteId properties: branchId: $ref: '#/components/schemas/EntityId' @@ -837,8 +836,6 @@ components: Attribute: type: object description: Attribute (Label, Relation) is a key-value record attached to a note. - required: - - noteId properties: attributeId: $ref: '#/components/schemas/EntityId' @@ -851,7 +848,7 @@ components: enum: [label, relation] name: type: string - pattern: '^[\p{L}\p{N}_:]+' + pattern: '^[^\s]+' example: shareCss value: type: string @@ -881,7 +878,7 @@ components: description: debugging info on parsing the search query enabled with &debug=true parameter EntityId: type: string - pattern: '[a-zA-Z0-9]{4,32}' + pattern: '[a-zA-Z0-9_]{4,32}' example: evnnmvHTCgIn EntityIdList: type: array @@ -889,7 +886,7 @@ components: $ref: '#/components/schemas/EntityId' LocalDateTime: type: string - pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}\+[0-9]{4}' + pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[\+\-][0-9]{4}' example: 2021-12-31 20:18:11.939+0100 UtcDateTime: type: string @@ -897,10 +894,6 @@ components: example: 2021-12-31 19:18:11.939Z AppInfo: type: object - required: - - statu - - code - - message properties: appVersion: type: string diff --git a/src/public/app/desktop.js b/src/public/app/desktop.js index e83f4477e..7b0da9e35 100644 --- a/src/public/app/desktop.js +++ b/src/public/app/desktop.js @@ -8,6 +8,7 @@ import contextMenu from "./menus/context_menu.js"; import DesktopLayout from "./layouts/desktop_layout.js"; import glob from "./services/glob.js"; import zoomService from './components/zoom.js'; +import options from "./services/options.js"; bundleService.getWidgetBundlesByParent().then(widgetBundles => { appContext.setLayout(new DesktopLayout(widgetBundles)); @@ -115,11 +116,27 @@ if (utils.isElectron()) { ? (`${params.selectionText.substr(0, 13)}…`) : params.selectionText; + // Read the search engine from the options and fallback to DuckDuckGo if the option is not set. + const customSearchEngineName = options.get("customSearchEngineName"); + const customSearchEngineUrl = options.get("customSearchEngineUrl"); + let searchEngineName; + let searchEngineUrl; + if (customSearchEngineName && customSearchEngineUrl) { + searchEngineName = customSearchEngineName; + searchEngineUrl = customSearchEngineUrl; + } else { + searchEngineName = "Duckduckgo"; + searchEngineUrl = "https://duckduckgo.com/?q={keyword}"; + } + + // Replace the placeholder with the real search keyword. + let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText)); + items.push({ enabled: editFlags.canPaste, - title: `Search for "${shortenedSelection}" with DuckDuckGo`, + title: `Search for "${shortenedSelection}" with ${searchEngineName}`, uiIcon: "bx bx-search-alt", - handler: () => electron.shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(params.selectionText)}`) + handler: () => electron.shell.openExternal(searchUrl) }); } diff --git a/src/public/app/widgets/containers/ribbon_container.js b/src/public/app/widgets/containers/ribbon_container.js index 3bb19d95c..e9fbd307c 100644 --- a/src/public/app/widgets/containers/ribbon_container.js +++ b/src/public/app/widgets/containers/ribbon_container.js @@ -39,7 +39,7 @@ const TPL = ` .ribbon-tab-title.active { color: var(--main-text-color); - border-bottom: 1px solid var(--main-text-color); + border-bottom: 3px solid var(--main-text-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/src/public/app/widgets/type_widgets/content_widget.js b/src/public/app/widgets/type_widgets/content_widget.js index e187f9b80..1747256df 100644 --- a/src/public/app/widgets/type_widgets/content_widget.js +++ b/src/public/app/widgets/type_widgets/content_widget.js @@ -18,6 +18,7 @@ import PasswordOptions from "./options/password.js"; import EtapiOptions from "./options/etapi.js"; import BackupOptions from "./options/backup.js"; import SyncOptions from "./options/sync.js"; +import SearchEngineOptions from "./options/other/search_engine.js"; import TrayOptions from "./options/other/tray.js"; import OpenNoteInOptions from "./options/other/open_note_in.js" import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js"; @@ -76,6 +77,7 @@ const CONTENT_WIDGETS = { _optionsBackup: [ BackupOptions ], _optionsSync: [ SyncOptions ], _optionsOther: [ + SearchEngineOptions, TrayOptions, NoteErasureTimeoutOptions, NoteRevisionsSnapshotIntervalOptions, diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index 2c6cf55f2..826a9eab3 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -186,6 +186,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { const noteComplement = await froca.getNoteComplement(note.noteId); await this.spacedUpdate.allowUpdateWithoutChange(() => { + // https://github.com/zadam/trilium/issues/3914 + // todo: quite hacky, but it works. remove it if ckeditor has fixed it. + this.$editor.trigger('focus'); + this.$editor.trigger('blur') + this.watchdog.editor.setData(noteComplement.content || ""); }); } diff --git a/src/public/app/widgets/type_widgets/options/other/search_engine.js b/src/public/app/widgets/type_widgets/options/other/search_engine.js new file mode 100644 index 000000000..081aa0aa8 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/other/search_engine.js @@ -0,0 +1,78 @@ +import OptionsWidget from "../options_widget.js"; +import utils from "../../../../services/utils.js"; + +const TPL = ` +
+ + +

Search Engine

+ +

Custom search engine requires both a name and a URL to be set. If either of these is not set, DuckDuckGo will be used as the default search engine.

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
`; + +const SEARCH_ENGINES = { + "Bing": "https://www.bing.com/search?q={keyword}", + "Baidu": "https://www.baidu.com/s?wd={keyword}", + "Duckduckgo": "https://duckduckgo.com/?q={keyword}", + "Google": "https://www.google.com/search?q={keyword}", +} + +export default class SearchEngineOptions extends OptionsWidget { + isEnabled() { + return super.isEnabled() && utils.isElectron(); + } + + doRender() { + this.$widget = $(TPL); + + this.$form = this.$widget.find(".sync-setup-form"); + this.$predefinedSearchEngineSelect = this.$widget.find(".predefined-search-engine-select"); + this.$customSearchEngineName = this.$widget.find(".custom-search-engine-name"); + this.$customSearchEngineUrl = this.$widget.find(".custom-search-engine-url"); + + this.$predefinedSearchEngineSelect.on('change', () => { + const predefinedSearchEngine = this.$predefinedSearchEngineSelect.val(); + this.$customSearchEngineName[0].value = predefinedSearchEngine; + this.$customSearchEngineUrl[0].value = SEARCH_ENGINES[predefinedSearchEngine]; + }); + + this.$form.on('submit', () => { + this.updateMultipleOptions({ + 'customSearchEngineName': this.$customSearchEngineName.val(), + 'customSearchEngineUrl': this.$customSearchEngineUrl.val() + }); + }); + } + + async optionsLoaded(options) { + this.$predefinedSearchEngineSelect.val(""); + this.$customSearchEngineName[0].value = options.customSearchEngineName; + this.$customSearchEngineUrl[0].value = options.customSearchEngineUrl; + } +} diff --git a/src/public/stylesheets/tree.css b/src/public/stylesheets/tree.css index 8962e812d..6e26b872c 100644 --- a/src/public/stylesheets/tree.css +++ b/src/public/stylesheets/tree.css @@ -11,6 +11,7 @@ span.fancytree-node.fancytree-hide { } .fancytree-title { + flex: auto; margin-left: 7px; outline: none; position: relative; diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 840770444..21ccd70c3 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -62,6 +62,8 @@ const ALLOWED_OPTIONS = new Set([ 'minTocHeadings', 'checkForUpdates', 'disableTray', + 'customSearchEngineName', + 'customSearchEngineUrl',, 'openNoteIn' ]); diff --git a/src/services/export/zip.js b/src/services/export/zip.js index c58afca9f..1bc73f768 100644 --- a/src/services/export/zip.js +++ b/src/services/export/zip.js @@ -248,6 +248,7 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true) if (noteMeta.format === 'html') { if (!content.substr(0, 100).toLowerCase().includes(" element will make sure external links are openable - https://github.com/zadam/trilium/issues/1289#issuecomment-704066809 content = ` @@ -256,10 +257,11 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true) + ${htmlTitle}
-

${utils.escapeHtml(title)}

+

${htmlTitle}

${content}
@@ -415,7 +417,7 @@ ${markdownContent}`; const rootMeta = getNoteMeta(branch, { notePath: [] }, existingFileNames); const metaFile = { - formatVersion: 1, + formatVersion: 2, appVersion: packageInfo.version, files: [ rootMeta ] }; diff --git a/src/services/import/zip.js b/src/services/import/zip.js index abe3b58a9..74a942113 100644 --- a/src/services/import/zip.js +++ b/src/services/import/zip.js @@ -240,6 +240,8 @@ async function importZip(taskContext, fileBuffer, importRootNote) { return /^(?:[a-z]+:)?\/\//i.test(url); } + content = removeTrilumTags(content); + content = content.replace(/

([^<]*)<\/h1>/gi, (match, text) => { if (noteTitle.trim() === text.trim()) { return ""; // remove whole H1 tag @@ -325,6 +327,18 @@ async function importZip(taskContext, fileBuffer, importRootNote) { return content; } + function removeTrilumTags(content) { + const tagsToRemove = [ + '

([^<]*)<\/h1>', + '([^<]*)<\/title>' + ] + for (const tag of tagsToRemove) { + let re = new RegExp(tag, "gi"); + content = content.replace(re, ''); + } + return content; + } + function processNoteContent(noteMeta, type, mime, content, noteTitle, filePath) { if (noteMeta?.format === 'markdown' || (!noteMeta && taskContext.data.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime))) { diff --git a/src/services/options_init.js b/src/services/options_init.js index e3227b3c4..ab5b5f926 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -89,6 +89,8 @@ const defaultOptions = [ { name: 'minTocHeadings', value: '5', isSynced: true }, { name: 'checkForUpdates', value: 'true', isSynced: true }, { name: 'disableTray', value: 'false', isSynced: false }, + { name: 'customSearchEngineName', value: 'Duckduckgo', isSynced: false }, + { name: 'customSearchEngineUrl', value: 'https://duckduckgo.com/?q={keyword}', isSynced: false }, { name: 'openNoteIn', value: 'curtab', isSynced: true }, ];