mishig's picture
mishig HF Staff
First build
6426ece
raw
history blame
4.22 kB
<!-- original from: https://github.com/touchifyapp/svelte-codemirror-editor/blob/main/src/lib/CodeMirror.svelte -->
<script lang="ts">
import type { ViewUpdate } from '@codemirror/view';
import { createEventDispatcher, onMount } from 'svelte';
import { EditorView, keymap, placeholder as placeholderExt } from '@codemirror/view';
import { StateEffect, EditorState, type Extension } from '@codemirror/state';
import { indentWithTab } from '@codemirror/commands';
import { oneDark } from '@codemirror/theme-one-dark';
import IconSpin from '../Icons/IconSpin.svelte';
import { basicSetup } from './basicSetup';
import CodeMirrorSearch from '$lib/CodeMirrorSearch/CodeMirrorSearch.svelte';
export let classNames = '';
export let loaderClassNames = '';
export let value = '';
export let fontSize: string | undefined = undefined;
export let basic = true;
export let extensions: Extension[] = [];
export let useTab = true;
export let editable = true;
export let readonly = false;
export let placeholder: string | HTMLElement | null | undefined = undefined;
export let focusOnMount = false;
export let view: EditorView | undefined = undefined;
const isBrowser = typeof window !== 'undefined';
const dispatch = createEventDispatcher<{ change: string }>();
let element: HTMLDivElement;
let isSearchOpen = false;
$: reconfigure(), extensions;
$: setDoc(value);
function setDoc(newDoc: string) {
if (view && newDoc !== view.state.doc.toString()) {
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: newDoc
}
});
}
}
function createEditorView(): EditorView {
return new EditorView({
parent: element,
state: createEditorState(value)
});
}
function handleChange(vu: ViewUpdate): void {
if (vu.docChanged) {
const doc = vu.state.doc;
const text = doc.toString();
dispatch('change', text);
}
}
function getExtensions() {
const stateExtensions = [
...getBaseExtensions(basic, useTab, placeholder, editable, readonly),
...getTheme(),
...extensions
];
return stateExtensions;
}
function createEditorState(value: string | null | undefined): EditorState {
return EditorState.create({
doc: value ?? undefined,
extensions: getExtensions()
});
}
function getBaseExtensions(
basic: boolean,
useTab: boolean,
placeholder: string | HTMLElement | null | undefined,
editable: boolean,
readonly: boolean
): Extension[] {
const extensions: Extension[] = [
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 (fontSize) {
extensions.push(
EditorView.theme({
'&': {
fontSize: fontSize
}
})
);
}
extensions.push(EditorView.updateListener.of(handleChange));
return extensions;
}
function getTheme(): Extension[] {
const extensions: Extension[] = [];
const isDarkMode = document.querySelector('body')?.classList.contains('dark') ?? false;
if (isDarkMode) {
extensions.push(oneDark);
}
return extensions;
}
function reconfigure(): void {
view?.dispatch({
effects: StateEffect.reconfigure.of(getExtensions())
});
}
function onKeyDown(e: KeyboardEvent) {
const { ctrlKey, metaKey, key } = e;
const isOpenShortcut = key === 'f3' || ((metaKey || ctrlKey) && key === 'f');
if (isOpenShortcut) {
isSearchOpen = true;
e.preventDefault();
}
}
onMount(() => {
view = createEditorView();
if (view && focusOnMount) {
const tr = view.state.update({
selection: { anchor: view.state.doc.length }
});
view.dispatch(tr);
view.focus();
}
return () => view?.destroy();
});
</script>
{#if isBrowser}
<div class="relative">
<div class="codemirror-wrapper {classNames}" bind:this={element} on:keydown={onKeyDown} />
{#if isSearchOpen && view}
<CodeMirrorSearch {view} on:close={() => (isSearchOpen = false)} />
{/if}
</div>
{:else}
<div class="flex h-64 items-center justify-center {loaderClassNames}">
<IconSpin classNames="animate-spin text-xs" />
</div>
{/if}