refactor(data-stores): extract shared element_data_store_form component
New element_data_store_form.svelte handles all form fields for creating/
editing a Data Store. Replaces the inline form in the management page modal.
Features vs old inline form:
- Help text on every label (type descriptions, constraint notes, etc.)
- Advanced section (collapsible, hidden by default): Enable, Hide, Priority,
Sort, Group, Notes — each with hint text
- For ID: editable on new, read-only on edit (with explanation why)
- show_account_field / show_for_fields props for embedded widget use later
- html_edit_mode + show_advanced are internal state, reset via {#key} on parent
Management page: drops html_edit_mode state + Code/Eye/editor imports; uses
{#key editing_obj?.id ?? 'new'} to recreate the form on each record change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
320
src/lib/elements/element_data_store_form.svelte
Normal file
320
src/lib/elements/element_data_store_form.svelte
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Shared form fields for creating/editing a Data Store record.
|
||||||
|
* Does NOT include the modal wrapper or action buttons — the parent handles those.
|
||||||
|
*
|
||||||
|
* Usage contexts:
|
||||||
|
* - /core/data_stores/ management page (full fields, show_for_fields=true)
|
||||||
|
* - element_data_store.svelte inline editor (show_for_fields=false, show_account_field=false)
|
||||||
|
*/
|
||||||
|
import { ChevronDown, ChevronUp, Code, Eye } from '@lucide/svelte';
|
||||||
|
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
||||||
|
import AE_Comp_Editor_TipTap from '$lib/elements/element_editor_tiptap.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
// Core fields — always visible
|
||||||
|
draft_code: string;
|
||||||
|
draft_name: string;
|
||||||
|
draft_type: string;
|
||||||
|
draft_value: string; // content payload (text, json string, html, etc.)
|
||||||
|
|
||||||
|
// Context fields
|
||||||
|
draft_account_id?: string;
|
||||||
|
draft_for_type?: string;
|
||||||
|
draft_for_id?: string; // read-only when !is_new (DB stores integer FK; can't PATCH with string)
|
||||||
|
|
||||||
|
// Advanced / flag fields — shown inside the collapsible section
|
||||||
|
draft_enable?: boolean;
|
||||||
|
draft_hide?: boolean;
|
||||||
|
draft_priority?: boolean;
|
||||||
|
draft_sort?: string;
|
||||||
|
draft_group?: string;
|
||||||
|
draft_notes?: string;
|
||||||
|
|
||||||
|
// Display controls
|
||||||
|
is_new?: boolean; // true → for_id is editable; false → read-only
|
||||||
|
show_account_field?: boolean; // show account_id text input (false for embedded widget)
|
||||||
|
show_for_fields?: boolean; // show for_type + for_id (false for embedded widget)
|
||||||
|
readonly_code?: boolean; // lock the code field (non-manager users)
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
draft_code = $bindable(''),
|
||||||
|
draft_name = $bindable(''),
|
||||||
|
draft_type = $bindable('text'),
|
||||||
|
draft_value = $bindable(''),
|
||||||
|
draft_account_id = $bindable(''),
|
||||||
|
draft_for_type = $bindable(''),
|
||||||
|
draft_for_id = $bindable(''),
|
||||||
|
draft_enable = $bindable(true),
|
||||||
|
draft_hide = $bindable(false),
|
||||||
|
draft_priority = $bindable(false),
|
||||||
|
draft_sort = $bindable(''),
|
||||||
|
draft_group = $bindable(''),
|
||||||
|
draft_notes = $bindable(''),
|
||||||
|
is_new = false,
|
||||||
|
show_account_field = true,
|
||||||
|
show_for_fields = true,
|
||||||
|
readonly_code = false,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
// Internal — not bindable; resets automatically when parent uses {#key} on record change
|
||||||
|
let html_edit_mode = $state<'source' | 'visual'>('source');
|
||||||
|
let show_advanced = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
|
||||||
|
<!-- ── Code + Name ───────────────────────────────────────────────────────── -->
|
||||||
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<label class="label space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Code <span class="text-error-500">*</span>
|
||||||
|
<span class="font-normal opacity-60">— unique key, snake_case convention</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input font-mono text-sm"
|
||||||
|
bind:value={draft_code}
|
||||||
|
required
|
||||||
|
readonly={readonly_code}
|
||||||
|
placeholder="event__my_event__my_store" />
|
||||||
|
</label>
|
||||||
|
<label class="label space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Name <span class="text-error-500">*</span>
|
||||||
|
<span class="font-normal opacity-60">— human-readable label</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input"
|
||||||
|
bind:value={draft_name}
|
||||||
|
required
|
||||||
|
placeholder="My Data Store" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── Type + Account ID ─────────────────────────────────────────────────── -->
|
||||||
|
<div class="grid grid-cols-1 gap-4 {show_account_field ? 'md:grid-cols-2' : ''}">
|
||||||
|
<label class="label space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Type
|
||||||
|
<span class="font-normal opacity-60">— determines how content is stored and rendered</span>
|
||||||
|
</span>
|
||||||
|
<select class="select" bind:value={draft_type}>
|
||||||
|
<option value="text">Text — plain text or string value</option>
|
||||||
|
<option value="html">HTML — rich markup (CodeMirror source / TipTap visual)</option>
|
||||||
|
<option value="json">JSON — structured data, parsed on save</option>
|
||||||
|
<option value="md">Markdown — rendered at display time</option>
|
||||||
|
<option value="sql">SQL — query string, stored as text</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{#if show_account_field}
|
||||||
|
<label class="label space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Account ID
|
||||||
|
<span class="font-normal opacity-60">— blank = global (shared across all accounts)</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input font-mono text-sm"
|
||||||
|
bind:value={draft_account_id}
|
||||||
|
placeholder="Leave blank for global" />
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── For Type + For ID ─────────────────────────────────────────────────── -->
|
||||||
|
{#if show_for_fields}
|
||||||
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<label class="label space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
For Type
|
||||||
|
<span class="font-normal opacity-60">— polymorphic parent object type</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input font-mono text-sm"
|
||||||
|
bind:value={draft_for_type}
|
||||||
|
placeholder="event, event_session, person, journal_entry…" />
|
||||||
|
</label>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
For ID
|
||||||
|
<span class="font-normal opacity-60">— random string ID of the parent object</span>
|
||||||
|
</span>
|
||||||
|
{#if is_new}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input font-mono text-sm"
|
||||||
|
bind:value={draft_for_id}
|
||||||
|
placeholder="random string ID" />
|
||||||
|
<p class="text-[10px] opacity-50">
|
||||||
|
The backend resolves this string to an integer FK on create.
|
||||||
|
Once set, For ID cannot be changed via the UI.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<!-- for_id is stored as an integer FK in DB; backend resolves string→int on create
|
||||||
|
but rejects the string on PATCH. Read-only after creation. -->
|
||||||
|
<div class="input bg-surface-200-700-token flex items-center font-mono text-sm opacity-70">
|
||||||
|
{#if draft_for_id}
|
||||||
|
{draft_for_id}
|
||||||
|
{:else}
|
||||||
|
<span class="italic opacity-50">not set</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p class="text-[10px] opacity-50">
|
||||||
|
For ID is fixed at creation time (DB stores an integer FK). To change it, update the record directly in phpMyAdmin.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- ── Content editor ────────────────────────────────────────────────────── -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Content
|
||||||
|
{#if draft_type === 'json'}
|
||||||
|
<span class="font-normal opacity-60">— parsed as JSON on save; invalid JSON is stored as-is</span>
|
||||||
|
{:else if draft_type === 'sql'}
|
||||||
|
<span class="font-normal opacity-60">— stored as raw SQL string; not executed here</span>
|
||||||
|
{:else if draft_type === 'md'}
|
||||||
|
<span class="font-normal opacity-60">— Markdown; rendered by the display component</span>
|
||||||
|
{:else if draft_type === 'html'}
|
||||||
|
<span class="font-normal opacity-60">— HTML markup; rendered as raw HTML in display</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
{#if draft_type === 'html'}
|
||||||
|
<div class="flex items-center gap-1 rounded bg-surface-500/10 p-0.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
|
||||||
|
class:bg-primary-500={html_edit_mode === 'source'}
|
||||||
|
class:text-white={html_edit_mode === 'source'}
|
||||||
|
class:opacity-50={html_edit_mode !== 'source'}
|
||||||
|
onclick={() => (html_edit_mode = 'source')}
|
||||||
|
title="Edit raw HTML source">
|
||||||
|
<Code size="10" /> Source
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
|
||||||
|
class:bg-primary-500={html_edit_mode === 'visual'}
|
||||||
|
class:text-white={html_edit_mode === 'visual'}
|
||||||
|
class:opacity-50={html_edit_mode !== 'visual'}
|
||||||
|
onclick={() => (html_edit_mode = 'visual')}
|
||||||
|
title="WYSIWYG visual editor">
|
||||||
|
<Eye size="10" /> Visual
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if draft_type === 'html'}
|
||||||
|
{#if html_edit_mode === 'source'}
|
||||||
|
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter HTML source…" />
|
||||||
|
{:else}
|
||||||
|
<AE_Comp_Editor_TipTap bind:content={draft_value} placeholder="Enter HTML content…" />
|
||||||
|
{/if}
|
||||||
|
{:else if draft_type === 'json' || draft_type === 'sql' || draft_type === 'md'}
|
||||||
|
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter {draft_type.toUpperCase()} content…" />
|
||||||
|
{:else}
|
||||||
|
<textarea
|
||||||
|
bind:value={draft_value}
|
||||||
|
class="textarea font-mono text-sm"
|
||||||
|
rows="10"
|
||||||
|
placeholder="Enter text content…"></textarea>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── Advanced section (collapsible) ────────────────────────────────────── -->
|
||||||
|
<div class="overflow-hidden rounded-lg border border-surface-500/20">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center justify-between bg-surface-500/5 px-4 py-2 text-[10px] font-bold uppercase tracking-widest opacity-50 transition-opacity hover:opacity-80"
|
||||||
|
onclick={() => (show_advanced = !show_advanced)}>
|
||||||
|
<span>Advanced — Enable · Hide · Priority · Sort · Group · Notes</span>
|
||||||
|
{#if show_advanced}
|
||||||
|
<ChevronUp size={12} />
|
||||||
|
{:else}
|
||||||
|
<ChevronDown size={12} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if show_advanced}
|
||||||
|
<div class="space-y-4 p-4">
|
||||||
|
|
||||||
|
<!-- Flags -->
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-[10px] font-bold uppercase tracking-widest opacity-40">Flags</p>
|
||||||
|
<div class="flex flex-wrap gap-x-6 gap-y-2">
|
||||||
|
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
||||||
|
<input type="checkbox" class="checkbox" bind:checked={draft_enable} />
|
||||||
|
<span>
|
||||||
|
Enable
|
||||||
|
<span class="ml-1 text-[10px] font-normal opacity-50">— record is active and queryable</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
||||||
|
<input type="checkbox" class="checkbox" bind:checked={draft_hide} />
|
||||||
|
<span>
|
||||||
|
Hide
|
||||||
|
<span class="ml-1 text-[10px] font-normal opacity-50">— suppress from standard display</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
||||||
|
<input type="checkbox" class="checkbox" bind:checked={draft_priority} />
|
||||||
|
<span>
|
||||||
|
Priority
|
||||||
|
<span class="ml-1 text-[10px] font-normal opacity-50">— float above other records in sort</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sort + Group -->
|
||||||
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
<label class="space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Sort
|
||||||
|
<span class="font-normal opacity-60">— numeric ordering; lower = higher position</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input input-sm font-mono text-xs"
|
||||||
|
bind:value={draft_sort}
|
||||||
|
placeholder="0" />
|
||||||
|
</label>
|
||||||
|
<label class="space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Group
|
||||||
|
<span class="font-normal opacity-60">— optional grouping or category label</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input input-sm font-mono text-xs"
|
||||||
|
bind:value={draft_group}
|
||||||
|
placeholder="group_name" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notes -->
|
||||||
|
<label class="space-y-1">
|
||||||
|
<span class="text-xs font-bold opacity-70">
|
||||||
|
Notes
|
||||||
|
<span class="font-normal opacity-60">— internal reference only; not shown to end users</span>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input text-sm"
|
||||||
|
bind:value={draft_notes}
|
||||||
|
placeholder="Internal notes or context…" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -11,9 +11,7 @@ import {
|
|||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
Code,
|
|
||||||
Database,
|
Database,
|
||||||
Eye,
|
|
||||||
Filter,
|
Filter,
|
||||||
LoaderCircle,
|
LoaderCircle,
|
||||||
Pencil,
|
Pencil,
|
||||||
@@ -30,8 +28,7 @@ import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
|||||||
import { db_core } from '$lib/ae_core/db_core';
|
import { db_core } from '$lib/ae_core/db_core';
|
||||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||||
import type { ae_DataStore } from '$lib/types/ae_types';
|
import type { ae_DataStore } from '$lib/types/ae_types';
|
||||||
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
import AE_DataStore_Form from '$lib/elements/element_data_store_form.svelte';
|
||||||
import AE_Comp_Editor_TipTap from '$lib/elements/element_editor_tiptap.svelte';
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!$ae_loc.manager_access) {
|
if (!$ae_loc.manager_access) {
|
||||||
@@ -73,7 +70,6 @@ let draft_priority = $state(false);
|
|||||||
let draft_sort = $state('');
|
let draft_sort = $state('');
|
||||||
let draft_group = $state('');
|
let draft_group = $state('');
|
||||||
let draft_notes = $state('');
|
let draft_notes = $state('');
|
||||||
let html_edit_mode = $state<'source' | 'visual'>('source');
|
|
||||||
let submit_status = $state<'idle' | 'processing' | 'saved' | 'error'>('idle');
|
let submit_status = $state<'idle' | 'processing' | 'saved' | 'error'>('idle');
|
||||||
|
|
||||||
// ── Bulk rename state ─────────────────────────────────────────────────────────
|
// ── Bulk rename state ─────────────────────────────────────────────────────────
|
||||||
@@ -170,7 +166,6 @@ function open_edit(obj: ae_DataStore) {
|
|||||||
? obj.json
|
? obj.json
|
||||||
: JSON.stringify(obj.json ?? '', null, 2)
|
: JSON.stringify(obj.json ?? '', null, 2)
|
||||||
: (obj.text ?? obj.html ?? '');
|
: (obj.text ?? obj.html ?? '');
|
||||||
html_edit_mode = 'source';
|
|
||||||
submit_status = 'idle';
|
submit_status = 'idle';
|
||||||
show_edit = true;
|
show_edit = true;
|
||||||
}
|
}
|
||||||
@@ -191,7 +186,6 @@ function open_new() {
|
|||||||
draft_sort = '';
|
draft_sort = '';
|
||||||
draft_group = '';
|
draft_group = '';
|
||||||
draft_notes = '';
|
draft_notes = '';
|
||||||
html_edit_mode = 'source';
|
|
||||||
submit_status = 'idle';
|
submit_status = 'idle';
|
||||||
show_edit = true;
|
show_edit = true;
|
||||||
}
|
}
|
||||||
@@ -748,174 +742,24 @@ function content_preview(ds: ae_DataStore): string {
|
|||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
onsubmit={(e) => { e.preventDefault(); handle_save(); }}>
|
onsubmit={(e) => { e.preventDefault(); handle_save(); }}>
|
||||||
|
|
||||||
<!-- Code + Name -->
|
<!-- {#key} recreates the form on each new record, resetting internal state (html_edit_mode, show_advanced) -->
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
{#key editing_obj?.id ?? 'new'}
|
||||||
<label class="label space-y-1">
|
<AE_DataStore_Form
|
||||||
<span class="text-xs font-bold opacity-70">Code <span class="text-error-500">*</span></span>
|
bind:draft_code
|
||||||
<input
|
bind:draft_name
|
||||||
type="text"
|
bind:draft_type
|
||||||
class="input font-mono text-sm"
|
bind:draft_value
|
||||||
bind:value={draft_code}
|
bind:draft_account_id
|
||||||
required
|
bind:draft_for_type
|
||||||
placeholder="my_data_store_code" />
|
bind:draft_for_id
|
||||||
</label>
|
bind:draft_enable
|
||||||
<label class="label space-y-1">
|
bind:draft_hide
|
||||||
<span class="text-xs font-bold opacity-70">Name <span class="text-error-500">*</span></span>
|
bind:draft_priority
|
||||||
<input
|
bind:draft_sort
|
||||||
type="text"
|
bind:draft_group
|
||||||
class="input"
|
bind:draft_notes
|
||||||
bind:value={draft_name}
|
{is_new} />
|
||||||
required
|
{/key}
|
||||||
placeholder="Human-readable name" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Type + Account ID -->
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
||||||
<label class="label space-y-1">
|
|
||||||
<span class="text-xs font-bold opacity-70">Type</span>
|
|
||||||
<select class="select" bind:value={draft_type}>
|
|
||||||
<option value="text">Text</option>
|
|
||||||
<option value="html">HTML</option>
|
|
||||||
<option value="json">JSON</option>
|
|
||||||
<option value="md">Markdown</option>
|
|
||||||
<option value="sql">SQL</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="label space-y-1">
|
|
||||||
<span class="text-xs font-bold opacity-70">
|
|
||||||
Account ID
|
|
||||||
<span class="font-normal opacity-60">— blank = global</span>
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input font-mono text-sm"
|
|
||||||
bind:value={draft_account_id}
|
|
||||||
placeholder="Leave blank for global (no account)" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- For Type + For ID -->
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
||||||
<label class="label space-y-1">
|
|
||||||
<span class="text-xs font-bold opacity-70">
|
|
||||||
For Type
|
|
||||||
<span class="font-normal opacity-60">— polymorphic parent object type</span>
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input font-mono text-sm"
|
|
||||||
bind:value={draft_for_type}
|
|
||||||
placeholder="event, event_session, person, journal_entry…" />
|
|
||||||
</label>
|
|
||||||
<div class="space-y-1">
|
|
||||||
<span class="text-xs font-bold opacity-70">
|
|
||||||
For ID
|
|
||||||
<span class="font-normal opacity-60">— random string ID of parent</span>
|
|
||||||
</span>
|
|
||||||
{#if is_new}
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input font-mono text-sm"
|
|
||||||
bind:value={draft_for_id}
|
|
||||||
placeholder="random string ID" />
|
|
||||||
{:else}
|
|
||||||
<!-- for_id is stored as an integer FK in DB; backend resolves string→int on create
|
|
||||||
but does not accept the string on PATCH. Shown read-only here. -->
|
|
||||||
<div class="input bg-surface-200-700-token flex items-center font-mono text-sm opacity-70">
|
|
||||||
{#if editing_obj?.for_id}
|
|
||||||
{editing_obj.for_id}
|
|
||||||
{:else}
|
|
||||||
<span class="italic opacity-50">not set</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<p class="text-[10px] opacity-50">
|
|
||||||
For ID is set at creation time. To change it, use phpMyAdmin (DB stores the integer FK).
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Flags row -->
|
|
||||||
<div class="border-surface-500/20 flex flex-wrap items-center gap-x-6 gap-y-2 rounded-lg border bg-surface-500/10 px-4 py-3">
|
|
||||||
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
|
||||||
<input type="checkbox" class="checkbox" bind:checked={draft_enable} />
|
|
||||||
Enable
|
|
||||||
</label>
|
|
||||||
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
|
||||||
<input type="checkbox" class="checkbox" bind:checked={draft_hide} />
|
|
||||||
Hide
|
|
||||||
</label>
|
|
||||||
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
|
||||||
<input type="checkbox" class="checkbox" bind:checked={draft_priority} />
|
|
||||||
Priority
|
|
||||||
</label>
|
|
||||||
<label class="flex items-center gap-2 text-sm">
|
|
||||||
<span class="opacity-70 shrink-0">Sort:</span>
|
|
||||||
<input type="text" class="input input-sm w-20 font-mono text-xs" bind:value={draft_sort} placeholder="0" />
|
|
||||||
</label>
|
|
||||||
<label class="flex items-center gap-2 text-sm">
|
|
||||||
<span class="opacity-70 shrink-0">Group:</span>
|
|
||||||
<input type="text" class="input input-sm w-28 font-mono text-xs" bind:value={draft_group} placeholder="group" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Notes -->
|
|
||||||
<label class="label space-y-1">
|
|
||||||
<span class="text-xs font-bold opacity-70">Notes</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input text-sm"
|
|
||||||
bind:value={draft_notes}
|
|
||||||
placeholder="Optional notes" />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- Content editor -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-xs font-bold opacity-70">Content</span>
|
|
||||||
{#if draft_type === 'html'}
|
|
||||||
<div class="flex items-center gap-1 rounded bg-surface-500/10 p-0.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
|
|
||||||
class:bg-primary-500={html_edit_mode === 'source'}
|
|
||||||
class:text-white={html_edit_mode === 'source'}
|
|
||||||
class:opacity-50={html_edit_mode !== 'source'}
|
|
||||||
onclick={() => (html_edit_mode = 'source')}
|
|
||||||
title="Edit raw HTML source">
|
|
||||||
<Code size="10" /> Source
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
|
|
||||||
class:bg-primary-500={html_edit_mode === 'visual'}
|
|
||||||
class:text-white={html_edit_mode === 'visual'}
|
|
||||||
class:opacity-50={html_edit_mode !== 'visual'}
|
|
||||||
onclick={() => (html_edit_mode = 'visual')}
|
|
||||||
title="Visual / WYSIWYG editor">
|
|
||||||
<Eye size="10" /> Visual
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if draft_type === 'html'}
|
|
||||||
{#if html_edit_mode === 'source'}
|
|
||||||
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter HTML source…" />
|
|
||||||
{:else}
|
|
||||||
<AE_Comp_Editor_TipTap bind:content={draft_value} placeholder="Enter HTML content…" />
|
|
||||||
{/if}
|
|
||||||
{:else if draft_type === 'json' || draft_type === 'sql' || draft_type === 'md'}
|
|
||||||
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter {draft_type.toUpperCase()} content…" />
|
|
||||||
{:else}
|
|
||||||
<textarea
|
|
||||||
bind:value={draft_value}
|
|
||||||
class="textarea font-mono text-sm"
|
|
||||||
rows="12"
|
|
||||||
placeholder="Enter text content…"></textarea>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer actions -->
|
<!-- Footer actions -->
|
||||||
<div class="flex items-center justify-between border-t border-surface-500/20 pt-4">
|
<div class="flex items-center justify-between border-t border-surface-500/20 pt-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user