Real work on getting CodeMirror working. I at least have a fancy highlighter and basic editor.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte specific
|
||||
// *** Import Svelte version 5 specific
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// BEGIN: CodeMirror
|
||||
@@ -19,7 +19,7 @@ import {languages} from "@codemirror/language-data"
|
||||
import { css } from "@codemirror/lang-css";
|
||||
import { html } from "@codemirror/lang-html";
|
||||
import {javascript} from "@codemirror/lang-javascript";
|
||||
import {markdown} from "@codemirror/lang-markdown"
|
||||
import {markdown} from "@codemirror/lang-markdown";
|
||||
import { oneDark } from "@codemirror/theme-one-dark";
|
||||
|
||||
// END: CodeMirror
|
||||
|
||||
226
src/lib/e_app_codemirror_sv4.svelte
Normal file
226
src/lib/e_app_codemirror_sv4.svelte
Normal file
@@ -0,0 +1,226 @@
|
||||
<script lang="ts" context="module">
|
||||
export type ThemeSpec = Record<string, StyleSpec>;
|
||||
export type StyleSpec = {
|
||||
[propOrSelector: string]: string | number | StyleSpec | null;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy, onMount } from "svelte";
|
||||
import { basicSetup } from "codemirror";
|
||||
import { EditorView, keymap, placeholder as placeholderExt } from "@codemirror/view";
|
||||
import { EditorState, StateEffect, type Extension } from "@codemirror/state";
|
||||
import { indentWithTab } from "@codemirror/commands";
|
||||
import { indentUnit, type LanguageSupport } from "@codemirror/language";
|
||||
import { debounce } from "./util";
|
||||
|
||||
let classes = "";
|
||||
export { classes as class };
|
||||
export let value: string | null | undefined = "";
|
||||
|
||||
export let basic = true;
|
||||
export let lang: LanguageSupport | null | undefined = undefined;
|
||||
export let theme: Extension | null | undefined = undefined;
|
||||
export let extensions: Extension[] = [];
|
||||
|
||||
export let useTab = true;
|
||||
export let tabSize = 2;
|
||||
|
||||
export let styles: ThemeSpec | null | undefined = undefined;
|
||||
export let lineWrapping = false;
|
||||
export let editable = true;
|
||||
export let readonly = false;
|
||||
export let placeholder: string | HTMLElement | null | undefined = undefined;
|
||||
|
||||
export let nodebounce = false;
|
||||
|
||||
const is_browser = typeof window !== "undefined";
|
||||
const dispatch = createEventDispatcher<{ change: string, ready: EditorView, reconfigure: EditorView }>();
|
||||
|
||||
let element: HTMLDivElement;
|
||||
let view: EditorView;
|
||||
|
||||
let update_from_prop = false;
|
||||
let update_from_state = false;
|
||||
let first_config = true;
|
||||
let first_update = true;
|
||||
|
||||
$: state_extensions = [
|
||||
...get_base_extensions(basic, useTab, tabSize, lineWrapping, placeholder, editable, readonly, lang),
|
||||
...get_theme(theme, styles),
|
||||
...extensions,
|
||||
];
|
||||
|
||||
$: view && update(value);
|
||||
$: view && state_extensions && reconfigure();
|
||||
|
||||
$: on_change = nodebounce ? handle_change : debounce(handle_change, 300);
|
||||
|
||||
onMount(() => {
|
||||
view = create_editor_view();
|
||||
dispatch('ready', view);
|
||||
});
|
||||
onDestroy(() => view?.destroy());
|
||||
|
||||
function create_editor_view(): EditorView {
|
||||
return new EditorView({
|
||||
parent: element,
|
||||
state: create_editor_state(value),
|
||||
dispatch(transaction) {
|
||||
view.update([transaction]);
|
||||
|
||||
if (!update_from_prop && transaction.docChanged) {
|
||||
on_change();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function reconfigure(): void {
|
||||
if (first_config) {
|
||||
first_config = false;
|
||||
return;
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
effects: StateEffect.reconfigure.of(state_extensions),
|
||||
});
|
||||
|
||||
dispatch('reconfigure', view);
|
||||
}
|
||||
|
||||
function update(value: string | null | undefined): void {
|
||||
if (first_update) {
|
||||
first_update = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (update_from_state) {
|
||||
update_from_state = false;
|
||||
return;
|
||||
}
|
||||
|
||||
update_from_prop = true;
|
||||
|
||||
view.setState(create_editor_state(value));
|
||||
|
||||
update_from_prop = false;
|
||||
}
|
||||
|
||||
function handle_change(): void {
|
||||
const new_value = view.state.doc.toString();
|
||||
if (new_value === value) return;
|
||||
|
||||
update_from_state = true;
|
||||
|
||||
value = new_value;
|
||||
dispatch("change", value);
|
||||
}
|
||||
|
||||
function create_editor_state(value: string | null | undefined): EditorState {
|
||||
return EditorState.create({
|
||||
doc: value ?? undefined,
|
||||
extensions: state_extensions,
|
||||
});
|
||||
}
|
||||
|
||||
function get_base_extensions(
|
||||
basic: boolean,
|
||||
useTab: boolean,
|
||||
tabSize: number,
|
||||
lineWrapping: boolean,
|
||||
placeholder: string | HTMLElement | null | undefined,
|
||||
editable: boolean,
|
||||
readonly: boolean,
|
||||
lang: LanguageSupport | null | undefined
|
||||
): Extension[] {
|
||||
const extensions: Extension[] = [
|
||||
indentUnit.of(" ".repeat(tabSize)),
|
||||
EditorView.editable.of(editable),
|
||||
EditorState.readOnly.of(readonly),
|
||||
];
|
||||
|
||||
if (basic) extensions.push(basicSetup);
|
||||
if (useTab) extensions.push(keymap.of([indentWithTab]));
|
||||
if (placeholder) extensions.push(placeholderExt(placeholder));
|
||||
if (lang) extensions.push(lang);
|
||||
if (lineWrapping) extensions.push(EditorView.lineWrapping);
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
function get_theme(theme: Extension | null | undefined, styles: ThemeSpec | null | undefined): Extension[] {
|
||||
const extensions: Extension[] = [];
|
||||
if (styles) extensions.push(EditorView.theme(styles));
|
||||
if (theme) extensions.push(theme);
|
||||
return extensions;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if is_browser}
|
||||
<div class="codemirror-wrapper {classes}" bind:this={element} />
|
||||
{:else}
|
||||
<div class="scm-waiting {classes}">
|
||||
<div class="scm-waiting__loading scm-loading">
|
||||
<div class="scm-loading__spinner" />
|
||||
<p class="scm-loading__text">Loading editor...</p>
|
||||
</div>
|
||||
|
||||
<pre class="scm-pre cm-editor">{value}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.codemirror-wrapper :global(.cm-focused) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.scm-waiting {
|
||||
position: relative;
|
||||
}
|
||||
.scm-waiting__loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.scm-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scm-loading__spinner {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 100%;
|
||||
border: solid 2px #000;
|
||||
border-top-color: transparent;
|
||||
margin-right: 0.75rem;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.scm-loading__text {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.scm-pre {
|
||||
font-size: 0.85rem;
|
||||
font-family: monospace;
|
||||
tab-size: 2;
|
||||
-moz-tab-size: 2;
|
||||
resize: none;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
76
src/lib/e_app_codemirror_v5.svelte
Normal file
76
src/lib/e_app_codemirror_v5.svelte
Normal file
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
||||
import { EditorView, keymap, placeholder as placeholderExt } from '@codemirror/view';
|
||||
import { EditorState, StateEffect, type Extension } from '@codemirror/state';
|
||||
import { indentWithTab } from '@codemirror/commands';
|
||||
import { basicSetup } from 'codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from "@codemirror/theme-one-dark";
|
||||
|
||||
// Props
|
||||
export let content: string = 'test test test test';
|
||||
export let new_content: string = '';
|
||||
export let language: Extension = javascript();
|
||||
export let theme: Extension = oneDark;
|
||||
export let extensions: Extension[] = [];
|
||||
export let editable: boolean = true;
|
||||
export let placeholder: string = 'Start typing...';
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: string }>();
|
||||
|
||||
let editor_element: HTMLDivElement;
|
||||
let editorView: EditorView;
|
||||
|
||||
// Reactive declaration for extensions
|
||||
$: editor_extensions = [
|
||||
basicSetup,
|
||||
EditorView.editable.of(editable),
|
||||
keymap.of([indentWithTab]),
|
||||
placeholderExt(placeholder),
|
||||
language,
|
||||
theme,
|
||||
...extensions
|
||||
];
|
||||
|
||||
// Initialize CodeMirror on mount
|
||||
onMount(() => {
|
||||
editorView = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: content,
|
||||
extensions: editor_extensions,
|
||||
}),
|
||||
parent: editor_element,
|
||||
dispatch: (transaction) => {
|
||||
editorView.update([transaction]);
|
||||
if (transaction.docChanged) {
|
||||
new_content = editorView.state.doc.toString();
|
||||
const newContent = editorView.state.doc.toString();
|
||||
dispatch('change', newContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clean up on destroy
|
||||
onDestroy(() => {
|
||||
editorView?.destroy();
|
||||
});
|
||||
|
||||
// Update editor content when `content` prop changes
|
||||
$: if (editorView && editorView.state.doc.toString() !== content) {
|
||||
editorView.setState(
|
||||
EditorState.create({
|
||||
doc: content,
|
||||
extensions: editor_extensions
|
||||
})
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div bind:this={editor_element} class="codemirror-wrapper"></div>
|
||||
|
||||
<style>
|
||||
.codemirror-wrapper :global(.cm-focused) {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
@@ -25,7 +25,11 @@ import {
|
||||
} from '@lucide/svelte';
|
||||
|
||||
|
||||
import E_app_codemirror from '$lib/e_app_codemirror.svelte';
|
||||
// import E_app_codemirror from '$lib/e_app_codemirror.svelte';
|
||||
// import E_app_codemirror_v4 from '$lib/e_app_codemirror_v4.svelte';
|
||||
import E_app_codemirror_v5 from '$lib/e_app_codemirror_v5.svelte';
|
||||
import {javascript} from "@codemirror/lang-javascript";
|
||||
import {markdown} from "@codemirror/lang-markdown";
|
||||
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
@@ -2038,18 +2042,30 @@ zzzz
|
||||
{@html test_html}
|
||||
</div> -->
|
||||
|
||||
xxx
|
||||
{#if $ae_loc.edit_mode && $ae_loc.trusted_access}
|
||||
<div
|
||||
id="journal_entry_codemirror"
|
||||
class:hidden={!$ae_loc.edit_mode}
|
||||
>
|
||||
<!-- {cm_view} -->
|
||||
<!-- <CodeMirror bind:value class="editor" {...props} /> -->
|
||||
<!-- bind:value={cm_view} -->
|
||||
<E_app_codemirror
|
||||
<!-- <E_app_codemirror
|
||||
classes="editor"
|
||||
/> -->
|
||||
<!-- bind:content={tmp_entry_obj.content} -->
|
||||
<E_app_codemirror_v5
|
||||
bind:content={tmp_entry_obj.content}
|
||||
bind:new_content={tmp_entry_obj.new_content}
|
||||
language={markdown()}
|
||||
placeholder="Write your JavaScript code here..."
|
||||
/>
|
||||
|
||||
<pre>
|
||||
{tmp_entry_obj.new_content}
|
||||
</pre>
|
||||
</div>
|
||||
zzz
|
||||
{/if}
|
||||
|
||||
<section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-between w-full">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user