mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
Merge remote-tracking branch 'origin/develop' into find_replace
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "9.25.0",
|
||||
"@eslint/js": "9.26.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.17",
|
||||
"@fullcalendar/daygrid": "6.1.17",
|
||||
@@ -21,16 +21,17 @@
|
||||
"@mermaid-js/layout-elk": "0.1.7",
|
||||
"@mind-elixir/node-menu": "1.0.5",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"bootstrap": "5.3.5",
|
||||
"bootstrap": "5.3.6",
|
||||
"dayjs": "1.11.13",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "2.2.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"eslint-linter-browserify": "9.26.0",
|
||||
"force-graph": "1.49.5",
|
||||
"globals": "16.0.0",
|
||||
"i18next": "25.0.2",
|
||||
"globals": "16.1.0",
|
||||
"i18next": "25.1.2",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
@@ -44,19 +45,21 @@
|
||||
"mermaid": "11.6.0",
|
||||
"mind-elixir": "4.5.2",
|
||||
"panzoom": "9.4.3",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"split.js": "1.6.5",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"vanilla-js-wheel-zoom": "9.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-inspector": "4.1.0",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/leaflet": "1.9.17",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/react": "18.3.20",
|
||||
"@types/react-dom": "18.3.6",
|
||||
"@types/react": "19.1.3",
|
||||
"@types/react-dom": "19.1.3",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"happy-dom": "17.4.6",
|
||||
"script-loader": "0.7.2"
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js";
|
||||
import type { NativeImage, TouchBar } from "electron";
|
||||
import TouchBarComponent from "./touch_bar.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||
@@ -187,7 +188,7 @@ export type CommandMappings = {
|
||||
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
|
||||
};
|
||||
executeWithTextEditor: CommandData &
|
||||
ExecuteCommandData<TextEditor> & {
|
||||
ExecuteCommandData<CKTextEditor> & {
|
||||
callback?: GetTextEditorCallback;
|
||||
};
|
||||
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>;
|
||||
|
||||
@@ -10,13 +10,14 @@ import options from "../services/options.js";
|
||||
import type { ViewScope } from "../services/link.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
|
||||
export interface SetNoteOpts {
|
||||
triggerSwitchEvent?: unknown;
|
||||
viewScope?: ViewScope;
|
||||
}
|
||||
|
||||
export type GetTextEditorCallback = (editor: TextEditor) => void;
|
||||
export type GetTextEditorCallback = (editor: CKTextEditor) => void;
|
||||
|
||||
class NoteContext extends Component implements EventListener<"entitiesReloaded"> {
|
||||
ntxId: string | null;
|
||||
@@ -298,7 +299,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
}
|
||||
|
||||
async getTextEditor(callback?: GetTextEditorCallback) {
|
||||
return this.timeout<TextEditor>(
|
||||
return this.timeout<CKTextEditor>(
|
||||
new Promise((resolve) =>
|
||||
appContext.triggerCommand("executeWithTextEditor", {
|
||||
callback,
|
||||
|
||||
@@ -1,593 +0,0 @@
|
||||
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
||||
|
||||
.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-break {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.printed-content .page-break:after,
|
||||
.printed-content .page-break > * {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ck-content li p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
--accent-color: var(--card-border-color);
|
||||
border: 1px solid var(--accent-color);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
background: var(--card-background-color);
|
||||
border-radius: 0.5em;
|
||||
padding: 1em;
|
||||
margin: 1.25em 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admonition p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.admonition p, h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.admonition.note { --accent-color: #69c7ff; }
|
||||
.admonition.tip { --accent-color: #40c025; }
|
||||
.admonition.important { --accent-color: #9839f7; }
|
||||
.admonition.caution { --accent-color: #ff2e2e; }
|
||||
.admonition.warning { --accent-color: #e2aa03; }
|
||||
|
||||
/*
|
||||
* CKEditor 5 (v41.0.0) content styles.
|
||||
* Generated on Fri, 26 Jan 2024 10:23:49 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
||||
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
||||
--ck-image-style-spacing: 1.5em;
|
||||
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
|
||||
--ck-todo-list-checkmark-size: 16px;
|
||||
}
|
||||
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table .ck-table-resized {
|
||||
table-layout: fixed;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table table {
|
||||
overflow: hidden;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table td,
|
||||
.ck-content .table th {
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 0.9em auto;
|
||||
display: table;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border: 1px solid hsl(0, 0%, 75%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="rtl"] .table th {
|
||||
text-align: right;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-selector-caption-text);
|
||||
background-color: var(--ck-color-selector-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: hsl(0, 0%, 100%);
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 0.9em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list {
|
||||
list-style: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list li {
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list li .todo-list {
|
||||
margin-top: 5px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input {
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: var(--ck-todo-list-checkmark-size);
|
||||
height: var(--ck-todo-list-checkmark-size);
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
left: -25px;
|
||||
margin-right: -15px;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content[dir=rtl] .todo-list .todo-list__label > input {
|
||||
left: 0;
|
||||
margin-right: 0;
|
||||
right: -25px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid hsl(0, 0%, 20%);
|
||||
border-radius: 2px;
|
||||
transition: 250ms ease-in-out box-shadow;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input[checked]::before {
|
||||
background: hsl(126, 64%, 41%);
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input[checked]::after {
|
||||
border-color: hsl(0, 0%, 100%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
||||
vertical-align: middle;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
||||
position: absolute;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input,
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
||||
cursor: pointer;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before {
|
||||
box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: var(--ck-todo-list-checkmark-size);
|
||||
height: var(--ck-todo-list-checkmark-size);
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
left: -25px;
|
||||
margin-right: -15px;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input {
|
||||
left: 0;
|
||||
margin-right: 0;
|
||||
right: -25px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid hsl(0, 0%, 20%);
|
||||
border-radius: 2px;
|
||||
transition: 250ms ease-in-out box-shadow;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before {
|
||||
background: hsl(126, 64%, 41%);
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after {
|
||||
border-color: hsl(0, 0%, 100%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
||||
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
||||
position: absolute;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol {
|
||||
list-style-type: lower-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol {
|
||||
list-style-type: upper-latin;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ol ol ol ol ol {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
||||
.ck-content ul ul ul ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 0.9em auto;
|
||||
min-width: 50px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline {
|
||||
/*
|
||||
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
|
||||
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
|
||||
* This strange behavior does not happen with inline-flex.
|
||||
*/
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline picture {
|
||||
display: flex;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline picture,
|
||||
.ck-content .image-inline img {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content img.image_resized {
|
||||
height: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized img {
|
||||
width: 100%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: var(--ck-color-image-caption-text);
|
||||
background-color: var(--ck-color-image-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left,
|
||||
.ck-content .image-style-block-align-right {
|
||||
max-width: calc(100% - var(--ck-image-style-spacing));
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left,
|
||||
.ck-content .image-style-align-right {
|
||||
clear: none;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
max-width: 50%;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-right {
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content p + .image-style-align-left,
|
||||
.ck-content p + .image-style-align-right,
|
||||
.ck-content p + .image-style-side {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-left,
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-top: var(--ck-inline-image-style-spacing);
|
||||
margin-bottom: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-left {
|
||||
margin-right: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-left: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-yellow {
|
||||
background-color: var(--ck-highlight-marker-yellow);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-green {
|
||||
background-color: var(--ck-highlight-marker-green);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-pink {
|
||||
background-color: var(--ck-highlight-marker-pink);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-blue {
|
||||
background-color: var(--ck-highlight-marker-blue);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-red {
|
||||
color: var(--ck-highlight-pen-red);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-green {
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
padding-right: 1.5em;
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
border-left: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content[dir="rtl"] blockquote {
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-tiny {
|
||||
font-size: .7em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-small {
|
||||
font-size: .85em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-big {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-huge {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-mention/theme/mention.css */
|
||||
.ck-content .mention {
|
||||
background: var(--ck-color-mention-background);
|
||||
color: var(--ck-color-mention-text);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
margin: 15px 0;
|
||||
height: 4px;
|
||||
background: hsl(0, 0%, 87%);
|
||||
border: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre {
|
||||
padding: 1em;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
tab-size: 4;
|
||||
white-space: pre-wrap;
|
||||
font-style: normal;
|
||||
min-width: 200px;
|
||||
border: 0px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.ck-content pre:not(.hljs) {
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
}
|
||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre code {
|
||||
background: unset;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
@media print {
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
padding: 0;
|
||||
}
|
||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
49
apps/client/src/libraries/ckeditor/ckeditor.d.ts
vendored
49
apps/client/src/libraries/ckeditor/ckeditor.d.ts
vendored
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||
*/
|
||||
import { DecoupledEditor as DecoupledEditorBase } from '@ckeditor/ckeditor5-editor-decoupled';
|
||||
import { Essentials } from '@ckeditor/ckeditor5-essentials';
|
||||
import { Alignment } from '@ckeditor/ckeditor5-alignment';
|
||||
import { FontSize, FontFamily, FontColor, FontBackgroundColor } from '@ckeditor/ckeditor5-font';
|
||||
import { CKFinderUploadAdapter } from '@ckeditor/ckeditor5-adapter-ckfinder';
|
||||
import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
|
||||
import { Bold, Italic, Strikethrough, Underline } from '@ckeditor/ckeditor5-basic-styles';
|
||||
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
|
||||
import { CKBox } from '@ckeditor/ckeditor5-ckbox';
|
||||
import { CKFinder } from '@ckeditor/ckeditor5-ckfinder';
|
||||
import { EasyImage } from '@ckeditor/ckeditor5-easy-image';
|
||||
import { Heading } from '@ckeditor/ckeditor5-heading';
|
||||
import { Image, ImageCaption, ImageResize, ImageStyle, ImageToolbar, ImageUpload, PictureEditing } from '@ckeditor/ckeditor5-image';
|
||||
import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent';
|
||||
import { Link } from '@ckeditor/ckeditor5-link';
|
||||
import { List, ListProperties } from '@ckeditor/ckeditor5-list';
|
||||
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
|
||||
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
|
||||
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
|
||||
import { Table, TableToolbar } from '@ckeditor/ckeditor5-table';
|
||||
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
|
||||
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
|
||||
export default class DecoupledEditor extends DecoupledEditorBase {
|
||||
static builtinPlugins: (typeof TextTransformation | typeof Essentials | typeof Alignment | typeof FontBackgroundColor | typeof FontColor | typeof FontFamily | typeof FontSize | typeof CKFinderUploadAdapter | typeof Paragraph | typeof Heading | typeof Autoformat | typeof Bold | typeof Italic | typeof Strikethrough | typeof Underline | typeof BlockQuote | typeof Image | typeof ImageCaption | typeof ImageResize | typeof ImageStyle | typeof ImageToolbar | typeof ImageUpload | typeof CloudServices | typeof CKBox | typeof CKFinder | typeof EasyImage | typeof List | typeof ListProperties | typeof Indent | typeof IndentBlock | typeof Link | typeof MediaEmbed | typeof PasteFromOffice | typeof Table | typeof TableToolbar | typeof PictureEditing)[];
|
||||
static defaultConfig: {
|
||||
toolbar: {
|
||||
items: string[];
|
||||
};
|
||||
image: {
|
||||
resizeUnit: "px";
|
||||
toolbar: string[];
|
||||
};
|
||||
table: {
|
||||
contentToolbar: string[];
|
||||
};
|
||||
list: {
|
||||
properties: {
|
||||
styles: boolean;
|
||||
startIndex: boolean;
|
||||
reversed: boolean;
|
||||
};
|
||||
};
|
||||
language: string;
|
||||
};
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -9,7 +9,7 @@ export interface Froca {
|
||||
branches: Record<string, FBranch>;
|
||||
attributes: Record<string, FAttribute>;
|
||||
attachments: Record<string, FAttachment>;
|
||||
blobPromises: Record<string, Promise<void | FBlob> | null>;
|
||||
blobPromises: Record<string, Promise<void | FBlob | null> | null>;
|
||||
|
||||
getBlob(entityType: string, entityId: string): Promise<FBlob | null>;
|
||||
getNote(noteId: string, silentNotFoundError?: boolean): Promise<FNote | null>;
|
||||
|
||||
@@ -36,7 +36,7 @@ class FrocaImpl implements Froca {
|
||||
branches!: Record<string, FBranch>;
|
||||
attributes!: Record<string, FAttribute>;
|
||||
attachments!: Record<string, FAttachment>;
|
||||
blobPromises!: Record<string, Promise<FBlob> | null>;
|
||||
blobPromises!: Record<string, Promise<FBlob | null> | null>;
|
||||
|
||||
constructor() {
|
||||
this.initializedPromise = this.loadInitialTree();
|
||||
|
||||
@@ -7,10 +7,6 @@ export interface Library {
|
||||
css?: string[];
|
||||
}
|
||||
|
||||
const CKEDITOR: Library = {
|
||||
js: ["libraries/ckeditor/ckeditor.js"]
|
||||
};
|
||||
|
||||
const CODE_MIRROR: Library = {
|
||||
js: () => {
|
||||
const scriptsToLoad = [
|
||||
@@ -156,7 +152,6 @@ export default {
|
||||
requireCss,
|
||||
requireLibrary,
|
||||
loadHighlightingTheme,
|
||||
CKEDITOR,
|
||||
CODE_MIRROR,
|
||||
KATEX,
|
||||
HIGHLIGHT_JS
|
||||
|
||||
@@ -38,7 +38,7 @@ let mimeToHighlightJsMapping: Record<string, string> | null = null;
|
||||
* @param mimeType The MIME type of the code block, in the CKEditor-normalized format (e.g. `text-c-src` instead of `text/c-src`).
|
||||
* @returns the corresponding highlight.js tag, for example `c` for `text-c-src`.
|
||||
*/
|
||||
function getHighlightJsNameForMime(mimeType: string) {
|
||||
export function getHighlightJsNameForMime(mimeType: string) {
|
||||
if (!mimeToHighlightJsMapping) {
|
||||
const mimeTypes = getMimeTypes();
|
||||
mimeToHighlightJsMapping = {};
|
||||
|
||||
@@ -3,6 +3,7 @@ import appContext from "../components/app_context.js";
|
||||
import noteCreateService from "./note_create.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
import type { MentionFeedObjectItem } from "@triliumnext/ckeditor5";
|
||||
|
||||
// this key needs to have this value, so it's hit by the tooltip
|
||||
const SELECTED_NOTE_PATH_KEY = "data-note-path";
|
||||
@@ -43,7 +44,7 @@ interface Options {
|
||||
}
|
||||
|
||||
async function autocompleteSourceForCKEditor(queryText: string) {
|
||||
return await new Promise<MentionItem[]>((res, rej) => {
|
||||
return await new Promise<MentionFeedObjectItem[]>((res, rej) => {
|
||||
autocompleteSource(
|
||||
queryText,
|
||||
(rows) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { t } from "./i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type FBranch from "../entities/fbranch.js";
|
||||
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
|
||||
interface CreateNoteOpts {
|
||||
isProtected?: boolean;
|
||||
@@ -22,7 +23,7 @@ interface CreateNoteOpts {
|
||||
focus?: "title" | "content";
|
||||
target?: string;
|
||||
targetBranchId?: string;
|
||||
textEditor?: TextEditor;
|
||||
textEditor?: CKTextEditor;
|
||||
}
|
||||
|
||||
interface Response {
|
||||
|
||||
7
apps/client/src/types-assets.d.ts
vendored
7
apps/client/src/types-assets.d.ts
vendored
@@ -3,4 +3,9 @@ declare module "*.png" {
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module "script-loader!mark.js/dist/jquery.mark.min.js";
|
||||
declare module "*.json?external" {
|
||||
var path: string;
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module "script-loader!mark.js/dist/jquery.mark.min.js";
|
||||
|
||||
273
apps/client/src/types.d.ts
vendored
273
apps/client/src/types.d.ts
vendored
@@ -21,7 +21,7 @@ interface CustomGlobals {
|
||||
getHeaders: typeof server.getHeaders;
|
||||
getReferenceLinkTitle: (href: string) => Promise<string>;
|
||||
getReferenceLinkTitleSync: (href: string) => string;
|
||||
getActiveContextNote: () => FNote;
|
||||
getActiveContextNote: () => FNote | null;
|
||||
requireLibrary: typeof library_loader.requireLibrary;
|
||||
ESLINT: Library;
|
||||
appContext: AppContext;
|
||||
@@ -74,6 +74,9 @@ declare global {
|
||||
type AutoCompleteCallback = (values: AutoCompleteArg[]) => void;
|
||||
|
||||
interface AutoCompleteArg {
|
||||
name?: string;
|
||||
value?: string;
|
||||
notePathTitle?: string;
|
||||
displayKey?: "name" | "value" | "notePathTitle";
|
||||
cache?: boolean;
|
||||
source?: (term: string, cb: AutoCompleteCallback) => void,
|
||||
@@ -83,7 +86,7 @@ declare global {
|
||||
}
|
||||
|
||||
interface JQuery {
|
||||
autocomplete: (action?: "close" | "open" | "destroy" | "val" | AutoCompleteConfig, args?: object[] | string) => JQuery<HTMLElement>;
|
||||
autocomplete: (action?: "close" | "open" | "destroy" | "val" | AutoCompleteConfig, args?: AutoCompleteArg[] | string) => JQuery<HTMLElement>;
|
||||
|
||||
getSelectedNotePath(): string | undefined;
|
||||
getSelectedNoteId(): string | null;
|
||||
@@ -131,56 +134,6 @@ declare global {
|
||||
var renderMathInElement: (element: HTMLElement, options: {
|
||||
trust: boolean;
|
||||
}) => void;
|
||||
interface CKCodeBlockLanguage {
|
||||
language: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface CKEditorInstance {
|
||||
create(elementOrData: any, finalConfig: any): TextEditor;
|
||||
}
|
||||
|
||||
class CKWatchdog {
|
||||
constructor(editorClass: CKEditorInstance, opts: {
|
||||
minimumNonErrorTimePeriod: number;
|
||||
crashNumberLimit: number,
|
||||
saveInterval: number
|
||||
});
|
||||
on(event: string, callback: () => void);
|
||||
state: string;
|
||||
crashes: unknown[];
|
||||
editor: TextEditor;
|
||||
setCreator(callback: (elementOrData, editorConfig) => void);
|
||||
create(el: HTMLElement, opts: {
|
||||
placeholder: string,
|
||||
mention: MentionConfig,
|
||||
codeBlock: {
|
||||
languages: CKCodeBlockLanguage[]
|
||||
},
|
||||
math: {
|
||||
engine: string,
|
||||
outputType: string,
|
||||
lazyLoad: () => Promise<void>,
|
||||
forceOutputType: boolean,
|
||||
enablePreview: boolean
|
||||
},
|
||||
mermaid: {
|
||||
lazyLoad: () => Promise<Mermaid>,
|
||||
config: MermaidConfig
|
||||
}
|
||||
});
|
||||
destroy();
|
||||
}
|
||||
|
||||
var CKEditor: {
|
||||
BalloonEditor: CKEditorInstance;
|
||||
DecoupledEditor: CKEditorInstance;
|
||||
EditorWatchdog: typeof CKWatchdog;
|
||||
};
|
||||
|
||||
var CKEditorInspector: {
|
||||
attach(editor: TextEditor);
|
||||
};
|
||||
|
||||
interface CodeMirrorOpts {
|
||||
value: string;
|
||||
@@ -256,222 +209,6 @@ declare global {
|
||||
});
|
||||
}
|
||||
|
||||
interface Range {
|
||||
toJSON(): object;
|
||||
getItems(): TextNode[];
|
||||
}
|
||||
interface Writer {
|
||||
setAttribute(name: string, value: string, el: CKNode);
|
||||
createPositionAt(el: CKNode, opt?: "end" | number);
|
||||
setSelection(pos: number, pos2?: number);
|
||||
insertText(text: string, opts: Record<string, unknown> | undefined | TextPosition, position?: TextPosition);
|
||||
addMarker(name: string, opts: {
|
||||
range: Range;
|
||||
usingOperation: boolean;
|
||||
});
|
||||
removeMarker(name: string);
|
||||
createRange(start: number, end: number): Range;
|
||||
createElement(type: string, opts: Record<string, string | null | undefined>);
|
||||
}
|
||||
interface TextNode {
|
||||
previousSibling?: TextNode;
|
||||
name: string;
|
||||
data: string;
|
||||
startOffset: number;
|
||||
_attrs: {
|
||||
get(key: string): {
|
||||
length: number
|
||||
}
|
||||
}
|
||||
}
|
||||
interface TextPosition {
|
||||
textNode: TextNode;
|
||||
offset: number;
|
||||
compareWith(pos: TextPosition): string;
|
||||
}
|
||||
|
||||
interface TextRange {
|
||||
|
||||
}
|
||||
|
||||
interface Marker {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface CKNode {
|
||||
_children: CKNode[];
|
||||
name: string;
|
||||
childCount: number;
|
||||
isEmpty: boolean;
|
||||
toJSON(): object;
|
||||
is(type: string, name?: string);
|
||||
getAttribute(name: string): string;
|
||||
getChild(index: number): CKNode;
|
||||
data: string;
|
||||
startOffset: number;
|
||||
root: {
|
||||
document: {
|
||||
model: {
|
||||
createRangeIn(el: CKNode): TextRange;
|
||||
markers: {
|
||||
getMarkersIntersectingRange(range: TextRange): Marker[];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface CKEvent {
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
interface PluginEventData {
|
||||
title: string;
|
||||
message: {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface TextEditor {
|
||||
create(el: HTMLElement, config: {
|
||||
removePlugins?: string[];
|
||||
toolbar: {
|
||||
items: any[];
|
||||
},
|
||||
placeholder: string;
|
||||
mention: MentionConfig
|
||||
});
|
||||
enableReadOnlyMode(reason: string);
|
||||
commands: {
|
||||
get(name: string): {
|
||||
value: unknown;
|
||||
on(event: string, callback: () => void): void;
|
||||
};
|
||||
}
|
||||
model: {
|
||||
document: {
|
||||
on(event: string, cb: () => void);
|
||||
getRoot(): CKNode;
|
||||
registerPostFixer(callback: (writer: Writer) => boolean);
|
||||
selection: {
|
||||
getFirstPosition(): undefined | TextPosition;
|
||||
getLastPosition(): undefined | TextPosition;
|
||||
getSelectedElement(): CKNode;
|
||||
hasAttribute(attribute: string): boolean;
|
||||
getAttribute(attribute: string): string;
|
||||
getFirstRange(): Range;
|
||||
isCollapsed: boolean;
|
||||
};
|
||||
differ: {
|
||||
getChanges(): {
|
||||
type: string;
|
||||
name: string;
|
||||
position?: {
|
||||
nodeAfter?: CKNode;
|
||||
parent: CKNode;
|
||||
toJSON(): Object;
|
||||
}
|
||||
}[];
|
||||
}
|
||||
},
|
||||
insertContent(modelFragment: any, selection?: any);
|
||||
change(cb: (writer: Writer) => void)
|
||||
},
|
||||
editing: {
|
||||
view: {
|
||||
document: {
|
||||
on(event: string, cb: (event: CKEvent, data: {
|
||||
preventDefault();
|
||||
}) => void, opts?: {
|
||||
priority: "high"
|
||||
});
|
||||
getRoot(): CKNode
|
||||
},
|
||||
domRoots: {
|
||||
values: () => {
|
||||
next: () => {
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
change(cb: (writer: Writer) => void);
|
||||
scrollToTheSelection(): void;
|
||||
focus(): void;
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
get(command: string)
|
||||
},
|
||||
data: {
|
||||
processor: {
|
||||
toView(html: string);
|
||||
};
|
||||
toModel(viewFeragment: any);
|
||||
},
|
||||
ui: {
|
||||
view: {
|
||||
toolbar: {
|
||||
items: any[];
|
||||
element: HTMLElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
conversion: {
|
||||
for(filter: string): {
|
||||
markerToHighlight(data: {
|
||||
model: string;
|
||||
view: (data: {
|
||||
markerName: string;
|
||||
}) => void;
|
||||
})
|
||||
}
|
||||
}
|
||||
getData(): string;
|
||||
setData(data: string): void;
|
||||
getSelectedHtml(): string;
|
||||
removeSelection(): void;
|
||||
execute<T>(action: string, ...args: unknown[]): T;
|
||||
focus(): void;
|
||||
sourceElement: HTMLElement;
|
||||
}
|
||||
|
||||
interface EditingState {
|
||||
highlightedResult: string;
|
||||
results: unknown[];
|
||||
}
|
||||
|
||||
interface CKFindResult {
|
||||
results: {
|
||||
get(number): {
|
||||
marker: {
|
||||
getStart(): TextPosition;
|
||||
getRange(): number;
|
||||
};
|
||||
}
|
||||
} & [];
|
||||
}
|
||||
|
||||
interface MentionItem {
|
||||
action?: string;
|
||||
noteTitle?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
link?: string;
|
||||
notePath?: string;
|
||||
highlightedNotePathTitle?: string;
|
||||
}
|
||||
|
||||
interface MentionConfig {
|
||||
feeds: {
|
||||
marker: string;
|
||||
feed: (queryText: string) => MentionItem[] | Promise<MentionItem[]>;
|
||||
itemRenderer?: (item: {
|
||||
highlightedNotePathTitle: string
|
||||
}) => void;
|
||||
minimumCharacters: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
/*
|
||||
* Panzoom
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
|
||||
import server from "../../services/server.js";
|
||||
import contextMenuService from "../../menus/context_menu.js";
|
||||
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import { AttributeEditor, type EditorConfig, type Element, type MentionFeed, type Node, type Position } from "@triliumnext/ckeditor5";
|
||||
import froca from "../../services/froca.js";
|
||||
import attributeRenderer from "../../services/attribute_renderer.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
@@ -15,7 +15,6 @@ import type { CommandData, EventData, EventListener, FilteredCommandNames } from
|
||||
import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { escapeQuotes } from "../../services/utils.js";
|
||||
import { buildConfig } from "../type_widgets/ckeditor/config.js";
|
||||
|
||||
const HELP_TEXT = `
|
||||
<p>${t("attribute_editor.help_text_body1")}</p>
|
||||
@@ -85,109 +84,59 @@ const TPL = /*html*/`
|
||||
</div>
|
||||
`;
|
||||
|
||||
const mentionSetup: MentionConfig = {
|
||||
feeds: [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (item) => {
|
||||
const itemElement = document.createElement("button");
|
||||
const mentionSetup: MentionFeed[] = [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (_item) => {
|
||||
const item = _item as Suggestion;
|
||||
const itemElement = document.createElement("button");
|
||||
|
||||
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
||||
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
return itemElement;
|
||||
},
|
||||
{
|
||||
marker: "#",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
|
||||
minimumCharacters: 0
|
||||
},
|
||||
{
|
||||
marker: "#",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
|
||||
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `#${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `#${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
{
|
||||
marker: "~",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
|
||||
minimumCharacters: 0
|
||||
},
|
||||
{
|
||||
marker: "~",
|
||||
feed: async (queryText) => {
|
||||
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
|
||||
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `~${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
return names.map((name) => {
|
||||
return {
|
||||
id: `~${name}`,
|
||||
name: name
|
||||
};
|
||||
});
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
];
|
||||
|
||||
const editorConfig = {
|
||||
...buildConfig(),
|
||||
removePlugins: [
|
||||
"Heading",
|
||||
"Link",
|
||||
"Autoformat",
|
||||
"Bold",
|
||||
"Italic",
|
||||
"Underline",
|
||||
"Strikethrough",
|
||||
"Code",
|
||||
"Superscript",
|
||||
"Subscript",
|
||||
"BlockQuote",
|
||||
"Image",
|
||||
"ImageCaption",
|
||||
"ImageStyle",
|
||||
"ImageToolbar",
|
||||
"ImageUpload",
|
||||
"ImageResize",
|
||||
"List",
|
||||
"TodoList",
|
||||
"PasteFromOffice",
|
||||
"Table",
|
||||
"TableToolbar",
|
||||
"TableProperties",
|
||||
"TableCellProperties",
|
||||
"Indent",
|
||||
"IndentBlock",
|
||||
"BlockToolbar",
|
||||
"ParagraphButtonUI",
|
||||
"HeadingButtonsUI",
|
||||
"UploadimagePlugin",
|
||||
"InternalLinkPlugin",
|
||||
"MarkdownImportPlugin",
|
||||
"CuttonotePlugin",
|
||||
"TextTransformation",
|
||||
"Font",
|
||||
"FontColor",
|
||||
"FontBackgroundColor",
|
||||
"CodeBlock",
|
||||
"SelectAll",
|
||||
"IncludeNote",
|
||||
"CutToNote",
|
||||
"Math",
|
||||
"AutoformatMath",
|
||||
"indentBlockShortcutPlugin",
|
||||
"removeFormatLinksPlugin",
|
||||
"Footnotes",
|
||||
"Mermaid",
|
||||
"Kbd",
|
||||
"Admonition"
|
||||
],
|
||||
const editorConfig: EditorConfig = {
|
||||
toolbar: {
|
||||
items: []
|
||||
},
|
||||
placeholder: t("attribute_editor.placeholder"),
|
||||
mention: mentionSetup
|
||||
mention: {
|
||||
feeds: mentionSetup
|
||||
},
|
||||
licenseKey: "GPL"
|
||||
};
|
||||
|
||||
type AttributeCommandNames = FilteredCommandNames<CommandData>;
|
||||
@@ -199,7 +148,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
private $saveAttributesButton!: JQuery<HTMLElement>;
|
||||
private $errors!: JQuery<HTMLElement>;
|
||||
|
||||
private textEditor!: TextEditor;
|
||||
private textEditor!: AttributeEditor;
|
||||
private lastUpdatedNoteId!: string | undefined;
|
||||
private lastSavedContent!: string;
|
||||
|
||||
@@ -369,13 +318,11 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
}
|
||||
|
||||
async initEditor() {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||
|
||||
this.$widget.show();
|
||||
|
||||
this.$editor.on("click", (e) => this.handleEditorClick(e));
|
||||
|
||||
this.textEditor = await CKEditor.BalloonEditor.create(this.$editor[0], editorConfig);
|
||||
this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig);
|
||||
this.textEditor.model.document.on("change:data", () => this.dataChanged());
|
||||
this.textEditor.editing.view.document.on(
|
||||
"enter",
|
||||
@@ -388,7 +335,10 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
);
|
||||
|
||||
// disable spellcheck for attribute editor
|
||||
this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", this.textEditor.editing.view.document.getRoot()));
|
||||
const documentRoot = this.textEditor.editing.view.document.getRoot();
|
||||
if (documentRoot) {
|
||||
this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot));
|
||||
}
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
@@ -465,18 +415,18 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
this.$editor.tooltip("show");
|
||||
}
|
||||
|
||||
getClickIndex(pos: TextPosition) {
|
||||
let clickIndex = pos.offset - pos.textNode.startOffset;
|
||||
getClickIndex(pos: Position) {
|
||||
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
|
||||
|
||||
let curNode = pos.textNode;
|
||||
let curNode: Node | Text | Element | null = pos.textNode;
|
||||
|
||||
while (curNode.previousSibling) {
|
||||
while (curNode?.previousSibling) {
|
||||
curNode = curNode.previousSibling;
|
||||
|
||||
if (curNode.name === "reference") {
|
||||
clickIndex += curNode._attrs.get("notePath").length + 1;
|
||||
} else {
|
||||
clickIndex += curNode.data.length;
|
||||
if ((curNode as Element).name === "reference") {
|
||||
clickIndex += (curNode.getAttribute("notePath") as string).length + 1;
|
||||
} else if ("data" in curNode) {
|
||||
clickIndex += (curNode.data as string).length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,8 +484,12 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
||||
this.$editor.trigger("focus");
|
||||
|
||||
this.textEditor.model.change((writer) => {
|
||||
const positionAt = writer.createPositionAt(this.textEditor.model.document.getRoot(), "end");
|
||||
const documentRoot = this.textEditor.editing.model.document.getRoot();
|
||||
if (!documentRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const positionAt = writer.createPositionAt(documentRoot, "end");
|
||||
writer.setSelection(positionAt);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import utils from "../../services/utils.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
import type FAttachment from "../../entities/fattachment.js";
|
||||
import type AttachmentDetailWidget from "../attachment_detail.js";
|
||||
import { NoteRow } from "@triliumnext/commons";
|
||||
import type { NoteRow } from "@triliumnext/commons";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown attachment-actions">
|
||||
@@ -83,7 +83,7 @@ const TPL = /*html*/`
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface AttachmentResponse {
|
||||
note: NoteRow;
|
||||
note: NoteRow;
|
||||
}
|
||||
|
||||
export default class AttachmentActionsWidget extends BasicWidget {
|
||||
|
||||
@@ -11,7 +11,7 @@ import dayjs, { Dayjs } from "dayjs";
|
||||
import utc from "dayjs/plugin/utc.js";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
|
||||
import "../../stylesheets/calendar.css";
|
||||
import { AttributeRow } from "@triliumnext/commons";
|
||||
import type { AttributeRow } from "@triliumnext/commons";
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(isSameOrAfter);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FindAndReplaceState, FindCommandResult } from "@triliumnext/ckeditor5";
|
||||
import type { FindResult } from "./find.js";
|
||||
import type FindWidget from "./find.js";
|
||||
|
||||
@@ -14,8 +15,8 @@ interface Match {
|
||||
export default class FindInText {
|
||||
|
||||
private parent: FindWidget;
|
||||
private findResult?: CKFindResult | null;
|
||||
private editingState?: EditingState;
|
||||
private findResult?: FindCommandResult | null;
|
||||
private editingState?: FindAndReplaceState;
|
||||
|
||||
constructor(parent: FindWidget) {
|
||||
this.parent = parent;
|
||||
@@ -40,7 +41,7 @@ export default class FindInText {
|
||||
|
||||
// Clear
|
||||
const findAndReplaceEditing = textEditor.plugins.get("FindAndReplaceEditing");
|
||||
findAndReplaceEditing.state.clear(model);
|
||||
findAndReplaceEditing.state?.clear(model);
|
||||
findAndReplaceEditing.stop();
|
||||
this.editingState = findAndReplaceEditing.state;
|
||||
if (searchTerm !== "") {
|
||||
@@ -52,7 +53,7 @@ export default class FindInText {
|
||||
// let m = text.match(re);
|
||||
// totalFound = m ? m.length : 0;
|
||||
const options = { matchCase: matchCase, wholeWords: wholeWord };
|
||||
findResult = textEditor.execute<CKFindResult>("find", searchTerm, options);
|
||||
findResult = textEditor.execute("find", searchTerm, options);
|
||||
totalFound = findResult.results.length;
|
||||
const selection = model.document.selection;
|
||||
// If text is selected, highlight the corresponding result;
|
||||
@@ -60,17 +61,17 @@ export default class FindInText {
|
||||
if (!selection.isCollapsed) {
|
||||
const cursorPos = selection.getFirstPosition();
|
||||
for (let i = 0; i < findResult.results.length; ++i) {
|
||||
const marker = findResult.results.get(i).marker;
|
||||
const fromPos = marker.getStart();
|
||||
if (cursorPos && fromPos.compareWith(cursorPos) !== "before") {
|
||||
const marker = findResult.results.get(i)?.marker;
|
||||
const fromPos = marker?.getStart();
|
||||
if (cursorPos && fromPos?.compareWith(cursorPos) !== "before") {
|
||||
currentFound = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const editorEl = textEditor?.sourceElement;
|
||||
const findResultElement = editorEl.querySelectorAll(".ck-find-result");
|
||||
const scrollingContainer = editorEl.closest('.scrolling-container');
|
||||
const findResultElement = editorEl?.querySelectorAll(".ck-find-result");
|
||||
const scrollingContainer = editorEl?.closest('.scrolling-container');
|
||||
const containerTop = scrollingContainer?.getBoundingClientRect().top ?? 0;
|
||||
const closestIndex = Array.from(findResultElement ?? []).findIndex((el) => el.getBoundingClientRect().top >= containerTop);
|
||||
currentFound = closestIndex >= 0 ? closestIndex : 0;
|
||||
@@ -86,7 +87,7 @@ export default class FindInText {
|
||||
// XXX Do this accessing the private data?
|
||||
// See https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findnextcommand.js
|
||||
for (let i = 0; i < currentFound; ++i) {
|
||||
textEditor?.execute("findNext", searchTerm);
|
||||
textEditor?.execute("findNext");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,17 +121,17 @@ export default class FindInText {
|
||||
// Clear the markers and set the caret to the
|
||||
// current occurrence
|
||||
const model = textEditor.model;
|
||||
const range = this.findResult?.results?.get(currentFound).marker.getRange();
|
||||
const range = this.findResult?.results?.get(currentFound)?.marker?.getRange();
|
||||
// From
|
||||
// https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findandreplace.js#L92
|
||||
// XXX Roll our own since already done for codeEditor and
|
||||
// will probably allow more refactoring?
|
||||
let findAndReplaceEditing = textEditor.plugins.get("FindAndReplaceEditing");
|
||||
findAndReplaceEditing.state.clear(model);
|
||||
findAndReplaceEditing.state?.clear(model);
|
||||
findAndReplaceEditing.stop();
|
||||
if (range) {
|
||||
model.change((writer) => {
|
||||
writer.setSelection(range, 0);
|
||||
writer.setSelection(range);
|
||||
});
|
||||
}
|
||||
textEditor.editing.view.scrollToTheSelection();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import library_loader from "../../../services/library_loader.js";
|
||||
import { ALLOWED_PROTOCOLS } from "../../../services/link.js";
|
||||
import { MIME_TYPE_AUTO } from "../../../services/mime_type_definitions.js";
|
||||
import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
|
||||
import options from "../../../services/options.js";
|
||||
import { isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
|
||||
import utils from "../../../services/utils.js";
|
||||
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/emoji_definitions/en.json?external";
|
||||
|
||||
const TEXT_FORMATTING_GROUP = {
|
||||
label: "Text formatting",
|
||||
@@ -18,7 +23,6 @@ export function buildConfig() {
|
||||
"alignBlockRight",
|
||||
"alignLeft",
|
||||
"alignRight",
|
||||
"full", // full and side are for BC since the old images have been created with these styles
|
||||
"side"
|
||||
]
|
||||
},
|
||||
@@ -96,6 +100,18 @@ export function buildConfig() {
|
||||
defaultProtocol: "https://",
|
||||
allowedProtocols: ALLOWED_PROTOCOLS
|
||||
},
|
||||
emoji: {
|
||||
definitionsUrl: emojiDefinitionsUrl
|
||||
},
|
||||
syntaxHighlighting: {
|
||||
async loadHighlightJs() {
|
||||
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
|
||||
return hljs;
|
||||
},
|
||||
mapLanguageName: getHighlightJsNameForMime,
|
||||
defaultMimeType: MIME_TYPE_AUTO,
|
||||
enabled: isSyntaxHighlightEnabled
|
||||
},
|
||||
// This value must be kept in sync with the language defined in webpack.config.js.
|
||||
language: "en"
|
||||
};
|
||||
@@ -169,7 +185,7 @@ export function buildClassicToolbar(multilineToolbar: boolean) {
|
||||
{
|
||||
label: "Insert",
|
||||
icon: "plus",
|
||||
items: ["imageUpload", "|", "link", "internallink", "includeNote", "|", "specialCharacters", "math", "mermaid", "horizontalLine", "pageBreak"]
|
||||
items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak"]
|
||||
},
|
||||
"|",
|
||||
"outdent",
|
||||
@@ -202,6 +218,7 @@ export function buildFloatingToolbar() {
|
||||
"|",
|
||||
"code",
|
||||
"link",
|
||||
"bookmark",
|
||||
"removeFormat",
|
||||
"internallink",
|
||||
"cuttonote"
|
||||
@@ -232,6 +249,7 @@ export function buildFloatingToolbar() {
|
||||
"imageUpload",
|
||||
"markdownImport",
|
||||
"specialCharacters",
|
||||
"emoji",
|
||||
"findAndReplace"
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
/*
|
||||
* This code is an adaptation of https://github.com/antoniotejada/Trilium-SyntaxHighlightWidget with additional improvements, such as:
|
||||
*
|
||||
* - support for selecting the language manually;
|
||||
* - support for determining the language automatically, if a special language is selected ("Auto-detected");
|
||||
* - limit for highlighting.
|
||||
*
|
||||
* TODO: Generally this class can be done directly in the CKEditor repository.
|
||||
*/
|
||||
|
||||
import library_loader from "../../../services/library_loader.js";
|
||||
import mime_types from "../../../services/mime_types.js";
|
||||
import { isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
|
||||
|
||||
export async function initSyntaxHighlighting(editor: TextEditor) {
|
||||
if (!isSyntaxHighlightEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
|
||||
initTextEditor(editor);
|
||||
}
|
||||
|
||||
const HIGHLIGHT_MAX_BLOCK_COUNT = 500;
|
||||
|
||||
const tag = "SyntaxHighlightWidget";
|
||||
const debugLevels = ["error", "warn", "info", "log", "debug"];
|
||||
const debugLevel = debugLevels.indexOf("warn");
|
||||
|
||||
let warn = function (...args: unknown[]) {};
|
||||
if (debugLevel >= debugLevels.indexOf("warn")) {
|
||||
warn = console.warn.bind(console, tag + ": ");
|
||||
}
|
||||
|
||||
let info = function (...args: unknown[]) {};
|
||||
if (debugLevel >= debugLevels.indexOf("info")) {
|
||||
info = console.info.bind(console, tag + ": ");
|
||||
}
|
||||
|
||||
let log = function (...args: unknown[]) {};
|
||||
if (debugLevel >= debugLevels.indexOf("log")) {
|
||||
log = console.log.bind(console, tag + ": ");
|
||||
}
|
||||
|
||||
let dbg = function (...args: unknown[]) {};
|
||||
if (debugLevel >= debugLevels.indexOf("debug")) {
|
||||
dbg = console.debug.bind(console, tag + ": ");
|
||||
}
|
||||
|
||||
function assert(e: boolean, msg?: string) {
|
||||
console.assert(e, tag + ": " + msg);
|
||||
}
|
||||
|
||||
// TODO: Should this be scoped to note?
|
||||
let markerCounter = 0;
|
||||
|
||||
function initTextEditor(textEditor: TextEditor) {
|
||||
log("initTextEditor");
|
||||
|
||||
const document = textEditor.model.document;
|
||||
|
||||
// Create a conversion from model to view that converts
|
||||
// hljs:hljsClassName:uniqueId into a span with hljsClassName
|
||||
// See the list of hljs class names at
|
||||
// https://github.com/highlightjs/highlight.js/blob/6b8c831f00c4e87ecd2189ebbd0bb3bbdde66c02/docs/css-classes-reference.rst
|
||||
|
||||
textEditor.conversion.for("editingDowncast").markerToHighlight({
|
||||
model: "hljs",
|
||||
view: ({ markerName }) => {
|
||||
dbg("markerName " + markerName);
|
||||
// markerName has the pattern addMarker:cssClassName:uniqueId
|
||||
const [, cssClassName, id] = markerName.split(":");
|
||||
|
||||
// The original code at
|
||||
// https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-find-and-replace/src/findandreplaceediting.js
|
||||
// has this comment
|
||||
// Marker removal from the view has a bug:
|
||||
// https://github.com/ckeditor/ckeditor5/issues/7499
|
||||
// A minimal option is to return a new object for each converted marker...
|
||||
return {
|
||||
name: "span",
|
||||
classes: [cssClassName],
|
||||
attributes: {
|
||||
// ...however, adding a unique attribute should be future-proof..
|
||||
"data-syntax-result": id
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// XXX This is done at BalloonEditor.create time, so it assumes this
|
||||
// document is always attached to this textEditor, empirically that
|
||||
// seems to be the case even with two splits showing the same note,
|
||||
// it's not clear if CKEditor5 has apis to attach and detach
|
||||
// documents around
|
||||
document.registerPostFixer(function (writer) {
|
||||
log("postFixer");
|
||||
// Postfixers are a simpler way of tracking changes than onchange
|
||||
// See
|
||||
// https://github.com/ckeditor/ckeditor5/blob/b53d2a4b49679b072f4ae781ac094e7e831cfb14/packages/ckeditor5-block-quote/src/blockquoteediting.js#L54
|
||||
const changes = document.differ.getChanges();
|
||||
let dirtyCodeBlocks = new Set<CKNode>();
|
||||
|
||||
function lookForCodeBlocks(node: CKNode) {
|
||||
for (const child of node._children) {
|
||||
if (child.is("element", "paragraph")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.is("element", "codeBlock")) {
|
||||
dirtyCodeBlocks.add(child);
|
||||
} else if (child.childCount > 0) {
|
||||
lookForCodeBlocks(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const change of changes) {
|
||||
dbg("change " + JSON.stringify(change));
|
||||
|
||||
if (change.name !== "paragraph" && change.name !== "codeBlock" && change?.position?.nodeAfter && change.position.nodeAfter.childCount > 0) {
|
||||
/*
|
||||
* We need to look for code blocks recursively, as they can be placed within a <div> due to
|
||||
* general HTML support or normally underneath other elements such as tables, blockquotes, etc.
|
||||
*/
|
||||
lookForCodeBlocks(change.position.nodeAfter);
|
||||
} else if (change.type == "insert" && change.name == "codeBlock") {
|
||||
// A new code block was inserted
|
||||
const codeBlock = change.position?.nodeAfter;
|
||||
// Even if it's a new codeblock, it needs dirtying in case
|
||||
// it already has children, like when pasting one or more
|
||||
// full codeblocks, undoing a delete, changing the language,
|
||||
// etc (the postfixer won't get later changes for those).
|
||||
if (codeBlock) {
|
||||
log("dirtying inserted codeBlock " + JSON.stringify(codeBlock.toJSON()));
|
||||
dirtyCodeBlocks.add(codeBlock);
|
||||
}
|
||||
} else if (change.type == "remove" && change.name == "codeBlock" && change.position) {
|
||||
// An existing codeblock was removed, do nothing. Note the
|
||||
// node is no longer in the editor so the codeblock cannot
|
||||
// be inspected here. No need to dirty the codeblock since
|
||||
// it has been removed
|
||||
log("removing codeBlock at path " + JSON.stringify(change.position.toJSON()));
|
||||
} else if ((change.type == "remove" || change.type == "insert") && change?.position?.parent.is("element", "codeBlock")) {
|
||||
// Text was added or removed from the codeblock, force a
|
||||
// highlight
|
||||
const codeBlock = change.position.parent;
|
||||
log("dirtying codeBlock " + JSON.stringify(codeBlock.toJSON()));
|
||||
dirtyCodeBlocks.add(codeBlock);
|
||||
}
|
||||
}
|
||||
for (let codeBlock of dirtyCodeBlocks) {
|
||||
highlightCodeBlock(codeBlock, writer);
|
||||
}
|
||||
// Adding markers doesn't modify the document data so no need for
|
||||
// postfixers to run again
|
||||
return false;
|
||||
});
|
||||
|
||||
// This assumes the document is empty and a explicit call to highlight
|
||||
// is not necessary here. Empty documents have a single children of type
|
||||
// paragraph with no text
|
||||
assert(document.getRoot().childCount == 1 && document.getRoot().getChild(0).name == "paragraph" && document.getRoot().getChild(0).isEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implements highlighting via ephemeral markers (not stored in the
|
||||
* document).
|
||||
*
|
||||
* XXX Another option would be to use formatting markers, which would have
|
||||
* the benefit of making it work for readonly notes. On the flip side,
|
||||
* the formatting would be stored with the note and it would need a
|
||||
* way to remove that formatting when editing back the note.
|
||||
*/
|
||||
function highlightCodeBlock(codeBlock: CKNode, writer: Writer) {
|
||||
log("highlighting codeblock " + JSON.stringify(codeBlock.toJSON()));
|
||||
const model = codeBlock.root.document.model;
|
||||
|
||||
// Can't invoke addMarker with an already existing marker name,
|
||||
// clear all highlight markers first. Marker names follow the
|
||||
// pattern hljs:cssClassName:uniqueId, eg hljs:hljs-comment:1
|
||||
const codeBlockRange = model.createRangeIn(codeBlock);
|
||||
for (const marker of model.markers.getMarkersIntersectingRange(codeBlockRange)) {
|
||||
dbg("removing marker " + marker.name);
|
||||
writer.removeMarker(marker.name);
|
||||
}
|
||||
|
||||
// Don't highlight if plaintext (note this needs to remove the markers
|
||||
// above first, in case this was a switch from non plaintext to
|
||||
// plaintext)
|
||||
const mimeType = codeBlock.getAttribute("language");
|
||||
if (mimeType == "text-plain") {
|
||||
// XXX There's actually a plaintext language that could be used
|
||||
// if you wanted the non-highlight formatting of
|
||||
// highlight.js css applied, see
|
||||
// https://github.com/highlightjs/highlight.js/issues/700
|
||||
log("not highlighting plaintext codeblock");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the corresponding language for the given mimetype.
|
||||
const highlightJsLanguage = mime_types.getHighlightJsNameForMime(mimeType);
|
||||
|
||||
if (mimeType !== mime_types.MIME_TYPE_AUTO && !highlightJsLanguage) {
|
||||
console.warn(`Unsupported highlight.js for mime type ${mimeType}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't highlight if the code is too big, as the typing performance will be highly degraded.
|
||||
if (codeBlock.childCount >= HIGHLIGHT_MAX_BLOCK_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// highlight.js needs the full text without HTML tags, eg for the
|
||||
// text
|
||||
// #include <stdio.h>
|
||||
// the highlighted html is
|
||||
// <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span>
|
||||
// But CKEditor codeblocks have <br> instead of \n
|
||||
|
||||
// Do a two pass algorithm:
|
||||
// - First pass collect the codeblock children text, change <br> to
|
||||
// \n
|
||||
// - invoke highlight.js on the collected text generating html
|
||||
// - Second pass parse the highlighted html spans and match each
|
||||
// char to the CodeBlock text. Issue addMarker CKEditor calls for
|
||||
// each span
|
||||
|
||||
// XXX This is brittle and assumes how highlight.js generates html
|
||||
// (blanks, which characters escapes, etc), a better approach
|
||||
// would be to use highlight.js beta api TreeTokenizer?
|
||||
|
||||
// Collect all the text nodes to pass to the highlighter Text is
|
||||
// direct children of the codeBlock
|
||||
let text = "";
|
||||
for (let i = 0; i < codeBlock.childCount; ++i) {
|
||||
let child = codeBlock.getChild(i);
|
||||
|
||||
// We only expect text and br elements here
|
||||
if (child.is("$text")) {
|
||||
dbg("child text " + child.data);
|
||||
text += child.data;
|
||||
} else if (child.is("element") && child.name == "softBreak") {
|
||||
dbg("softBreak");
|
||||
text += "\n";
|
||||
} else {
|
||||
warn("Unkown child " + JSON.stringify(child.toJSON()));
|
||||
}
|
||||
}
|
||||
|
||||
let highlightRes;
|
||||
if (mimeType === mime_types.MIME_TYPE_AUTO) {
|
||||
highlightRes = hljs.highlightAuto(text);
|
||||
} else {
|
||||
highlightRes = hljs.highlight(text, { language: highlightJsLanguage });
|
||||
}
|
||||
dbg("text\n" + text);
|
||||
dbg("html\n" + highlightRes.value);
|
||||
|
||||
let iHtml = 0;
|
||||
let html = highlightRes.value;
|
||||
let spanStack = [];
|
||||
let iChild = -1;
|
||||
let childText = "";
|
||||
let child = null;
|
||||
let iChildText = 0;
|
||||
|
||||
while (iHtml < html.length) {
|
||||
// Advance the text index and fetch a new child if necessary
|
||||
if (iChildText >= childText.length) {
|
||||
iChild++;
|
||||
if (iChild < codeBlock.childCount) {
|
||||
dbg("Fetching child " + iChild);
|
||||
child = codeBlock.getChild(iChild);
|
||||
if (child.is("$text")) {
|
||||
dbg("child text " + child.data);
|
||||
childText = child.data;
|
||||
iChildText = 0;
|
||||
} else if (child.is("element", "softBreak")) {
|
||||
dbg("softBreak");
|
||||
iChildText = 0;
|
||||
childText = "\n";
|
||||
} else {
|
||||
warn("child unknown!!!");
|
||||
}
|
||||
} else {
|
||||
// Don't bail if beyond the last children, since there's
|
||||
// still html text, it must be a closing span tag that
|
||||
// needs to be dealt with below
|
||||
childText = "";
|
||||
}
|
||||
}
|
||||
|
||||
// This parsing is made slightly simpler and faster by only
|
||||
// expecting <span> and </span> tags in the highlighted html
|
||||
if (html[iHtml] == "<" && html[iHtml + 1] != "/") {
|
||||
// new span, note they can be nested eg C preprocessor lines
|
||||
// are inside a hljs-meta span, hljs-title function names
|
||||
// inside a hljs-function span, etc
|
||||
let iStartQuot = html.indexOf('"', iHtml + 1);
|
||||
let iEndQuot = html.indexOf('"', iStartQuot + 1);
|
||||
let className = html.slice(iStartQuot + 1, iEndQuot);
|
||||
// XXX highlight js uses scope for Python "title function_",
|
||||
// etc for now just use the first style only
|
||||
// See https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html#a-note-on-scopes-with-sub-scopes
|
||||
let iBlank = className.indexOf(" ");
|
||||
if (iBlank > 0) {
|
||||
className = className.slice(0, iBlank);
|
||||
}
|
||||
dbg("Found span start " + className);
|
||||
|
||||
iHtml = html.indexOf(">", iHtml) + 1;
|
||||
|
||||
// push the span
|
||||
let posStart = writer.createPositionAt(codeBlock, (child?.startOffset ?? 0) + iChildText);
|
||||
spanStack.push({ className: className, posStart: posStart });
|
||||
} else if (html[iHtml] == "<" && html[iHtml + 1] == "/") {
|
||||
// Done with this span, pop the span and mark the range
|
||||
iHtml = html.indexOf(">", iHtml + 1) + 1;
|
||||
|
||||
let stackTop = spanStack.pop();
|
||||
let posStart = stackTop?.posStart;
|
||||
let className = stackTop?.className;
|
||||
let posEnd = writer.createPositionAt(codeBlock, (child?.startOffset ?? 0) + iChildText);
|
||||
let range = writer.createRange(posStart, posEnd);
|
||||
let markerName = "hljs:" + className + ":" + markerCounter;
|
||||
// Use an incrementing number for the uniqueId, random of
|
||||
// 10000000 is known to cause collisions with a few
|
||||
// codeblocks of 10s of lines on real notes (each line is
|
||||
// one or more marker).
|
||||
// Wrap-around for good measure so all numbers are positive
|
||||
// XXX Another option is to catch the exception and retry or
|
||||
// go through the markers and get the largest + 1
|
||||
markerCounter = (markerCounter + 1) & 0xffffff;
|
||||
dbg("Found span end " + className);
|
||||
dbg("Adding marker " + markerName + ": " + JSON.stringify(range.toJSON()));
|
||||
writer.addMarker(markerName, { range: range, usingOperation: false });
|
||||
} else {
|
||||
// Text, we should also have text in the children
|
||||
assert(iChild < codeBlock.childCount && iChildText < childText.length, "Found text in html with no corresponding child text!!!!");
|
||||
if (html[iHtml] == "&") {
|
||||
// highlight.js only encodes
|
||||
// .replace(/&/g, '&')
|
||||
// .replace(/</g, '<')
|
||||
// .replace(/>/g, '>')
|
||||
// .replace(/"/g, '"')
|
||||
// .replace(/'/g, ''');
|
||||
// see https://github.com/highlightjs/highlight.js/blob/7addd66c19036eccd7c602af61f1ed84d215c77d/src/lib/utils.js#L5
|
||||
let iAmpEnd = html.indexOf(";", iHtml);
|
||||
dbg(html.slice(iHtml, iAmpEnd));
|
||||
iHtml = iAmpEnd + 1;
|
||||
} else {
|
||||
// regular text
|
||||
dbg(html[iHtml]);
|
||||
iHtml++;
|
||||
}
|
||||
iChildText++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
|
||||
import mimeTypesService from "../../services/mime_types.js";
|
||||
import utils, { hasTouchBar } from "../../services/utils.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
@@ -10,7 +10,6 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
|
||||
import link from "../../services/link.js";
|
||||
import appContext, { type CommandListenerData, type EventData } from "../../components/app_context.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import { initSyntaxHighlighting } from "./ckeditor/syntax_highlight.js";
|
||||
import options from "../../services/options.js";
|
||||
import toast from "../../services/toast.js";
|
||||
import { normalizeMimeTypeForCKEditor } from "../../services/mime_type_definitions.js";
|
||||
@@ -18,25 +17,25 @@ import { buildSelectedBackgroundColor } from "../../components/touch_bar.js";
|
||||
import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { getMermaidConfig } from "../../services/mermaid.js";
|
||||
import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig } from "@triliumnext/ckeditor5";
|
||||
import "@triliumnext/ckeditor5/index.css";
|
||||
|
||||
const ENABLE_INSPECTOR = false;
|
||||
|
||||
const mentionSetup: MentionConfig = {
|
||||
feeds: [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (item) => {
|
||||
const itemElement = document.createElement("button");
|
||||
const mentionSetup: MentionFeed[] = [
|
||||
{
|
||||
marker: "@",
|
||||
feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||
itemRenderer: (item) => {
|
||||
const itemElement = document.createElement("button");
|
||||
|
||||
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
||||
itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
];
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-detail-editable-text note-detail-printable">
|
||||
@@ -127,7 +126,7 @@ function buildListOfLanguages() {
|
||||
export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
private contentLanguage?: string | null;
|
||||
private watchdog!: CKWatchdog;
|
||||
private watchdog!: EditorWatchdog<ClassicEditor | PopupEditor>;
|
||||
|
||||
private $editor!: JQuery<HTMLElement>;
|
||||
|
||||
@@ -149,16 +148,15 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
async initEditor() {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||
const isClassicEditor = utils.isMobile() || options.get("textNoteEditorType") === "ckeditor-classic";
|
||||
const editorClass = isClassicEditor ? CKEditor.DecoupledEditor : CKEditor.BalloonEditor;
|
||||
const editorClass = isClassicEditor ? ClassicEditor : PopupEditor;
|
||||
|
||||
// CKEditor since version 12 needs the element to be visible before initialization. At the same time,
|
||||
// we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate
|
||||
// display of $widget in both branches.
|
||||
this.$widget.show();
|
||||
|
||||
this.watchdog = new CKEditor.EditorWatchdog(editorClass, {
|
||||
const config: WatchdogConfig = {
|
||||
// An average number of milliseconds between the last editor errors (defaults to 5000).
|
||||
// When the period of time between errors is lower than that and the crashNumberLimit
|
||||
// is also reached, the watchdog changes its state to crashedPermanently, and it stops
|
||||
@@ -173,7 +171,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
// A minimum number of milliseconds between saving the editor data internally (defaults to 5000).
|
||||
// Note that for large documents, this might impact the editor performance.
|
||||
saveInterval: 5000
|
||||
});
|
||||
};
|
||||
this.watchdog = isClassicEditor ? new EditorWatchdog(ClassicEditor, config) : new EditorWatchdog(PopupEditor, config);
|
||||
|
||||
this.watchdog.on("stateChange", () => {
|
||||
const currentState = this.watchdog.state;
|
||||
@@ -189,7 +188,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
if (currentState === "crashedPermanently") {
|
||||
dialogService.info(`Editing component keeps crashing. Please try restarting Trilium. If problem persists, consider creating a bug report.`);
|
||||
|
||||
this.watchdog.editor.enableReadOnlyMode("crashed-editor");
|
||||
this.watchdog.editor?.enableReadOnlyMode("crashed-editor");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -205,11 +204,14 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
styles: true,
|
||||
classes: true,
|
||||
attributes: true
|
||||
}
|
||||
},
|
||||
licenseKey: "GPL"
|
||||
};
|
||||
|
||||
const contentLanguage = this.note?.getLabelValue("language");
|
||||
if (contentLanguage) {
|
||||
// TODO: Wrong type?
|
||||
//@ts-ignore
|
||||
finalConfig.language = {
|
||||
ui: (typeof finalConfig.language === "string" ? finalConfig.language : "en"),
|
||||
content: contentLanguage
|
||||
@@ -219,10 +221,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.contentLanguage = null;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const editor = await editorClass.create(elementOrData, finalConfig);
|
||||
|
||||
const notificationsPlugin = editor.plugins.get("Notification");
|
||||
notificationsPlugin.on("show:warning", (evt: CKEvent, data: PluginEventData) => {
|
||||
notificationsPlugin.on("show:warning", (evt, data) => {
|
||||
const title = data.title;
|
||||
const message = data.message.message;
|
||||
|
||||
@@ -235,8 +238,6 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
evt.stop();
|
||||
});
|
||||
|
||||
await initSyntaxHighlighting(editor);
|
||||
|
||||
if (isClassicEditor) {
|
||||
let $classicToolbarWidget;
|
||||
if (!utils.isMobile()) {
|
||||
@@ -248,7 +249,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
$classicToolbarWidget.empty();
|
||||
if ($classicToolbarWidget.length) {
|
||||
$classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element);
|
||||
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
|
||||
if (toolbarView.element) {
|
||||
$classicToolbarWidget[0].appendChild(toolbarView.element);
|
||||
}
|
||||
}
|
||||
|
||||
if (utils.isMobile()) {
|
||||
@@ -256,17 +260,18 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
// Reposition all dropdowns to point upwards instead of downwards.
|
||||
// See https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for more info.
|
||||
const toolbarView = editor.ui.view.toolbar;
|
||||
const toolbarView = (editor as ClassicEditor).ui.view.toolbar;
|
||||
for (const item of toolbarView.items) {
|
||||
if (!("panelView" in item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item.on("change:isOpen", () => {
|
||||
if ( !item.isOpen ) {
|
||||
if (!("isOpen" in item) || !item.isOpen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
item.panelView.position = item.panelView.position.replace("s", "n");
|
||||
});
|
||||
}
|
||||
@@ -276,15 +281,14 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
editor.model.document.on("change:data", () => this.spacedUpdate.scheduleUpdate());
|
||||
|
||||
if (glob.isDev && ENABLE_INSPECTOR) {
|
||||
// TODO: Check if this still works.
|
||||
await import(/* webpackIgnore: true */ "../../../libraries/ckeditor/inspector.js");
|
||||
const CKEditorInspector = (await import("@ckeditor/ckeditor5-inspector")).default;
|
||||
CKEditorInspector.attach(editor);
|
||||
}
|
||||
|
||||
// Touch bar integration
|
||||
if (hasTouchBar) {
|
||||
for (const event of [ "bold", "italic", "underline", "paragraph", "heading" ]) {
|
||||
editor.commands.get(event).on("change", () => this.triggerCommand("refreshTouchBar"));
|
||||
editor.commands.get(event)?.on("change", () => this.triggerCommand("refreshTouchBar"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +301,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
async createEditor() {
|
||||
await this.watchdog.create(this.$editor[0], {
|
||||
placeholder: t("editable_text.placeholder"),
|
||||
//@ts-ignore TODO: FIX TYPES
|
||||
mention: mentionSetup,
|
||||
codeBlock: {
|
||||
languages: buildListOfLanguages()
|
||||
@@ -324,13 +329,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
if (this.contentLanguage !== newContentLanguage) {
|
||||
await this.reinitialize(data);
|
||||
} else {
|
||||
this.watchdog.editor.setData(data);
|
||||
this.watchdog.editor?.setData(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getData() {
|
||||
const content = this.watchdog.editor.getData();
|
||||
const content = this.watchdog.editor?.getData() ?? "";
|
||||
|
||||
// if content is only tags/whitespace (typically <p> </p>), then just make it empty,
|
||||
// this is important when setting a new note to code
|
||||
@@ -344,11 +349,14 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
scrollToEnd() {
|
||||
this.watchdog?.editor.model.change((writer) => {
|
||||
writer.setSelection(writer.createPositionAt(this.watchdog?.editor.model.document.getRoot(), "end"));
|
||||
this.watchdog?.editor?.model.change((writer) => {
|
||||
const rootItem = this.watchdog?.editor?.model.document.getRoot();
|
||||
if (rootItem) {
|
||||
writer.setSelection(writer.createPositionAt(rootItem, "end"));
|
||||
}
|
||||
});
|
||||
|
||||
this.watchdog?.editor.editing.view.focus();
|
||||
this.watchdog?.editor?.editing.view.focus();
|
||||
}
|
||||
|
||||
show() { }
|
||||
@@ -360,7 +368,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
cleanup() {
|
||||
if (this.watchdog?.editor) {
|
||||
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||
this.watchdog.editor.setData("");
|
||||
this.watchdog.editor?.setData("");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -375,18 +383,22 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
async addLinkToEditor(linkHref: string, linkTitle: string) {
|
||||
await this.initialized;
|
||||
|
||||
this.watchdog.editor.model.change((writer) => {
|
||||
const insertPosition = this.watchdog.editor.model.document.selection.getFirstPosition();
|
||||
writer.insertText(linkTitle, { linkHref: linkHref }, insertPosition);
|
||||
this.watchdog.editor?.model.change((writer) => {
|
||||
const insertPosition = this.watchdog.editor?.model.document.selection.getFirstPosition();
|
||||
if (insertPosition) {
|
||||
writer.insertText(linkTitle, { linkHref: linkHref }, insertPosition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async addTextToEditor(text: string) {
|
||||
await this.initialized;
|
||||
|
||||
this.watchdog.editor.model.change((writer) => {
|
||||
const insertPosition = this.watchdog.editor.model.document.selection.getLastPosition();
|
||||
writer.insertText(text, insertPosition);
|
||||
this.watchdog.editor?.model.change((writer) => {
|
||||
const insertPosition = this.watchdog.editor?.model.document.selection.getLastPosition();
|
||||
if (insertPosition) {
|
||||
writer.insertText(text, insertPosition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -403,23 +415,23 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
if (linkTitle) {
|
||||
if (this.hasSelection()) {
|
||||
this.watchdog.editor.execute("link", externalLink ? `${notePath}` : `#${notePath}`);
|
||||
this.watchdog.editor?.execute("link", externalLink ? `${notePath}` : `#${notePath}`);
|
||||
} else {
|
||||
await this.addLinkToEditor(externalLink ? `${notePath}` : `#${notePath}`, linkTitle);
|
||||
}
|
||||
} else {
|
||||
this.watchdog.editor.execute("referenceLink", { href: "#" + notePath });
|
||||
this.watchdog.editor?.execute("referenceLink", { href: "#" + notePath });
|
||||
}
|
||||
|
||||
this.watchdog.editor.editing.view.focus();
|
||||
this.watchdog.editor?.editing.view.focus();
|
||||
}
|
||||
|
||||
// returns true if user selected some text, false if there's no selection
|
||||
hasSelection() {
|
||||
const model = this.watchdog.editor.model;
|
||||
const selection = model.document.selection;
|
||||
const model = this.watchdog.editor?.model;
|
||||
const selection = model?.document.selection;
|
||||
|
||||
return !selection.isCollapsed;
|
||||
return !selection?.isCollapsed;
|
||||
}
|
||||
|
||||
async executeWithTextEditorEvent({ callback, resolve, ntxId }: EventData<"executeWithTextEditor">) {
|
||||
@@ -429,11 +441,15 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
await this.initialized;
|
||||
|
||||
if (callback) {
|
||||
callback(this.watchdog.editor);
|
||||
if (!this.watchdog.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(this.watchdog.editor);
|
||||
if (callback) {
|
||||
callback(this.watchdog.editor as CKTextEditor);
|
||||
}
|
||||
|
||||
resolve(this.watchdog.editor as CKTextEditor);
|
||||
}
|
||||
|
||||
addLinkToTextCommand() {
|
||||
@@ -443,11 +459,15 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
getSelectedText() {
|
||||
const range = this.watchdog.editor.model.document.selection.getFirstRange();
|
||||
const range = this.watchdog.editor?.model.document.selection.getFirstRange();
|
||||
let text = "";
|
||||
|
||||
if (!range) {
|
||||
return text;
|
||||
}
|
||||
|
||||
for (const item of range.getItems()) {
|
||||
if (item.data) {
|
||||
if ("data" in item && item.data) {
|
||||
text += item.data;
|
||||
}
|
||||
}
|
||||
@@ -458,12 +478,12 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
async followLinkUnderCursorCommand() {
|
||||
await this.initialized;
|
||||
|
||||
const selection = this.watchdog.editor.model.document.selection;
|
||||
const selectedElement = selection.getSelectedElement();
|
||||
const selection = this.watchdog.editor?.model.document.selection;
|
||||
const selectedElement = selection?.getSelectedElement();
|
||||
|
||||
if (selectedElement?.name === "reference") {
|
||||
// reference link
|
||||
const notePath = selectedElement.getAttribute("notePath");
|
||||
const notePath = selectedElement.getAttribute("notePath") as string | undefined;
|
||||
|
||||
if (notePath) {
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
@@ -471,11 +491,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
if (!selection.hasAttribute("linkHref")) {
|
||||
if (!selection?.hasAttribute("linkHref")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedLinkUrl = selection.getAttribute("linkHref");
|
||||
const selectedLinkUrl = selection.getAttribute("linkHref") as string;
|
||||
const notePath = link.getNotePathFromUrl(selectedLinkUrl);
|
||||
|
||||
if (notePath) {
|
||||
@@ -490,10 +510,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
addIncludeNote(noteId: string, boxSize?: string) {
|
||||
this.watchdog.editor.model.change((writer) => {
|
||||
this.watchdog.editor?.model.change((writer) => {
|
||||
// Insert <includeNote>*</includeNote> at the current selection position
|
||||
// in a way that will result in creating a valid model structure
|
||||
this.watchdog.editor.model.insertContent(
|
||||
this.watchdog.editor?.model.insertContent(
|
||||
writer.createElement("includeNote", {
|
||||
noteId: noteId,
|
||||
boxSize: boxSize
|
||||
@@ -504,7 +524,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
async addImage(noteId: string) {
|
||||
const note = await froca.getNote(noteId);
|
||||
if (!note) {
|
||||
if (!note || !this.watchdog.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,7 +532,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
const encodedTitle = encodeURIComponent(note.title);
|
||||
const src = `api/images/${note.noteId}/${encodedTitle}`;
|
||||
|
||||
this.watchdog.editor.execute("insertImage", { source: src });
|
||||
this.watchdog.editor?.execute("insertImage", { source: src });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -544,12 +564,12 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
|
||||
this.watchdog.destroy();
|
||||
await this.createEditor();
|
||||
this.watchdog.editor.setData(data);
|
||||
this.watchdog.editor?.setData(data);
|
||||
}
|
||||
|
||||
async onLanguageChanged() {
|
||||
const data = this.watchdog.editor.getData();
|
||||
await this.reinitialize(data);
|
||||
const data = this.watchdog.editor?.getData();
|
||||
await this.reinitialize(data ?? "");
|
||||
}
|
||||
|
||||
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
|
||||
@@ -557,20 +577,24 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;
|
||||
const { editor } = this.watchdog;
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commandButton = (icon: string, command: string) => new TouchBarButton({
|
||||
icon: buildIcon(icon),
|
||||
click: () => editor.execute(command),
|
||||
backgroundColor: buildSelectedBackgroundColor(editor.commands.get(command).value as boolean)
|
||||
backgroundColor: buildSelectedBackgroundColor(editor.commands.get(command)?.value as boolean)
|
||||
});
|
||||
|
||||
let headingSelectedIndex = undefined;
|
||||
const headingCommand = editor.commands.get("heading");
|
||||
const paragraphCommand = editor.commands.get("paragraph");
|
||||
if (paragraphCommand.value) {
|
||||
if (paragraphCommand?.value) {
|
||||
headingSelectedIndex = 0;
|
||||
} else if (headingCommand.value === "heading2") {
|
||||
} else if (headingCommand?.value === "heading2") {
|
||||
headingSelectedIndex = 1;
|
||||
} else if (headingCommand.value === "heading3") {
|
||||
} else if (headingCommand?.value === "heading3") {
|
||||
headingSelectedIndex = 2;
|
||||
}
|
||||
|
||||
@@ -581,7 +605,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
{ label: "H2" },
|
||||
{ label: "H3" }
|
||||
],
|
||||
change(selectedIndex, isSelected) {
|
||||
change(selectedIndex: number, isSelected: boolean) {
|
||||
switch (selectedIndex) {
|
||||
case 0:
|
||||
editor.execute("paragraph")
|
||||
|
||||
@@ -100,7 +100,7 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
// we load CKEditor also for read only notes because they contain content styles required for correct rendering of even read only notes
|
||||
// we could load just ckeditor-content.css but that causes CSS conflicts when both build CSS and this content CSS is loaded at the same time
|
||||
// (see https://github.com/zadam/trilium/issues/1590 for example of such conflict)
|
||||
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||
await import("@triliumnext/ckeditor5");
|
||||
|
||||
this.onLanguageChanged();
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/ckeditor5/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/commons/tsconfig.lib.json"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/ckeditor5"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/commons"
|
||||
},
|
||||
|
||||
@@ -1,80 +1,116 @@
|
||||
|
||||
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
||||
const { composePlugins, withNx, withWeb } = require('@nx/webpack');
|
||||
const { join } = require('path');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, 'dist'),
|
||||
},
|
||||
devServer: {
|
||||
port: 4200,
|
||||
client: {
|
||||
overlay: {
|
||||
errors: true,
|
||||
warnings: false,
|
||||
runtimeErrors: true
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new NxAppWebpackPlugin({
|
||||
tsConfig: './tsconfig.app.json',
|
||||
compiler: 'swc',
|
||||
main: "./src/index.ts",
|
||||
additionalEntryPoints: [
|
||||
{
|
||||
entryName: "desktop",
|
||||
entryPath: "./src/desktop.ts"
|
||||
},
|
||||
{
|
||||
entryName: "mobile",
|
||||
entryPath: "./src/mobile.ts"
|
||||
},
|
||||
{
|
||||
entryName: "login",
|
||||
entryPath: "./src/login.ts"
|
||||
},
|
||||
{
|
||||
entryName: "setup",
|
||||
entryPath: "./src/setup.ts"
|
||||
},
|
||||
{
|
||||
entryName: "share",
|
||||
entryPath: "./src/share.ts"
|
||||
},
|
||||
{
|
||||
// TriliumNextTODO: integrate set_password into setup entry point/view
|
||||
entryName: "set_password",
|
||||
entryPath: "./src/set_password.ts"
|
||||
}
|
||||
],
|
||||
externalDependencies: [
|
||||
"electron"
|
||||
],
|
||||
baseHref: '/',
|
||||
assets: [
|
||||
"./src/assets",
|
||||
"./src/stylesheets",
|
||||
"./src/libraries",
|
||||
"./src/fonts",
|
||||
"./src/translations"
|
||||
],
|
||||
styles: [],
|
||||
stylePreprocessorOptions: {
|
||||
sassOptions: {
|
||||
quietDeps: true
|
||||
}
|
||||
module.exports = composePlugins(
|
||||
withNx({
|
||||
tsConfig: join(__dirname, './tsconfig.app.json'),
|
||||
compiler: "tsc",
|
||||
main: join(__dirname, "./src/index.ts"),
|
||||
additionalEntryPoints: [
|
||||
{
|
||||
entryName: "desktop",
|
||||
entryPath: join(__dirname, "./src/desktop.ts")
|
||||
},
|
||||
outputHashing: false,
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
fallback: {
|
||||
path: false,
|
||||
fs: false,
|
||||
util: false
|
||||
}
|
||||
}
|
||||
};
|
||||
{
|
||||
entryName: "mobile",
|
||||
entryPath: join(__dirname, "./src/mobile.ts")
|
||||
},
|
||||
{
|
||||
entryName: "login",
|
||||
entryPath: join(__dirname, "./src/login.ts")
|
||||
},
|
||||
{
|
||||
entryName: "setup",
|
||||
entryPath: join(__dirname, "./src/setup.ts")
|
||||
},
|
||||
{
|
||||
entryName: "share",
|
||||
entryPath: join(__dirname, "./src/share.ts")
|
||||
},
|
||||
{
|
||||
// TriliumNextTODO: integrate set_password into setup entry point/view
|
||||
entryName: "set_password",
|
||||
entryPath: join(__dirname, "./src/set_password.ts")
|
||||
}
|
||||
],
|
||||
externalDependencies: [
|
||||
"electron"
|
||||
],
|
||||
baseHref: '/',
|
||||
outputHashing: false,
|
||||
optimization: process.env['NODE_ENV'] === 'production'
|
||||
}),
|
||||
withWeb({
|
||||
styles: [],
|
||||
stylePreprocessorOptions: {
|
||||
sassOptions: {
|
||||
quietDeps: true
|
||||
}
|
||||
},
|
||||
}),
|
||||
(config) => {
|
||||
config.output = {
|
||||
path: join(__dirname, 'dist')
|
||||
};
|
||||
|
||||
config.devServer = {
|
||||
port: 4200,
|
||||
client: {
|
||||
overlay: {
|
||||
errors: true,
|
||||
warnings: false,
|
||||
runtimeErrors: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.resolve.fallback = {
|
||||
path: false,
|
||||
fs: false,
|
||||
util: false
|
||||
};
|
||||
|
||||
const assets = [ "assets", "stylesheets", "libraries", "fonts", "translations" ]
|
||||
config.plugins.push(new CopyPlugin({
|
||||
patterns: assets.map((asset) => ({
|
||||
from: join(__dirname, "src", asset),
|
||||
to: asset
|
||||
}))
|
||||
}));
|
||||
|
||||
inlineSvg(config);
|
||||
externalJson(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
);
|
||||
|
||||
function inlineSvg(config) {
|
||||
if (!config.module?.rules) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Alter Nx's asset rule to avoid inlining SVG if they have ?raw prepended.
|
||||
const existingRule = config.module.rules.find((r) => r.test.toString() === /\.svg$/.toString());
|
||||
existingRule.resourceQuery = { not: [/raw/] };
|
||||
|
||||
// Add a rule for prepending ?raw SVGs.
|
||||
config.module.rules.push({
|
||||
resourceQuery: /raw/,
|
||||
type: 'asset/source',
|
||||
});
|
||||
}
|
||||
|
||||
function externalJson(config) {
|
||||
if (!config.module?.rules) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a rule for prepending ?external.
|
||||
config.module.rules.push({
|
||||
resourceQuery: /external/,
|
||||
type: 'asset/resource',
|
||||
});
|
||||
}
|
||||
1
apps/desktop/.serve-nodir.env
Normal file
1
apps/desktop/.serve-nodir.env
Normal file
@@ -0,0 +1 @@
|
||||
TRILIUM_PORT=37743
|
||||
@@ -18,16 +18,15 @@
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "35.2.2",
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@electron-forge/cli": "7.8.0",
|
||||
"@electron-forge/maker-deb": "7.8.0",
|
||||
"@electron-forge/maker-dmg": "7.8.0",
|
||||
"@electron-forge/maker-flatpak": "7.8.0",
|
||||
"@electron-forge/maker-rpm": "7.8.0",
|
||||
"@electron-forge/maker-squirrel": "7.8.0",
|
||||
"@electron-forge/maker-zip": "7.8.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.8.0",
|
||||
"electron": "36.2.0",
|
||||
"@electron-forge/cli": "7.8.1",
|
||||
"@electron-forge/maker-deb": "7.8.1",
|
||||
"@electron-forge/maker-dmg": "7.8.1",
|
||||
"@electron-forge/maker-flatpak": "7.8.1",
|
||||
"@electron-forge/maker-rpm": "7.8.1",
|
||||
"@electron-forge/maker-squirrel": "7.8.1",
|
||||
"@electron-forge/maker-zip": "7.8.1",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.8.1",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"config": {
|
||||
@@ -55,12 +54,10 @@
|
||||
"cache": true,
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/rebuild.mts",
|
||||
"cwd": "{projectRoot}"
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "electron-rebuild -f -v $(nix-shell -p electron_35 --run \"electron --version\") dist/main.js -m dist",
|
||||
"cwd": "{projectRoot}"
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist $(nix-shell -p electron_33 --run \"electron --version\")"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -76,7 +73,25 @@
|
||||
"cwd": "{projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "nix-shell -p electron_35 --run \"electron {projectRoot}/dist/main.js\"",
|
||||
"command": "nix-shell -p electron_33 --run \"electron {projectRoot}/dist/main.js\"",
|
||||
"cwd": ".",
|
||||
"forwardAllArgs": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-nodir": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"rebuild-deps"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "electron .",
|
||||
"cwd": "{projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "nix-shell -p electron_33 --run \"electron {projectRoot}/dist/main.js\"",
|
||||
"cwd": ".",
|
||||
"forwardAllArgs": false
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*
|
||||
* This script is used internally by the `rebuild-deps` target of the `desktop`. Normally we could use
|
||||
* `electron-rebuild` CLI directly, but it would rebuild the monorepo-level dependencies and breaks
|
||||
* the server build (and it doesn't expose a CLI option to override this).
|
||||
*/
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
import { rebuild } from "@electron/rebuild"
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = join(scriptDir, "..");
|
||||
|
||||
function getElectronVersion() {
|
||||
const packageJsonPath = join(rootDir, "package.json");
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
||||
return packageJson.devDependencies.electron;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const distDir = join(rootDir, "dist");
|
||||
|
||||
rebuild({
|
||||
// We force the project root path to avoid electron-rebuild from rebuilding the monorepo-level dependency and breaking the server.
|
||||
projectRootPath: distDir,
|
||||
buildPath: distDir,
|
||||
force: true,
|
||||
electronVersion: getElectronVersion(),
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -8,6 +8,14 @@ module.exports = {
|
||||
output: {
|
||||
path: outputDir,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/i,
|
||||
type: "asset/source"
|
||||
}
|
||||
]
|
||||
},
|
||||
target: [ "node" ],
|
||||
plugins: [
|
||||
new NxAppWebpackPlugin({
|
||||
|
||||
@@ -4,32 +4,34 @@
|
||||
"private": true,
|
||||
"description": "Desktop version of Trilium which imports the demo database (presented to new users at start-up) or the user guide and other documentation and saves the modifications for committing.",
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@triliumnext/client": "workspace:*",
|
||||
"@triliumnext/desktop": "workspace:*",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "35.2.2",
|
||||
"electron": "36.2.0",
|
||||
"fs-extra": "11.3.0"
|
||||
},
|
||||
"nx": {
|
||||
"name": "edit-docs",
|
||||
"implicitDependencies": [
|
||||
"server"
|
||||
],
|
||||
"targets": {
|
||||
"rebuild-deps": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [ "build" ],
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"cache": true,
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/rebuild.mts",
|
||||
"cwd": "{projectRoot}"
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "electron-rebuild -f -v $(nix-shell -p electron_35 --run \"electron --version\") dist/main.js -m dist",
|
||||
"cwd": "{projectRoot}"
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist $(nix-shell -p electron_33 --run \"electron --version\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
*
|
||||
* This script is used internally by the `rebuild-deps` target of the `desktop`. Normally we could use
|
||||
* `electron-rebuild` CLI directly, but it would rebuild the monorepo-level dependencies and breaks
|
||||
* the server build (and it doesn't expose a CLI option to override this).
|
||||
*/
|
||||
|
||||
// TODO: Deduplicate with apps/desktop/scripts/rebuild.ts.
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
import { rebuild } from "@electron/rebuild"
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = join(scriptDir, "..");
|
||||
|
||||
function getElectronVersion() {
|
||||
const packageJsonPath = join(rootDir, "package.json");
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
||||
return packageJson.devDependencies.electron;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const distDir = join(rootDir, "dist");
|
||||
|
||||
rebuild({
|
||||
// We force the project root path to avoid electron-rebuild from rebuilding the monorepo-level dependency and breaking the server.
|
||||
projectRootPath: distDir,
|
||||
buildPath: distDir,
|
||||
force: true,
|
||||
electronVersion: getElectronVersion(),
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -8,6 +8,14 @@ module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, 'dist'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/i,
|
||||
type: "asset/source"
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new NxAppWebpackPlugin({
|
||||
target: 'node',
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
"implicitDependencies": [
|
||||
"client",
|
||||
"server"
|
||||
]
|
||||
],
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"dependsOn": [
|
||||
"server:build"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "16.5.0"
|
||||
|
||||
@@ -55,6 +55,8 @@ test("Displays math popup", async ({ page, context }) => {
|
||||
await app.goto();
|
||||
await app.goToNoteInNewTab("Empty text");
|
||||
const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor");
|
||||
await expect(noteContent.locator("p")).toBeVisible();
|
||||
await noteContent.focus();
|
||||
await noteContent.fill("Hello world");
|
||||
await noteContent.press("ControlOrMeta+M");
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ FROM node:22.15.0-bullseye-slim AS builder
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
WORKDIR /usr/src/app/build
|
||||
COPY ./dist/package.json ./dist/pnpm-lock.yaml ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
RUN pnpm install --frozen-lockfile --prod && pnpm rebuild
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.15.0-bullseye-slim
|
||||
# Install only runtime dependencies
|
||||
|
||||
@@ -4,7 +4,8 @@ FROM node:22.15.0-alpine AS builder
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
WORKDIR /usr/src/app
|
||||
COPY ./dist/package.json ./dist/pnpm-lock.yaml ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
RUN pnpm install --frozen-lockfile --prod && pnpm rebuild
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.15.0-alpine
|
||||
# Install runtime dependencies
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"better-sqlite3": "11.9.1",
|
||||
"better-sqlite3": "11.10.0",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
"@highlightjs/cdn-assets": "11.11.1"
|
||||
@@ -14,7 +14,6 @@
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@types/archiver": "6.0.3",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/cheerio": "0.22.35",
|
||||
"@types/cls-hooked": "4.3.9",
|
||||
"@types/compression": "1.7.5",
|
||||
"@types/cookie-parser": "1.4.8",
|
||||
@@ -31,7 +30,7 @@
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/multer": "1.4.12",
|
||||
"@types/safe-compare": "1.1.2",
|
||||
"@types/sanitize-html": "2.15.0",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/sax": "1.2.7",
|
||||
"@types/serve-favicon": "2.5.7",
|
||||
"@types/serve-static": "1.15.7",
|
||||
@@ -50,7 +49,7 @@
|
||||
"jquery": "3.7.1",
|
||||
"katex": "0.16.22",
|
||||
"normalize.css": "8.0.1",
|
||||
"@anthropic-ai/sdk": "0.40.1",
|
||||
"@anthropic-ai/sdk": "0.50.3",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/express-partial-content": "workspace:*",
|
||||
@@ -70,7 +69,7 @@
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.0",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "35.2.2",
|
||||
"electron": "36.2.0",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
@@ -85,7 +84,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.0.2",
|
||||
"i18next": "25.1.2",
|
||||
"i18next-fs-backend": "2.6.0",
|
||||
"image-type": "5.2.0",
|
||||
"ini": "5.0.0",
|
||||
@@ -99,7 +98,7 @@
|
||||
"multer": "1.4.5-lts.2",
|
||||
"normalize-strings": "1.1.1",
|
||||
"ollama": "0.5.15",
|
||||
"openai": "4.97.0",
|
||||
"openai": "4.98.0",
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
@@ -117,7 +116,7 @@
|
||||
"tmp": "0.2.3",
|
||||
"turndown": "7.2.0",
|
||||
"unescape": "1.0.1",
|
||||
"webpack": "5.99.7",
|
||||
"webpack": "5.99.8",
|
||||
"ws": "8.18.2",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "3.2.0",
|
||||
@@ -125,10 +124,13 @@
|
||||
},
|
||||
"nx": {
|
||||
"name": "server",
|
||||
"implicitDependencies": [
|
||||
"client"
|
||||
],
|
||||
"targets": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"client:build"
|
||||
]
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/js:node",
|
||||
"defaultConfiguration": "development",
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/1_Bookmarks_plus.png
generated
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/1_Bookmarks_plus.png
generated
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 703 B |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/1_Insert buttons_plus.png
generated
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/1_Insert buttons_plus.png
generated
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
29
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Bookmarks.html
generated
Normal file
29
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Bookmarks.html
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
<p>Bookmarks allows creating <a href="#root/_help_QEAPj01N5f7w">links</a> to
|
||||
a certain part of a note, such as referencing a particular heading.</p>
|
||||
<p>Technically, bookmarks are HTML anchors.</p>
|
||||
<p>This feature was introduced in TriliumNext 0.94.0.</p>
|
||||
<h2>Interaction</h2>
|
||||
<ul>
|
||||
<li>To create a bookmark:
|
||||
<ul>
|
||||
<li>Place the cursor at the desired position where to place the bookmark.</li>
|
||||
<li>Look for the
|
||||
<img src="Bookmarks_plus.png" width="15" height="16">button in the <a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>,
|
||||
and then press the
|
||||
<img src="1_Bookmarks_plus.png" width="12" height="15">button.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>To place a link to a bookmark:
|
||||
<ul>
|
||||
<li>Place the cursor at the desired position of the link.</li>
|
||||
<li>From the <a href="#root/_help_QEAPj01N5f7w">link</a> pane, select the <em>Bookmarks</em> section
|
||||
and select the desired bookmark.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Limitations</h2>
|
||||
<ul>
|
||||
<li>Currently it's not possible to create a link to a bookmark from a different
|
||||
note. This functionality will be added after the internal links feature
|
||||
is enhanced to support bookmarks.</li>
|
||||
</ul>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Bookmarks_plus.png
generated
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Bookmarks_plus.png
generated
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 636 B |
@@ -2,59 +2,76 @@
|
||||
<img src="4_Insert buttons_image.png" width="34" height="16">button in the <a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a> to
|
||||
reveal special inserable items and blocks such as symbols, Math expressions
|
||||
and separators.</p>
|
||||
<h2>Symbols</h2>
|
||||
<figure class="image image-style-align-right">
|
||||
<img style="aspect-ratio:346/322;" src="1_Insert buttons_image.png" width="346"
|
||||
height="322">
|
||||
<h2>Bookmarks</h2>
|
||||
<p>See the dedicated <a class="reference-link" href="#root/_help_oSuaNgyyKnhu">Bookmarks</a> section.</p>
|
||||
<h2>Emoji</h2>
|
||||
<figure class="image image-style-align-right image_resized" style="width:42.4%;">
|
||||
<img style="aspect-ratio:366/410;" src="Insert buttons_plus.png" width="366"
|
||||
height="410">
|
||||
</figure>
|
||||
<p>Pressing the
|
||||
<img src="7_Insert buttons_image.png" width="18" height="15">button will reveal a popup window displaying a list of characters that
|
||||
are generally more difficult to insert directly from the keyboard, such
|
||||
as a subset of emojis, quotation characters, etc.</p>
|
||||
<p>Interaction:</p>
|
||||
<ul>
|
||||
<li>Click on a character to insert it at the current cursor position.</li>
|
||||
<li>The window can be dragged around by the top bar where the title is, to
|
||||
avoid it getting in the way of the text.</li>
|
||||
<li>Click on the <em>Category</em> selector to filter the characters.</li>
|
||||
</ul>
|
||||
<h2>Math equations</h2>
|
||||
<p>See the dedicated <a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a> page.</p>
|
||||
<h2>Mermaid diagram</h2>
|
||||
<p>Press the
|
||||
<img src="2_Insert buttons_image.png" width="16" height="17">button to create an inline Mermaid diagram.</p>
|
||||
<p>This feature is quite similar to the <a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a> note
|
||||
types and is meant as an alternative to it for simple diagrams. For more
|
||||
complex diagrams, use the <a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a> feature
|
||||
for a dedicated Mermaid note.</p>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1174/358;" src="6_Insert buttons_image.png" width="1174"
|
||||
height="358">
|
||||
</figure>
|
||||
|
||||
<h2>Horizontal ruler</h2>
|
||||
<p>This feature will display a horizontal line, generally useful to separate
|
||||
different sections of the text. To do so, press the
|
||||
<img src="5_Insert buttons_image.png"
|
||||
width="18" height="16">button in the <a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>.</p>
|
||||
<p>This feature allows inserting Unicode emoji characters. Simply select
|
||||
a category and a desired emoji to insert it.</p>
|
||||
<p>Emojis can also be searched by their English name and the skin tone can
|
||||
be selected via a combo box to the right.</p>
|
||||
<p>There is also the possibility of inserting emojis directly by typing <code>:</code> followed
|
||||
by a name of an emoji, triggering the display of a list of emojis. Simply
|
||||
use the arrow keys to select one and press <kbd>Enter</kbd> to insert it.</p>
|
||||
<img
|
||||
src="3_Insert buttons_image.png" width="502" height="95">
|
||||
<p>Alternatively, it's possible to insert a horizontal ruler by typing <code>---</code>.</p>
|
||||
<h2>Page break</h2>
|
||||
src="1_Insert buttons_plus.png" width="272" height="187">
|
||||
|
||||
<h2>Symbols</h2>
|
||||
<figure class="image image-style-align-right">
|
||||
<img style="aspect-ratio:371/79;" src="8_Insert buttons_image.png" width="371"
|
||||
height="79">
|
||||
<img style="aspect-ratio:346/322;" src="1_Insert buttons_image.png" width="346"
|
||||
height="322">
|
||||
</figure>
|
||||
<p>Page breaks provide a way to force the next paragraph or block (table,
|
||||
image, etc.) to be displayed onto the next page when printing (either to
|
||||
a real printer to <a href="#root/_help_NRnIZmSMc5sj">when exporting to PDF</a>).</p>
|
||||
<p>Page breaks are marked in the editor with the words <em>Page break</em>,
|
||||
but they will not actually be shown when printed.</p>
|
||||
<p>Pressing the
|
||||
<img src="7_Insert buttons_image.png" width="18" height="15">button will reveal a popup window displaying a list of characters that
|
||||
are generally more difficult to insert directly from the keyboard, such
|
||||
as a subset of emojis, quotation characters, etc.</p>
|
||||
<p>Interaction:</p>
|
||||
<ul>
|
||||
<li>To insert a page break, press the
|
||||
<img src="Insert buttons_image.png" width="20"
|
||||
height="19">in the formatting toolbar.</li>
|
||||
<li>To insert many page breaks at once, insert a page break first, click on
|
||||
it and press <kbd>Ctrl</kbd>+<kbd>C</kbd>. Then use <kbd>Ctrl</kbd>+<kbd>V</kbd>,
|
||||
to paste as many times as needed.</li>
|
||||
</ul>
|
||||
<li>Click on a character to insert it at the current cursor position.</li>
|
||||
<li>The window can be dragged around by the top bar where the title is, to
|
||||
avoid it getting in the way of the text.</li>
|
||||
<li>Click on the <em>Category</em> selector to filter the characters.</li>
|
||||
</ul>
|
||||
<h2>Math equations</h2>
|
||||
<p>See the dedicated <a class="reference-link" href="#root/_help_YfYAtQBcfo5V">Math Equations</a> page.</p>
|
||||
<h2>Mermaid diagram</h2>
|
||||
<p>Press the
|
||||
<img src="2_Insert buttons_image.png" width="16" height="17">button to create an inline Mermaid diagram.</p>
|
||||
<p>This feature is quite similar to the <a class="reference-link" href="#root/_help_s1aBHPd79XYj">Mermaid Diagrams</a> note
|
||||
types and is meant as an alternative to it for simple diagrams. For more
|
||||
complex diagrams, use the <a class="reference-link" href="#root/_help_nBAXQFj20hS1">Include Note</a> feature
|
||||
for a dedicated Mermaid note.</p>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1174/358;" src="6_Insert buttons_image.png" width="1174"
|
||||
height="358">
|
||||
</figure>
|
||||
|
||||
<h2>Horizontal ruler</h2>
|
||||
<p>This feature will display a horizontal line, generally useful to separate
|
||||
different sections of the text. To do so, press the
|
||||
<img src="5_Insert buttons_image.png"
|
||||
width="18" height="16">button in the <a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>.</p>
|
||||
<img
|
||||
src="3_Insert buttons_image.png" width="502" height="95">
|
||||
<p>Alternatively, it's possible to insert a horizontal ruler by typing <code>---</code>.</p>
|
||||
<h2>Page break</h2>
|
||||
<figure class="image image-style-align-right">
|
||||
<img style="aspect-ratio:371/79;" src="8_Insert buttons_image.png" width="371"
|
||||
height="79">
|
||||
</figure>
|
||||
<p>Page breaks provide a way to force the next paragraph or block (table,
|
||||
image, etc.) to be displayed onto the next page when printing (either to
|
||||
a real printer to <a href="#root/_help_NRnIZmSMc5sj">when exporting to PDF</a>).</p>
|
||||
<p>Page breaks are marked in the editor with the words <em>Page break</em>,
|
||||
but they will not actually be shown when printed.</p>
|
||||
<ul>
|
||||
<li>To insert a page break, press the
|
||||
<img src="Insert buttons_image.png" width="20"
|
||||
height="19">in the formatting toolbar.</li>
|
||||
<li>To insert many page breaks at once, insert a page break first, click on
|
||||
it and press <kbd>Ctrl</kbd>+<kbd>C</kbd>. Then use <kbd>Ctrl</kbd>+<kbd>V</kbd>,
|
||||
to paste as many times as needed.</li>
|
||||
</ul>
|
||||
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Insert buttons_plus.png
generated
Normal file
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Text/Insert buttons_plus.png
generated
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -59,6 +59,8 @@
|
||||
with the text inside of it.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>For <a href="#root/_help_CohkqWQC1iBv">emojis</a>, type <code>:</code> followed
|
||||
by an emoji name to trigger an auto-completion.</li>
|
||||
</ul>
|
||||
<p>If auto-formatting is not desirable, press <kbd>Ctrl</kbd> + <kbd>Z</kbd> to
|
||||
revert the text to its original form.</p>
|
||||
@@ -7,7 +7,8 @@
|
||||
<img src="1_Math Equations_image.png" width="20" height="15">button from the <a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a> (generally
|
||||
found under the <a class="reference-link" href="#root/_help_CohkqWQC1iBv">Insert buttons</a>).</p>
|
||||
<p>If inserting equations frequently, using the <kbd>Ctrl</kbd>+<kbd>M</kbd> keyboard
|
||||
shortcut can be more comfortable.</p>
|
||||
shortcut can be more comfortable. Alternatively, type <code>$$</code> or <code>\[</code> to
|
||||
trigger the popup directly.</p>
|
||||
<p>There is currently no quick way to insert an equation, such as surrounding
|
||||
it with <code>$</code> or pressing <kbd>Ctrl</kbd>+<kbd>M</kbd> on an already
|
||||
typed-out equation.</p>
|
||||
|
||||
@@ -5,6 +5,7 @@ import express from "express";
|
||||
import { getResourceDir, isDev } from "../services/utils.js";
|
||||
import type serveStatic from "serve-static";
|
||||
import proxy from "express-http-proxy";
|
||||
import contentCss from "@triliumnext/ckeditor5/content.css";
|
||||
|
||||
const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<unknown, Record<string, unknown>>>) => {
|
||||
if (!isDev) {
|
||||
@@ -20,6 +21,8 @@ async function register(app: express.Application) {
|
||||
const srcRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const resourceDir = getResourceDir();
|
||||
|
||||
app.use(`/${assetPath}/libraries/ckeditor/ckeditor-content.css`, (req, res) => res.contentType("text/css").send(contentCss));
|
||||
|
||||
if (isDev) {
|
||||
const publicUrl = process.env.TRILIUM_PUBLIC_SERVER;
|
||||
if (!publicUrl) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getStylesDirectory, readThemesFromFileSystem } from "./code_block_theme.js";
|
||||
import { readThemesFromFileSystem } from "./code_block_theme.js";
|
||||
|
||||
import themeNames from "./code_block_theme_names.json" with { type: "json" };
|
||||
import path = require("path");
|
||||
|
||||
describe("Code block theme", () => {
|
||||
it("all themes are mapped", () => {
|
||||
const themes = readThemesFromFileSystem(getStylesDirectory());
|
||||
const themes = readThemesFromFileSystem(path.join(__dirname, "../../node_modules/@highlightjs/cdn-assets/styles"));
|
||||
|
||||
const mappedThemeNames = new Set(Object.values(themeNames));
|
||||
const unmappedThemeNames = new Set<string>();
|
||||
|
||||
@@ -30,7 +30,7 @@ interface ColorTheme {
|
||||
* @returns the supported themes, grouped.
|
||||
*/
|
||||
export function listSyntaxHighlightingThemes() {
|
||||
const path = join(getResourceDir(), getStylesDirectory());
|
||||
const path = getStylesDirectory();
|
||||
const systemThemes = readThemesFromFileSystem(path);
|
||||
|
||||
return {
|
||||
@@ -46,11 +46,11 @@ export function listSyntaxHighlightingThemes() {
|
||||
|
||||
export function getStylesDirectory() {
|
||||
if (isElectron && !isDev) {
|
||||
return "styles";
|
||||
return join(getResourceDir(), "styles");
|
||||
} else if (!isDev) {
|
||||
return "node_modules/@highlightjs/cdn-assets/styles";
|
||||
return join(getResourceDir(), "node_modules/@highlightjs/cdn-assets/styles");
|
||||
} else {
|
||||
return join(__dirname, "../../node_modules/@highlightjs/cdn-assets/styles");
|
||||
return join(__dirname, "../node_modules/@highlightjs/cdn-assets/styles");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import type AttributeMeta from "../meta/attribute_meta.js";
|
||||
import type BBranch from "../../becca/entities/bbranch.js";
|
||||
import type { Response } from "express";
|
||||
import type { NoteMetaFile } from "../meta/note_meta.js";
|
||||
import cssContent from "@triliumnext/ckeditor5/content.css";
|
||||
|
||||
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
|
||||
|
||||
@@ -511,8 +512,6 @@ ${markdownContent}`;
|
||||
return;
|
||||
}
|
||||
|
||||
const cssContent = fs.readFileSync(`${getResourceDir()}/public/libraries/ckeditor/ckeditor-content.css`);
|
||||
|
||||
archive.append(cssContent, { name: cssMeta.dataFileName });
|
||||
}
|
||||
|
||||
|
||||
5
apps/server/src/types.d.ts
vendored
5
apps/server/src/types.d.ts
vendored
@@ -22,3 +22,8 @@ declare module "is-animated" {
|
||||
function isAnimated(buffer: Buffer): boolean;
|
||||
export default isAnimated;
|
||||
}
|
||||
|
||||
declare module "@triliumnext/ckeditor5/content.css" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"src/**/*.spec.jsx"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/ckeditor5/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/turndown-plugin-gfm/tsconfig.lib.json"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/ckeditor5"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/turndown-plugin-gfm"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const { join, default: path } = require('path');
|
||||
const { join } = require('path');
|
||||
|
||||
const outputDir = join(__dirname, 'dist');
|
||||
|
||||
@@ -48,6 +48,14 @@ module.exports = {
|
||||
output: {
|
||||
path: outputDir
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/i,
|
||||
type: "asset/source"
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new NxAppWebpackPlugin({
|
||||
target: 'node',
|
||||
|
||||
Reference in New Issue
Block a user