Upgrade codemirror to v6 (#18524)
Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
1 parent
1ce53af15b
commit
b261f60dcf
12 files changed
+1598
-1224
No files matched your search
@@ -74,13 +74,17 @@
|
||||
groupName: "GitHub Actions",
|
||||
matchManagers: ["github-actions"],
|
||||
},
|
||||
{
|
||||
groupName: "Markdown dependencies",
|
||||
matchPackageNames: ["micromark-*", "mdast-", "remark-", "unified"],
|
||||
},
|
||||
{
|
||||
groupName: "Docusaurus",
|
||||
matchPackageNames: ["@docusaurus/*"],
|
||||
},
|
||||
{
|
||||
groupName: "Markdown dependencies",
|
||||
matchPackageNames: ["micromark-*", "mdast-", "remark-", "unified"],
|
||||
groupName: "CodeMirror",
|
||||
matchPackageNames: ["@codemirror/*", "cm6-graphql"],
|
||||
},
|
||||
],
|
||||
ignoreDeps: [
|
||||
@@ -90,6 +94,8 @@
|
||||
"prettier",
|
||||
// Old versions are required to test
|
||||
"node",
|
||||
// Only pinned version works, https://github.com/prettier/prettier/pull/18524#issuecomment-3694806317
|
||||
"@codemirror/view",
|
||||
],
|
||||
ignorePaths: ["tests/**", "packages/**"],
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
"Burgener",
|
||||
"callees",
|
||||
"camelified",
|
||||
"catppuccin",
|
||||
"Chedeau",
|
||||
"cherow",
|
||||
"Cheung",
|
||||
@@ -142,10 +143,12 @@
|
||||
"kddnewton",
|
||||
"Kearney",
|
||||
"Kempson",
|
||||
"Keymap",
|
||||
"Khatri",
|
||||
"Konstantin",
|
||||
"l’objectif",
|
||||
"layed",
|
||||
"lezer",
|
||||
"libdef",
|
||||
"lintstaged",
|
||||
"lintstagedrc",
|
||||
|
||||
@@ -26,124 +26,6 @@
|
||||
<link rel="icon" href="/icon.png" />
|
||||
<link rel="apple-touch-icon" href="/icon.png" />
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/codemirror.min.css"
|
||||
integrity="sha512-uf06llspW44/LZpHzHT6qBOIVODjWtv4MxCricRxkzvopAlSWnTf6hpZTFxuuZcuNE9CBQhqE0Seu1CoRk84nQ=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/foldgutter.min.css"
|
||||
integrity="sha512-YwkMTlTHn8dBnwa47IF+cKsS00HPiiVhQ4DpwT1KF2gUftfFR7aefepabSPLAs6zrMyD89M3w0Ow6mQ5XJEUCw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/codemirror.min.js"
|
||||
integrity="sha512-6cPYokihlrofMNApz7OXVQNObWjLiKGIBBb7+UB+AuMiRCLKmFKgrwms21sHq3bdFFZWpfHYRJBJvMFMPj1S9g=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/javascript/javascript.min.js"
|
||||
integrity="sha512-Cbz+kvn+l5pi5HfXsEB/FYgZVKjGIhOgYNBwj4W2IHP2y8r3AdyDCQRnEUqIQ+6aJjygKPTyaNT2eIihaykJlw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/xml/xml.min.js"
|
||||
integrity="sha512-LarNmzVokUmcA7aUDtqZ6oTS+YXmUKzpGdm8DxC46A6AHu+PQiYCUlwEGWidjVYMo/QXZMFMIadZtrkfApYp/g=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/jsx/jsx.min.js"
|
||||
integrity="sha512-jlNQWdDsv1W4EG4blPl9HPp+5AXT/uozlC1HWLydcdG9gT8qM2PtWbk5sCZb8eZTX+lT/oHlXKn1SU6LXogFow=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/css/css.js"
|
||||
integrity="sha512-kbf0gMc1+KPTTDWkjkGwf3h2Nx4djnWBVtcCKJcwLQpQ/TIMAjRBKM9f0ZDgj9kT5WTs1Qzsq+5Si2p7j9whkg=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/mode/markdown/markdown.min.js"
|
||||
integrity="sha512-DmMao0nRIbyDjbaHc8fNd3kxGsZj9PCU6Iu/CeidLQT9Py8nYVA5n0PqXYmvqNdU+lCiTHOM/4E7bM/G8BttJg=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/display/placeholder.min.js"
|
||||
integrity="sha512-acBo6sW2h2GZQ9BqU9v5RyYGPUEr1a9jrukJg825Y0ahxAg/7aqTNPtcalloqnf4DfsRVdcdNmcBNWPD8b8W8Q=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/display/rulers.min.js"
|
||||
integrity="sha512-sF+AF3zpVOHMt4f4mN1dVS2/Hon6pr/7h1gc7rDHtga0SeD9z0UiL1J0k3umDFEz5qkY06ARcNzi45NkJ9GsYw=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/search/searchcursor.min.js"
|
||||
integrity="sha512-+ZfZDC9gi1y9Xoxi9UUsSp+5k+AcFE0TRNjI0pfaAHQ7VZTaaoEpBZp9q9OvHdSomOze/7s5w27rcsYpT6xU6g=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/edit/matchbrackets.min.js"
|
||||
integrity="sha512-GSYCbN/le5gNmfAWVEjg1tKnOH7ilK6xCLgA7c48IReoIR2g2vldxTM6kZlN6o3VtWIe6fHu/qhwxIt11J8EBA=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/edit/closebrackets.min.js"
|
||||
integrity="sha512-tsjcYO5hFvViRssxiM7Jhd8601epWOx1He3Hl4yuI5dKKPxr43KxkOhc9GZeeqzlYJm9ABb7UPA9697NiqZZ7Q=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/comment/comment.min.js"
|
||||
integrity="sha512-UaJ8Lcadz5cc5mkWmdU8cJr0wMn7d8AZX5A24IqVGUd1MZzPJTq9dLLW6I102iljTcdB39YvVcCgBhM0raGAZQ=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/wrap/hardwrap.min.js"
|
||||
integrity="sha512-ZPPL1o5pyZOIF/+PL/pRRBdxrYENhEbcNas/6PJcXMM1aBu5wI2yeFzJY6Jv+fJjH8o48HBYomInIYsYvFlXig=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/foldcode.min.js"
|
||||
integrity="sha512-Q2qfEJEU257Qlqc4/5g6iKuJNnn5L0xu2D48p8WHe9YC/kLj2UfkdGD01qfxWk+XIcHsZngcA8WuKcizF8MAHA=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/foldgutter.js"
|
||||
integrity="sha512-tGjjnhNOLvNMtP2S/fpsalcaJofEksE83moo23We+afRBd1kx3JtSSDn9CDmEyKhwk4N7dJqLq3iUWyVOs9ejQ=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/addon/fold/brace-fold.min.js"
|
||||
integrity="sha512-5MuaB1PVXvhsYVG0Ozb0bwauN7/D1VU4P8dwo5E/xiB9SXY+VSEhIyxt1ggYk2xaB/RKqKL7rPXpm1o1IlTQDA=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.18/keymap/sublime.min.js"
|
||||
integrity="sha512-SV3qeFFtzcmGtUQPLM7HLy/7GKJ/x3c2PdiF5GZQnbHzIlI2q7r77y0IgLLbBDeHiNfCSBYDQt898Xp0tcZOeA=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"
|
||||
integrity="sha512-7O5pXpc0oCRrxk8RUfDYFgn0nO1t+jLuIOQdOMRp4APB7uZ4vSjspzp5y6YDtDs4VzUSTbWzBFZ/LKJhnyFOKw=="
|
||||
|
||||
+17
-1
@@ -15,12 +15,24 @@
|
||||
"update-stable-docs": "node ./scripts/update-stable-docs.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@catppuccin/codemirror": "1.0.1",
|
||||
"@codemirror/autocomplete": "6.20.0",
|
||||
"@codemirror/commands": "6.10.1",
|
||||
"@codemirror/lang-css": "6.3.1",
|
||||
"@codemirror/lang-javascript": "6.2.4",
|
||||
"@codemirror/lang-markdown": "6.5.0",
|
||||
"@codemirror/language": "6.12.1",
|
||||
"@codemirror/lint": "6.9.2",
|
||||
"@codemirror/state": "6.5.3",
|
||||
"@codemirror/view": "6.39.4",
|
||||
"@docusaurus/core": "3.9.2",
|
||||
"@docusaurus/preset-classic": "3.9.2",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@mdx-js/react": "3.1.1",
|
||||
"@sandhose/prettier-animated-logo": "1.0.3",
|
||||
"@types/react": "19.2.7",
|
||||
"clsx": "2.1.1",
|
||||
"codemirror-graphql": "2.2.4",
|
||||
"cm6-graphql": "0.2.1",
|
||||
"graphql": "16.12.0",
|
||||
"prism-react-renderer": "2.4.1",
|
||||
"react": "19.2.1",
|
||||
@@ -36,9 +48,13 @@
|
||||
"@vitejs/plugin-vue": "6.0.2",
|
||||
"@vitejs/plugin-vue-jsx": "5.1.2",
|
||||
"concurrently": "9.2.1",
|
||||
"rollup-plugin-visualizer": "6.0.5",
|
||||
"vite": "7.2.7",
|
||||
"yaml": "2.8.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@codemirror/view": "6.39.4"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
|
||||
@@ -13,8 +13,6 @@ import SidebarOptions from "./sidebar/SidebarOptions.jsx";
|
||||
import * as urlHash from "./urlHash.js";
|
||||
import {
|
||||
buildCliArgs,
|
||||
convertOffsetToSelection,
|
||||
convertSelectionToRange,
|
||||
getAstAutoFold,
|
||||
getCodemirrorMode,
|
||||
getDefaults,
|
||||
@@ -114,16 +112,21 @@ function setup(props) {
|
||||
const setSelection = (selection) => {
|
||||
Object.assign(state, { selection });
|
||||
if (state.trackCursorOffset) {
|
||||
handleOptionValueChange(
|
||||
cursorOffsetOption,
|
||||
convertSelectionToRange(selection, state.content)[0],
|
||||
);
|
||||
handleOptionValueChange(cursorOffsetOption, selection?.anchor ?? -1);
|
||||
}
|
||||
};
|
||||
|
||||
const setSelectionAsRange = () => {
|
||||
const { selection, content, options } = state;
|
||||
const [rangeStart, rangeEnd] = convertSelectionToRange(selection, content);
|
||||
const { selection, options } = state;
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
const anchor = selection.anchor ?? 0;
|
||||
const head = selection.head ?? anchor;
|
||||
|
||||
const rangeStart = Math.min(anchor, head);
|
||||
const rangeEnd = Math.max(anchor, head);
|
||||
|
||||
const updatedOptions = { ...options, rangeStart, rangeEnd };
|
||||
if (rangeStart === rangeEnd) {
|
||||
delete updatedOptions.rangeStart;
|
||||
@@ -194,7 +197,7 @@ function setup(props) {
|
||||
return props.worker
|
||||
.format(content, {
|
||||
parser: "__js_expression",
|
||||
cursorOffset: convertSelectionToRange(selection, content)[0],
|
||||
cursorOffset: selection?.anchor ?? -1,
|
||||
})
|
||||
.then(({ error, formatted, cursorOffset }) => {
|
||||
if (error) {
|
||||
@@ -203,24 +206,34 @@ function setup(props) {
|
||||
|
||||
Object.assign(state, {
|
||||
content: formatted,
|
||||
selection: convertOffsetToSelection(cursorOffset, formatted),
|
||||
selection:
|
||||
cursorOffset >= 0
|
||||
? { anchor: cursorOffset, head: cursorOffset }
|
||||
: {},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const insertDummyId = () => {
|
||||
const { content, selection } = state;
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
const anchor = selection.anchor ?? 0;
|
||||
const head = selection.head ?? anchor;
|
||||
const start = Math.min(anchor, head);
|
||||
const end = Math.max(anchor, head);
|
||||
|
||||
const dummyId = generateDummyId();
|
||||
const range = convertSelectionToRange(selection, content);
|
||||
const modifiedContent =
|
||||
content.slice(0, range[0]) + dummyId + content.slice(range[1]);
|
||||
content.slice(0, start) + dummyId + content.slice(end);
|
||||
|
||||
Object.assign(state, {
|
||||
content: modifiedContent,
|
||||
selection: convertOffsetToSelection(
|
||||
range[0] + dummyId.length,
|
||||
modifiedContent,
|
||||
),
|
||||
selection: {
|
||||
anchor: start + dummyId.length,
|
||||
head: start + dummyId.length,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -417,8 +430,8 @@ function setup(props) {
|
||||
onChange={setContent}
|
||||
onSelectionChange={setSelection}
|
||||
extraKeys={{
|
||||
"Shift-Alt-F": formatInput,
|
||||
"Ctrl-Q": insertDummyId,
|
||||
"Shift-Alt-f": formatInput,
|
||||
"Ctrl-q": insertDummyId,
|
||||
}}
|
||||
foldGutter={options.parser === "doc-explorer"}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ref } from "vue";
|
||||
|
||||
const getInitialTheme = () => {
|
||||
const saved = localStorage.getItem("theme");
|
||||
if (saved) {
|
||||
return saved;
|
||||
}
|
||||
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
};
|
||||
|
||||
const applyTheme = (value = getInitialTheme()) => {
|
||||
document.documentElement.dataset.theme = value;
|
||||
theme.value = value;
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
const value = theme.value === "dark" ? "light" : "dark";
|
||||
localStorage.setItem("theme", value);
|
||||
applyTheme(value);
|
||||
};
|
||||
|
||||
const theme = ref(undefined);
|
||||
applyTheme();
|
||||
|
||||
function useTheme() {
|
||||
return { theme, toggleTheme };
|
||||
}
|
||||
|
||||
export { useTheme };
|
||||
@@ -1,42 +1,14 @@
|
||||
import "codemirror-graphql/cm6-legacy/mode.esm.js";
|
||||
import "./install-service-worker.js";
|
||||
|
||||
import { createApp, onMounted, reactive, ref, watch } from "vue";
|
||||
import { createApp, onMounted, reactive } from "vue";
|
||||
import { useTheme } from "./composables/use-theme.js";
|
||||
import Playground from "./Playground.jsx";
|
||||
import { fixPrettierVersion } from "./utilities.js";
|
||||
import VersionLink from "./VersionLink.jsx";
|
||||
import WorkerApi from "./WorkerApi.js";
|
||||
|
||||
function getInitialTheme() {
|
||||
const saved = localStorage.getItem("theme");
|
||||
if (saved) {
|
||||
return saved;
|
||||
}
|
||||
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
}
|
||||
|
||||
function ThemeToggle() {
|
||||
const theme = ref(getInitialTheme());
|
||||
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme.value === "dark" ? "light" : "dark";
|
||||
theme.value = newTheme;
|
||||
localStorage.setItem("theme", newTheme);
|
||||
applyTheme(newTheme);
|
||||
};
|
||||
|
||||
applyTheme(theme.value);
|
||||
|
||||
watch(theme, (newTheme) => {
|
||||
applyTheme(newTheme);
|
||||
});
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
+343
-153
@@ -1,17 +1,125 @@
|
||||
import { catppuccinLatte, catppuccinMocha } from "@catppuccin/codemirror";
|
||||
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
|
||||
import {
|
||||
defaultKeymap,
|
||||
history,
|
||||
historyKeymap,
|
||||
indentWithTab,
|
||||
} from "@codemirror/commands";
|
||||
import {
|
||||
bracketMatching,
|
||||
foldable,
|
||||
foldEffect,
|
||||
foldGutter as foldGutterExt,
|
||||
foldKeymap,
|
||||
} from "@codemirror/language";
|
||||
import {
|
||||
Compartment,
|
||||
EditorState,
|
||||
StateEffect,
|
||||
StateField,
|
||||
} from "@codemirror/state";
|
||||
import {
|
||||
Decoration,
|
||||
EditorView,
|
||||
keymap,
|
||||
lineNumbers as lineNumbersExt,
|
||||
} from "@codemirror/view";
|
||||
import { onMounted, onUnmounted, useTemplateRef, watch } from "vue";
|
||||
|
||||
const { CodeMirror } = window;
|
||||
import { useTheme } from "./composables/use-theme.js";
|
||||
|
||||
function setup(props, { emit }) {
|
||||
const textareaRef = useTemplateRef("textarea");
|
||||
const editorRef = useTemplateRef("editorRef");
|
||||
|
||||
let _codeMirror = null;
|
||||
let _cached = "";
|
||||
let _overlay = null;
|
||||
const _overlay = StateEffect.define();
|
||||
|
||||
const overlayField = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(decorations, tr) {
|
||||
for (const effect of tr.effects) {
|
||||
if (effect.is(_overlay)) {
|
||||
let { start, end } = effect.value;
|
||||
if (start !== undefined && end !== undefined) {
|
||||
if (end < start) {
|
||||
[start, end] = [end, start];
|
||||
}
|
||||
const { length } = _codeMirror.state.doc;
|
||||
start = Math.min(Math.max(start, 0), length);
|
||||
end = Math.min(Math.max(end, 0), length);
|
||||
if (start !== end) {
|
||||
return Decoration.set([overlayMark.range(start, end)]);
|
||||
}
|
||||
}
|
||||
return Decoration.none;
|
||||
}
|
||||
}
|
||||
return decorations.map(tr.changes);
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
const overlayMark = Decoration.mark({
|
||||
class: "cm-overlay-highlight",
|
||||
});
|
||||
|
||||
const languageExt = new Compartment();
|
||||
const rulerTheme = new Compartment();
|
||||
const foldGutterCompartment = new Compartment();
|
||||
|
||||
const { theme } = useTheme();
|
||||
const editorTheme = new Compartment();
|
||||
|
||||
const onChange = (value) => {
|
||||
emit("change", value);
|
||||
};
|
||||
|
||||
const componentDidMount = () => {
|
||||
const createRulerTheme = (printWidth, color) => {
|
||||
if (!printWidth) {
|
||||
return EditorView.theme({});
|
||||
}
|
||||
|
||||
return EditorView.theme({
|
||||
".cm-content::before": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
bottom: "0",
|
||||
left: `${printWidth}ch`,
|
||||
width: "1px",
|
||||
backgroundColor: color || "var(--color-gray-300)",
|
||||
pointerEvents: "none",
|
||||
zIndex: "1",
|
||||
marginLeft: "6px",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
async function getLanguageExtension(mode) {
|
||||
switch (mode) {
|
||||
case "css": {
|
||||
const { css } = await import("@codemirror/lang-css");
|
||||
return css();
|
||||
}
|
||||
case "graphql": {
|
||||
const { graphql } = await import("cm6-graphql");
|
||||
return graphql();
|
||||
}
|
||||
case "markdown": {
|
||||
const { markdown } = await import("@codemirror/lang-markdown");
|
||||
return markdown();
|
||||
}
|
||||
default: {
|
||||
const { javascript } = await import("@codemirror/lang-javascript");
|
||||
return javascript({ jsx: true, typescript: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const componentDidMount = async () => {
|
||||
const options = { ...props };
|
||||
for (const property of [
|
||||
"ruler",
|
||||
@@ -29,123 +137,274 @@ function setup(props, { emit }) {
|
||||
}
|
||||
}
|
||||
|
||||
options.rulers = [makeRuler(props)];
|
||||
options.gutters = makeGutters(props);
|
||||
const state = EditorState.create({
|
||||
doc: props.value || "",
|
||||
extensions: [
|
||||
props.lineNumbers ? lineNumbersExt() : undefined,
|
||||
foldGutterCompartment.of(
|
||||
props.foldGutter
|
||||
? foldGutterExt({
|
||||
openText: "▾",
|
||||
closedText: "▸",
|
||||
})
|
||||
: [],
|
||||
),
|
||||
props.autoCloseBrackets ? closeBrackets() : undefined,
|
||||
props.matchBrackets ? bracketMatching() : undefined,
|
||||
|
||||
options.rulers = [makeRuler(props)];
|
||||
options.gutters = makeGutters(props);
|
||||
languageExt.of(await getLanguageExtension(props.mode)),
|
||||
editorTheme.of(
|
||||
theme.value === "dark" ? catppuccinMocha : catppuccinLatte,
|
||||
),
|
||||
overlayField,
|
||||
history(),
|
||||
keymap.of([
|
||||
...defaultKeymap,
|
||||
...closeBracketsKeymap,
|
||||
...historyKeymap,
|
||||
...foldKeymap,
|
||||
indentWithTab,
|
||||
{ key: "Ctrl-l", run: () => false },
|
||||
...(props.extraKeys
|
||||
? Object.entries(props.extraKeys).map(([key, cmd]) => ({
|
||||
key,
|
||||
run: cmd,
|
||||
}))
|
||||
: []),
|
||||
]),
|
||||
|
||||
_codeMirror = CodeMirror.fromTextArea(textareaRef.value, options);
|
||||
_codeMirror.on("change", handleChange);
|
||||
_codeMirror.on("focus", handleFocus);
|
||||
_codeMirror.on("beforeSelectionChange", handleSelectionChange);
|
||||
EditorState.tabSize.of(props.tabSize ?? 4),
|
||||
EditorState.readOnly.of(props.readOnly ?? false),
|
||||
|
||||
window.CodeMirror.keyMap.pcSublime["Ctrl-L"] = false;
|
||||
window.CodeMirror.keyMap.sublime["Ctrl-L"] = false;
|
||||
rulerTheme.of(createRulerTheme(props.ruler, props.rulerColor)),
|
||||
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
const value = update.state.doc.toString();
|
||||
if (value !== _cached) {
|
||||
handleChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (update.selectionSet) {
|
||||
const selection = update.state.selection.main;
|
||||
handleSelectionChange(selection);
|
||||
}
|
||||
}),
|
||||
].filter(Boolean),
|
||||
});
|
||||
|
||||
_codeMirror = new EditorView({
|
||||
state,
|
||||
parent: editorRef.value,
|
||||
});
|
||||
|
||||
updateValue(props.value || "");
|
||||
updateSelection();
|
||||
updateOverlay();
|
||||
};
|
||||
|
||||
const componentWillUnmount = () => {
|
||||
_codeMirror?.toTextArea();
|
||||
};
|
||||
|
||||
const componentDidUpdate = (_, prevProps) => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.value !== _cached) {
|
||||
updateValue(props.value);
|
||||
}
|
||||
if (
|
||||
!isEqualSelection(props.selection, prevProps.selection) &&
|
||||
!isEqualSelection(props.selection, _codeMirror.listSelections()[0])
|
||||
) {
|
||||
updateSelection();
|
||||
}
|
||||
if (
|
||||
props.overlayStart !== prevProps.overlayStart ||
|
||||
props.overlayEnd !== prevProps.overlayEnd
|
||||
) {
|
||||
updateOverlay();
|
||||
}
|
||||
if (props.mode !== prevProps.mode) {
|
||||
_codeMirror.setOption("mode", props.mode);
|
||||
}
|
||||
if (props.ruler !== prevProps.ruler) {
|
||||
_codeMirror.setOption("rulers", [makeRuler(props)]);
|
||||
}
|
||||
if (props.foldGutter !== prevProps.foldGutter) {
|
||||
_codeMirror.setOption("gutters", makeGutters(props));
|
||||
}
|
||||
_codeMirror?.destroy();
|
||||
};
|
||||
|
||||
const updateValue = (value) => {
|
||||
if (_cached === value) {
|
||||
return;
|
||||
}
|
||||
_cached = value;
|
||||
_codeMirror.setValue(value);
|
||||
_codeMirror.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: _codeMirror.state.doc.length,
|
||||
insert: value,
|
||||
},
|
||||
});
|
||||
|
||||
if (props.autoFold instanceof RegExp) {
|
||||
const lines = value.split("\n");
|
||||
// going backwards to prevent unfolding folds created earlier
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
if (props.autoFold.test(lines[i])) {
|
||||
_codeMirror.foldCode(i);
|
||||
if (!(props.autoFold instanceof RegExp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = value.split("\n");
|
||||
const effects = [];
|
||||
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
if (props.autoFold.test(lines[i])) {
|
||||
const lineNumber = i + 1;
|
||||
const range = foldable(
|
||||
_codeMirror.state,
|
||||
_codeMirror.state.doc.line(lineNumber).from,
|
||||
_codeMirror.state.doc.line(lineNumber).to,
|
||||
);
|
||||
|
||||
if (range) {
|
||||
effects.push(foldEffect.of(range));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (effects.length > 0) {
|
||||
_codeMirror.dispatch({ effects });
|
||||
}
|
||||
};
|
||||
|
||||
const updateSelection = () => {
|
||||
_codeMirror.setSelection(
|
||||
props.selection?.anchor ?? { line: 0, ch: 0 },
|
||||
props.selection?.head,
|
||||
);
|
||||
if (!props.selection || !_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { doc } = _codeMirror.state;
|
||||
const anchor = Math.max(props.selection.anchor ?? 0, 0);
|
||||
const head = Math.min(props.selection.head ?? anchor, doc.length);
|
||||
|
||||
const currentSelection = _codeMirror.state.selection.main;
|
||||
if (currentSelection.anchor !== anchor || currentSelection.head !== head) {
|
||||
_codeMirror.dispatch({
|
||||
selection: { anchor, head },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateOverlay = () => {
|
||||
if (_overlay) {
|
||||
_codeMirror.removeOverlay(_overlay);
|
||||
}
|
||||
if (props.overlayStart !== undefined) {
|
||||
const [start, end] = getIndexPosition(props.value, [
|
||||
props.overlayStart,
|
||||
props.overlayEnd,
|
||||
]);
|
||||
_overlay = createOverlay(start, end);
|
||||
_codeMirror.addOverlay(_overlay);
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
_codeMirror.dispatch({
|
||||
effects: _overlay.of({
|
||||
start: props.overlayStart,
|
||||
end: props.overlayEnd,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const handleFocus = (/* codeMirror, event */) => {
|
||||
if (_codeMirror.getValue() === props.codeSample) {
|
||||
_codeMirror.execCommand("selectAll");
|
||||
}
|
||||
const handleChange = (value) => {
|
||||
_cached = value;
|
||||
onChange(value);
|
||||
updateOverlay();
|
||||
};
|
||||
|
||||
const handleChange = (doc, change) => {
|
||||
if (change.origin !== "setValue") {
|
||||
_cached = doc.getValue();
|
||||
onChange(_cached);
|
||||
updateOverlay();
|
||||
const handleSelectionChange = (selection) => {
|
||||
if (!props.onSelectionChange || !selection) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectionChange = (doc, change) => {
|
||||
props.onSelectionChange?.(change.ranges[0]);
|
||||
props.onSelectionChange({
|
||||
anchor: selection.anchor,
|
||||
head: selection.head,
|
||||
});
|
||||
};
|
||||
|
||||
const render = () => (
|
||||
<div class="editor input">
|
||||
<textarea ref="textarea" />
|
||||
<div ref="editorRef" style={{ position: "absolute", inset: 0 }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
onMounted(componentDidMount);
|
||||
onUnmounted(componentWillUnmount);
|
||||
watch(() => props, componentDidUpdate, { deep: true });
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value !== _cached) {
|
||||
updateValue(value);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.mode,
|
||||
async (mode) => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
_codeMirror.dispatch({
|
||||
effects: languageExt.reconfigure(await getLanguageExtension(mode)),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.ruler,
|
||||
(ruler) => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
_codeMirror.dispatch({
|
||||
effects: rulerTheme.reconfigure(
|
||||
createRulerTheme(ruler, props.rulerColor),
|
||||
),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.rulerColor,
|
||||
(rulerColor) => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
_codeMirror.dispatch({
|
||||
effects: rulerTheme.reconfigure(
|
||||
createRulerTheme(props.ruler, rulerColor),
|
||||
),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSelection();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.overlayStart, props.overlayEnd],
|
||||
() => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOverlay();
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.foldGutter,
|
||||
(foldGutter) => {
|
||||
if (!_codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
_codeMirror.dispatch({
|
||||
effects: foldGutterCompartment.reconfigure(
|
||||
foldGutter ? foldGutterExt() : [],
|
||||
),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
watch(theme, (newTheme) => {
|
||||
_codeMirror.dispatch({
|
||||
effects: editorTheme.reconfigure(
|
||||
newTheme === "dark" ? catppuccinMocha : catppuccinLatte,
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
return render;
|
||||
}
|
||||
|
||||
@@ -170,81 +429,12 @@ const CodeMirrorPanel = {
|
||||
showCursorWhenSelecting: { type: Boolean, default: undefined },
|
||||
tabSize: { type: Number, default: undefined },
|
||||
readOnly: { type: Boolean, default: undefined },
|
||||
extraKeys: { type: Object, default: undefined },
|
||||
},
|
||||
emits: ["change"],
|
||||
setup,
|
||||
};
|
||||
|
||||
function getIndexPosition(text, indexes) {
|
||||
indexes = [...indexes];
|
||||
let line = 0;
|
||||
let count = 0;
|
||||
let lineStart = 0;
|
||||
const result = [];
|
||||
|
||||
while (indexes.length > 0) {
|
||||
const index = indexes.shift();
|
||||
|
||||
while (count < index && count < text.length) {
|
||||
if (text[count] === "\n") {
|
||||
line++;
|
||||
lineStart = count + 1;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
result.push({ line, pos: count - lineStart });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function createOverlay(start, end) {
|
||||
return {
|
||||
token(stream) {
|
||||
const { line } = stream.lineOracle;
|
||||
|
||||
if (line < start.line || line > end.line) {
|
||||
stream.skipToEnd();
|
||||
} else if (line === start.line && stream.pos < start.pos) {
|
||||
stream.pos = start.pos;
|
||||
} else if (line === end.line) {
|
||||
if (stream.pos < end.pos) {
|
||||
stream.pos = end.pos;
|
||||
return "searching";
|
||||
}
|
||||
stream.skipToEnd();
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
return "searching";
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeRuler(props) {
|
||||
return { column: props.ruler, color: props.rulerColor };
|
||||
}
|
||||
|
||||
function makeGutters(props) {
|
||||
return props.foldGutter
|
||||
? ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
|
||||
: [];
|
||||
}
|
||||
|
||||
function isEqualSelection(selection1, selection2) {
|
||||
const anchor1 = selection1?.anchor ?? { line: 0, ch: 0 };
|
||||
const head1 = selection1?.head ?? anchor1;
|
||||
const anchor2 = selection2?.anchor ?? { line: 0, ch: 0 };
|
||||
const head2 = selection2?.head ?? anchor2;
|
||||
return (
|
||||
head1.line === head2.line &&
|
||||
head1.ch === head2.ch &&
|
||||
anchor1.line === anchor2.line &&
|
||||
anchor1.ch === anchor2.ch
|
||||
);
|
||||
}
|
||||
|
||||
export function InputPanel(props) {
|
||||
return (
|
||||
<CodeMirrorPanel
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
html {
|
||||
background-color: light-dark(#fafafa, #1b1b1d);
|
||||
--background-color: light-dark(#fafafa, #1b1b1d);
|
||||
color: light-dark(#6a6a6a, #e3e3e3);
|
||||
--color-border: light-dark(#ddd, #606770);
|
||||
color-scheme: light dark;
|
||||
@@ -169,94 +170,6 @@ header h1 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
line-height: 1.6;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
|
||||
&:is([data-theme="dark"] *) {
|
||||
/* Credit: Chris Kempson, Gerard Braad */
|
||||
background: #000000;
|
||||
color: #eaeaea;
|
||||
div.CodeMirror-selected {
|
||||
background: #424242;
|
||||
}
|
||||
.CodeMirror-gutters {
|
||||
background: #000000;
|
||||
border-right: 0px;
|
||||
}
|
||||
.CodeMirror-guttermarker {
|
||||
color: #e78c45;
|
||||
}
|
||||
.CodeMirror-guttermarker-subtle {
|
||||
color: #777;
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
color: #424242;
|
||||
}
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid #6a6a6a;
|
||||
}
|
||||
|
||||
span.cm-comment {
|
||||
color: #d27b53;
|
||||
}
|
||||
span.cm-atom {
|
||||
color: #a16a94;
|
||||
}
|
||||
span.cm-number {
|
||||
color: #a16a94;
|
||||
}
|
||||
|
||||
span.cm-property,
|
||||
span.cm-attribute {
|
||||
color: #99cc99;
|
||||
}
|
||||
span.cm-keyword {
|
||||
color: #d54e53;
|
||||
}
|
||||
span.cm-string {
|
||||
color: #e7c547;
|
||||
}
|
||||
|
||||
span.cm-variable {
|
||||
color: #b9ca4a;
|
||||
}
|
||||
span.cm-variable-2 {
|
||||
color: #7aa6da;
|
||||
}
|
||||
span.cm-def {
|
||||
color: #e78c45;
|
||||
}
|
||||
span.cm-bracket {
|
||||
color: #eaeaea;
|
||||
}
|
||||
span.cm-tag {
|
||||
color: #d54e53;
|
||||
}
|
||||
span.cm-link {
|
||||
color: #a16a94;
|
||||
}
|
||||
span.cm-error {
|
||||
background: #d54e53;
|
||||
color: #6a6a6a;
|
||||
}
|
||||
|
||||
.CodeMirror-activeline-background {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
.CodeMirror-matchingbracket {
|
||||
text-decoration: underline;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: relative;
|
||||
}
|
||||
@@ -418,3 +331,54 @@ input[type="number"] {
|
||||
border-bottom: none;
|
||||
border-top-color: #000;
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
background-color: var(--background-color) !important;
|
||||
}
|
||||
|
||||
.cm-editor .cm-scroller {
|
||||
font-family:
|
||||
Consolas,
|
||||
Monaco,
|
||||
Andale Mono,
|
||||
Ubuntu Mono,
|
||||
monospace;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cm-editor.cm-focused {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cm-editor .cm-gutters {
|
||||
border-right: 1px solid var(--color-border);
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.cm-gutter {
|
||||
padding-inline-start: 5px;
|
||||
padding-inline-end: 3px;
|
||||
}
|
||||
|
||||
.cm-overlay-highlight {
|
||||
background-color: rgba(255, 220, 120, 0.45);
|
||||
}
|
||||
|
||||
.cm-foldGutter .cm-gutterElement {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .cm-overlay-highlight {
|
||||
background-color: rgba(255, 220, 120, 0.32);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .cm-gutters {
|
||||
background-color: #121212;
|
||||
border-right-color: transparent;
|
||||
color: #424242;
|
||||
}
|
||||
@@ -96,27 +96,3 @@ export function getAstAutoFold(parser) {
|
||||
return astAutoFold[parser];
|
||||
}
|
||||
}
|
||||
|
||||
export function convertSelectionToRange({ head, anchor }, content) {
|
||||
const lines = content.split("\n");
|
||||
return [head, anchor]
|
||||
.map(
|
||||
({ ch, line }) =>
|
||||
lines.slice(0, line).join("\n").length + ch + (line ? 1 : 0),
|
||||
)
|
||||
.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export function convertOffsetToSelection(offset, content) {
|
||||
let line = 0;
|
||||
let ch = 0;
|
||||
for (let i = 0; i < offset && i <= content.length; i++) {
|
||||
if (content[i] === "\n") {
|
||||
line++;
|
||||
ch = 0;
|
||||
} else {
|
||||
ch++;
|
||||
}
|
||||
}
|
||||
return { anchor: { line, ch } };
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
import vitePluginVue from "@vitejs/plugin-vue";
|
||||
import vitePluginVueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import { visualizer as rollupPluginVisualizer } from "rollup-plugin-visualizer";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
const IS_PRODUCTION = process.env.NODE_ENV === "production";
|
||||
const IS_CI = Boolean(process.env.CI);
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: IS_PRODUCTION ? "/playground/" : undefined,
|
||||
publicDir: IS_PRODUCTION ? undefined : "./static/",
|
||||
plugins: [vitePluginVue(), vitePluginVueJsx()],
|
||||
plugins: [
|
||||
vitePluginVue(),
|
||||
vitePluginVueJsx(),
|
||||
IS_CI || !IS_PRODUCTION
|
||||
? undefined
|
||||
: rollupPluginVisualizer({ filename: "./static/playground/report.html" }),
|
||||
].filter(Boolean),
|
||||
build: {
|
||||
outDir: "./static/playground/",
|
||||
minify: IS_CI,
|
||||
|
||||
+1101
-787
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user