chore: move tiptap scss to styles/, trash orphaned codemirror files, update project doc
- element_tiptap_editor.scss → elements/styles/ (single importer path updated) - Trashed element_codemirror_editor.svelte and element_codemirror_editor_wrapper.svelte (zero importers — active component is element_editor_codemirror.svelte) - PROJECT__AE_Object_Field_Editor_V3_upgrade.md: mark v1/v2 removal complete, update status to 🟡 Mostly Complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# Project Plan: Aether AE Obj Field Editor v3 (Consolidated)
|
# Project Plan: Aether AE Obj Field Editor v3 (Consolidated)
|
||||||
|
|
||||||
> **Status:** 🔵 Active / Testing & Stabilization
|
> **Status:** 🟡 Mostly Complete — Phase 3 items + GUIDE update remaining
|
||||||
> **Date:** February 13, 2026
|
> **Date:** February 13, 2026 (last updated: 2026-03-20)
|
||||||
> **Target Component:** `src/lib/elements/element_ae_obj_field_editor_v3.svelte`
|
> **Target Component:** `src/lib/elements/element_ae_obj_field_editor_v3.svelte`
|
||||||
> **Replaces:** `element_ae_crud.svelte` and `element_ae_crud_v2.svelte`
|
> **Replaces:** `element_ae_crud.svelte` and `element_ae_crud_v2.svelte`
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ Consolidate the legacy CRUD components into a single, high-performance "Aether O
|
|||||||
|
|
||||||
### Phase 4: Migration & Cleanup
|
### Phase 4: Migration & Cleanup
|
||||||
- [x] Create a playground route for V3 verification (`/testing/ae_obj_field_editor_v3`).
|
- [x] Create a playground route for V3 verification (`/testing/ae_obj_field_editor_v3`).
|
||||||
- [ ] Deprecate and eventually remove `v1` and `v2` files.
|
- [x] Deprecate and remove `v1` and `v2` files — `element_ae_crud.svelte` and `element_ae_crud_v2.svelte` removed 2026-03-20.
|
||||||
- [ ] Update `GUIDE__Development.md` with the new usage patterns.
|
- [ ] Update `GUIDE__Development.md` with the new usage patterns.
|
||||||
|
|
||||||
## ⚠️ Security & Reliability Stabilization (NEW)
|
## ⚠️ Security & Reliability Stabilization (NEW)
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount, onDestroy } from 'svelte';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { ensureCodeMirrorModules } from './codemirror_modules';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
content?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { content = $bindable(''), placeholder = 'Start typing...' }: Props = $props();
|
|
||||||
|
|
||||||
let editor_container: HTMLDivElement | undefined = $state();
|
|
||||||
let editor_view: any;
|
|
||||||
let cm: any; // Declare cm at the top level
|
|
||||||
|
|
||||||
async function createEditor() {
|
|
||||||
if (!browser) return;
|
|
||||||
cm = await ensureCodeMirrorModules(); // Assign to the top-level cm
|
|
||||||
if (!cm) return;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Create editor
|
|
||||||
if (!editor_container) {
|
|
||||||
console.error('Editor container not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_view = new cm.EditorView({
|
|
||||||
state: cm.EditorState.create({
|
|
||||||
doc: content ?? '',
|
|
||||||
extensions
|
|
||||||
}),
|
|
||||||
parent: editor_container as HTMLElement
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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: cm.EditorSelection.range(
|
|
||||||
range.from - before.length,
|
|
||||||
range.to - before.length
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
changes: [
|
|
||||||
{ from: range.from, insert: before },
|
|
||||||
{ from: range.to, insert: after }
|
|
||||||
],
|
|
||||||
range: cm.EditorSelection.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: cm.EditorSelection.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
|
|
||||||
type="button"
|
|
||||||
onclick={() => wrapSelection('**')}
|
|
||||||
class="px-2 py-1 rounded hover:bg-gray-200"><b>B</b></button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onclick={() => wrapSelection('*')}
|
|
||||||
class="px-2 py-1 rounded hover:bg-gray-200"><i>I</i></button
|
|
||||||
>
|
|
||||||
<button type="button" onclick={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,29 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import AE_Comp_Editor_CodeMirror from './element_editor_codemirror.svelte';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
html_text?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
classes?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let {
|
|
||||||
html_text = $bindable(''),
|
|
||||||
placeholder = 'Type your text here...',
|
|
||||||
classes = ''
|
|
||||||
}: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="block w-full h-full {classes}">
|
|
||||||
{#if browser}
|
|
||||||
<AE_Comp_Editor_CodeMirror
|
|
||||||
bind:content={html_text}
|
|
||||||
{placeholder}
|
|
||||||
class_li="p-1 transition-all duration-1000"
|
|
||||||
/>
|
|
||||||
{: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>
|
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
/* Import your existing TipTap styles */
|
/* Import your existing TipTap styles */
|
||||||
@import './element_tiptap_editor.scss';
|
@import './styles/element_tiptap_editor.scss';
|
||||||
|
|
||||||
.ae__comp__editor_tiptap :global(.tiptap) {
|
.ae__comp__editor_tiptap :global(.tiptap) {
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
|
|||||||
Reference in New Issue
Block a user