feat: CodeMirror integration and bug fixes
This commit addresses several issues related to the migration from TipTap to CodeMirror:
- **CodeMirror Initialization Fixes:**
- Resolved 'Unrecognized extension value' errors by refactoring to explicitly import individual CodeMirror extensions instead of relying on . This ensures proper singleton usage and prevents module duplication issues.
- Updated and to utilize these individual extensions.
- **Text Wrapping Enabled:**
- Added to the extensions in and to enable text wrapping in the CodeMirror editors.
- **Content Saving Fixes:**
- Corrected content binding for CodeMirror editor instances in various IDAA components:
- (description, location_text, attend_text)
- (content, notes)
- (content)
- (description, notes)
- (description, notes)
- Ensured that the prop of is correctly bound to the respective state variables in the parent components, and these state variables are initialized with existing content.
- **Save Button Enablement:**
- Fixed an issue in where the Save button was not enabling on content changes. The logic now directly compares the and with the original object's content, ensuring reactivity.
This commit is contained in:
@@ -58,7 +58,7 @@ import { add_url_params, clean_headers } from '$lib/ae_core/core__api_helpers';
|
||||
const ae_promises: key_val = {}; // Promise<any>;
|
||||
|
||||
// Updated 2024-03-29
|
||||
async function handle_load_ae_obj_id__site_domain({
|
||||
async function load_ae_obj_id__site_domain({
|
||||
api_cfg,
|
||||
fqdn,
|
||||
try_cache = false,
|
||||
@@ -73,7 +73,7 @@ async function handle_load_ae_obj_id__site_domain({
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
`*** handle_load_ae_obj_id__site_domain() *** api.base_url=${api_cfg.base_url}, fqdn=${fqdn}, timeout=${timeout}`
|
||||
`*** load_ae_obj_id__site_domain() *** api.base_url=${api_cfg.base_url}, fqdn=${fqdn}, timeout=${timeout}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ async function handle_load_ae_obj_id__site_domain({
|
||||
}
|
||||
|
||||
// Updated 2024-03-29
|
||||
async function handle_load_ae_obj_code__data_store({
|
||||
async function load_ae_obj_code__data_store({
|
||||
api_cfg,
|
||||
code,
|
||||
data_type = 'text',
|
||||
@@ -143,7 +143,7 @@ async function handle_load_ae_obj_code__data_store({
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
console.log(`*** handle_get_data_store_obj_w_code() *** code=${code}`);
|
||||
console.log(`*** load_ae_obj_code__data_store() *** code=${code}`);
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
@@ -271,7 +271,7 @@ async function handle_load_ae_obj_code__data_store({
|
||||
}
|
||||
|
||||
// Updated 2024-03-27
|
||||
async function handle_update_ae_obj_id_crud({
|
||||
async function update_ae_obj_id_crud({
|
||||
api_cfg,
|
||||
object_type,
|
||||
object_id,
|
||||
@@ -561,7 +561,7 @@ async function update_ae_obj_id_crud_v2({
|
||||
return ae_promises.api_update__ae_obj;
|
||||
}
|
||||
|
||||
async function handle_download_export__obj_type({
|
||||
async function download_export__obj_type({
|
||||
api_cfg,
|
||||
get_obj_type, // The type of object to return: event_badge, event_presenter, sponsorship, etc.
|
||||
for_obj_type, // Usually for an account, event, event_exhibit, or sponsorship_cfg
|
||||
@@ -588,7 +588,7 @@ async function handle_download_export__obj_type({
|
||||
params?: key_val;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
console.log('*** ae_core_functions.js: handle_download_export__obj_type() ***');
|
||||
console.log('*** ae_core_functions.js: download_export__obj_type() ***');
|
||||
|
||||
const task_id = for_obj_id;
|
||||
|
||||
@@ -651,8 +651,8 @@ const export_obj = {
|
||||
add_url_params: add_url_params,
|
||||
clean_headers: clean_headers,
|
||||
|
||||
handle_load_ae_obj_id__site_domain: handle_load_ae_obj_id__site_domain,
|
||||
handle_load_ae_obj_code__data_store: handle_load_ae_obj_code__data_store,
|
||||
load_ae_obj_id__site_domain: load_ae_obj_id__site_domain,
|
||||
load_ae_obj_code__data_store: load_ae_obj_code__data_store,
|
||||
|
||||
load_ae_obj_id__activity_log: load_ae_obj_id__activity_log,
|
||||
load_ae_obj_li__activity_log: load_ae_obj_li__activity_log,
|
||||
@@ -671,9 +671,9 @@ const export_obj = {
|
||||
qry_ae_obj_li__user_email: qry_ae_obj_li__user_email,
|
||||
auth_ae_obj__user_id_change_password: auth_ae_obj__user_id_change_password,
|
||||
|
||||
handle_update_ae_obj_id_crud: handle_update_ae_obj_id_crud,
|
||||
update_ae_obj_id_crud: update_ae_obj_id_crud,
|
||||
update_ae_obj_id_crud_v2: update_ae_obj_id_crud_v2,
|
||||
handle_download_export__obj_type: handle_download_export__obj_type,
|
||||
download_export__obj_type: download_export__obj_type,
|
||||
generate_qr_code: generate_qr_code,
|
||||
js_generate_qr_code: js_generate_qr_code
|
||||
};
|
||||
|
||||
@@ -5,50 +5,7 @@
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import {
|
||||
EditorView,
|
||||
keymap,
|
||||
highlightSpecialChars,
|
||||
drawSelection,
|
||||
highlightActiveLine,
|
||||
dropCursor,
|
||||
rectangularSelection,
|
||||
crosshairCursor,
|
||||
gutter,
|
||||
GutterMarker,
|
||||
highlightActiveLineGutter,
|
||||
lineNumbers,
|
||||
placeholder as placeholderExt
|
||||
} from '@codemirror/view';
|
||||
import { EditorState, RangeSet, StateEffect, type Extension, Text } from '@codemirror/state';
|
||||
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
|
||||
import { indentUnit } from '@codemirror/language';
|
||||
import { languages } from '@codemirror/language-data';
|
||||
// import {
|
||||
// defaultHighlightStyle, syntaxHighlighting, indentOnInput,
|
||||
// bracketMatching, foldGutter, foldKeymap
|
||||
// } from "@codemirror/language"
|
||||
// import {
|
||||
// defaultHighlightStyle, syntaxHighlighting, indentOnInput,
|
||||
// bracketMatching, foldGutter, foldKeymap
|
||||
// } from "@codemirror/language"
|
||||
// import {
|
||||
// searchKeymap, highlightSelectionMatches
|
||||
// } from "@codemirror/search"
|
||||
// import {
|
||||
// autocompletion, completionKeymap, closeBrackets,
|
||||
// closeBracketsKeymap
|
||||
// } from "@codemirror/autocomplete"
|
||||
// import {lintKeymap} from "@codemirror/lint"
|
||||
|
||||
// import { } from '@codemirror/gutter'; // Merged into @codemirror/view
|
||||
import { basicSetup } from 'codemirror';
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
// import { css } from '@codemirror/lang-css';
|
||||
// import { javascript } from '@codemirror/lang-javascript';
|
||||
// import { json } from '@codemirror/lang-json';
|
||||
// import { html } from '@codemirror/lang-html';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { ensureCodeMirrorModules } from '../elements/codemirror_modules';
|
||||
|
||||
// Props
|
||||
export let content: string = 'test test test test';
|
||||
@@ -56,9 +13,7 @@
|
||||
|
||||
// export let language: Extension = markdown(); // javascript()
|
||||
export let theme_mode: string = 'light'; // 'dark' | 'light'
|
||||
export let theme: Extension = EditorView.baseTheme(); // EditorView.baseTheme(); // oneDark
|
||||
|
||||
export let extensions: Extension[] = [];
|
||||
export let extensions: any[] = []; // Changed to any[] because Extension type is not directly available here
|
||||
|
||||
export let editable: boolean = true;
|
||||
export let readonly: boolean = false;
|
||||
@@ -73,146 +28,75 @@
|
||||
export { classes as class };
|
||||
|
||||
let editor_element: HTMLDivElement;
|
||||
let editorView: EditorView;
|
||||
let editorView: any; // Changed to any
|
||||
|
||||
// theme = [
|
||||
// // baseTheme,
|
||||
// theme,
|
||||
// ];
|
||||
// console.log(`Theme:`, theme);
|
||||
let cm_modules: any; // To hold the dynamically loaded CodeMirror modules
|
||||
|
||||
if (theme_mode == 'dark') {
|
||||
theme = oneDark;
|
||||
} else {
|
||||
// theme = EditorView.baseTheme({
|
||||
// "&": {
|
||||
// color: "black",
|
||||
// backgroundColor: "white"
|
||||
// },
|
||||
// ".cm-cursor, .cm-dropCursor": { borderLeftColor: "#000" },
|
||||
// "&.cm-focused .cm-selectionBackground, ::selection": {
|
||||
// backgroundColor: "#B7D5FF"
|
||||
// },
|
||||
// "&.cm-focused .cm-selectionForeground": { color: "black" }
|
||||
// });
|
||||
}
|
||||
async function initializeCodeMirror() {
|
||||
if (!browser) return;
|
||||
|
||||
if (editable) {
|
||||
extensions.push(EditorView.editable.of(true));
|
||||
} else {
|
||||
// extensions.push(EditorState.editable.of(false));
|
||||
}
|
||||
if (readonly) {
|
||||
extensions.push(EditorState.readOnly.of(true));
|
||||
} else {
|
||||
// extensions.push(EditorState.readOnly.of(false));
|
||||
}
|
||||
cm_modules = await ensureCodeMirrorModules();
|
||||
if (!cm_modules) return;
|
||||
|
||||
if (placeholder) {
|
||||
extensions.push(placeholderExt(placeholder));
|
||||
}
|
||||
// Reactive declaration for extensions
|
||||
let editor_extensions = [
|
||||
// Core extensions
|
||||
cm_modules.highlightSpecialChars(),
|
||||
cm_modules.history(),
|
||||
cm_modules.foldGutter(),
|
||||
cm_modules.drawSelection(),
|
||||
cm_modules.dropCursor(),
|
||||
cm_modules.EditorState_allowMultipleSelections.of(true),
|
||||
cm_modules.indentOnInput(),
|
||||
cm_modules.bracketMatching(),
|
||||
cm_modules.closeBrackets(),
|
||||
cm_modules.autocompletion(),
|
||||
cm_modules.rectangularSelection(),
|
||||
cm_modules.crosshairCursor(),
|
||||
cm_modules.highlightActiveLine(),
|
||||
cm_modules.highlightActiveLineGutter(),
|
||||
cm_modules.keymap.of([
|
||||
...cm_modules.defaultKeymap,
|
||||
...cm_modules.searchKeymap,
|
||||
...cm_modules.historyKeymap,
|
||||
...cm_modules.foldKeymap,
|
||||
...cm_modules.completionKeymap,
|
||||
...cm_modules.lintKeymap
|
||||
]),
|
||||
cm_modules.markdown({ base: cm_modules.markdownLanguage, codeLanguages: cm_modules.languages }),
|
||||
theme_mode == 'dark' ? cm_modules.oneDark : cm_modules.EditorView.baseTheme(),
|
||||
cm_modules.EditorView.contentAttributes.of({ spellcheck: 'true' }), // Enable spell check
|
||||
|
||||
if (show_line_numbers) {
|
||||
// extensions.push(lineNumbers({ class: "line-numbers" }));
|
||||
} else {
|
||||
// extensions.push(gutter({ class: "hidden-gutter" }));
|
||||
// extensions.push(lineNumbers(false));
|
||||
// extensions.push(gutter(false));
|
||||
// extensions.pop();
|
||||
// extensions.slice(extensions.indexOf(lineNumbers), 1);
|
||||
}
|
||||
// Conditional extensions based on props
|
||||
editable ? cm_modules.EditorView.editable.of(true) : null,
|
||||
readonly ? cm_modules.EditorState.readOnly.of(true) : null,
|
||||
placeholder ? cm_modules.placeholderExt(placeholder) : null,
|
||||
show_line_numbers ? cm_modules.lineNumbers() : null,
|
||||
wrap_lines ? cm_modules.EditorView_lineWrapping : null,
|
||||
use_tab ? cm_modules.keymap.of([cm_modules.indentWithTab]) : null,
|
||||
tab_size ? cm_modules.indentUnit.of(' '.repeat(tab_size)) : null,
|
||||
|
||||
if (wrap_lines) {
|
||||
extensions.push(EditorView.lineWrapping);
|
||||
}
|
||||
...extensions // Add any custom extensions passed in props
|
||||
].filter(Boolean);
|
||||
|
||||
if (use_tab) {
|
||||
extensions.push(keymap.of([indentWithTab]));
|
||||
}
|
||||
if (tab_size) {
|
||||
extensions.push(indentUnit.of(' '.repeat(tab_size)));
|
||||
}
|
||||
|
||||
// Enable spell check
|
||||
extensions.push(EditorView.contentAttributes.of({ spellcheck: 'true' }));
|
||||
|
||||
// let languages = [
|
||||
// { name: 'CSS', mode: 'css' },
|
||||
// { name: 'HTML', mode: 'html' },
|
||||
// { name: 'JavaScript', mode: 'javascript' },
|
||||
// { name: 'Markdown', mode: 'markdown' },
|
||||
// { name: 'Python', mode: 'python' },
|
||||
// ];
|
||||
|
||||
// Reactive declaration for extensions
|
||||
$: editor_extensions = [
|
||||
basicSetup,
|
||||
// gutter({class: "hidden-gutter"}),
|
||||
// A line number gutter
|
||||
lineNumbers(false),
|
||||
// A gutter with code folding markers
|
||||
// foldGutter(false),
|
||||
// lineWrapping(false),
|
||||
// EditorView.lineWrapping, // Enable line wrapping
|
||||
|
||||
// EditorView.indentUnit.of(" ".repeat(tab_size)),
|
||||
// EditorView.tabSize.of(tab_size),
|
||||
// indentUnit.of(" ".repeat(tab_size)),
|
||||
|
||||
// EditorView.editable.of(editable),
|
||||
// EditorState.readOnly.of(readonly),
|
||||
|
||||
// EditorState.tabSize.of(tab_size),
|
||||
// keymap.of([indentWithTab]),
|
||||
// placeholderExt(placeholder),
|
||||
// language,
|
||||
markdown({ base: markdownLanguage, codeLanguages: languages }),
|
||||
// javascript({typescript: true}),
|
||||
// json(),
|
||||
// css(),
|
||||
// html(),
|
||||
theme,
|
||||
|
||||
...extensions
|
||||
];
|
||||
|
||||
// baseTheme = {
|
||||
// ".cm-o-replacement": {
|
||||
// display: "inline-block",
|
||||
// width: ".5em",
|
||||
// height: ".5em",
|
||||
// borderRadius: ".25em"
|
||||
// },
|
||||
// "&light .cm-o-replacement": {
|
||||
// backgroundColor: "#04c"
|
||||
// },
|
||||
// "&dark .cm-o-replacement": {
|
||||
// backgroundColor: "#5bf"
|
||||
// }
|
||||
// },
|
||||
// let dimensions = {
|
||||
// width: '100%',
|
||||
// height: 'calc(100vh - 48px)' // Adjust for header and other elements
|
||||
// };
|
||||
// editorView.setSize(dimensions.width, dimensions.height);
|
||||
|
||||
// Initialize CodeMirror on mount
|
||||
onMount(() => {
|
||||
editorView = new EditorView({
|
||||
state: EditorState.create({
|
||||
editorView = new cm_modules.EditorView({
|
||||
state: cm_modules.EditorState.create({
|
||||
doc: content,
|
||||
extensions: editor_extensions
|
||||
}),
|
||||
parent: editor_element, // document.body
|
||||
dispatch: (transaction) => {
|
||||
parent: editor_element,
|
||||
dispatch: (transaction: any) => {
|
||||
editorView.update([transaction]);
|
||||
if (transaction.docChanged) {
|
||||
new_content = editorView.state.doc.toString();
|
||||
const newContent = editorView.state.doc.toString();
|
||||
// dispatch('change', newContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize CodeMirror on mount
|
||||
onMount(async () => {
|
||||
await initializeCodeMirror();
|
||||
});
|
||||
|
||||
// Clean up on destroy
|
||||
@@ -221,11 +105,11 @@
|
||||
});
|
||||
|
||||
// Update editor content when `content` prop changes
|
||||
$: if (editorView && editorView.state.doc.toString() !== content) {
|
||||
$: if (cm_modules && editorView && editorView.state.doc.toString() !== content) {
|
||||
editorView.setState(
|
||||
EditorState.create({
|
||||
cm_modules.EditorState.create({
|
||||
doc: content,
|
||||
extensions: editor_extensions
|
||||
extensions: editor_extensions // Use the reactive extensions
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
169
src/lib/elements/codemirror_modules.ts
Normal file
169
src/lib/elements/codemirror_modules.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
let _cmCache: {
|
||||
EditorView?: any;
|
||||
EditorState?: any;
|
||||
basicSetup?: any;
|
||||
markdown?: any;
|
||||
markdownLanguage?: any;
|
||||
keymap?: any;
|
||||
defaultKeymap?: any;
|
||||
history?: any;
|
||||
historyKeymap?: any;
|
||||
lineNumbers?: any;
|
||||
highlightSpecialChars?: any;
|
||||
drawSelection?: any;
|
||||
dropCursor?: any;
|
||||
rectangularSelection?: any;
|
||||
crosshairCursor?: any;
|
||||
highlightActiveLine?: any;
|
||||
highlightActiveLineGutter?: any;
|
||||
indentWithTab?: any;
|
||||
indentUnit?: any;
|
||||
autocompletion?: any;
|
||||
completionKeymap?: any;
|
||||
closeBrackets?: any;
|
||||
closeBracketsKeymap?: any;
|
||||
searchKeymap?: any;
|
||||
highlightSelectionMatches?: any;
|
||||
lintKeymap?: any;
|
||||
EditorState_allowMultipleSelections?: any;
|
||||
EditorView_lineWrapping?: any;
|
||||
EditorView_editable?: any;
|
||||
EditorView_contentAttributes?: any;
|
||||
bracketMatching?: any;
|
||||
foldGutter?: any;
|
||||
foldKeymap?: any;
|
||||
indentOnInput?: any;
|
||||
languages?: any;
|
||||
oneDark?: any;
|
||||
placeholderExt?: any;
|
||||
} | null = null;
|
||||
|
||||
// ...existing code...
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
type CMCache = {
|
||||
EditorView: any;
|
||||
EditorState: any;
|
||||
basicSetup?: any;
|
||||
markdown?: any;
|
||||
markdownLanguage?: any;
|
||||
keymap?: any;
|
||||
defaultKeymap?: any;
|
||||
history?: any;
|
||||
historyKeymap?: any;
|
||||
lineNumbers?: any;
|
||||
highlightSpecialChars?: any;
|
||||
drawSelection?: any;
|
||||
dropCursor?: any;
|
||||
rectangularSelection?: any;
|
||||
crosshairCursor?: any;
|
||||
highlightActiveLine?: any;
|
||||
highlightActiveLineGutter?: any;
|
||||
indentWithTab?: any;
|
||||
indentUnit?: any;
|
||||
autocompletion?: any;
|
||||
completionKeymap?: any;
|
||||
closeBrackets?: any;
|
||||
closeBracketsKeymap?: any;
|
||||
searchKeymap?: any;
|
||||
highlightSelectionMatches?: any;
|
||||
lintKeymap?: any;
|
||||
EditorState_allowMultipleSelections?: any;
|
||||
EditorView_lineWrapping?: any;
|
||||
EditorView_editable?: any;
|
||||
EditorView_contentAttributes?: any;
|
||||
bracketMatching?: any;
|
||||
foldGutter?: any;
|
||||
foldKeymap?: any;
|
||||
indentOnInput?: any;
|
||||
languages?: any;
|
||||
oneDark?: any;
|
||||
placeholderExt?: any;
|
||||
} | null;
|
||||
|
||||
const GLOBAL_KEY = '__cm_singleton_modules_v1';
|
||||
|
||||
export async function ensureCodeMirrorModules(): Promise<CMCache> {
|
||||
if (!browser) return null;
|
||||
|
||||
// Use a single global object so HMR/multiple module copies reuse same instance
|
||||
const globalAny = globalThis as any;
|
||||
if (globalAny[GLOBAL_KEY]) return globalAny[GLOBAL_KEY] as CMCache;
|
||||
|
||||
const [
|
||||
viewMod,
|
||||
stateMod,
|
||||
basicSetupMod,
|
||||
markdownMod,
|
||||
commandsMod,
|
||||
languageMod,
|
||||
autocompleteMod,
|
||||
searchMod,
|
||||
lintMod,
|
||||
themeOneDarkMod
|
||||
] = await Promise.all([
|
||||
import('@codemirror/view'),
|
||||
import('@codemirror/state'),
|
||||
import('@codemirror/basic-setup'),
|
||||
import('@codemirror/lang-markdown'),
|
||||
import('@codemirror/commands'),
|
||||
import('@codemirror/language'),
|
||||
import('@codemirror/autocomplete'),
|
||||
import('@codemirror/search'),
|
||||
import('@codemirror/lint'),
|
||||
import('@codemirror/theme-one-dark')
|
||||
]);
|
||||
|
||||
const cache: CMCache = {
|
||||
EditorView: viewMod.EditorView,
|
||||
keymap: viewMod.keymap,
|
||||
lineNumbers: viewMod.lineNumbers,
|
||||
highlightSpecialChars: viewMod.highlightSpecialChars,
|
||||
drawSelection: viewMod.drawSelection,
|
||||
dropCursor: viewMod.dropCursor,
|
||||
rectangularSelection: viewMod.rectangularSelection,
|
||||
crosshairCursor: viewMod.crosshairCursor,
|
||||
highlightActiveLine: viewMod.highlightActiveLine,
|
||||
highlightActiveLineGutter: viewMod.highlightActiveLineGutter,
|
||||
EditorView_lineWrapping: viewMod.EditorView.lineWrapping,
|
||||
EditorView_editable: viewMod.EditorView.editable,
|
||||
EditorView_contentAttributes: viewMod.EditorView.contentAttributes,
|
||||
placeholderExt: viewMod.placeholder,
|
||||
|
||||
EditorState: stateMod.EditorState,
|
||||
EditorState_allowMultipleSelections: stateMod.EditorState.allowMultipleSelections,
|
||||
EditorState_readOnly: stateMod.EditorState.readOnly,
|
||||
|
||||
basicSetup: basicSetupMod?.basicSetup,
|
||||
|
||||
markdown: markdownMod?.markdown,
|
||||
markdownLanguage: markdownMod?.markdownLanguage,
|
||||
languages: languageMod?.languages, // From @codemirror/language-data, often re-exported by @codemirror/language
|
||||
|
||||
defaultKeymap: (commandsMod && commandsMod.defaultKeymap) || [],
|
||||
history: commandsMod?.history,
|
||||
historyKeymap: commandsMod?.historyKeymap || [],
|
||||
indentWithTab: commandsMod?.indentWithTab,
|
||||
|
||||
indentUnit: languageMod?.indentUnit,
|
||||
indentOnInput: languageMod?.indentOnInput,
|
||||
bracketMatching: languageMod?.bracketMatching,
|
||||
foldGutter: languageMod?.foldGutter,
|
||||
foldKeymap: languageMod?.foldKeymap,
|
||||
|
||||
autocompletion: autocompleteMod?.autocompletion,
|
||||
completionKeymap: autocompleteMod?.completionKeymap,
|
||||
closeBrackets: autocompleteMod?.closeBrackets,
|
||||
closeBracketsKeymap: autocompleteMod?.closeBracketsKeymap,
|
||||
|
||||
searchKeymap: searchMod?.searchKeymap,
|
||||
highlightSelectionMatches: searchMod?.highlightSelectionMatches,
|
||||
|
||||
lintKeymap: lintMod?.lintKeymap,
|
||||
|
||||
oneDark: themeOneDarkMod?.oneDark
|
||||
};
|
||||
|
||||
globalAny[GLOBAL_KEY] = cache;
|
||||
return cache;
|
||||
}
|
||||
@@ -1,80 +1,130 @@
|
||||
<script lang="ts">
|
||||
import CodeMirror from 'svelte-codemirror-editor';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { ensureCodeMirrorModules } from './codemirror_modules';
|
||||
|
||||
export let content: string = '';
|
||||
export let placeholder: string = 'Start typing...';
|
||||
export let content: string = '';
|
||||
export let placeholder: string = 'Start typing...';
|
||||
|
||||
let editor: EditorView;
|
||||
let editor_container: HTMLDivElement;
|
||||
let editor_view: any;
|
||||
|
||||
const wrapSelection = (before: string, after: string = before) => {
|
||||
if (!editor) return;
|
||||
const state = editor.state;
|
||||
const changes = state.changeByRange((range) => {
|
||||
const isAlreadyWrapped =
|
||||
state.sliceDoc(range.from - before.length, range.from) === before &&
|
||||
state.sliceDoc(range.to, range.to + after.length) === after;
|
||||
async function createEditor() {
|
||||
if (!browser) return;
|
||||
const cm = await ensureCodeMirrorModules();
|
||||
if (!cm) return;
|
||||
|
||||
if (isAlreadyWrapped) {
|
||||
return {
|
||||
changes: [
|
||||
{ from: range.from - before.length, to: range.from, insert: '' },
|
||||
{ from: range.to, to: range.to + after.length, insert: '' }
|
||||
],
|
||||
range: EditorState.range(range.from - before.length, range.to - before.length)
|
||||
};
|
||||
}
|
||||
// If an existing instance exists (HMR / remount), destroy it first
|
||||
if (editor_view && typeof editor_view.destroy === 'function') {
|
||||
try { editor_view.destroy(); } catch (e) { /* ignore */ }
|
||||
editor_view = null;
|
||||
}
|
||||
|
||||
return {
|
||||
changes: [
|
||||
{ from: range.from, insert: before },
|
||||
{ from: range.to, insert: after }
|
||||
],
|
||||
range: EditorState.range(range.from + before.length, range.to + before.length)
|
||||
};
|
||||
});
|
||||
editor.dispatch(changes);
|
||||
editor.focus();
|
||||
};
|
||||
// Build extensions defensively
|
||||
const extensions = [
|
||||
cm.lineNumbers(),
|
||||
cm.highlightSpecialChars(),
|
||||
cm.history(),
|
||||
cm.foldGutter(),
|
||||
cm.drawSelection(),
|
||||
cm.dropCursor(),
|
||||
cm.EditorState_allowMultipleSelections.of(true),
|
||||
cm.indentOnInput(),
|
||||
cm.bracketMatching(),
|
||||
cm.closeBrackets(),
|
||||
cm.autocompletion(),
|
||||
cm.rectangularSelection(),
|
||||
cm.crosshairCursor(),
|
||||
cm.highlightActiveLine(),
|
||||
cm.highlightActiveLineGutter(),
|
||||
cm.EditorView_lineWrapping,
|
||||
cm.keymap.of([
|
||||
...cm.defaultKeymap,
|
||||
...cm.searchKeymap,
|
||||
...cm.historyKeymap,
|
||||
...cm.foldKeymap,
|
||||
...cm.completionKeymap,
|
||||
...cm.lintKeymap
|
||||
]),
|
||||
cm.markdown ? cm.markdown({ base: cm.markdownLanguage }) : null,
|
||||
cm.EditorView && cm.EditorView.updateListener ? cm.EditorView.updateListener.of((update: any) => {
|
||||
if (update.docChanged) content = update.state.doc.toString();
|
||||
}) : null
|
||||
].filter(Boolean);
|
||||
|
||||
const insertList = () => {
|
||||
if (!editor) return;
|
||||
const state = editor.state;
|
||||
const changes = state.changeByRange((range) => {
|
||||
const line = state.doc.lineAt(range.from);
|
||||
return {
|
||||
changes: [{ from: line.from, insert: '- ' }],
|
||||
range: EditorState.range(range.from + 2, range.to + 2)
|
||||
};
|
||||
});
|
||||
editor.dispatch(changes);
|
||||
editor.focus();
|
||||
};
|
||||
// Create editor
|
||||
editor_view = new cm.EditorView({
|
||||
state: cm.EditorState.create({
|
||||
doc: content ?? '',
|
||||
extensions
|
||||
}),
|
||||
parent: editor_container
|
||||
});
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
// ensure it's created only in browser and after modules resolved
|
||||
await createEditor();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (editor_view && typeof editor_view.destroy === 'function') {
|
||||
try { editor_view.destroy(); } catch (e) { /* ignore */ }
|
||||
editor_view = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions using the live editor_view (unchanged)
|
||||
const wrapSelection = (before: string, after: string = before) => {
|
||||
if (!editor_view) return;
|
||||
const state = editor_view.state;
|
||||
const changes = state.changeByRange((range: any) => {
|
||||
const isAlreadyWrapped =
|
||||
state.sliceDoc(range.from - before.length, range.from) === before &&
|
||||
state.sliceDoc(range.to, range.to + after.length) === after;
|
||||
|
||||
if (isAlreadyWrapped) {
|
||||
return {
|
||||
changes: [
|
||||
{ from: range.from - before.length, to: range.from, insert: '' },
|
||||
{ from: range.to, to: range.to + after.length, insert: '' }
|
||||
],
|
||||
range: (state as any).constructor.range(range.from - before.length, range.to - before.length)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
changes: [
|
||||
{ from: range.from, insert: before },
|
||||
{ from: range.to, insert: after }
|
||||
],
|
||||
range: (state as any).constructor.range(range.from + before.length, range.to + before.length)
|
||||
};
|
||||
});
|
||||
editor_view.dispatch(changes);
|
||||
editor_view.focus();
|
||||
};
|
||||
|
||||
const insertList = () => {
|
||||
if (!editor_view) return;
|
||||
const state = editor_view.state;
|
||||
const changes = state.changeByRange((range: any) => {
|
||||
const line = state.doc.lineAt(range.from);
|
||||
return {
|
||||
changes: [{ from: line.from, insert: '- ' }],
|
||||
range: (state as any).constructor.range(range.from + 2, range.to + 2)
|
||||
};
|
||||
});
|
||||
editor_view.dispatch(changes);
|
||||
editor_view.focus();
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="codemirror-wrapper border rounded">
|
||||
<div class="toolbar p-1 bg-gray-100 border-b">
|
||||
<button on:click={() => wrapSelection('**')} class="px-2 py-1 rounded hover:bg-gray-200"
|
||||
><b>B</b></button
|
||||
>
|
||||
<button on:click={() => wrapSelection('*')} class="px-2 py-1 rounded hover:bg-gray-200"
|
||||
><i>I</i></button
|
||||
>
|
||||
<button on:click={insertList} class="px-2 py-1 rounded hover:bg-gray-200">List</button>
|
||||
</div>
|
||||
<Codemirror
|
||||
bind:value={content}
|
||||
bind:view={editor}
|
||||
{placeholder}
|
||||
styles={{
|
||||
'&': {
|
||||
height: '100%',
|
||||
minHeight: '150px'
|
||||
},
|
||||
'.cm-scroller': {
|
||||
overflow: 'auto'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="toolbar p-1 bg-gray-100 border-b">
|
||||
<button on:click={() => wrapSelection('**')} class="px-2 py-1 rounded hover:bg-gray-200"><b>B</b></button>
|
||||
<button on:click={() => wrapSelection('*')} class="px-2 py-1 rounded hover:bg-gray-200"><i>I</i></button>
|
||||
<button on:click={insertList} class="px-2 py-1 rounded hover:bg-gray-200">List</button>
|
||||
</div>
|
||||
<div bind:this={editor_container} class="h-full min-h-[150px] overflow-auto"></div>
|
||||
</div>
|
||||
@@ -1,15 +1,21 @@
|
||||
<script lang="ts">
|
||||
import ElementCodemirrorEditor from './element_codemirror_editor.svelte';
|
||||
import ElementCodemirrorEditor from './element_codemirror_editor.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let html_text: string = '';
|
||||
export let placeholder: string = 'Type your text here...';
|
||||
export let classes: string = '';
|
||||
export let html_text: string = '';
|
||||
export let placeholder: string = 'Type your text here...';
|
||||
export let classes: string = '';
|
||||
</script>
|
||||
|
||||
<div class="block w-full h-full {classes}">
|
||||
<ElementCodemirrorEditor
|
||||
class="p-1 transition-all duration-1000"
|
||||
bind:content={html_text}
|
||||
{placeholder}
|
||||
/>
|
||||
</div>
|
||||
{#if browser}
|
||||
<ElementCodemirrorEditor
|
||||
class="p-1 transition-all duration-1000"
|
||||
bind:content={html_text}
|
||||
{placeholder}
|
||||
/>
|
||||
{:else}
|
||||
<!-- server / prerender placeholder to avoid SSR loading CM -->
|
||||
<div class="p-2 text-sm text-surface-600-400">Editor (client only)</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -36,9 +36,9 @@
|
||||
let prom_api__archive_content_obj: any = $state();
|
||||
let prom_api__archive_content_obj__hosted_file: any = $state();
|
||||
|
||||
let description_new_html = $state('');
|
||||
let description_new_html = $state($idaa_slct.archive_content_obj?.description ?? '');
|
||||
let description_changed = $state(false);
|
||||
let notes_new_html = $state('');
|
||||
let notes_new_html = $state($idaa_slct.archive_content_obj?.notes ?? '');
|
||||
let notes_changed = $state(false);
|
||||
let disable_submit_btn = $state(false);
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
<label for="description" class="ae_label w-full">
|
||||
<span class="legend text-sm font-semibold"> Description </span>
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.archive_content_obj.description}
|
||||
bind:html_text={description_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Your content description here..."
|
||||
/>
|
||||
@@ -910,7 +910,7 @@
|
||||
>Internal Staff Notes</span
|
||||
>
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.archive_content_obj.notes}
|
||||
bind:html_text={notes_new_html}
|
||||
classes="preset-tonal-surface preset-filled-surface-100-900"
|
||||
placeholder="Internal notes for staff only. Not shown to the public."
|
||||
/>
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
let prom_api__archive_obj: any;
|
||||
let prom_api__archive_obj__hosted_file: any;
|
||||
|
||||
let description_new_html = $state('');
|
||||
let description_new_html = $state($idaa_slct.archive_obj?.description ?? '');
|
||||
let description_changed = $state(false);
|
||||
let notes_new_html = $state('');
|
||||
let notes_new_html = $state($idaa_slct.archive_obj?.notes ?? '');
|
||||
let notes_changed = $state(false);
|
||||
let disable_submit_btn = true;
|
||||
|
||||
@@ -339,7 +339,7 @@
|
||||
<label for="description" class="ae_label w-full">
|
||||
<span class="legend text-sm font-semibold"> Description </span>
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.archive_obj.description}
|
||||
bind:html_text={description_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Your archive description here..."
|
||||
/>
|
||||
@@ -678,7 +678,7 @@
|
||||
>Internal Staff Notes</span
|
||||
>
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.archive_obj.notes}
|
||||
bind:html_text={notes_new_html}
|
||||
classes="preset-tonal-surface preset-filled-surface-100-900"
|
||||
placeholder="Internal notes for staff only. Not shown to the public."
|
||||
/>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
let prom_api__post_comment_obj: any = $state();
|
||||
|
||||
let content_new_html = $state('');
|
||||
let content_new_html = $state($idaa_slct.post_comment_obj?.content ?? '');
|
||||
let content_changed = $state(false);
|
||||
// let notes_new_html = $state('');
|
||||
// let notes_changed = $state(false);
|
||||
@@ -487,7 +487,7 @@
|
||||
</span>
|
||||
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.post_comment_obj?.content}
|
||||
bind:html_text={content_new_html}
|
||||
classes="bg-gray-100 dark:bg-gray-800"
|
||||
placeholder="Your post content here..."
|
||||
/>
|
||||
|
||||
@@ -61,10 +61,8 @@
|
||||
let prom_api__post_obj: any = $state();
|
||||
// let prom_api__post_obj__hosted_file: any;
|
||||
|
||||
let content_new_html = $state('');
|
||||
let content_changed = $state(false);
|
||||
let notes_new_html = $state('');
|
||||
let notes_changed = $state(false);
|
||||
let content_new_html = $state($idaa_slct.post_obj?.content ?? '');
|
||||
let notes_new_html = $state($idaa_slct.post_obj?.notes ?? '');
|
||||
let hosted_file_id_li = $state<string[]>([]); // Array of hosted file IDs
|
||||
let hosted_file_obj_li = $state<any[]>([]); // Array of hosted file objects
|
||||
let upload_complete = $state(false);
|
||||
@@ -372,8 +370,8 @@
|
||||
!obj_changed &&
|
||||
orig_post_obj?.id &&
|
||||
(JSON.stringify($idaa_slct.post_obj) !== JSON.stringify(orig_post_obj) ||
|
||||
content_changed ||
|
||||
notes_changed)
|
||||
content_new_html !== (orig_post_obj.content ?? '') ||
|
||||
notes_new_html !== (orig_post_obj.notes ?? ''))
|
||||
) {
|
||||
// console.log('Post object has changed from original.', $inspect(orig_post_obj));
|
||||
console.log('Post object has changed from original.', orig_post_obj);
|
||||
@@ -383,8 +381,8 @@
|
||||
obj_changed &&
|
||||
orig_post_obj?.id &&
|
||||
JSON.stringify($idaa_slct.post_obj) === JSON.stringify(orig_post_obj) &&
|
||||
!content_changed &&
|
||||
!notes_changed
|
||||
content_new_html === (orig_post_obj.content ?? '') &&
|
||||
notes_new_html === (orig_post_obj.notes ?? '')
|
||||
) {
|
||||
obj_changed = false;
|
||||
}
|
||||
@@ -483,14 +481,14 @@
|
||||
|
||||
{#if $ae_loc.administrator_access}
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.post_obj?.content}
|
||||
bind:html_text={content_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Your post content here..."
|
||||
/>
|
||||
{:else}
|
||||
<Tiptap_editor
|
||||
default_minimal={true}
|
||||
html_text={$idaa_slct.post_obj?.content}
|
||||
bind:html_text={content_new_html}
|
||||
show_button_kv={{
|
||||
// text: true,
|
||||
// bullet_list: true,
|
||||
@@ -498,8 +496,6 @@
|
||||
// link: true,
|
||||
// unset_link: true
|
||||
}}
|
||||
bind:new_html={content_new_html}
|
||||
bind:changed={content_changed}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Your post content here..."
|
||||
/>
|
||||
@@ -1043,7 +1039,7 @@
|
||||
>Internal Staff Notes</span
|
||||
>
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.post_obj.notes}
|
||||
bind:html_text={notes_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Internal notes for staff only. Not shown to the public."
|
||||
/>
|
||||
|
||||
@@ -57,15 +57,15 @@
|
||||
|
||||
let prom_api__event_obj: any = $state();
|
||||
|
||||
let description_new_html = $state('');
|
||||
let description_new_html = $state($idaa_slct.event_obj?.description ?? '');
|
||||
let description_changed = $state(false);
|
||||
let location_text_new_html = $state('');
|
||||
let location_text_new_html = $state($idaa_slct.event_obj?.location_text ?? '');
|
||||
let location_text_changed = $state(false);
|
||||
let attend_text_new_html = $state('');
|
||||
let attend_text_new_html = $state($idaa_slct.event_obj?.attend_text ?? '');
|
||||
let attend_text_changed = $state(false);
|
||||
let recurring_text_new_html = $state('');
|
||||
let recurring_text_new_html = $state($idaa_slct.event_obj?.recurring_text ?? '');
|
||||
let recurring_text_changed = $state(false);
|
||||
let notes_new_html = $state('');
|
||||
let notes_new_html = $state($idaa_slct.event_obj?.notes ?? '');
|
||||
let notes_changed = $state(false);
|
||||
|
||||
let disable_submit_btn = $state(true);
|
||||
@@ -933,7 +933,7 @@
|
||||
<!-- <textarea name="description" id="description" class="ae_value event__description tinymce_editor editor_basic textarea" rows="5" cols="70" bind:value={$idaa_slct.event_obj.description} ></textarea> -->
|
||||
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.event_obj?.description}
|
||||
bind:html_text={description_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900 font-mono font-normal"
|
||||
placeholder="A short description or overview of this recovery meeting"
|
||||
/>
|
||||
@@ -1311,7 +1311,7 @@
|
||||
</span>
|
||||
<!-- <textarea class="ae_value event__location_text tinymce_editor editor_less_100 textarea" id="location_text" name="location_text" placeholder="Additional information about the meeting location" rows="2" cols="70" bind:value={$idaa_slct.event_obj.location_text}></textarea> -->
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.event_obj?.location_text}
|
||||
bind:html_text={location_text_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Additional information about the meeting location"
|
||||
/>
|
||||
@@ -1780,7 +1780,7 @@
|
||||
</span>
|
||||
<!-- <textarea class="ae_value event__attend_text tinymce_editor editor_less_100 textarea" id="attend_text" name="attend_text" placeholder="Additional information on how to attend or join the meeting" rows="2" cols="70" value={$lq__event_obj?.attend_text ?? ''}></textarea> -->
|
||||
<Tiptap_editor
|
||||
html_text={$idaa_slct.event_obj?.attend_text}
|
||||
bind:html_text={attend_text_new_html}
|
||||
classes="preset-tonal-surface hover:preset-filled-surface-100-900"
|
||||
placeholder="Additional information on how to attend or join the meeting"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user