mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 02:52:27 +08:00
Merge branch 'develop' into feat/add-rootless-dockerfiles
This commit is contained in:
commit
6430d7048d
41
README.md
41
README.md
@ -20,6 +20,17 @@ There are no special migration steps to migrate from a zadam/Trilium instance to
|
||||
|
||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext have their sync versions incremented.
|
||||
|
||||
## Documentation
|
||||
|
||||
We're currently in the progress of moving the documentation to in-app (hit the `F1` key within Trilium). As a result, there may be some missing parts until we've completed the migration. If you'd prefer to navigate through the documentation within GitHub, you can navigate the [User Guide](./docs/User%20Guide/User%20Guide/) documentation.
|
||||
|
||||
Below are some quick links for your convenience to navigate the documentation:
|
||||
- [Server installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
|
||||
- [Docker installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
|
||||
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
|
||||
- [Concepts and Features - Note](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
|
||||
|
||||
|
||||
## 💬 Discuss with us
|
||||
|
||||
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
|
||||
@ -63,7 +74,7 @@ Feel free to join our official conversations. We would love to hear what feature
|
||||
|
||||
To use TriliumNext on your desktop machine (Linux, MacOS, and Windows) you have a few options:
|
||||
|
||||
* Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the ```trilium``` executable.
|
||||
* Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable.
|
||||
* Access TriliumNext via the web interface of a server installation (see below)
|
||||
* Currently only the latest versions of Chrome & Firefox are supported (and tested).
|
||||
* TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
||||
@ -90,18 +101,38 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
|
||||
|
||||
### Code
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
npm install
|
||||
npm run server:start
|
||||
pnpm install
|
||||
pnpm run server:start
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
pnpm install
|
||||
pnpm nx run edit-docs:serve
|
||||
```
|
||||
|
||||
### Building the Executable
|
||||
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Notes.git
|
||||
cd Notes
|
||||
pnpm install
|
||||
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
||||
```
|
||||
|
||||
For more details, see the [development docs](https://github.com/TriliumNext/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Building%20and%20deployment/Running%20a%20development%20build.md).
|
||||
|
||||
### Documentation
|
||||
### Developer Documentation
|
||||
|
||||
See the [documentation guide](https://github.com/TriliumNext/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Documentation.md) for details.
|
||||
Please view the [documentation guide](./docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
||||
|
||||
## 👏 Shoutouts
|
||||
|
||||
|
@ -23,13 +23,13 @@
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/codemirror": "workspace:*",
|
||||
"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",
|
||||
"force-graph": "1.49.6",
|
||||
"globals": "16.1.0",
|
||||
"i18next": "25.1.2",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
@ -57,8 +57,8 @@
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/leaflet": "1.9.17",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/react": "19.1.3",
|
||||
"@types/react-dom": "19.1.3",
|
||||
"@types/react": "19.1.4",
|
||||
"@types/react-dom": "19.1.5",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"happy-dom": "17.4.7",
|
||||
"script-loader": "0.7.2"
|
||||
|
@ -11,6 +11,7 @@ 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";
|
||||
import type CodeMirror from "@triliumnext/codemirror";
|
||||
|
||||
export interface SetNoteOpts {
|
||||
triggerSwitchEvent?: unknown;
|
||||
@ -312,7 +313,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
|
||||
async getCodeEditor() {
|
||||
return this.timeout(
|
||||
new Promise<CodeMirrorInstance>((resolve) =>
|
||||
new Promise<CodeMirror>((resolve) =>
|
||||
appContext.triggerCommand("executeWithCodeEditor", {
|
||||
resolve,
|
||||
ntxId: this.ntxId
|
||||
|
74
apps/client/src/libraries/codemirror/eslint.js
vendored
74
apps/client/src/libraries/codemirror/eslint.js
vendored
@ -1,74 +0,0 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
async function validatorHtml(text, options) {
|
||||
const result = /<script[^>]*>([\s\S]+)<\/script>/ig.exec(text);
|
||||
|
||||
if (result !== null) {
|
||||
// preceding code is copied over but any (non-newline) character is replaced with space
|
||||
// this will preserve line numbers etc.
|
||||
const prefix = text.substr(0, result.index).replace(/./g, " ");
|
||||
|
||||
const js = prefix + result[1];
|
||||
|
||||
return await validatorJavaScript(js, options);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function validatorJavaScript(text, options) {
|
||||
if (glob.isMobile()
|
||||
|| glob.getActiveContextNote() == null
|
||||
|| glob.getActiveContextNote().mime === 'application/json') {
|
||||
// eslint doesn't seem to validate pure JSON well
|
||||
return [];
|
||||
}
|
||||
|
||||
if (text.length > 20000) {
|
||||
console.log("Skipping linting because of large size: ", text.length);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
const errors = await glob.linter(text, glob.getActiveContextNote().mime);
|
||||
|
||||
console.log(errors);
|
||||
|
||||
const result = [];
|
||||
if (errors) {
|
||||
parseErrors(errors, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("lint", "javascript", validatorJavaScript);
|
||||
CodeMirror.registerHelper("lint", "html", validatorHtml);
|
||||
|
||||
function parseErrors(errors, output) {
|
||||
for (const error of errors) {
|
||||
const startLine = error.line - 1;
|
||||
const endLine = error.endLine !== undefined ? error.endLine - 1 : startLine;
|
||||
const startCol = error.column - 1;
|
||||
const endCol = error.endColumn !== undefined ? error.endColumn - 1 : startCol + 1;
|
||||
|
||||
output.push({
|
||||
message: error.message,
|
||||
severity: error.severity === 1 ? "warning" : "error",
|
||||
from: CodeMirror.Pos(startLine, startCol),
|
||||
to: CodeMirror.Pos(endLine, endCol)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -5,7 +5,6 @@ import libraryLoader from "./library_loader.js";
|
||||
import ws from "./ws.js";
|
||||
import froca from "./froca.js";
|
||||
import linkService from "./link.js";
|
||||
import { lint } from "./eslint.js";
|
||||
|
||||
function setupGlobs() {
|
||||
window.glob.isDesktop = utils.isDesktop;
|
||||
@ -19,7 +18,6 @@ function setupGlobs() {
|
||||
// required for ESLint plugin and CKEditor
|
||||
window.glob.getActiveContextNote = () => appContext.tabManager.getActiveContextNote();
|
||||
window.glob.requireLibrary = libraryLoader.requireLibrary;
|
||||
window.glob.linter = lint;
|
||||
window.glob.appContext = appContext; // for debugging
|
||||
window.glob.froca = froca;
|
||||
window.glob.treeCache = froca; // compatibility for CKEditor builds for a while
|
||||
|
@ -7,37 +7,6 @@ export interface Library {
|
||||
css?: string[];
|
||||
}
|
||||
|
||||
const CODE_MIRROR: Library = {
|
||||
js: () => {
|
||||
const scriptsToLoad = [
|
||||
"node_modules/codemirror/lib/codemirror.js",
|
||||
"node_modules/codemirror/addon/display/placeholder.js",
|
||||
"node_modules/codemirror/addon/edit/matchbrackets.js",
|
||||
"node_modules/codemirror/addon/edit/matchtags.js",
|
||||
"node_modules/codemirror/addon/fold/xml-fold.js",
|
||||
"node_modules/codemirror/addon/lint/lint.js",
|
||||
"node_modules/codemirror/addon/mode/loadmode.js",
|
||||
"node_modules/codemirror/addon/mode/multiplex.js",
|
||||
"node_modules/codemirror/addon/mode/overlay.js",
|
||||
"node_modules/codemirror/addon/mode/simple.js",
|
||||
"node_modules/codemirror/addon/search/match-highlighter.js",
|
||||
"node_modules/codemirror/mode/meta.js",
|
||||
"node_modules/codemirror/keymap/vim.js",
|
||||
"libraries/codemirror/eslint.js"
|
||||
];
|
||||
|
||||
const mimeTypes = mimeTypesService.getMimeTypes();
|
||||
for (const mimeType of mimeTypes) {
|
||||
if (mimeType.enabled && mimeType.codeMirrorSource) {
|
||||
scriptsToLoad.push(mimeType.codeMirrorSource);
|
||||
}
|
||||
}
|
||||
|
||||
return scriptsToLoad;
|
||||
},
|
||||
css: ["node_modules/codemirror/lib/codemirror.css", "node_modules/codemirror/addon/lint/lint.css"]
|
||||
};
|
||||
|
||||
const KATEX: Library = {
|
||||
js: ["node_modules/katex/dist/katex.min.js", "node_modules/katex/dist/contrib/mhchem.min.js", "node_modules/katex/dist/contrib/auto-render.min.js"],
|
||||
css: ["node_modules/katex/dist/katex.min.css"]
|
||||
@ -152,7 +121,6 @@ export default {
|
||||
requireCss,
|
||||
requireLibrary,
|
||||
loadHighlightingTheme,
|
||||
CODE_MIRROR,
|
||||
KATEX,
|
||||
HIGHLIGHT_JS
|
||||
};
|
||||
|
@ -68,6 +68,7 @@ export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
|
||||
{ title: "Forth", mime: "text/x-forth" },
|
||||
{ title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
|
||||
{ title: "Gas", mime: "text/x-gas" },
|
||||
{ title: "GDScript (Godot)", mime: "text/x-gdscript" },
|
||||
{ title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
|
||||
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
|
||||
{ title: "Go", mime: "text/x-go", highlightJs: "go", default: true },
|
||||
@ -106,6 +107,7 @@ export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
|
||||
{ title: "msgenny", mime: "text/x-msgenny" },
|
||||
{ title: "MUMPS", mime: "text/x-mumps" },
|
||||
{ title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
|
||||
{ title: "Nix", mime: "text/x-nix", highlightJs: "nix" },
|
||||
{ title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
|
||||
{ title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
|
||||
{ title: "NTriples", mime: "application/n-triples" },
|
||||
|
@ -420,33 +420,24 @@ body.desktop #context-menu-container .dropdown-item > span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
background: inherit;
|
||||
outline: none !important;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
body .CodeMirror {
|
||||
body .cm-editor {
|
||||
font-size: var(--monospace-font-size);
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
body .cm-editor .cm-gutters {
|
||||
background-color: inherit !important;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.cm-matchhighlight {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.cm-matchhighlight.ck-find-result{
|
||||
background: var(--ck-color-highlight-background);
|
||||
}
|
||||
|
||||
.cm-matchhighlight.ck-find-result_selected {
|
||||
background-color: #ff9633;
|
||||
}
|
||||
|
||||
.CodeMirror pre.CodeMirror-placeholder {
|
||||
body .cm-editor .cm-placeholder {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
@ -457,11 +448,11 @@ body .CodeMirror {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#sql-console-query .CodeMirror {
|
||||
#sql-console-query .cm-editor {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
#sql-console-query .CodeMirror-scroll {
|
||||
#sql-console-query .cm-editor .cm-scroller {
|
||||
min-height: inherit !important;
|
||||
}
|
||||
|
||||
@ -524,7 +515,7 @@ button.btn-sm {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre:not(.CodeMirror-line):not(.hljs) {
|
||||
pre:not(.hljs) {
|
||||
color: var(--main-text-color) !important;
|
||||
white-space: pre-wrap;
|
||||
font-size: 100%;
|
||||
|
@ -81,10 +81,6 @@ body ::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
body .CodeMirror {
|
||||
filter: invert(90%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.excalidraw.theme--dark {
|
||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||
}
|
||||
|
@ -244,10 +244,6 @@ body ::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
body .CodeMirror {
|
||||
filter: invert(90%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.excalidraw.theme--dark {
|
||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||
}
|
||||
|
@ -1616,7 +1616,7 @@
|
||||
"auto-detect-language": "自动检测"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "文本笔记的代码语法高亮",
|
||||
"title": "",
|
||||
"description": "控制文本笔记中代码块的语法高亮,代码笔记不会受到影响。",
|
||||
"color-scheme": "颜色方案"
|
||||
},
|
||||
|
@ -1568,7 +1568,7 @@
|
||||
"auto-detect-language": "Automatisch erkannt"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "Code-Syntax-Hervorhebung für Textnotizen",
|
||||
"title": "",
|
||||
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
|
||||
"color-scheme": "Farbschema"
|
||||
},
|
||||
|
@ -1247,13 +1247,11 @@
|
||||
"reprocessing_embeddings": "Reprocessing...",
|
||||
"reprocess_started": "Embedding reprocessing started in the background",
|
||||
"reprocess_error": "Error starting embedding reprocessing",
|
||||
|
||||
"reprocess_index": "Rebuild Search Index",
|
||||
"reprocess_index_description": "Optimize the search index for better performance. This uses existing embeddings without regenerating them (much faster than reprocessing all embeddings).",
|
||||
"reprocessing_index": "Rebuilding...",
|
||||
"reprocess_index_started": "Search index optimization started in the background",
|
||||
"reprocess_index_error": "Error rebuilding search index",
|
||||
|
||||
"index_rebuild_progress": "Index Rebuild Progress",
|
||||
"index_rebuilding": "Optimizing index ({{percentage}}%)",
|
||||
"index_rebuild_complete": "Index optimization complete",
|
||||
@ -1824,7 +1822,7 @@
|
||||
"auto-detect-language": "Auto-detected"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "Code Syntax Highlighting for Text Notes",
|
||||
"title": "Code Blocks",
|
||||
"description": "Controls the syntax highlighting for code blocks inside text notes, code notes will not be affected.",
|
||||
"color-scheme": "Color Scheme"
|
||||
},
|
||||
@ -1953,5 +1951,10 @@
|
||||
},
|
||||
"svg": {
|
||||
"export_to_png": "The diagram could not be exported to PNG."
|
||||
},
|
||||
"code_theme": {
|
||||
"title": "Appearance",
|
||||
"word_wrapping": "Word wrapping",
|
||||
"color-scheme": "Color scheme"
|
||||
}
|
||||
}
|
||||
|
@ -1584,7 +1584,7 @@
|
||||
"auto-detect-language": "Detectado automáticamente"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "Resaltado de sintaxis de de código para Notas de Texto",
|
||||
"title": "",
|
||||
"description": "Controla el resaltado de sintaxis para bloques de código dentro de las notas de texto, las notas de código no serán afectadas.",
|
||||
"color-scheme": "Esquema de color"
|
||||
},
|
||||
|
@ -1574,7 +1574,7 @@
|
||||
"auto-detect-language": "Détecté automatiquement"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "Coloration syntaxique du code pour les notes texte",
|
||||
"title": "",
|
||||
"description": "Contrôle la coloration syntaxique des blocs de code à l'intérieur des notes texte, les notes de code ne seront pas affectées.",
|
||||
"color-scheme": "Jeu de couleurs"
|
||||
},
|
||||
|
@ -1581,7 +1581,7 @@
|
||||
},
|
||||
"highlighting": {
|
||||
"color-scheme": "Temă de culori",
|
||||
"title": "Evidențiere de sintaxă pentru notițele de tip text",
|
||||
"title": "",
|
||||
"description": "Controlează evidențierea de sintaxă pentru blocurile de cod în interiorul notițelor text, notițele de tip cod nu vor fi afectate de aceste setări."
|
||||
},
|
||||
"code_block": {
|
||||
|
@ -1514,7 +1514,7 @@
|
||||
"auto-detect-language": "自動檢測"
|
||||
},
|
||||
"highlighting": {
|
||||
"title": "文字筆記的程式碼語法高亮",
|
||||
"title": "",
|
||||
"description": "控制文字筆記中程式碼塊的語法高亮,程式碼筆記不會受到影響。",
|
||||
"color-scheme": "顏色方案"
|
||||
},
|
||||
|
68
apps/client/src/types.d.ts
vendored
68
apps/client/src/types.d.ts
vendored
@ -135,74 +135,6 @@ declare global {
|
||||
trust: boolean;
|
||||
}) => void;
|
||||
|
||||
interface CodeMirrorOpts {
|
||||
value: string;
|
||||
viewportMargin: number;
|
||||
indentUnit: number;
|
||||
matchBrackets: boolean;
|
||||
matchTags: { bothTags: boolean };
|
||||
highlightSelectionMatches: {
|
||||
showToken: boolean;
|
||||
annotateScrollbar: boolean;
|
||||
};
|
||||
lineNumbers: boolean;
|
||||
lineWrapping: boolean;
|
||||
keyMap?: "vim" | "default";
|
||||
lint?: boolean;
|
||||
gutters?: string[];
|
||||
tabindex?: number;
|
||||
dragDrop?: boolean;
|
||||
placeholder?: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
var CodeMirror: {
|
||||
(el: HTMLElement, opts: CodeMirrorOpts): CodeMirrorInstance;
|
||||
keyMap: {
|
||||
default: Record<string, string>;
|
||||
};
|
||||
modeURL: string;
|
||||
modeInfo: ModeInfo[];
|
||||
findModeByMIME(mime: string): ModeInfo;
|
||||
autoLoadMode(instance: CodeMirrorInstance, mode: string)
|
||||
registerHelper(type: string, filter: string | null, callback: (text: string, options: object) => unknown);
|
||||
Pos(line: number, col: number);
|
||||
}
|
||||
|
||||
interface ModeInfo {
|
||||
name: string;
|
||||
mode: string;
|
||||
mime: string;
|
||||
mimes: string[];
|
||||
}
|
||||
|
||||
interface CodeMirrorInstance {
|
||||
getValue(): string;
|
||||
setValue(val: string);
|
||||
clearHistory();
|
||||
setOption(name: string, value: string);
|
||||
refresh();
|
||||
focus();
|
||||
getCursor(): { line: number, col: number, ch: number };
|
||||
setCursor(line: number, col: number);
|
||||
getSelection(): string;
|
||||
lineCount(): number;
|
||||
on(event: string, callback: () => void);
|
||||
operation(callback: () => void);
|
||||
scrollIntoView(pos: number);
|
||||
doc: {
|
||||
getValue(): string;
|
||||
markText(
|
||||
from: { line: number, ch: number } | number,
|
||||
to: { line: number, ch: number } | number,
|
||||
opts: {
|
||||
className: string
|
||||
});
|
||||
setSelection(from: number, to: number);
|
||||
replaceRange(text: string, from: number, to: number);
|
||||
}
|
||||
}
|
||||
|
||||
var katex: {
|
||||
renderToString(text: string, opts: {
|
||||
throwOnError: boolean
|
||||
|
@ -198,13 +198,8 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
|
||||
let selectedText = "";
|
||||
if (this.note?.type === "code" && this.noteContext) {
|
||||
if (isReadOnly){
|
||||
const $content = await this.noteContext.getContentElement();
|
||||
selectedText = $content.find('.cm-matchhighlight').first().text();
|
||||
} else {
|
||||
const codeEditor = await this.noteContext.getCodeEditor();
|
||||
selectedText = codeEditor.getSelection();
|
||||
}
|
||||
const codeEditor = await this.noteContext.getCodeEditor();
|
||||
selectedText = codeEditor.getSelectedText();
|
||||
} else {
|
||||
selectedText = window.getSelection()?.toString() || "";
|
||||
}
|
||||
@ -247,16 +242,16 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async getHandler() {
|
||||
if (this.note?.type === "render") {
|
||||
return this.htmlHandler;
|
||||
}
|
||||
|
||||
const readOnly = await this.noteContext?.isReadOnly();
|
||||
|
||||
if (readOnly) {
|
||||
return this.htmlHandler;
|
||||
} else {
|
||||
return this.note?.type === "code" ? this.codeHandler : this.textHandler;
|
||||
switch (this.note?.type) {
|
||||
case "render":
|
||||
return this.htmlHandler;
|
||||
case "code":
|
||||
return this.codeHandler;
|
||||
case "text":
|
||||
return this.textHandler;
|
||||
default:
|
||||
const readOnly = await this.noteContext?.isReadOnly();
|
||||
return readOnly ? this.htmlHandler : this.textHandler;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,10 +17,16 @@ interface Match {
|
||||
};
|
||||
}
|
||||
|
||||
interface SearchParameters {
|
||||
searchTerm: string;
|
||||
matchCase: boolean;
|
||||
wholeWord: boolean;
|
||||
}
|
||||
|
||||
export default class FindInCode {
|
||||
|
||||
private parent: FindWidget;
|
||||
private findResult?: Match[] | null;
|
||||
private searchParameters: SearchParameters | null = null;
|
||||
|
||||
constructor(parent: FindWidget) {
|
||||
this.parent = parent;
|
||||
@ -31,217 +37,54 @@ export default class FindInCode {
|
||||
}
|
||||
|
||||
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
||||
let findResult: Match[] | null = null;
|
||||
let totalFound = 0;
|
||||
let currentFound = -1;
|
||||
|
||||
// See https://codemirror.net/addon/search/searchcursor.js for tips
|
||||
const codeEditor = await this.getCodeEditor();
|
||||
if (!codeEditor) {
|
||||
return { totalFound: 0, currentFound: 0 };
|
||||
}
|
||||
|
||||
const doc = codeEditor.doc;
|
||||
const text = doc.getValue();
|
||||
|
||||
// Clear all markers
|
||||
if (this.findResult) {
|
||||
codeEditor.operation(() => {
|
||||
const findResult = this.findResult as Match[];
|
||||
for (let i = 0; i < findResult.length; ++i) {
|
||||
const marker = findResult[i];
|
||||
marker.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (searchTerm !== "") {
|
||||
searchTerm = utils.escapeRegExp(searchTerm);
|
||||
|
||||
// Find and highlight matches
|
||||
// Find and highlight matches
|
||||
// XXX Using \\b and not using the unicode flag probably doesn't
|
||||
// work with non-ASCII alphabets, findAndReplace uses a more
|
||||
// complicated regexp, see
|
||||
// https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/utils.js#L145
|
||||
const wholeWordChar = wholeWord ? "\\b" : "";
|
||||
const re = new RegExp(wholeWordChar + searchTerm + wholeWordChar, "g" + (matchCase ? "" : "i"));
|
||||
let curLine = 0;
|
||||
let curChar = 0;
|
||||
let curMatch: RegExpExecArray | null = null;
|
||||
findResult = [];
|
||||
// All those markText take several seconds on e.g., this ~500-line
|
||||
// script, batch them inside an operation, so they become
|
||||
// unnoticeable. Alternatively, an overlay could be used, see
|
||||
// https://codemirror.net/addon/search/match-highlighter.js ?
|
||||
codeEditor.operation(() => {
|
||||
for (let i = 0; i < text.length; ++i) {
|
||||
// Fetch the next match if it's the first time or if past the current match start
|
||||
if (curMatch == null || curMatch.index < i) {
|
||||
curMatch = re.exec(text);
|
||||
if (curMatch == null) {
|
||||
// No more matches
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Create a non-selected highlight marker for the match, the
|
||||
// selected marker highlight will be done later
|
||||
if (i === curMatch.index) {
|
||||
let fromPos = { line: curLine, ch: curChar };
|
||||
// If multiline is supported, this needs to recalculate curLine since the match may span lines
|
||||
let toPos = { line: curLine, ch: curChar + curMatch[0].length };
|
||||
// or css = "color: #f3"
|
||||
let marker = doc.markText(fromPos, toPos, { className: FIND_RESULT_CSS_CLASSNAME });
|
||||
findResult?.push(marker);
|
||||
|
||||
// Set the first match beyond the cursor as the current match
|
||||
if (currentFound === -1) {
|
||||
const cursorPos = codeEditor.getCursor();
|
||||
if (fromPos.line > cursorPos.line || (fromPos.line === cursorPos.line && fromPos.ch >= cursorPos.ch)) {
|
||||
currentFound = totalFound;
|
||||
}
|
||||
}
|
||||
|
||||
totalFound++;
|
||||
}
|
||||
// Do line and char position tracking
|
||||
if (text[i] === "\n") {
|
||||
curLine++;
|
||||
curChar = 0;
|
||||
} else {
|
||||
curChar++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.findResult = findResult;
|
||||
|
||||
// Calculate curfound if not already, highlight it as selected
|
||||
if (findResult && totalFound > 0) {
|
||||
currentFound = Math.max(0, currentFound);
|
||||
let marker = findResult[currentFound];
|
||||
let pos = marker.find();
|
||||
codeEditor.scrollIntoView(pos.to);
|
||||
marker.clear();
|
||||
findResult[currentFound] = doc.markText(pos.from, pos.to, { className: FIND_RESULT_SELECTED_CSS_CLASSNAME });
|
||||
}
|
||||
|
||||
return {
|
||||
totalFound,
|
||||
currentFound: Math.min(currentFound + 1, totalFound)
|
||||
this.searchParameters = {
|
||||
searchTerm,
|
||||
matchCase,
|
||||
wholeWord,
|
||||
};
|
||||
const { totalFound, currentFound } = await codeEditor.performFind(searchTerm, matchCase, wholeWord);
|
||||
return { totalFound, currentFound };
|
||||
}
|
||||
|
||||
async findNext(direction: number, currentFound: number, nextFound: number) {
|
||||
const codeEditor = await this.getCodeEditor();
|
||||
if (!codeEditor || !this.findResult) {
|
||||
if (!codeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = codeEditor.doc;
|
||||
|
||||
//
|
||||
// Dehighlight current, highlight & scrollIntoView next
|
||||
//
|
||||
|
||||
let marker = this.findResult[currentFound];
|
||||
let pos = marker.find();
|
||||
marker.clear();
|
||||
marker = doc.markText(pos.from, pos.to, { className: FIND_RESULT_CSS_CLASSNAME });
|
||||
this.findResult[currentFound] = marker;
|
||||
|
||||
marker = this.findResult[nextFound];
|
||||
pos = marker.find();
|
||||
marker.clear();
|
||||
marker = doc.markText(pos.from, pos.to, { className: FIND_RESULT_SELECTED_CSS_CLASSNAME });
|
||||
this.findResult[nextFound] = marker;
|
||||
|
||||
codeEditor.scrollIntoView(pos.from);
|
||||
codeEditor.findNext(direction, currentFound, nextFound);
|
||||
}
|
||||
|
||||
async findBoxClosed(totalFound: number, currentFound: number) {
|
||||
const codeEditor = await this.getCodeEditor();
|
||||
|
||||
if (codeEditor && totalFound > 0) {
|
||||
const doc = codeEditor.doc;
|
||||
const pos = this.findResult?.[currentFound].find();
|
||||
// Note setting the selection sets the cursor to
|
||||
// the end of the selection and scrolls it into
|
||||
// view
|
||||
if (pos) {
|
||||
doc.setSelection(pos.from, pos.to);
|
||||
}
|
||||
// Clear all markers
|
||||
codeEditor.operation(() => {
|
||||
if (!this.findResult) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.findResult.length; ++i) {
|
||||
let marker = this.findResult[i];
|
||||
marker.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.findResult = null;
|
||||
|
||||
codeEditor?.cleanSearch();
|
||||
codeEditor?.focus();
|
||||
}
|
||||
|
||||
async replace(replaceText: string) {
|
||||
// this.findResult may be undefined and null
|
||||
if (!this.findResult || this.findResult.length === 0) {
|
||||
return;
|
||||
}
|
||||
let currentFound = -1;
|
||||
this.findResult.forEach((marker, index) => {
|
||||
const pos = marker.find();
|
||||
if (pos) {
|
||||
if (marker.className === FIND_RESULT_SELECTED_CSS_CLASSNAME) {
|
||||
currentFound = index;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (currentFound >= 0) {
|
||||
let marker = this.findResult[currentFound];
|
||||
let pos = marker.find();
|
||||
const codeEditor = await this.getCodeEditor();
|
||||
const doc = codeEditor?.doc;
|
||||
if (doc) {
|
||||
doc.replaceRange(replaceText, pos.from, pos.to);
|
||||
}
|
||||
marker.clear();
|
||||
|
||||
let nextFound;
|
||||
if (currentFound === this.findResult.length - 1) {
|
||||
nextFound = 0;
|
||||
} else {
|
||||
nextFound = currentFound;
|
||||
}
|
||||
this.findResult.splice(currentFound, 1);
|
||||
if (this.findResult.length > 0) {
|
||||
this.findNext(0, nextFound, nextFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
async replaceAll(replaceText: string) {
|
||||
if (!this.findResult || this.findResult.length === 0) {
|
||||
return;
|
||||
}
|
||||
const codeEditor = await this.getCodeEditor();
|
||||
const doc = codeEditor?.doc;
|
||||
codeEditor?.operation(() => {
|
||||
if (!this.findResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let currentFound = 0; currentFound < this.findResult.length; currentFound++) {
|
||||
let marker = this.findResult[currentFound];
|
||||
let pos = marker.find();
|
||||
doc?.replaceRange(replaceText, pos.from, pos.to);
|
||||
marker.clear();
|
||||
}
|
||||
});
|
||||
this.findResult = [];
|
||||
await codeEditor?.replace(replaceText);
|
||||
this.rerunSearch();
|
||||
}
|
||||
|
||||
async replaceAll(replaceText: string) {
|
||||
const codeEditor = await this.getCodeEditor();
|
||||
await codeEditor?.replaceAll(replaceText);
|
||||
this.rerunSearch();
|
||||
}
|
||||
|
||||
private rerunSearch() {
|
||||
if (this.searchParameters) {
|
||||
this.performFind(
|
||||
this.searchParameters.searchTerm,
|
||||
this.searchParameters.matchCase,
|
||||
this.searchParameters.wholeWord);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import options from "../../services/options.js";
|
||||
import { getThemeById } from "@triliumnext/codemirror";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import options from "../../services/options.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import CodeMirror, { type EditorConfig } from "@triliumnext/codemirror";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
|
||||
export const DEFAULT_PREFIX = "default:";
|
||||
|
||||
/**
|
||||
* An abstract {@link TypeWidget} which implements the CodeMirror editor, meant to be used as a parent for
|
||||
@ -19,43 +23,27 @@ import type FNote from "../../entities/fnote.js";
|
||||
export default class AbstractCodeTypeWidget extends TypeWidget {
|
||||
|
||||
protected $editor!: JQuery<HTMLElement>;
|
||||
protected codeEditor!: CodeMirrorInstance;
|
||||
protected codeEditor!: CodeMirror;
|
||||
|
||||
doRender() {
|
||||
this.initialized = this.#initEditor();
|
||||
}
|
||||
|
||||
async #initEditor() {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
||||
|
||||
// these conflict with backward/forward navigation shortcuts
|
||||
delete CodeMirror.keyMap.default["Alt-Left"];
|
||||
delete CodeMirror.keyMap.default["Alt-Right"];
|
||||
|
||||
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`;
|
||||
const jsMode = CodeMirror.modeInfo.find((mode) => mode.name === "JavaScript");
|
||||
if (jsMode) {
|
||||
jsMode.mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]);
|
||||
}
|
||||
const sqlMode = CodeMirror.modeInfo.find((mode) => mode.name === "SQLite");
|
||||
if (sqlMode) {
|
||||
sqlMode.mimes = ["text/x-sqlite", "text/x-sqlite;schema=trilium"];
|
||||
}
|
||||
|
||||
this.codeEditor = CodeMirror(this.$editor[0], {
|
||||
value: "",
|
||||
viewportMargin: Infinity,
|
||||
indentUnit: 4,
|
||||
matchBrackets: true,
|
||||
matchTags: { bothTags: true },
|
||||
highlightSelectionMatches: { showToken: false, annotateScrollbar: false },
|
||||
lineNumbers: true,
|
||||
// we line wrap partly also because without it horizontal scrollbar displays only when you scroll
|
||||
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
|
||||
this.codeEditor = new CodeMirror({
|
||||
parent: this.$editor[0],
|
||||
lineWrapping: options.is("codeLineWrapEnabled"),
|
||||
...this.getExtraOpts()
|
||||
});
|
||||
this.onEditorInitialized();
|
||||
|
||||
// Load the theme.
|
||||
const themeId = options.get("codeNoteTheme");
|
||||
if (themeId?.startsWith(DEFAULT_PREFIX)) {
|
||||
const theme = getThemeById(themeId.substring(DEFAULT_PREFIX.length));
|
||||
if (theme) {
|
||||
await this.codeEditor.setTheme(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +52,7 @@ export default class AbstractCodeTypeWidget extends TypeWidget {
|
||||
*
|
||||
* @returns the extra options to be passed to the CodeMirror constructor.
|
||||
*/
|
||||
getExtraOpts(): Partial<CodeMirrorOpts> {
|
||||
getExtraOpts(): Partial<EditorConfig> {
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -81,50 +69,68 @@ export default class AbstractCodeTypeWidget extends TypeWidget {
|
||||
/**
|
||||
* Must be called by the derived classes in `#doRefresh(note)` in order to react to changes.
|
||||
*
|
||||
* @param {*} note the note that was changed.
|
||||
* @param {*} content the new content of the note.
|
||||
* @param the note that was changed.
|
||||
* @param new content of the note.
|
||||
*/
|
||||
_update(note: { mime: string }, content: string) {
|
||||
// CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
|
||||
// we provide fallback
|
||||
this.codeEditor.setValue(content || "");
|
||||
_update(note: FNote, content: string) {
|
||||
this.codeEditor.setText(content);
|
||||
this.codeEditor.setMimeType(note.mime);
|
||||
this.codeEditor.clearHistory();
|
||||
|
||||
let info = CodeMirror.findModeByMIME(note.mime);
|
||||
if (!info) {
|
||||
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
|
||||
// To avoid inheriting a mode from a previously open code note.
|
||||
info = CodeMirror.findModeByMIME("text/plain");
|
||||
}
|
||||
|
||||
this.codeEditor.setOption("mode", info.mime);
|
||||
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$widget.show();
|
||||
|
||||
if (this.codeEditor) {
|
||||
// show can be called before render
|
||||
this.codeEditor.refresh();
|
||||
}
|
||||
this.#updateBackgroundColor();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.$editor.focus();
|
||||
this.codeEditor.focus();
|
||||
}
|
||||
|
||||
scrollToEnd() {
|
||||
this.codeEditor.setCursor(this.codeEditor.lineCount(), 0);
|
||||
this.codeEditor.scrollToEnd();
|
||||
this.codeEditor.focus();
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.codeEditor) {
|
||||
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||
this.codeEditor.setValue("");
|
||||
this.codeEditor.setText("");
|
||||
});
|
||||
}
|
||||
this.#updateBackgroundColor("unset");
|
||||
}
|
||||
|
||||
async executeWithCodeEditorEvent({ resolve, ntxId }: EventData<"executeWithCodeEditor">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.initialized;
|
||||
|
||||
resolve(this.codeEditor);
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isOptionReloaded("codeNoteTheme")) {
|
||||
const themeId = options.get("codeNoteTheme");
|
||||
if (themeId?.startsWith(DEFAULT_PREFIX)) {
|
||||
const theme = getThemeById(themeId.substring(DEFAULT_PREFIX.length));
|
||||
if (theme) {
|
||||
await this.codeEditor.setTheme(theme);
|
||||
}
|
||||
this.#updateBackgroundColor();
|
||||
}
|
||||
}
|
||||
|
||||
if (loadResults.isOptionReloaded("codeLineWrapEnabled")) {
|
||||
this.codeEditor.setLineWrapping(options.is("codeLineWrapEnabled"));
|
||||
}
|
||||
}
|
||||
|
||||
#updateBackgroundColor(color?: string) {
|
||||
const $editorEl = $(this.codeEditor.dom);
|
||||
this.$widget.closest(".scrolling-container").css("background-color", color ?? $editorEl.css("background-color"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import options from "../../services/options.js";
|
||||
import type SwitchSplitOrientationButton from "../floating_buttons/switch_layout_button.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type OnClickButtonWidget from "../buttons/onclick_button.js";
|
||||
import type { EditorConfig } from "@triliumnext/codemirror";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="note-detail-split note-detail-printable">
|
||||
@ -131,7 +132,14 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
|
||||
super();
|
||||
this.editorTypeWidget = new EditableCodeTypeWidget();
|
||||
this.editorTypeWidget.isEnabled = () => true;
|
||||
this.editorTypeWidget.getExtraOpts = this.buildEditorExtraOptions;
|
||||
|
||||
const defaultOptions = this.editorTypeWidget.getExtraOpts();
|
||||
this.editorTypeWidget.getExtraOpts = () => {
|
||||
return {
|
||||
...defaultOptions,
|
||||
...this.buildEditorExtraOptions()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
doRender(): void {
|
||||
@ -242,7 +250,7 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
|
||||
/**
|
||||
* Called upon when the code editor is being initialized. Can be used to add additional options to the editor.
|
||||
*/
|
||||
buildEditorExtraOptions(): Partial<CodeMirrorOpts> {
|
||||
buildEditorExtraOptions(): Partial<EditorConfig> {
|
||||
return {
|
||||
lineWrapping: false
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_
|
||||
import RibbonOptions from "./options/appearance/ribbon.js";
|
||||
import MultiFactorAuthenticationOptions from './options/multi_factor_authentication.js';
|
||||
import LocalizationOptions from "./options/i18n/i18n.js";
|
||||
import CodeBlockOptions from "./options/appearance/code_block.js";
|
||||
import CodeBlockOptions from "./options/text_notes/code_block.js";
|
||||
import EditorOptions from "./options/text_notes/editor.js";
|
||||
import ShareSettingsOptions from "./options/other/share_settings.js";
|
||||
import AiSettingsOptions from "./options/ai_settings.js";
|
||||
@ -42,8 +42,9 @@ import type FNote from "../../entities/fnote.js";
|
||||
import type NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import { t } from "i18next";
|
||||
import LanguageOptions from "./options/i18n/language.js";
|
||||
import type { EventData, EventNames } from "../../components/app_context.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import CodeTheme from "./options/code_notes/code_theme.js";
|
||||
import RelatedSettings from "./options/related_settings.js";
|
||||
|
||||
const TPL = /*html*/`<div class="note-detail-content-widget note-detail-printable">
|
||||
<style>
|
||||
@ -68,11 +69,12 @@ const TPL = /*html*/`<div class="note-detail-content-widget note-detail-printabl
|
||||
<div class="note-detail-content-widget-content"></div>
|
||||
</div>`;
|
||||
|
||||
const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
|
||||
export type OptionPages = "_optionsAppearance" | "_optionsShortcuts" | "_optionsTextNotes" | "_optionsCodeNotes" | "_optionsImages" | "_optionsSpellcheck" | "_optionsPassword" | "_optionsMFA" | "_optionsEtapi" | "_optionsBackup" | "_optionsSync" | "_optionsAi" | "_optionsOther" | "_optionsLocalization" | "_optionsAdvanced";
|
||||
|
||||
const CONTENT_WIDGETS: Record<OptionPages | "_backendLog", (typeof NoteContextAwareWidget)[]> = {
|
||||
_optionsAppearance: [
|
||||
ThemeOptions,
|
||||
FontsOptions,
|
||||
CodeBlockOptions,
|
||||
ElectronIntegrationOptions,
|
||||
MaxContentWidthOptions,
|
||||
RibbonOptions
|
||||
@ -83,12 +85,14 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
|
||||
_optionsTextNotes: [
|
||||
EditorOptions,
|
||||
HeadingStyleOptions,
|
||||
CodeBlockOptions,
|
||||
TableOfContentsOptions,
|
||||
HighlightsListOptions,
|
||||
TextAutoReadOnlySizeOptions
|
||||
],
|
||||
_optionsCodeNotes: [
|
||||
CodeEditorOptions,
|
||||
CodeTheme,
|
||||
CodeMimeTypesOptions,
|
||||
CodeAutoReadOnlySizeOptions
|
||||
],
|
||||
@ -164,7 +168,10 @@ export default class ContentWidgetTypeWidget extends TypeWidget {
|
||||
this.$content.empty();
|
||||
this.children = [];
|
||||
|
||||
const contentWidgets = CONTENT_WIDGETS[note.noteId];
|
||||
const contentWidgets = [
|
||||
...((CONTENT_WIDGETS as Record<string, typeof NoteContextAwareWidget[]>)[note.noteId]),
|
||||
RelatedSettings
|
||||
];
|
||||
this.$content.toggleClass("options", note.noteId.startsWith("_options"));
|
||||
|
||||
if (contentWidgets) {
|
||||
|
@ -7,6 +7,7 @@ import AbstractCodeTypeWidget from "./abstract_code_type_widget.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import type { TouchBarItem } from "../../components/touch_bar.js";
|
||||
import { hasTouchBar } from "../../services/utils.js";
|
||||
import type { EditorConfig } from "@triliumnext/codemirror";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-detail-code note-detail-printable">
|
||||
@ -41,19 +42,13 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
getExtraOpts(): Partial<CodeMirrorOpts> {
|
||||
getExtraOpts(): Partial<EditorConfig> {
|
||||
return {
|
||||
keyMap: options.is("vimKeymapEnabled") ? "vim" : "default",
|
||||
lint: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
tabindex: 300,
|
||||
dragDrop: false, // with true the editor inlines dropped files which is not what we expect
|
||||
placeholder: t("editable_code.placeholder")
|
||||
};
|
||||
}
|
||||
|
||||
onEditorInitialized() {
|
||||
this.codeEditor.on("change", () => this.spacedUpdate.scheduleUpdate());
|
||||
placeholder: t("editable_code.placeholder"),
|
||||
vimKeybindings: options.is("vimKeymapEnabled"),
|
||||
onContentChanged: () => this.spacedUpdate.scheduleUpdate(),
|
||||
tabIndex: 300
|
||||
}
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
@ -72,20 +67,10 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
|
||||
getData() {
|
||||
return {
|
||||
content: this.codeEditor.getValue()
|
||||
content: this.codeEditor.getText()
|
||||
};
|
||||
}
|
||||
|
||||
async executeWithCodeEditorEvent({ resolve, ntxId }: EventData<"executeWithCodeEditor">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.initialized;
|
||||
|
||||
resolve(this.codeEditor);
|
||||
}
|
||||
|
||||
buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) {
|
||||
const items: TouchBarItem[] = [];
|
||||
const note = this.note;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { EditorConfig } from "@triliumnext/codemirror";
|
||||
import { getMermaidConfig, loadElkIfNeeded, postprocessMermaidSvg } from "../../services/mermaid.js";
|
||||
import AbstractSvgSplitTypeWidget from "./abstract_svg_split_type_widget.js";
|
||||
|
||||
|
@ -43,7 +43,8 @@ const TPL = /*html*/`
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
</div>
|
||||
`;
|
||||
|
||||
interface Theme {
|
||||
val: string;
|
||||
|
@ -10,30 +10,19 @@ const TPL = /*html*/`
|
||||
${t("vim_key_bindings.use_vim_keybindings_in_code_notes")}
|
||||
</label>
|
||||
<p class="form-text">${t("vim_key_bindings.enable_vim_keybindings")}</p>
|
||||
|
||||
<label class="tn-checkbox">
|
||||
<input type="checkbox" class="line-wrap-enabled form-check-input">
|
||||
${t("wrap_lines.wrap_lines_in_code_notes")}
|
||||
</label>
|
||||
<p class="form-text">${t("wrap_lines.enable_line_wrap")}</p>
|
||||
</div>`;
|
||||
|
||||
export default class CodeEditorOptions extends OptionsWidget {
|
||||
|
||||
private $vimKeymapEnabled!: JQuery<HTMLElement>;
|
||||
private $codeLineWrapEnabled!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$vimKeymapEnabled = this.$widget.find(".vim-keymap-enabled");
|
||||
this.$vimKeymapEnabled.on("change", () => this.updateCheckboxOption("vimKeymapEnabled", this.$vimKeymapEnabled));
|
||||
|
||||
this.$codeLineWrapEnabled = this.$widget.find(".line-wrap-enabled");
|
||||
this.$codeLineWrapEnabled.on("change", () => this.updateCheckboxOption("codeLineWrapEnabled", this.$codeLineWrapEnabled));
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
this.setCheckboxState(this.$vimKeymapEnabled, options.vimKeymapEnabled);
|
||||
this.setCheckboxState(this.$codeLineWrapEnabled, options.codeLineWrapEnabled);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,173 @@
|
||||
import type { OptionMap } from "@triliumnext/commons";
|
||||
import OptionsWidget from "../options_widget";
|
||||
import server from "../../../../services/server";
|
||||
import CodeMirror, { getThemeById } from "@triliumnext/codemirror";
|
||||
import { DEFAULT_PREFIX } from "../../abstract_code_type_widget";
|
||||
import { t } from "../../../../services/i18n";
|
||||
import { ColorThemes } from "@triliumnext/codemirror";
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface Theme {
|
||||
title: string;
|
||||
val: string;
|
||||
}
|
||||
|
||||
type Response = Theme[];
|
||||
|
||||
const SAMPLE_MIME = "application/typescript";
|
||||
const SAMPLE_CODE = `\
|
||||
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
||||
import { EditorView, highlightActiveLine, keymap, lineNumbers, placeholder, ViewUpdate, type EditorViewConfig } from "@codemirror/view";
|
||||
import { defaultHighlightStyle, StreamLanguage, syntaxHighlighting, indentUnit, bracketMatching, foldGutter } from "@codemirror/language";
|
||||
import { Compartment, EditorState, type Extension } from "@codemirror/state";
|
||||
import { highlightSelectionMatches } from "@codemirror/search";
|
||||
import { vim } from "@replit/codemirror-vim";
|
||||
import byMimeType from "./syntax_highlighting.js";
|
||||
import smartIndentWithTab from "./extensions/custom_tab.js";
|
||||
import type { ThemeDefinition } from "./color_themes.js";
|
||||
|
||||
export { default as ColorThemes, type ThemeDefinition, getThemeById } from "./color_themes.js";
|
||||
|
||||
type ContentChangedListener = () => void;
|
||||
|
||||
export interface EditorConfig {
|
||||
parent: HTMLElement;
|
||||
placeholder?: string;
|
||||
lineWrapping?: boolean;
|
||||
vimKeybindings?: boolean;
|
||||
readOnly?: boolean;
|
||||
onContentChanged?: ContentChangedListener;
|
||||
}
|
||||
|
||||
export default class CodeMirror extends EditorView {
|
||||
|
||||
private config: EditorConfig;
|
||||
private languageCompartment: Compartment;
|
||||
private historyCompartment: Compartment;
|
||||
private themeCompartment: Compartment;
|
||||
|
||||
constructor(config: EditorConfig) {
|
||||
const languageCompartment = new Compartment();
|
||||
const historyCompartment = new Compartment();
|
||||
const themeCompartment = new Compartment();
|
||||
|
||||
let extensions: Extension[] = [];
|
||||
|
||||
if (config.vimKeybindings) {
|
||||
extensions.push(vim());
|
||||
}
|
||||
|
||||
extensions = [
|
||||
...extensions,
|
||||
languageCompartment.of([]),
|
||||
themeCompartment.of([
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true })
|
||||
]),
|
||||
highlightActiveLine(),
|
||||
highlightSelectionMatches(),
|
||||
bracketMatching(),
|
||||
lineNumbers(),
|
||||
foldGutter(),
|
||||
indentUnit.of(" ".repeat(4)),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
...smartIndentWithTab
|
||||
])
|
||||
]
|
||||
|
||||
super({
|
||||
parent: config.parent,
|
||||
extensions
|
||||
});
|
||||
}
|
||||
}`;
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="options-section">
|
||||
<h4>${t("code_theme.title")}</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="color-theme">${t("code_theme.color-scheme")}</label>
|
||||
<select id="color-theme" class="theme-select form-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 side-checkbox">
|
||||
<label class="form-check tn-checkbox">
|
||||
<input type="checkbox" class="word-wrap form-check-input" />
|
||||
${t("code_theme.word_wrapping")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="note-detail-readonly-code-content">
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.options-section .note-detail-readonly-code-content {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.options-section .note-detail-readonly-code-content .cm-editor {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class CodeTheme extends OptionsWidget {
|
||||
|
||||
private $themeSelect!: JQuery<HTMLElement>;
|
||||
private $sampleEl!: JQuery<HTMLElement>;
|
||||
private $lineWrapEnabled!: JQuery<HTMLElement>;
|
||||
private editor?: CodeMirror;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$themeSelect = this.$widget.find(".theme-select");
|
||||
this.$themeSelect.on("change", async () => {
|
||||
const newTheme = String(this.$themeSelect.val());
|
||||
await server.put(`options/codeNoteTheme/${newTheme}`);
|
||||
});
|
||||
|
||||
// Populate the list of themes.
|
||||
for (const theme of ColorThemes) {
|
||||
const option = $("<option>")
|
||||
.attr("value", `default:${theme.id}`)
|
||||
.text(theme.name);
|
||||
this.$themeSelect.append(option);
|
||||
}
|
||||
|
||||
this.$sampleEl = this.$widget.find(".note-detail-readonly-code-content");
|
||||
this.$lineWrapEnabled = this.$widget.find(".word-wrap");
|
||||
this.$lineWrapEnabled.on("change", () => this.updateCheckboxOption("codeLineWrapEnabled", this.$lineWrapEnabled));
|
||||
}
|
||||
|
||||
async #setupPreview(options: OptionMap) {
|
||||
if (!this.editor) {
|
||||
this.editor = new CodeMirror({
|
||||
parent: this.$sampleEl[0],
|
||||
});
|
||||
}
|
||||
this.editor.setText(SAMPLE_CODE);
|
||||
this.editor.setMimeType(SAMPLE_MIME);
|
||||
this.editor.setLineWrapping(options.codeLineWrapEnabled === "true");
|
||||
|
||||
// Load the theme.
|
||||
const themeId = options.codeNoteTheme;
|
||||
if (themeId?.startsWith(DEFAULT_PREFIX)) {
|
||||
const theme = getThemeById(themeId.substring(DEFAULT_PREFIX.length));
|
||||
if (theme) {
|
||||
await this.editor.setTheme(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
this.$themeSelect.val(options.codeNoteTheme);
|
||||
this.#setupPreview(options);
|
||||
this.setCheckboxState(this.$lineWrapEnabled, options.codeLineWrapEnabled);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
import type FNote from "../../../entities/fnote";
|
||||
import type { OptionPages } from "../content_widget";
|
||||
import OptionsWidget from "./options_widget";
|
||||
|
||||
const TPL = `\
|
||||
<div class="options-section">
|
||||
<h4>Related settings</h4>
|
||||
|
||||
<nav class="related-settings">
|
||||
<li>Color scheme for code blocks in text notes</li>
|
||||
<li>Color scheme for code notes</li>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.related-settings {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
interface RelatedSettingsConfig {
|
||||
items: {
|
||||
title: string;
|
||||
targetPage: OptionPages;
|
||||
}[];
|
||||
}
|
||||
|
||||
const RELATED_SETTINGS: Record<string, RelatedSettingsConfig> = {
|
||||
"_optionsAppearance": {
|
||||
items: [
|
||||
{
|
||||
title: "Color scheme for code blocks in text notes",
|
||||
targetPage: "_optionsTextNotes"
|
||||
},
|
||||
{
|
||||
title: "Color scheme for code notes",
|
||||
targetPage: "_optionsCodeNotes"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export default class RelatedSettings extends OptionsWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
const config = this.noteId && RELATED_SETTINGS[this.noteId];
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $relatedSettings = this.$widget.find(".related-settings");
|
||||
$relatedSettings.empty();
|
||||
for (const item of config.items) {
|
||||
const $item = $("<li>");
|
||||
const $link = $("<a>").text(item.title);
|
||||
|
||||
$item.append($link);
|
||||
$link.attr("href", `#root/_hidden/_options/${item.targetPage}`);
|
||||
$relatedSettings.append($item);
|
||||
}
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return (!!this.noteId && this.noteId in RELATED_SETTINGS);
|
||||
}
|
||||
|
||||
}
|
@ -25,8 +25,6 @@ const TPL = /*html*/`
|
||||
<div class="options-section">
|
||||
<h4>${t("highlighting.title")}</h4>
|
||||
|
||||
<p class="form-text">${t("highlighting.description")}</p>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="highlighting-color-scheme-select">${t("highlighting.color-scheme")}</label>
|
||||
@ -41,20 +39,23 @@ const TPL = /*html*/`
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="note-detail-readonly-text-content ck-content code-sample-wrapper">
|
||||
<pre class="hljs"><code class="code-sample">${SAMPLE_CODE}</code></pre>
|
||||
</div>
|
||||
<div class="note-detail-readonly-text-content ck-content code-sample-wrapper">
|
||||
<pre class="hljs"><code class="code-sample">${SAMPLE_CODE}</code></pre>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.code-sample-wrapper {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.code-sample-wrapper pre {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface Theme {
|
||||
title: string;
|
||||
val: string;
|
@ -9,10 +9,6 @@ const TPL = /*html*/`
|
||||
min-height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-detail-readonly-code-content {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<pre class="note-detail-readonly-code-content"></pre>
|
||||
@ -43,7 +39,7 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
this.show();
|
||||
}
|
||||
|
||||
getExtraOpts(): Partial<CodeMirrorOpts> {
|
||||
getExtraOpts() {
|
||||
return {
|
||||
readOnly: true
|
||||
};
|
||||
|
@ -35,10 +35,13 @@
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/ckeditor5/tsconfig.lib.json"
|
||||
"path": "../../packages/codemirror/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/commons/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/ckeditor5/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,11 +4,14 @@
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/ckeditor5"
|
||||
"path": "../../packages/codemirror"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/commons"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/ckeditor5"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
|
@ -1,13 +1,10 @@
|
||||
const child_process = require("child_process");
|
||||
const fs = require("fs");
|
||||
const { default: path } = require("path");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = function (filePath) {
|
||||
const { WINDOWS_SIGN_EXECUTABLE } = process.env;
|
||||
|
||||
const stats = fs.lstatSync(filePath);
|
||||
console.log(filePath, stats);
|
||||
|
||||
if (!WINDOWS_SIGN_EXECUTABLE) {
|
||||
console.warn("[Sign] Skip signing due to missing environment variable.");
|
||||
return;
|
||||
@ -19,11 +16,15 @@ module.exports = function (filePath) {
|
||||
fs.mkdirSync(outputDir);
|
||||
}
|
||||
|
||||
fs.copyFileSync(sourcePath, destPath);
|
||||
|
||||
const command = `${WINDOWS_SIGN_EXECUTABLE} --executable "${filePath}"`;
|
||||
console.log(`[Sign] ${command}`);
|
||||
console.log(`[Sign] Running ${command}`);
|
||||
|
||||
const output = child_process.execSync(command);
|
||||
console.log(`[Sign] ${output}`);
|
||||
try {
|
||||
child_process.execSync(command);
|
||||
} catch (e) {
|
||||
console.warn(`[Sign] Unable to sign ${filePath} due to:\n${e.stdout.toString("utf-8")})}`)
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Sign] Signed ${filePath} successfully.`);
|
||||
}
|
@ -29,7 +29,7 @@
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist $(nix-shell -p electron_33 --run \"electron --version\")"
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist $(nix-shell -p electron_35 --run \"electron --version\")"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -7,18 +7,14 @@ test("Displays lint warnings for backend script", async ({ page, context }) => {
|
||||
await app.closeAllTabs();
|
||||
await app.goToNoteInNewTab("Backend script with lint warnings");
|
||||
|
||||
const codeEditor = app.currentNoteSplit.locator(".CodeMirror");
|
||||
const codeEditor = app.currentNoteSplit.locator(".cm-editor");
|
||||
|
||||
// Expect two warning signs in the gutter.
|
||||
await expect(codeEditor.locator(".CodeMirror-gutter-wrapper .CodeMirror-lint-marker-warning")).toHaveCount(2);
|
||||
await expect(codeEditor.locator(".cm-gutter-lint .cm-lint-marker-warning")).toHaveCount(2);
|
||||
|
||||
// Hover over hello
|
||||
await codeEditor.getByText("hello").first().hover();
|
||||
await expectTooltip(page, "'hello' is defined but never used.");
|
||||
|
||||
// Hover over world
|
||||
await codeEditor.getByText("world").first().hover();
|
||||
await expectTooltip(page, "'world' is defined but never used.");
|
||||
});
|
||||
|
||||
test("Displays lint errors for backend script", async ({ page, context }) => {
|
||||
@ -27,10 +23,10 @@ test("Displays lint errors for backend script", async ({ page, context }) => {
|
||||
await app.closeAllTabs();
|
||||
await app.goToNoteInNewTab("Backend script with lint errors");
|
||||
|
||||
const codeEditor = app.currentNoteSplit.locator(".CodeMirror");
|
||||
const codeEditor = app.currentNoteSplit.locator(".cm-editor");
|
||||
|
||||
// Expect two warning signs in the gutter.
|
||||
const errorMarker = codeEditor.locator(".CodeMirror-gutter-wrapper .CodeMirror-lint-marker-error");
|
||||
const errorMarker = codeEditor.locator(".cm-gutter-lint .cm-lint-marker-error");
|
||||
await expect(errorMarker).toHaveCount(1);
|
||||
|
||||
// Hover over hello
|
||||
@ -40,7 +36,7 @@ test("Displays lint errors for backend script", async ({ page, context }) => {
|
||||
|
||||
async function expectTooltip(page: Page, tooltip: string) {
|
||||
await expect(
|
||||
page.locator(".CodeMirror-lint-tooltip:visible", {
|
||||
page.locator(".cm-tooltip:visible", {
|
||||
hasText: tooltip
|
||||
})
|
||||
).toBeVisible();
|
||||
|
@ -44,12 +44,11 @@
|
||||
"@types/xml2js": "0.4.14",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"boxicons": "2.1.4",
|
||||
"codemirror": "5.65.19",
|
||||
"express-http-proxy": "2.1.1",
|
||||
"jquery": "3.7.1",
|
||||
"katex": "0.16.22",
|
||||
"normalize.css": "8.0.1",
|
||||
"@anthropic-ai/sdk": "0.50.3",
|
||||
"@anthropic-ai/sdk": "0.50.4",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/express-partial-content": "workspace:*",
|
||||
@ -67,7 +66,7 @@
|
||||
"csrf-csrf": "3.2.2",
|
||||
"dayjs": "1.11.13",
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.0",
|
||||
"debug": "4.4.1",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "36.2.0",
|
||||
"electron-debug": "4.1.0",
|
||||
@ -109,7 +108,7 @@
|
||||
"stream-throttle": "0.1.3",
|
||||
"strip-bom": "5.0.0",
|
||||
"striptags": "3.2.0",
|
||||
"supertest": "7.1.0",
|
||||
"supertest": "7.1.1",
|
||||
"swagger-jsdoc": "6.2.8",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"time2fa": "^1.3.0",
|
||||
|
@ -37,13 +37,22 @@
|
||||
and other important Trilium data files are stored in the <a href="#root/_help_tAassRL4RSQL">data directory</a>.
|
||||
If you prefer a different location, you can change it by setting the <code>TRILIUM_DATA_DIR</code> environment
|
||||
variable:</p><pre><code class="language-text-x-trilium-auto">export TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data</code></pre>
|
||||
<h3>Disabling / Modifying the Upload Limit</h3>
|
||||
<p>If you're running into the 250MB limit imposed on the server by default,
|
||||
and you'd like to increase the upload limit, you can set the <code>TRILIUM_NO_UPLOAD_LIMIT</code> environment
|
||||
variable to <code>true</code> disable it completely:</p><pre><code class="language-text-x-trilium-auto">export TRILIUM_NO_UPLOAD_LIMIT=true </code></pre>
|
||||
<p>Or, if you'd simply like to <em>increase</em> the upload limit size to something
|
||||
beyond 250MB, you can set the <code>MAX_ALLOWED_FILE_SIZE_MB</code> environment
|
||||
variable to something larger than the integer <code>250</code> (e.g. <code>450</code> in
|
||||
the following example):</p><pre><code class="language-text-x-trilium-auto">export MAX_ALLOWED_FILE_SIZE_MB=450</code></pre>
|
||||
<h3>Disabling Authentication</h3>
|
||||
<p>If you are running Trilium on localhost only or if authentication is handled
|
||||
by another component, you can disable Trilium’s authentication by adding
|
||||
the following to <code>config.ini</code>:</p><pre><code class="language-text-x-trilium-auto">[General]
|
||||
noAuthentication=true</code></pre>
|
||||
<h2>Reverse Proxy Setup</h2>
|
||||
<p>To configure a reverse proxy for Trilium, you can use either <strong>nginx</strong> or <strong>Apache</strong>.</p>
|
||||
<p>To configure a reverse proxy for Trilium, you can use either <strong>nginx</strong> or <strong>Apache</strong>.
|
||||
You can also check out the documentation stored in the Reverse proxy folder.</p>
|
||||
<h3>nginx</h3>
|
||||
<p>Add the following configuration to your <code>nginx</code> setup to proxy
|
||||
requests to Trilium:</p><pre><code class="language-text-x-trilium-auto">location /trilium/ {
|
||||
|
@ -36,4 +36,18 @@
|
||||
<p>Note that the list of languages is not immediately refreshed, you'd have
|
||||
to manually <a href="#root/_help_s8alTXmpFR61">refresh the application</a>.</p>
|
||||
<p>The list of languages is also shared with the <a href="#root/_help_QxEyIjRBizuC">Code blocks</a> feature
|
||||
of <a href="#root/_help_iPIMuisry3hd">Text</a> notes.</p>
|
||||
of <a href="#root/_help_iPIMuisry3hd">Text</a> notes.</p>
|
||||
<h2>Color schemes</h2>
|
||||
<p>Since Trilium 0.94.0 the colors of code notes can be customized by going
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_4TIF1oA4VQRO">Options</a> → Code Notes and looking for the <em>Appearance</em> section.</p>
|
||||
<aside
|
||||
class="admonition note">
|
||||
<p><strong>Why are there only a few themes whereas the code block themes for text notes have a lot?</strong>
|
||||
<br>The reason is that Code notes use a different technology than the one
|
||||
used in Text notes, and as such there is a more limited selection of themes.
|
||||
If you find a CodeMirror 6 (not 5) theme that you would like to use, let
|
||||
us know and we might consider adding it to the set of default themes. There
|
||||
is no possibility of adding new themes (at least for now), since the themes
|
||||
are defined in JavaScript and not at CSS level.</p>
|
||||
</aside>
|
@ -25,7 +25,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Syntax highlighting</h2>
|
||||
<h2>Syntax highlighting & color schemes</h2>
|
||||
<p>Since TriliumNext v0.90.12, Trilium will try to offer syntax highlighting
|
||||
to the code block. Note that the syntax highlighting mechanism is slightly
|
||||
different than the one in <a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a> notes
|
||||
|
@ -22,6 +22,7 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
|
||||
"theme",
|
||||
"codeBlockTheme",
|
||||
"codeBlockWordWrap",
|
||||
"codeNoteTheme",
|
||||
"syncServerHost",
|
||||
"syncServerTimeout",
|
||||
"syncProxy",
|
||||
|
@ -91,12 +91,6 @@ async function register(app: express.Application) {
|
||||
|
||||
app.use(`/${assetPath}/node_modules/jquery.fancytree/dist/`, persistentCacheStatic(path.join(nodeModulesDir, "jquery.fancytree/dist/")));
|
||||
|
||||
// CodeMirror
|
||||
app.use(`/${assetPath}/node_modules/codemirror/lib/`, persistentCacheStatic(path.join(nodeModulesDir, "codemirror/lib/")));
|
||||
app.use(`/${assetPath}/node_modules/codemirror/addon/`, persistentCacheStatic(path.join(nodeModulesDir, "codemirror/addon/")));
|
||||
app.use(`/${assetPath}/node_modules/codemirror/mode/`, persistentCacheStatic(path.join(nodeModulesDir, "codemirror/mode/")));
|
||||
app.use(`/${assetPath}/node_modules/codemirror/keymap/`, persistentCacheStatic(path.join(nodeModulesDir, "codemirror/keymap/")));
|
||||
|
||||
app.use(`/${assetPath}/node_modules/@highlightjs/cdn-assets/`, persistentCacheStatic(path.join(nodeModulesDir, "@highlightjs/cdn-assets/")));
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,7 @@ export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
|
||||
{ title: "Forth", mime: "text/x-forth" },
|
||||
{ title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
|
||||
{ title: "Gas", mime: "text/x-gas" },
|
||||
{ title: "GDScript (Godot)", mime: "text/x-gdscript" },
|
||||
{ title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
|
||||
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
|
||||
{ title: "Go", mime: "text/x-go", highlightJs: "go", default: true },
|
||||
@ -106,6 +107,7 @@ export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
|
||||
{ title: "msgenny", mime: "text/x-msgenny" },
|
||||
{ title: "MUMPS", mime: "text/x-mumps" },
|
||||
{ title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
|
||||
{ title: "Nix", mime: "text/x-nix", highlightJs: "nix" },
|
||||
{ title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
|
||||
{ title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
|
||||
{ title: "NTriples", mime: "application/n-triples" },
|
||||
|
@ -137,6 +137,21 @@ const defaultOptions: DefaultOption[] = [
|
||||
|
||||
// Appearance
|
||||
{ name: "splitEditorOrientation", value: "horizontal", isSynced: true },
|
||||
{
|
||||
name: "codeNoteTheme",
|
||||
value: (optionsMap) => {
|
||||
switch (optionsMap.theme) {
|
||||
case "light":
|
||||
case "next-light":
|
||||
return "default:vs-code-light";
|
||||
case "dark":
|
||||
case "next-dark":
|
||||
default:
|
||||
return "default:vs-code-dark";
|
||||
}
|
||||
},
|
||||
isSynced: false
|
||||
},
|
||||
|
||||
// Internationalization
|
||||
{ name: "locale", value: "en", isSynced: true },
|
||||
|
@ -22,10 +22,6 @@ function buildFilesToCopy() {
|
||||
"autocomplete.js/dist",
|
||||
"normalize.css/normalize.css",
|
||||
"jquery.fancytree/dist",
|
||||
"codemirror/lib",
|
||||
"codemirror/addon",
|
||||
"codemirror/mode",
|
||||
"codemirror/keymap",
|
||||
"@highlightjs/cdn-assets",
|
||||
|
||||
// Required as they are native dependencies and cannot be well bundled.
|
||||
|
@ -87,19 +87,19 @@
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "BRhQZHgwaGyw",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-package",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "BRhQZHgwaGyw",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
|
@ -11,6 +11,10 @@
|
||||
* The goal is not to have basic API integration, but to really let the LLM understand the notes that are part of your knowledge base.
|
||||
* For more information, consult the in-app User Guide (<kbd>F1</kbd>) and look for the AI section.
|
||||
* Since this is highly experimental work, the LLM chat functionality might work well or it might have quite a few quirks, please keep this in mind.
|
||||
* This release brings significant updates to our libraries:
|
||||
* CKEditor, used by text notes is now updated to the latest version and that brings in new features (see below) and performance improvements.
|
||||
* CodeMirror, used by code notes has been upgraded to a new generation.
|
||||
* Code notes now also support themes, similar to code blocks.
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
@ -21,15 +25,25 @@
|
||||
|
||||
* Improved the text editor style, to match the TriliumNext.
|
||||
* Footnotes work in image captions by @werererer
|
||||
* Improvements to text notes, thanks updates to the editor (see the in-app help for more details):
|
||||
* Improvements to text notes (see the in-app help for more details):
|
||||
* Bookmarks, similar to HTML anchors.
|
||||
* Emojis.
|
||||
* [Make it show which node triggered the event when right-clicking on tree](https://github.com/TriliumNext/Notes/pull/1861) by @SiriusXT
|
||||
* [Only expand/collapse the left pane of the focused window](https://github.com/TriliumNext/Notes/pull/1905) by @SiriusXT
|
||||
* Code notes:
|
||||
* Added the GDScript (Godot) language.
|
||||
* Added the Nix language (and also in code blocks for text notes).
|
||||
* Added an indentation marker.
|
||||
* Note: syntax highlighting for some languages (mostly HTML-template languages such as EJS, JSP) is no longer supported due to lack of upstream support. If this is a problem, feel free to report an issue and we can see what can be done about it.
|
||||
* Mermaid diagrams: basic syntax highlight (not all diagram types are supported) and code folding.
|
||||
* Slight organization in Appearance settings: code block themes are now in "Text Notes", added a "Related settings" section in Appearance.
|
||||
* [Added support for opening and activating a note in a new tab using Ctrl+Shift+click on notes in the launcher pane, note tree, or note images](https://github.com/TriliumNext/Notes/pull/1854) by @SiriusXT
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
* \[…\]
|
||||
* Documented the new text note features: bookmarks and emojis.
|
||||
* Add documentation links and updated pnpm commands to README by @perfectra1n
|
||||
* Add documentation around setting the various environment variables to control upload size limit by @perfectra1n
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
@ -43,4 +57,5 @@
|
||||
* A large number of [dependency updates](https://github.com/TriliumNext/Notes/milestone/13).
|
||||
* OpenAPI documentation fixes by @FliegendeWurst
|
||||
* more info on several database table by @FliegendeWurst
|
||||
* CKEditor (the editor used for text notes) has been updated 7 versions, from v42 to 45.
|
||||
* CKEditor (the editor used for text notes) has been updated 7 versions, from v42 to 45.
|
||||
* Read-only search refactoring by @SiriusXT
|
@ -30,6 +30,20 @@ By default, `config.ini`, the [database](../Advanced%20Usage/Database.md), and o
|
||||
export TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data
|
||||
```
|
||||
|
||||
### Disabling / Modifying the Upload Limit
|
||||
|
||||
If you're running into the 250MB limit imposed on the server by default, and you'd like to increase the upload limit, you can set the `TRILIUM_NO_UPLOAD_LIMIT` environment variable to `true` disable it completely:
|
||||
|
||||
```
|
||||
export TRILIUM_NO_UPLOAD_LIMIT=true
|
||||
```
|
||||
|
||||
Or, if you'd simply like to _increase_ the upload limit size to something beyond 250MB, you can set the `MAX_ALLOWED_FILE_SIZE_MB` environment variable to something larger than the integer `250` (e.g. `450` in the following example):
|
||||
|
||||
```
|
||||
export MAX_ALLOWED_FILE_SIZE_MB=450
|
||||
```
|
||||
|
||||
### Disabling Authentication
|
||||
|
||||
If you are running Trilium on localhost only or if authentication is handled by another component, you can disable Trilium’s authentication by adding the following to `config.ini`:
|
||||
@ -41,7 +55,7 @@ noAuthentication=true
|
||||
|
||||
## Reverse Proxy Setup
|
||||
|
||||
To configure a reverse proxy for Trilium, you can use either **nginx** or **Apache**.
|
||||
To configure a reverse proxy for Trilium, you can use either **nginx** or **Apache**. You can also check out the documentation stored in the Reverse proxy folder.
|
||||
|
||||
### nginx
|
||||
|
||||
|
@ -24,4 +24,12 @@ Trilium supports syntax highlighting for many languages, but by default displays
|
||||
|
||||
Note that the list of languages is not immediately refreshed, you'd have to manually [refresh the application](../Troubleshooting/Refreshing%20the%20application.md).
|
||||
|
||||
The list of languages is also shared with the [Code blocks](Text/Developer-specific%20formatting/Code%20blocks.md) feature of [Text](Text.md) notes.
|
||||
The list of languages is also shared with the [Code blocks](Text/Developer-specific%20formatting/Code%20blocks.md) feature of [Text](Text.md) notes.
|
||||
|
||||
## Color schemes
|
||||
|
||||
Since Trilium 0.94.0 the colors of code notes can be customized by going <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Options.md">Options</a> → Code Notes and looking for the _Appearance_ section.
|
||||
|
||||
> [!NOTE]
|
||||
> **Why are there only a few themes whereas the code block themes for text notes have a lot?**
|
||||
> The reason is that Code notes use a different technology than the one used in Text notes, and as such there is a more limited selection of themes. If you find a CodeMirror 6 (not 5) theme that you would like to use, let us know and we might consider adding it to the set of default themes. There is no possibility of adding new themes (at least for now), since the themes are defined in JavaScript and not at CSS level.
|
@ -13,7 +13,7 @@ Note that this feature is meant for generally small snippets of code. For larger
|
||||
* Type ` ``` ` (as in Markdown).
|
||||
* Note that it's not possible to specify the language, as it will default to the last selected language.
|
||||
|
||||
## Syntax highlighting
|
||||
## Syntax highlighting & color schemes
|
||||
|
||||
Since TriliumNext v0.90.12, Trilium will try to offer syntax highlighting to the code block. Note that the syntax highlighting mechanism is slightly different than the one in <a class="reference-link" href="../../Code.md">Code</a> notes as different technologies are involved.
|
||||
|
||||
|
@ -89,7 +89,7 @@
|
||||
"axios": "^1.6.0",
|
||||
"express": "^4.21.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",
|
||||
|
7
packages/codemirror/README.md
Normal file
7
packages/codemirror/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# codemirror
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build codemirror` to build the library.
|
24
packages/codemirror/eslint.config.mjs
Normal file
24
packages/codemirror/eslint.config.mjs
Normal file
@ -0,0 +1,24 @@
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
"files": [
|
||||
"**/*.json"
|
||||
],
|
||||
"rules": {
|
||||
"@nx/dependency-checks": [
|
||||
"error",
|
||||
{
|
||||
"ignoredFiles": [
|
||||
"{projectRoot}/eslint.config.{js,cjs,mjs}",
|
||||
"{projectRoot}/vite.config.{js,ts,mjs,mts}"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"languageOptions": {
|
||||
"parser": (await import('jsonc-eslint-parser'))
|
||||
}
|
||||
}
|
||||
];
|
66
packages/codemirror/package.json
Normal file
66
packages/codemirror/package.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "@triliumnext/codemirror",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"development": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"nx": {
|
||||
"name": "codemirror"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "6.8.1",
|
||||
"@codemirror/lang-css": "6.3.1",
|
||||
"@codemirror/lang-html": "6.4.9",
|
||||
"@codemirror/lang-javascript": "6.2.3",
|
||||
"@codemirror/lang-json": "6.0.1",
|
||||
"@codemirror/lang-markdown": "6.3.2",
|
||||
"@codemirror/lang-php": "6.0.1",
|
||||
"@codemirror/lang-vue": "0.1.3",
|
||||
"@codemirror/lang-xml": "6.1.0",
|
||||
"@codemirror/legacy-modes": "6.5.1",
|
||||
"@codemirror/search": "6.5.10",
|
||||
"@codemirror/view": "6.36.8",
|
||||
"@fsegurai/codemirror-theme-abcdef": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-abyss": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-android-studio": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-andromeda": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-basic-dark": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-basic-light": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-forest": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-github-dark": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-github-light": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-gruvbox-dark": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-gruvbox-light": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-material-dark": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-material-light": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-monokai": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-nord": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-palenight": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-solarized-dark": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-solarized-light": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-tokyo-night-day": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-tokyo-night-storm": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-volcano": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-vscode-dark": "6.1.4",
|
||||
"@fsegurai/codemirror-theme-vscode-light": "6.1.4",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@replit/codemirror-lang-nix": "6.0.1",
|
||||
"@replit/codemirror-vim": "6.3.0",
|
||||
"@ssddanbrown/codemirror-lang-smarty": "1.0.0",
|
||||
"@ssddanbrown/codemirror-lang-twig": "1.0.0",
|
||||
"codemirror-lang-hcl": "0.1.0",
|
||||
"codemirror-lang-mermaid": "0.5.0",
|
||||
"eslint-linter-browserify": "9.26.0"
|
||||
}
|
||||
}
|
4
packages/codemirror/src/augmentation.d.ts
vendored
Normal file
4
packages/codemirror/src/augmentation.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module "@ssddanbrown/codemirror-lang-smarty" {
|
||||
import type { StreamParser } from "@codemirror/language"
|
||||
export const smarty: StreamParser<unknown>;
|
||||
}
|
137
packages/codemirror/src/color_themes.ts
Normal file
137
packages/codemirror/src/color_themes.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import type { Extension } from '@codemirror/state';
|
||||
|
||||
export interface ThemeDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
load(): Promise<Extension>;
|
||||
}
|
||||
|
||||
const themes: ThemeDefinition[] = [
|
||||
{
|
||||
id: "abyss",
|
||||
name: "Abyss",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-abyss")).abyss
|
||||
},
|
||||
{
|
||||
id: "abcdef",
|
||||
name: "ABCDEF",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-abcdef")).abcdef
|
||||
},
|
||||
{
|
||||
id: "android-studio",
|
||||
name: "Android Studio",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-android-studio")).androidStudio
|
||||
},
|
||||
{
|
||||
id: "andromeda",
|
||||
name: "Andromeda",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-andromeda")).andromeda
|
||||
},
|
||||
{
|
||||
id: "basic-dark",
|
||||
name: "Basic Dark",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-basic-dark")).basicDark
|
||||
},
|
||||
{
|
||||
id: "basic-light",
|
||||
name: "Basic Light",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-basic-light")).basicLight
|
||||
},
|
||||
{
|
||||
id: "forest",
|
||||
name: "Forest",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-forest")).forest
|
||||
},
|
||||
{
|
||||
id: "github-dark",
|
||||
name: "GitHub Dark",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-github-dark")).githubDark
|
||||
},
|
||||
{
|
||||
id: "github-light",
|
||||
name: "GitHub Light",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-github-light")).githubLight
|
||||
},
|
||||
{
|
||||
id: "gruvbox-dark",
|
||||
name: "Gruvbox Dark",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-gruvbox-dark")).gruvboxDark
|
||||
},
|
||||
{
|
||||
id: "gruvbox-light",
|
||||
name: "Gruvbox Light",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-gruvbox-light")).gruvboxLight
|
||||
},
|
||||
{
|
||||
id: "material-mark",
|
||||
name: "Material Dark",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-material-dark")).materialDark
|
||||
},
|
||||
{
|
||||
id: "material-light",
|
||||
name: "Material Light",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-material-light")).materialLight
|
||||
},
|
||||
{
|
||||
id: "monokai",
|
||||
name: "Monokai",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-monokai")).monokai
|
||||
},
|
||||
{
|
||||
id: "nord",
|
||||
name: "Nord",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-nord")).nord
|
||||
},
|
||||
{
|
||||
id: "palenight",
|
||||
name: "Palenight",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-palenight")).palenight
|
||||
},
|
||||
{
|
||||
id: "solarized-dark",
|
||||
name: "Solarized Dark",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-solarized-dark")).solarizedDark
|
||||
},
|
||||
{
|
||||
id: "solarized-light",
|
||||
name: "Solarized Light",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-solarized-light")).solarizedLight
|
||||
},
|
||||
{
|
||||
id: "tokyo-night-day",
|
||||
name: "Tokyo Night Day",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-tokyo-night-day")).tokyoNightDay
|
||||
},
|
||||
{
|
||||
id: "tokyo-night-storm",
|
||||
name: "Tokyo Night Storm",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-tokyo-night-storm")).tokyoNightStorm
|
||||
},
|
||||
{
|
||||
id: "volcano",
|
||||
name: "Volcano",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-volcano")).volcano
|
||||
},
|
||||
{
|
||||
id: "vs-code-dark",
|
||||
name: "VS Code Dark",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-vscode-dark")).vsCodeDark
|
||||
},
|
||||
{
|
||||
id: "vs-code-light",
|
||||
name: "VS Code Light",
|
||||
load: async () => (await import("@fsegurai/codemirror-theme-vscode-light")).vsCodeLight
|
||||
},
|
||||
]
|
||||
|
||||
export function getThemeById(id: string) {
|
||||
for (const theme of themes) {
|
||||
if (theme.id === id) {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default themes;
|
75
packages/codemirror/src/extensions/custom_tab.ts
Normal file
75
packages/codemirror/src/extensions/custom_tab.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { indentLess, indentMore } from "@codemirror/commands";
|
||||
import { EditorSelection, EditorState, type ChangeSpec } from "@codemirror/state";
|
||||
import type { KeyBinding } from "@codemirror/view";
|
||||
|
||||
/**
|
||||
* Custom key binding for indentation:
|
||||
*
|
||||
* - <kbd>Tab</kbd> while at the beginning of a line will indent the line.
|
||||
* - <kbd>Tab</kbd> while not at the beginning of a line will insert a tab character.
|
||||
* - <kbd>Tab</kbd> while not at the beginning of a line while text is selected will replace the txt with a tab character.
|
||||
* - <kbd>Shift</kbd>+<kbd>Tab</kbd> will always unindent.
|
||||
*/
|
||||
const smartIndentWithTab: KeyBinding[] = [
|
||||
{
|
||||
key: "Tab",
|
||||
run({ state, dispatch }) {
|
||||
if (state.facet(EditorState.readOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { selection } = state;
|
||||
const changes = [];
|
||||
const newSelections = [];
|
||||
|
||||
// Step 1: Handle non-empty selections → replace with tab
|
||||
if (selection.ranges.some(range => !range.empty)) {
|
||||
for (let range of selection.ranges) {
|
||||
changes.push({ from: range.from, to: range.to, insert: "\t" });
|
||||
newSelections.push(EditorSelection.cursor(range.from + 1));
|
||||
}
|
||||
|
||||
dispatch(
|
||||
state.update({
|
||||
changes,
|
||||
selection: EditorSelection.create(newSelections),
|
||||
scrollIntoView: true,
|
||||
userEvent: "input"
|
||||
})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 2: Handle empty selections
|
||||
for (let range of selection.ranges) {
|
||||
const line = state.doc.lineAt(range.head);
|
||||
const beforeCursor = state.doc.sliceString(line.from, range.head);
|
||||
|
||||
if (/^\s*$/.test(beforeCursor)) {
|
||||
// Only whitespace before cursor → indent line
|
||||
return indentMore({ state, dispatch });
|
||||
} else {
|
||||
// Insert tab character at cursor
|
||||
changes.push({ from: range.head, to: range.head, insert: "\t" });
|
||||
newSelections.push(EditorSelection.cursor(range.head + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.length) {
|
||||
dispatch(
|
||||
state.update({
|
||||
changes,
|
||||
selection: EditorSelection.create(newSelections),
|
||||
scrollIntoView: true,
|
||||
userEvent: "input"
|
||||
})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
shift: indentLess
|
||||
},
|
||||
]
|
||||
export default smartIndentWithTab;
|
@ -1,7 +1,17 @@
|
||||
import { lint } from "./eslint.js";
|
||||
import { lint as _lint } from "./eslint.js";
|
||||
import { trimIndentation } from "@triliumnext/commons";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
async function lint(code: string, mimeType: string) {
|
||||
const linterData = await _lint(mimeType);
|
||||
if (!("linter" in linterData)) {
|
||||
return [];
|
||||
}
|
||||
const { linter, config } = linterData;
|
||||
const result = linter.verify(code, config);
|
||||
return result;
|
||||
}
|
||||
|
||||
describe("Linter", () => {
|
||||
it("reports some basic errors", async () => {
|
||||
const result = await lint(trimIndentation`
|
@ -1,4 +1,6 @@
|
||||
export async function lint(code: string, mimeType: string) {
|
||||
import type { Linter } from "eslint-linter-browserify";
|
||||
|
||||
export async function lint(mimeType: string) {
|
||||
|
||||
const Linter = (await import("eslint-linter-browserify")).Linter;
|
||||
const js = (await import("@eslint/js"));
|
||||
@ -22,7 +24,7 @@ export async function lint(code: string, mimeType: string) {
|
||||
|
||||
}
|
||||
|
||||
return new Linter().verify(code, [
|
||||
const config: (Linter.LegacyConfig | Linter.Config | Linter.Config[]) = [
|
||||
js.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
@ -35,6 +37,10 @@ export async function lint(code: string, mimeType: string) {
|
||||
"no-unused-vars": [ "warn", { vars: "local", args: "after-used" }]
|
||||
}
|
||||
}
|
||||
]);
|
||||
];
|
||||
|
||||
return {
|
||||
linter: new Linter(),
|
||||
config
|
||||
}
|
||||
}
|
154
packages/codemirror/src/find_replace.ts
Normal file
154
packages/codemirror/src/find_replace.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import { EditorView, Decoration, MatchDecorator, ViewPlugin, ViewUpdate } from "@codemirror/view";
|
||||
import { Range, RangeSet } from "@codemirror/state";
|
||||
|
||||
const searchMatchDecoration = Decoration.mark({ class: "cm-searchMatch" });
|
||||
const activeMatchDecoration = Decoration.mark({ class: "cm-activeMatch" });
|
||||
|
||||
interface Match {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
export class SearchHighlighter {
|
||||
matches: RangeSet<Decoration>;
|
||||
activeMatch?: Range<Decoration>;
|
||||
|
||||
currentFound: number;
|
||||
totalFound: number;
|
||||
matcher?: MatchDecorator;
|
||||
private parsedMatches: Match[];
|
||||
|
||||
constructor(public view: EditorView) {
|
||||
this.parsedMatches = [];
|
||||
this.currentFound = 0;
|
||||
this.totalFound = 0;
|
||||
|
||||
this.matches = RangeSet.empty;
|
||||
}
|
||||
|
||||
searchFor(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
||||
if (!searchTerm) {
|
||||
this.matches = RangeSet.empty;
|
||||
return;
|
||||
}
|
||||
|
||||
// Escape the search term for use in RegExp
|
||||
const escapedTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const wordBoundary = wholeWord ? "\\b" : "";
|
||||
const flags = matchCase ? "g" : "gi";
|
||||
const regex = new RegExp(`${wordBoundary}${escapedTerm}${wordBoundary}`, flags);
|
||||
|
||||
this.matcher = new MatchDecorator({
|
||||
regexp: regex,
|
||||
decoration: searchMatchDecoration,
|
||||
});
|
||||
this.#updateSearchData(this.view);
|
||||
this.#scrollToMatchNearestSelection();
|
||||
}
|
||||
|
||||
replaceActiveMatch(replacementText: string) {
|
||||
if (!this.parsedMatches.length || this.currentFound === 0) return;
|
||||
|
||||
const matchIndex = this.currentFound - 1;
|
||||
const match = this.parsedMatches[matchIndex];
|
||||
|
||||
this.view.dispatch({
|
||||
changes: { from: match.from, to: match.to, insert: replacementText }
|
||||
});
|
||||
}
|
||||
|
||||
replaceAll(replacementText: string) {
|
||||
if (!this.parsedMatches.length) return;
|
||||
|
||||
this.view.dispatch({
|
||||
changes: this.parsedMatches.map(change => ({
|
||||
from: change.from,
|
||||
to: change.to,
|
||||
insert: replacementText
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
scrollToMatch(matchIndex: number) {
|
||||
if (this.parsedMatches.length <= matchIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = this.parsedMatches[matchIndex];
|
||||
this.currentFound = matchIndex + 1;
|
||||
this.activeMatch = activeMatchDecoration.range(match.from, match.to);
|
||||
this.view.dispatch({
|
||||
effects: EditorView.scrollIntoView(match.from, { y: "center" }),
|
||||
scrollIntoView: true
|
||||
});
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (update.docChanged || update.viewportChanged) {
|
||||
this.#updateSearchData(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
#updateSearchData(view: EditorView) {
|
||||
if (!this.matcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = this.matcher.createDeco(view);
|
||||
const cursor = matches.iter();
|
||||
while (cursor.value) {
|
||||
this.parsedMatches.push({
|
||||
from: cursor.from,
|
||||
to: cursor.to
|
||||
});
|
||||
cursor.next();
|
||||
}
|
||||
|
||||
this.matches = matches;
|
||||
this.totalFound = this.parsedMatches.length;
|
||||
}
|
||||
|
||||
#scrollToMatchNearestSelection() {
|
||||
const cursorPos = this.view.state.selection.main.head;
|
||||
let index = 0;
|
||||
for (const match of this.parsedMatches) {
|
||||
if (match.from >= cursorPos) {
|
||||
this.scrollToMatch(index);
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
static deco = (v: SearchHighlighter) => v.matches;
|
||||
}
|
||||
|
||||
export function createSearchHighlighter() {
|
||||
return ViewPlugin.fromClass(SearchHighlighter, {
|
||||
decorations: v => {
|
||||
if (v.activeMatch) {
|
||||
return v.matches.update({ add: [v.activeMatch] });
|
||||
} else {
|
||||
return v.matches;
|
||||
}
|
||||
},
|
||||
provide: (plugin) => plugin
|
||||
});
|
||||
}
|
||||
|
||||
export const searchMatchHighlightTheme = EditorView.baseTheme({
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "rgba(255, 255, 0, 0.4)",
|
||||
borderRadius: "2px"
|
||||
},
|
||||
".cm-activeMatch": {
|
||||
backgroundColor: "rgba(255, 165, 0, 0.6)",
|
||||
borderRadius: "2px",
|
||||
outline: "2px solid orange"
|
||||
}
|
||||
});
|
237
packages/codemirror/src/index.ts
Normal file
237
packages/codemirror/src/index.ts
Normal file
@ -0,0 +1,237 @@
|
||||
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
||||
import { EditorView, highlightActiveLine, keymap, lineNumbers, placeholder, ViewPlugin, ViewUpdate, type EditorViewConfig } from "@codemirror/view";
|
||||
import { defaultHighlightStyle, StreamLanguage, syntaxHighlighting, indentUnit, bracketMatching, foldGutter } from "@codemirror/language";
|
||||
import { Compartment, EditorSelection, EditorState, type Extension } from "@codemirror/state";
|
||||
import { highlightSelectionMatches } from "@codemirror/search";
|
||||
import { vim } from "@replit/codemirror-vim";
|
||||
import { indentationMarkers } from "@replit/codemirror-indentation-markers";
|
||||
import byMimeType from "./syntax_highlighting.js";
|
||||
import smartIndentWithTab from "./extensions/custom_tab.js";
|
||||
import type { ThemeDefinition } from "./color_themes.js";
|
||||
import { createSearchHighlighter, SearchHighlighter, searchMatchHighlightTheme } from "./find_replace.js";
|
||||
|
||||
export { default as ColorThemes, type ThemeDefinition, getThemeById } from "./color_themes.js";
|
||||
|
||||
type ContentChangedListener = () => void;
|
||||
|
||||
export interface EditorConfig {
|
||||
parent: HTMLElement;
|
||||
placeholder?: string;
|
||||
lineWrapping?: boolean;
|
||||
vimKeybindings?: boolean;
|
||||
readOnly?: boolean;
|
||||
tabIndex?: number;
|
||||
onContentChanged?: ContentChangedListener;
|
||||
}
|
||||
|
||||
export default class CodeMirror extends EditorView {
|
||||
|
||||
private config: EditorConfig;
|
||||
private languageCompartment: Compartment;
|
||||
private historyCompartment: Compartment;
|
||||
private themeCompartment: Compartment;
|
||||
private lineWrappingCompartment: Compartment;
|
||||
private searchHighlightCompartment: Compartment;
|
||||
private searchPlugin?: SearchHighlighter | null;
|
||||
|
||||
constructor(config: EditorConfig) {
|
||||
const languageCompartment = new Compartment();
|
||||
const historyCompartment = new Compartment();
|
||||
const themeCompartment = new Compartment();
|
||||
const lineWrappingCompartment = new Compartment();
|
||||
const searchHighlightCompartment = new Compartment();
|
||||
|
||||
let extensions: Extension[] = [];
|
||||
|
||||
if (config.vimKeybindings) {
|
||||
extensions.push(vim());
|
||||
}
|
||||
|
||||
extensions = [
|
||||
...extensions,
|
||||
languageCompartment.of([]),
|
||||
lineWrappingCompartment.of(config.lineWrapping ? EditorView.lineWrapping : []),
|
||||
themeCompartment.of([
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true })
|
||||
]),
|
||||
|
||||
searchMatchHighlightTheme,
|
||||
searchHighlightCompartment.of([]),
|
||||
|
||||
highlightActiveLine(),
|
||||
highlightSelectionMatches(),
|
||||
bracketMatching(),
|
||||
lineNumbers(),
|
||||
foldGutter(),
|
||||
indentationMarkers(),
|
||||
indentUnit.of(" ".repeat(4)),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
...smartIndentWithTab
|
||||
])
|
||||
]
|
||||
|
||||
if (!config.readOnly) {
|
||||
// Logic specific to editable notes
|
||||
if (config.placeholder) {
|
||||
extensions.push(placeholder(config.placeholder));
|
||||
}
|
||||
|
||||
if (config.onContentChanged) {
|
||||
extensions.push(EditorView.updateListener.of((v) => this.#onDocumentUpdated(v)));
|
||||
}
|
||||
|
||||
extensions.push(historyCompartment.of(history()));
|
||||
} else {
|
||||
// Logic specific to read-only notes
|
||||
extensions.push(EditorState.readOnly.of(true));
|
||||
}
|
||||
|
||||
super({
|
||||
parent: config.parent,
|
||||
extensions
|
||||
});
|
||||
|
||||
if (config.tabIndex) {
|
||||
this.dom.tabIndex = config.tabIndex;
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
this.languageCompartment = languageCompartment;
|
||||
this.historyCompartment = historyCompartment;
|
||||
this.themeCompartment = themeCompartment;
|
||||
this.lineWrappingCompartment = lineWrappingCompartment;
|
||||
this.searchHighlightCompartment = searchHighlightCompartment;
|
||||
}
|
||||
|
||||
#onDocumentUpdated(v: ViewUpdate) {
|
||||
if (v.docChanged) {
|
||||
this.config.onContentChanged?.();
|
||||
}
|
||||
}
|
||||
|
||||
getText() {
|
||||
return this.state.doc.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected text.
|
||||
*
|
||||
* If there are multiple selections, all of them will be concatenated.
|
||||
*/
|
||||
getSelectedText() {
|
||||
return this.state.selection.ranges
|
||||
.map((range) => this.state.sliceDoc(range.from, range.to))
|
||||
.join("");
|
||||
}
|
||||
|
||||
setText(content: string) {
|
||||
this.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: this.state.doc.length,
|
||||
insert: content || "",
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async setTheme(theme: ThemeDefinition) {
|
||||
const extension = await theme.load();
|
||||
this.dispatch({
|
||||
effects: [ this.themeCompartment.reconfigure([ extension ]) ]
|
||||
});
|
||||
}
|
||||
|
||||
setLineWrapping(wrapping: boolean) {
|
||||
this.dispatch({
|
||||
effects: [ this.lineWrappingCompartment.reconfigure(wrapping ? EditorView.lineWrapping : []) ]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the history of undo/redo. Generally useful when changing to a new document.
|
||||
*/
|
||||
clearHistory() {
|
||||
if (this.config.readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatch({
|
||||
effects: [ this.historyCompartment.reconfigure([]) ]
|
||||
});
|
||||
this.dispatch({
|
||||
effects: [ this.historyCompartment.reconfigure(history())]
|
||||
});
|
||||
}
|
||||
|
||||
scrollToEnd() {
|
||||
const endPos = this.state.doc.length;
|
||||
this.dispatch({
|
||||
selection: EditorSelection.cursor(endPos),
|
||||
effects: EditorView.scrollIntoView(endPos, { y: "end" }),
|
||||
scrollIntoView: true
|
||||
});
|
||||
}
|
||||
|
||||
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
||||
const plugin = createSearchHighlighter();
|
||||
this.dispatch({
|
||||
effects: this.searchHighlightCompartment.reconfigure(plugin)
|
||||
});
|
||||
|
||||
// Wait for the plugin to activate in the next render cycle
|
||||
await new Promise(requestAnimationFrame);
|
||||
const instance = this.plugin(plugin);
|
||||
instance?.searchFor(searchTerm, matchCase, wholeWord);
|
||||
this.searchPlugin = instance;
|
||||
|
||||
return {
|
||||
totalFound: instance?.totalFound ?? 0,
|
||||
currentFound: instance?.currentFound ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
async findNext(direction: number, currentFound: number, nextFound: number) {
|
||||
this.searchPlugin?.scrollToMatch(nextFound);
|
||||
}
|
||||
|
||||
async replace(replaceText: string) {
|
||||
this.searchPlugin?.replaceActiveMatch(replaceText);
|
||||
}
|
||||
|
||||
async replaceAll(replaceText: string) {
|
||||
this.searchPlugin?.replaceAll(replaceText);
|
||||
}
|
||||
|
||||
cleanSearch() {
|
||||
if (this.searchPlugin) {
|
||||
this.dispatch({
|
||||
effects: this.searchHighlightCompartment.reconfigure([])
|
||||
});
|
||||
this.searchPlugin = null;
|
||||
}
|
||||
}
|
||||
|
||||
async setMimeType(mime: string) {
|
||||
let newExtension: Extension[] = [];
|
||||
|
||||
const correspondingSyntax = byMimeType[mime];
|
||||
if (correspondingSyntax) {
|
||||
const resolvedSyntax = await correspondingSyntax();
|
||||
|
||||
if ("token" in resolvedSyntax) {
|
||||
const extension = StreamLanguage.define(resolvedSyntax);
|
||||
newExtension.push(extension);
|
||||
} else if (Array.isArray(resolvedSyntax)) {
|
||||
newExtension = [ ...newExtension, ...resolvedSyntax ];
|
||||
} else {
|
||||
newExtension.push(resolvedSyntax);
|
||||
}
|
||||
}
|
||||
|
||||
this.dispatch({
|
||||
effects: this.languageCompartment.reconfigure(newExtension)
|
||||
});
|
||||
}
|
||||
}
|
@ -1,51 +1,48 @@
|
||||
// Source: https://github.com/deathau/cm-editor-syntax-highlight-obsidian/issues/27#issuecomment-1340586596
|
||||
(() => {
|
||||
var varsAndArgsRegex = /(%[0-9]|%~\S+|%\S+%)/;
|
||||
/**
|
||||
* @module
|
||||
*
|
||||
* Ported to CodeMirror 6 from https://github.com/deathau/cm-editor-syntax-highlight-obsidian/issues/27#issuecomment-1340586596
|
||||
*/
|
||||
|
||||
CodeMirror.defineSimpleMode("batch", {
|
||||
start: [
|
||||
{ //comment
|
||||
import { simpleMode } from "@codemirror/legacy-modes/mode/simple-mode";
|
||||
|
||||
const varsAndArgsRegex = /(%[0-9]|%~\S+|%\S+%)/;
|
||||
|
||||
export const batch = simpleMode({
|
||||
start: [
|
||||
{ //comment
|
||||
regex: /(rem|::)(?:\s.*|$)/i,
|
||||
token: "comment",
|
||||
sol: true
|
||||
},
|
||||
{ //echo
|
||||
},
|
||||
{ //echo
|
||||
regex: /(@echo|echo)/i,
|
||||
token: "builtin",
|
||||
sol: true,
|
||||
next: "echo"
|
||||
},
|
||||
{ //commands
|
||||
},
|
||||
{ //commands
|
||||
regex: /(?:\s|^)(assoc|aux|break|call|cd|chcp|chdir|choice|cls|cmdextversion|color|com1|com2|com3|com4|com|con|copy|country|ctty|date|defined|del|dir|do|dpath|else|endlocal|erase|errorlevel|exist|exit|for|ftype|goto|if|in|loadfix|loadhigh|lpt|lpt1|lpt2|lpt3|lpt4|md|mkdir|move|not|nul|path|pause|popd|prn|prompt|pushd|rd|rename|ren|rmdir|setlocal|set|shift|start|time|title|type|verify|ver|vol)(?:\s|$)/i,
|
||||
token: "builtin"
|
||||
},
|
||||
{ //variables and arguments
|
||||
},
|
||||
{ //variables and arguments
|
||||
regex: varsAndArgsRegex,
|
||||
token: "variable-2"
|
||||
},
|
||||
{ //label
|
||||
},
|
||||
{ //label
|
||||
regex: /\s*:.*/,
|
||||
token: "string",
|
||||
sol: true
|
||||
}
|
||||
],
|
||||
echo: [
|
||||
{ //highlight variables and arguments in echo command
|
||||
}
|
||||
],
|
||||
echo: [
|
||||
{ //highlight variables and arguments in echo command
|
||||
regex: varsAndArgsRegex,
|
||||
token: "variable-2"
|
||||
},
|
||||
{ //go back to start state at end of line
|
||||
},
|
||||
{ //go back to start state at end of line
|
||||
regex: /.$/,
|
||||
next: "start"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("application/x-bat", "batch");
|
||||
CodeMirror.modeInfo.push({
|
||||
ext: [ "bat", "cmd" ],
|
||||
mime: "application/x-bat",
|
||||
mode: "batch",
|
||||
name: "Batch file"
|
||||
});
|
||||
})();
|
||||
}
|
||||
]
|
||||
});
|
37
packages/codemirror/src/languages/gdscript.ts
Normal file
37
packages/codemirror/src/languages/gdscript.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @module
|
||||
*
|
||||
* Ported to CodeMirror 6 from https://github.com/RobTheFiveNine/obsidian-gdscript/blob/main/src/main.js
|
||||
*/
|
||||
|
||||
import { simpleMode } from "@codemirror/legacy-modes/mode/simple-mode";
|
||||
|
||||
export const gdscript = simpleMode({
|
||||
start: [
|
||||
{ regex: /\b0x[0-9a-f]+\b/i, token: "number" },
|
||||
{ regex: /\b-?\d+\b/, token: "number" },
|
||||
{ regex: /#.+/, token: 'comment' },
|
||||
{ regex: /\s*(@onready|@export)\b/, token: 'keyword' },
|
||||
{ regex: /\b(?:and|as|assert|await|break|breakpoint|const|continue|elif|else|enum|for|if|in|is|master|mastersync|match|not|null|or|pass|preload|puppet|puppetsync|remote|remotesync|return|self|setget|static|tool|var|while|yield)\b/, token: 'keyword' },
|
||||
{ regex: /[()\[\]{},]/, token: "meta" },
|
||||
|
||||
// The words following func, class_name and class should be highlighted as attributes,
|
||||
// so push onto the definition stack
|
||||
{ regex: /\b(func|class_name|class|extends|signal)\b/, token: "keyword", push: "definition" },
|
||||
|
||||
{ regex: /@?(?:("|')(?:(?!\1)[^\n\\]|\\[\s\S])*\1(?!"|')|"""(?:[^\\]|\\[\s\S])*?""")/, token: "string" },
|
||||
{ regex: /\$[\w\/]+\b/, token: 'variable' },
|
||||
{ regex: /\:[\s]*$/, token: 'operator' },
|
||||
{ regex: /\:[ ]*/, token: 'meta', push: 'var_type' },
|
||||
{ regex: /\->[ ]*/, token: 'operator', push: 'definition' },
|
||||
{ regex: /\+|\*|-|\/|:=|>|<|\^|&|\||%|~|=/, token: "operator" },
|
||||
{ regex: /\b(?:false|true)\b/, token: 'number' },
|
||||
{ regex: /\b[A-Z][A-Z_\d]*\b/, token: 'operator' },
|
||||
],
|
||||
var_type: [
|
||||
{ regex: /(\w+)/, token: 'attribute', pop: true },
|
||||
],
|
||||
definition: [
|
||||
{ regex: /(\w+)/, token: "attribute", pop: true }
|
||||
]
|
||||
});
|
197
packages/codemirror/src/syntax_highlighting.ts
Normal file
197
packages/codemirror/src/syntax_highlighting.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import { LanguageSupport, type StreamParser } from "@codemirror/language";
|
||||
import {linter as linterExtension, lintGutter } from "@codemirror/lint";
|
||||
import type { Extension } from "@codemirror/state";
|
||||
|
||||
async function buildJavaScript(mimeType: string) {
|
||||
const { javascript, esLint } = await import('@codemirror/lang-javascript');
|
||||
const lint = (await import("./extensions/eslint.js")).lint;
|
||||
const extensions: Extension[] = [ javascript() ];
|
||||
|
||||
const result = await lint(mimeType);
|
||||
if ("linter" in result) {
|
||||
const { linter, config } = result;
|
||||
extensions.push(linterExtension(esLint(linter, config)));
|
||||
extensions.push(lintGutter())
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
async function buildMermaid() {
|
||||
const { mermaid, foldByIndent } = (await import('codemirror-lang-mermaid'));
|
||||
return [ mermaid(), foldByIndent() ];
|
||||
}
|
||||
|
||||
const byMimeType: Record<string, (() => Promise<StreamParser<unknown> | LanguageSupport | Extension[]>) | null> = {
|
||||
"text/plain": null,
|
||||
|
||||
"application/dart": async () => (await import('@codemirror/legacy-modes/mode/clike')).dart,
|
||||
"application/edn": async () => (await import('@codemirror/legacy-modes/mode/clojure')).clojure,
|
||||
"application/javascript;env=backend": async () => buildJavaScript("application/javascript;env=backend"),
|
||||
"application/javascript;env=frontend": async () => buildJavaScript("application/javascript;env=frontend"),
|
||||
"application/json": async () => ((await import('@codemirror/lang-json')).json()),
|
||||
"application/ld+json": async () => (await import('@codemirror/legacy-modes/mode/javascript')).jsonld,
|
||||
"application/mbox": async () => (await import('@codemirror/legacy-modes/mode/mbox')).mbox,
|
||||
"application/n-triples": async () => (await import('@codemirror/legacy-modes/mode/ntriples')).ntriples,
|
||||
"application/pgp": async () => (await import('@codemirror/legacy-modes/mode/asciiarmor')).asciiArmor,
|
||||
"application/sieve": async () => (await import('@codemirror/legacy-modes/mode/sieve')).sieve,
|
||||
"application/sparql-query": async () => (await import('@codemirror/legacy-modes/mode/sparql')).sparql,
|
||||
"application/typescript": async () => (await import('@codemirror/lang-javascript')).javascript({ typescript: true }),
|
||||
"application/x-aspx": null,
|
||||
"application/x-bat": async () => (await import("./languages/batch.js")).batch,
|
||||
"application/x-cypher-query": async () => (await import('@codemirror/legacy-modes/mode/cypher')).cypher,
|
||||
"application/x-ejs": null,
|
||||
"application/x-erb": null,
|
||||
"application/x-jsp": null,
|
||||
"application/x-powershell": async () => (await import('@codemirror/legacy-modes/mode/powershell')).powerShell,
|
||||
"application/xml-dtd": async () => (await import('@codemirror/legacy-modes/mode/xml')).xml,
|
||||
"application/xquery": async () => (await import('@codemirror/legacy-modes/mode/xquery')).xQuery,
|
||||
"message/http": async () => (await import('@codemirror/legacy-modes/mode/http')).http,
|
||||
"text/apl": async () => (await import('@codemirror/legacy-modes/mode/apl')).apl,
|
||||
"text/coffeescript": async () => (await import('@codemirror/legacy-modes/mode/coffeescript')).coffeeScript,
|
||||
"text/css": async () => (await import('@codemirror/lang-css')).css(),
|
||||
"text/html": async () => (await import('@codemirror/lang-html')).html(),
|
||||
"text/jinja2": async () => (await import('@codemirror/legacy-modes/mode/jinja2')).jinja2,
|
||||
"text/jsx": async () => (await import('@codemirror/lang-javascript')).javascript({ jsx: true }),
|
||||
"text/mirc": async () => (await import('@codemirror/legacy-modes/mode/mirc')).mirc,
|
||||
"text/tiki": async () => (await import('@codemirror/legacy-modes/mode/tiki')).tiki,
|
||||
"text/troff": async () => (await import('@codemirror/legacy-modes/mode/troff')).troff,
|
||||
"text/turtle": async () => (await import('@codemirror/legacy-modes/mode/turtle')).turtle,
|
||||
"text/typescript-jsx": async () => (await import('@codemirror/lang-javascript')).javascript({ typescript: true, jsx: true }),
|
||||
"text/vbscript": async () => (await import('@codemirror/legacy-modes/mode/vbscript')).vbScript,
|
||||
"text/velocity": async () => (await import('@codemirror/legacy-modes/mode/velocity')).velocity,
|
||||
"text/vnd.mermaid": async () => buildMermaid(),
|
||||
"text/mermaid": async () => buildMermaid(),
|
||||
"text/x-asm-mips": null,
|
||||
"text/x-asterisk": async () => (await import('@codemirror/legacy-modes/mode/asterisk')).asterisk,
|
||||
"text/x-brainfuck": async () => (await import('@codemirror/legacy-modes/mode/brainfuck')).brainfuck,
|
||||
"text/x-c++src": async () => (await import('@codemirror/legacy-modes/mode/clike')).cpp,
|
||||
"text/x-cassandra": async () => (await import('@codemirror/legacy-modes/mode/sql')).cassandra,
|
||||
"text/x-clojure": async () => (await import('@codemirror/legacy-modes/mode/clojure')).clojure,
|
||||
"text/x-clojurescript": async () => (await import('@codemirror/legacy-modes/mode/clojure')).clojure,
|
||||
"text/x-cmake": async () => (await import('@codemirror/legacy-modes/mode/cmake')).cmake,
|
||||
"text/x-cobol": async () => (await import('@codemirror/legacy-modes/mode/cobol')).cobol,
|
||||
"text/x-common-lisp": async () => (await import('@codemirror/legacy-modes/mode/commonlisp')).commonLisp,
|
||||
"text/x-crystal": async () => (await import('@codemirror/legacy-modes/mode/crystal')).crystal,
|
||||
"text/x-csharp": async () => (await import('@codemirror/legacy-modes/mode/clike')).csharp,
|
||||
"text/x-csrc": async () => (await import('@codemirror/legacy-modes/mode/clike')).c,
|
||||
"text/x-cython": async () => (await import('@codemirror/legacy-modes/mode/python')).cython,
|
||||
"text/x-d": async () => (await import('@codemirror/legacy-modes/mode/d')).d,
|
||||
"text/x-diff": async () => (await import('@codemirror/legacy-modes/mode/diff')).diff,
|
||||
"text/x-django": null,
|
||||
"text/x-dockerfile": async () => (await import('@codemirror/legacy-modes/mode/dockerfile')).dockerFile,
|
||||
"text/x-dylan": async () => (await import('@codemirror/legacy-modes/mode/dylan')).dylan,
|
||||
"text/x-ebnf": async () => (await import('@codemirror/legacy-modes/mode/ebnf')).ebnf,
|
||||
"text/x-ecl": async () => (await import('@codemirror/legacy-modes/mode/ecl')).ecl,
|
||||
"text/x-eiffel": async () => (await import('@codemirror/legacy-modes/mode/eiffel')).eiffel,
|
||||
"text/x-elm": async () => (await import('@codemirror/legacy-modes/mode/elm')).elm,
|
||||
"text/x-erlang": async () => (await import('@codemirror/legacy-modes/mode/erlang')).erlang,
|
||||
"text/x-esper": async () => (await import('@codemirror/legacy-modes/mode/sql')).esper,
|
||||
"text/x-factor": async () => (await import('@codemirror/legacy-modes/mode/factor')).factor,
|
||||
"text/x-fcl": async () => (await import('@codemirror/legacy-modes/mode/fcl')).fcl,
|
||||
"text/x-feature": async () => (await import('@codemirror/legacy-modes/mode/gherkin')).gherkin,
|
||||
"text/x-forth": async () => (await import('@codemirror/legacy-modes/mode/forth')).forth,
|
||||
"text/x-fortran": async () => (await import('@codemirror/legacy-modes/mode/fortran')).fortran,
|
||||
"text/x-fsharp": async () => (await import('@codemirror/legacy-modes/mode/mllike')).fSharp,
|
||||
"text/x-gas": async () => (await import('@codemirror/legacy-modes/mode/gas')).gas,
|
||||
"text/x-gdscript": async () => (await import('./languages/gdscript.js')).gdscript,
|
||||
"text/x-gfm": async () => {
|
||||
const { markdown, markdownLanguage } = (await import('@codemirror/lang-markdown'));
|
||||
return markdown({
|
||||
base: markdownLanguage
|
||||
});
|
||||
},
|
||||
"text/x-go": async () => (await import('@codemirror/legacy-modes/mode/go')).go,
|
||||
"text/x-groovy": async () => (await import('@codemirror/legacy-modes/mode/groovy')).groovy,
|
||||
"text/x-gss": async () => (await import('@codemirror/legacy-modes/mode/css')).gss,
|
||||
"text/x-haml": null,
|
||||
"text/x-haskell": async () => (await import('@codemirror/legacy-modes/mode/haskell')).haskell,
|
||||
"text/x-haxe": async () => (await import('@codemirror/legacy-modes/mode/haxe')).haxe,
|
||||
"text/x-hcl": async () => (await import('codemirror-lang-hcl')).hcl(),
|
||||
"text/x-hxml": async () => (await import('@codemirror/legacy-modes/mode/haxe')).hxml,
|
||||
"text/x-idl": async () => (await import('@codemirror/legacy-modes/mode/idl')).idl,
|
||||
"text/x-java": async () => (await import('@codemirror/legacy-modes/mode/clike')).java,
|
||||
"text/x-julia": async () => (await import('@codemirror/legacy-modes/mode/julia')).julia,
|
||||
"text/x-kotlin": async () => (await import('@codemirror/legacy-modes/mode/clike')).kotlin,
|
||||
"text/x-latex": async () => (await import('@codemirror/legacy-modes/mode/stex')).stex,
|
||||
"text/x-less": async () => (await import('@codemirror/legacy-modes/mode/css')).less,
|
||||
"text/x-literate-haskell": null,
|
||||
"text/x-livescript": async () => (await import('@codemirror/legacy-modes/mode/livescript')).liveScript,
|
||||
"text/x-lua": async () => (await import('@codemirror/legacy-modes/mode/lua')).lua,
|
||||
"text/x-mariadb": async () => (await import('@codemirror/legacy-modes/mode/sql')).sqlite,
|
||||
"text/x-markdown": async () => ((await import('@codemirror/lang-markdown')).markdown()),
|
||||
"text/x-mathematica": async () => (await import('@codemirror/legacy-modes/mode/mathematica')).mathematica,
|
||||
"text/x-modelica": async () => (await import('@codemirror/legacy-modes/mode/modelica')).modelica,
|
||||
"text/x-mscgen": async () => (await import('@codemirror/legacy-modes/mode/mscgen')).mscgen,
|
||||
"text/x-msgenny": async () => (await import('@codemirror/legacy-modes/mode/mscgen')).msgenny,
|
||||
"text/x-mssql": async () => (await import('@codemirror/legacy-modes/mode/sql')).msSQL,
|
||||
"text/x-mumps": async () => (await import('@codemirror/legacy-modes/mode/mumps')).mumps,
|
||||
"text/x-mysql": async () => (await import('@codemirror/legacy-modes/mode/sql')).mySQL,
|
||||
"text/x-nix": async () => (await import('@replit/codemirror-lang-nix')).nix(),
|
||||
"text/x-nginx-conf": async () => (await import('@codemirror/legacy-modes/mode/nginx')).nginx,
|
||||
"text/x-nsis": async () => (await import('@codemirror/legacy-modes/mode/nsis')).nsis,
|
||||
"text/x-objectivec": async () => (await import('@codemirror/legacy-modes/mode/clike')).objectiveC,
|
||||
"text/x-ocaml": async () => (await import('@codemirror/legacy-modes/mode/mllike')).oCaml,
|
||||
"text/x-octave": async () => (await import('@codemirror/legacy-modes/mode/octave')).octave,
|
||||
"text/x-oz": async () => (await import('@codemirror/legacy-modes/mode/oz')).oz,
|
||||
"text/x-pascal": async () => (await import('@codemirror/legacy-modes/mode/pascal')).pascal,
|
||||
"text/x-perl": async () => (await import('@codemirror/legacy-modes/mode/perl')).perl,
|
||||
"text/x-pgsql": async () => (await import('@codemirror/legacy-modes/mode/sql')).pgSQL,
|
||||
"text/x-php": async () => ((await import('@codemirror/lang-php')).php()),
|
||||
"text/x-pig": async () => (await import('@codemirror/legacy-modes/mode/pig')).pig,
|
||||
"text/x-plsql": async () => (await import('@codemirror/legacy-modes/mode/sql')).plSQL,
|
||||
"text/x-properties": async () => (await import('@codemirror/legacy-modes/mode/properties')).properties,
|
||||
"text/x-protobuf": async () => (await import('@codemirror/legacy-modes/mode/protobuf')).protobuf,
|
||||
"text/x-pug": async () => (await import('@codemirror/legacy-modes/mode/pug')).pug,
|
||||
"text/x-puppet": async () => (await import('@codemirror/legacy-modes/mode/puppet')).puppet,
|
||||
"text/x-python": async () => (await import('@codemirror/legacy-modes/mode/python')).python,
|
||||
"text/x-q": async () => (await import('@codemirror/legacy-modes/mode/q')).q,
|
||||
"text/x-rpm-changes": async () => (await import('@codemirror/legacy-modes/mode/rpm')).rpmChanges,
|
||||
"text/x-rpm-spec": async () => (await import('@codemirror/legacy-modes/mode/rpm')).rpmSpec,
|
||||
"text/x-rsrc": async () => (await import('@codemirror/legacy-modes/mode/r')).r,
|
||||
"text/x-rst": null,
|
||||
"text/x-ruby": async () => (await import('@codemirror/legacy-modes/mode/ruby')).ruby,
|
||||
"text/x-rustsrc": async () => (await import('@codemirror/legacy-modes/mode/rust')).rust,
|
||||
"text/x-sas": async () => (await import('@codemirror/legacy-modes/mode/sas')).sas,
|
||||
"text/x-sass": async () => (await import('@codemirror/legacy-modes/mode/sass')).sass,
|
||||
"text/x-scala": async () => (await import('@codemirror/legacy-modes/mode/clike')).scala,
|
||||
"text/x-scheme": async () => (await import('@codemirror/legacy-modes/mode/scheme')).scheme,
|
||||
"text/x-scss": async () => (await import('@codemirror/legacy-modes/mode/css')).sCSS,
|
||||
"text/x-sh": async () => (await import('@codemirror/legacy-modes/mode/shell')).shell,
|
||||
"text/x-slim": null,
|
||||
"text/x-smarty": async () => ((await import('@ssddanbrown/codemirror-lang-smarty')).smarty),
|
||||
"text/x-sml": async () => (await import('@codemirror/legacy-modes/mode/mllike')).sml,
|
||||
"text/x-solr": async () => (await import('@codemirror/legacy-modes/mode/solr')).solr,
|
||||
"text/x-soy": null,
|
||||
"text/x-spreadsheet": async () => (await import('@codemirror/legacy-modes/mode/spreadsheet')).spreadsheet,
|
||||
"text/x-sql": async () => (await import('@codemirror/legacy-modes/mode/sql')).mySQL,
|
||||
"text/x-sqlite;schema=trilium": async () => (await import('@codemirror/legacy-modes/mode/sql')).sqlite,
|
||||
"text/x-sqlite": async () => (await import('@codemirror/legacy-modes/mode/sql')).sqlite,
|
||||
"text/x-squirrel": async () => (await import('@codemirror/legacy-modes/mode/clike')).squirrel,
|
||||
"text/x-stex": async () => (await import('@codemirror/legacy-modes/mode/stex')).stex,
|
||||
"text/x-stsrc": async () => (await import('@codemirror/legacy-modes/mode/smalltalk')).smalltalk,
|
||||
"text/x-styl": async () => (await import('@codemirror/legacy-modes/mode/stylus')).stylus,
|
||||
"text/x-swift": async () => (await import('@codemirror/legacy-modes/mode/swift')).swift,
|
||||
"text/x-systemverilog": async () => (await import('@codemirror/legacy-modes/mode/verilog')).verilog,
|
||||
"text/x-tcl": async () => (await import('@codemirror/legacy-modes/mode/tcl')).tcl,
|
||||
"text/x-textile": async () => (await import('@codemirror/legacy-modes/mode/textile')).textile,
|
||||
"text/x-tiddlywiki": async () => (await import('@codemirror/legacy-modes/mode/tiddlywiki')).tiddlyWiki,
|
||||
"text/x-toml": async () => (await import('@codemirror/legacy-modes/mode/toml')).toml,
|
||||
"text/x-tornado": null,
|
||||
"text/x-ttcn-asn": async () => (await import('@codemirror/legacy-modes/mode/ttcn')).ttcn,
|
||||
"text/x-ttcn-cfg": async () => (await import('@codemirror/legacy-modes/mode/ttcn-cfg')).ttcnCfg,
|
||||
"text/x-ttcn": async () => (await import('@codemirror/legacy-modes/mode/ttcn')).ttcn,
|
||||
"text/x-twig": async () => ((await import('@ssddanbrown/codemirror-lang-twig')).twig()),
|
||||
"text/x-vb": async () => (await import('@codemirror/legacy-modes/mode/vb')).vb,
|
||||
"text/x-verilog": async () => (await import('@codemirror/legacy-modes/mode/verilog')).verilog,
|
||||
"text/x-vhdl": async () => (await import('@codemirror/legacy-modes/mode/vhdl')).vhdl,
|
||||
"text/x-vue": async () => ((await import('@codemirror/lang-vue')).vue()),
|
||||
"text/x-webidl": async () => (await import('@codemirror/legacy-modes/mode/webidl')).webIDL,
|
||||
"text/x-xu": async () => (await import('@codemirror/legacy-modes/mode/mscgen')).xu,
|
||||
"text/x-yacas": async () => (await import('@codemirror/legacy-modes/mode/yacas')).yacas,
|
||||
"text/x-yaml": async () => (await import('@codemirror/legacy-modes/mode/yaml')).yaml,
|
||||
"text/x-z80": async () => (await import('@codemirror/legacy-modes/mode/z80')).z80,
|
||||
"text/xml": async () => (await import('@codemirror/lang-xml')).xml()
|
||||
}
|
||||
|
||||
export default byMimeType;
|
16
packages/codemirror/tsconfig.json
Normal file
16
packages/codemirror/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../commons"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
42
packages/codemirror/tsconfig.lib.json
Normal file
42
packages/codemirror/tsconfig.lib.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
"emitDeclarationOnly": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"lib": [
|
||||
"DOM"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
"vite/client"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../commons/tsconfig.lib.json"
|
||||
}
|
||||
],
|
||||
"exclude": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx"
|
||||
]
|
||||
}
|
28
packages/codemirror/tsconfig.spec.json
Normal file
28
packages/codemirror/tsconfig.spec.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/vitest",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
37
packages/codemirror/vite.config.ts
Normal file
37
packages/codemirror/vite.config.ts
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
import { defineConfig } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import * as path from 'path';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/packages/codemirror',
|
||||
plugins: [dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') }), ],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
// Configuration for building your library.
|
||||
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||
build: {
|
||||
outDir: './dist',
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: true,
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points.
|
||||
entry: 'src/index.ts',
|
||||
name: 'codemirror',
|
||||
fileName: 'index',
|
||||
// Change this to the formats you want to support.
|
||||
// Don't forget to update your package.json as well.
|
||||
formats: ['es' as const]
|
||||
},
|
||||
rollupOptions: {
|
||||
// External packages that should not be bundled into your library.
|
||||
external: []
|
||||
},
|
||||
},
|
||||
}));
|
@ -91,6 +91,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
||||
|
||||
// Appearance
|
||||
splitEditorOrientation: "horziontal" | "vertical";
|
||||
codeNoteTheme: string;
|
||||
|
||||
initialized: boolean;
|
||||
isPasswordSet: boolean;
|
||||
|
2056
pnpm-lock.yaml
generated
2056
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -50,6 +50,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./packages/ckeditor5-math"
|
||||
},
|
||||
{
|
||||
"path": "./packages/codemirror"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user