mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 23:18:10 +00:00
265 lines
8.4 KiB
JavaScript
265 lines
8.4 KiB
JavaScript
import { EditorView, lineNumbers, keymap } from '@codemirror/view';
|
|
import { ViewPlugin } from '@codemirror/view';
|
|
import { indentWithTab, selectAll } from '@codemirror/commands';
|
|
import { foldGutter, foldKeymap } from '@codemirror/language';
|
|
import { history, historyKeymap } from '@codemirror/commands';
|
|
import { searchKeymap } from '@codemirror/search';
|
|
import { html } from '@codemirror/lang-html';
|
|
import { javascript } from '@codemirror/lang-javascript';
|
|
import { json } from '@codemirror/lang-json';
|
|
import { css } from '@codemirror/lang-css';
|
|
import { liquid } from '@codemirror/lang-liquid';
|
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
import { githubLight } from '@fsegurai/codemirror-theme-github-light';
|
|
|
|
// Language support mapping
|
|
const LANGUAGE_MAP = {
|
|
'javascript': javascript,
|
|
'js': javascript,
|
|
'json': json,
|
|
'css': css,
|
|
'liquid': liquid,
|
|
'html': html,
|
|
};
|
|
|
|
// Theme support mapping
|
|
const THEME_MAP = {
|
|
'light': githubLight,
|
|
'dark': oneDark,
|
|
};
|
|
|
|
/**
|
|
* Get language support based on language parameter
|
|
* @param {string} language - Language name or comma-separated list
|
|
* @returns {Array|Extension} Language extension(s)
|
|
*/
|
|
function getLanguageSupport(language) {
|
|
// Handle comma-separated languages
|
|
if (language.includes(',')) {
|
|
const languages = language.split(',').map(lang => lang.trim().toLowerCase());
|
|
const languageExtensions = [];
|
|
|
|
languages.forEach(lang => {
|
|
const languageFn = LANGUAGE_MAP[lang];
|
|
if (languageFn) {
|
|
languageExtensions.push(languageFn());
|
|
}
|
|
});
|
|
|
|
return languageExtensions;
|
|
}
|
|
|
|
// Handle single language
|
|
const languageFn = LANGUAGE_MAP[language.toLowerCase()] || LANGUAGE_MAP.html;
|
|
return languageFn();
|
|
}
|
|
|
|
/**
|
|
* Get theme support
|
|
* @param {string} theme - Theme name
|
|
* @returns {Array} Theme extensions
|
|
*/
|
|
function getThemeSupport(theme) {
|
|
const themeFn = THEME_MAP[theme] || THEME_MAP.light;
|
|
return [themeFn];
|
|
}
|
|
|
|
/**
|
|
* Create a resize plugin that handles container resizing
|
|
* @returns {ViewPlugin} Resize plugin
|
|
*/
|
|
function createResizePlugin() {
|
|
return ViewPlugin.fromClass(class {
|
|
constructor(view) {
|
|
this.view = view;
|
|
this.resizeObserver = null;
|
|
this.setupResizeObserver();
|
|
}
|
|
|
|
setupResizeObserver() {
|
|
const container = this.view.dom.parentElement;
|
|
if (container) {
|
|
this.resizeObserver = new ResizeObserver(() => {
|
|
// Use requestAnimationFrame to ensure proper timing
|
|
requestAnimationFrame(() => {
|
|
this.view.requestMeasure();
|
|
});
|
|
});
|
|
this.resizeObserver.observe(container);
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
if (this.resizeObserver) {
|
|
this.resizeObserver.disconnect();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get Flux-like theme styling based on theme
|
|
* @param {string} theme - Theme name ('light', 'dark', or 'auto')
|
|
* @returns {Object} Theme-specific styling
|
|
*/
|
|
function getFluxThemeStyling(theme) {
|
|
const isDark = theme === 'dark' || (theme === 'auto' && getSystemTheme() === 'dark');
|
|
|
|
if (isDark) {
|
|
return {
|
|
backgroundColor: 'oklab(0.999994 0.0000455678 0.0000200868 / 0.1)',
|
|
gutterBackgroundColor: 'oklch(26.9% 0 0)',
|
|
borderColor: '#374151',
|
|
focusBorderColor: 'rgb(224 91 68)',
|
|
};
|
|
} else {
|
|
return {
|
|
backgroundColor: '#fff', // zinc-50
|
|
gutterBackgroundColor: '#fafafa', // zinc-50
|
|
borderColor: '#e5e7eb', // gray-200
|
|
focusBorderColor: 'rgb(224 91 68)', // red-500
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create CodeMirror editor instance
|
|
* @param {HTMLElement} element - DOM element to mount editor
|
|
* @param {Object} options - Editor options
|
|
* @returns {EditorView} CodeMirror editor instance
|
|
*/
|
|
export function createCodeMirror(element, options = {}) {
|
|
const {
|
|
value = '',
|
|
language = 'html',
|
|
theme = 'light',
|
|
readOnly = false,
|
|
onChange = () => {},
|
|
onUpdate = () => {},
|
|
onBlur = () => {}
|
|
} = options;
|
|
|
|
// Get language and theme support
|
|
const languageSupport = getLanguageSupport(language);
|
|
const themeSupport = getThemeSupport(theme);
|
|
const fluxStyling = getFluxThemeStyling(theme);
|
|
|
|
// Create editor
|
|
const editor = new EditorView({
|
|
doc: value,
|
|
extensions: [
|
|
lineNumbers(),
|
|
foldGutter(),
|
|
history(),
|
|
EditorView.lineWrapping,
|
|
createResizePlugin(),
|
|
...(Array.isArray(languageSupport) ? languageSupport : [languageSupport]),
|
|
...themeSupport,
|
|
keymap.of([
|
|
indentWithTab,
|
|
...foldKeymap,
|
|
...historyKeymap,
|
|
...searchKeymap,
|
|
{
|
|
key: 'Mod-a',
|
|
run: selectAll,
|
|
},
|
|
]),
|
|
EditorView.theme({
|
|
'&': {
|
|
fontSize: '14px',
|
|
border: `1px solid ${fluxStyling.borderColor}`,
|
|
borderRadius: '0.375rem',
|
|
height: '100%',
|
|
maxHeight: '100%',
|
|
overflow: 'hidden',
|
|
backgroundColor: fluxStyling.backgroundColor + ' !important',
|
|
resize: 'vertical',
|
|
minHeight: '200px',
|
|
},
|
|
'.cm-gutters': {
|
|
borderTopLeftRadius: '0.375rem',
|
|
backgroundColor: fluxStyling.gutterBackgroundColor + ' !important',
|
|
},
|
|
'.cm-gutter': {
|
|
backgroundColor: fluxStyling.gutterBackgroundColor + ' !important',
|
|
},
|
|
'&.cm-focused': {
|
|
outline: 'none',
|
|
borderColor: fluxStyling.focusBorderColor,
|
|
},
|
|
'.cm-content': {
|
|
padding: '12px',
|
|
},
|
|
'.cm-scroller': {
|
|
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
height: '100%',
|
|
overflow: 'auto',
|
|
},
|
|
'.cm-editor': {
|
|
height: '100%',
|
|
},
|
|
'.cm-editor .cm-scroller': {
|
|
height: '100%',
|
|
overflow: 'auto',
|
|
},
|
|
'.cm-foldGutter': {
|
|
width: '12px',
|
|
},
|
|
'.cm-foldGutter .cm-gutterElement': {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
cursor: 'pointer',
|
|
fontSize: '12px',
|
|
color: '#6b7280',
|
|
},
|
|
'.cm-foldGutter .cm-gutterElement:hover': {
|
|
color: '#374151',
|
|
},
|
|
'.cm-foldGutter .cm-gutterElement.cm-folded': {
|
|
color: '#3b82f6',
|
|
}
|
|
}),
|
|
EditorView.updateListener.of((update) => {
|
|
if (update.docChanged) {
|
|
const newValue = update.state.doc.toString();
|
|
onChange(newValue);
|
|
onUpdate(newValue);
|
|
}
|
|
}),
|
|
EditorView.domEventHandlers({
|
|
blur: onBlur
|
|
}),
|
|
EditorView.editable.of(!readOnly),
|
|
],
|
|
parent: element
|
|
});
|
|
|
|
return editor;
|
|
}
|
|
|
|
/**
|
|
* Auto-detect system theme preference
|
|
* @returns {string} 'dark' or 'light'
|
|
*/
|
|
export function getSystemTheme() {
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
return 'dark';
|
|
}
|
|
return 'light';
|
|
}
|
|
|
|
/**
|
|
* Watch for system theme changes
|
|
* @param {Function} callback - Callback function when theme changes
|
|
* @returns {Function} Unwatch function
|
|
*/
|
|
export function watchThemeChange(callback) {
|
|
if (window.matchMedia) {
|
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
mediaQuery.addEventListener('change', callback);
|
|
return () => mediaQuery.removeEventListener('change', callback);
|
|
}
|
|
return () => {};
|
|
}
|