diff --git a/src/authoring_service/templates.py b/src/authoring_service/templates.py index 0f1a04d..c6e7dad 100755 --- a/src/authoring_service/templates.py +++ b/src/authoring_service/templates.py @@ -1556,13 +1556,27 @@ HIDDEN_APP_HTML = r""" background: rgba(251, 241, 220, 0.06); } .arc-card.active, .chapter-card.active { border-color: var(--blue); background: rgba(117, 169, 189, 0.16); } + .moment-card.active { border-color: var(--gold); background: rgba(211, 166, 77, 0.12); } .moment-card { border: 1px solid rgba(232, 202, 139, 0.14); color: #efd9b2; font-size: 13px; } .moment-card strong, .arc-card strong, .chapter-card strong { overflow-wrap: anywhere; } - .arc-toolbar { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; } + .arc-toolbar { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; } + .workspace-switcher { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; margin: 14px 0; } + .workspace-switcher button.active { border-color: var(--gold); background: rgba(211, 166, 77, 0.22); } + body:not(.arc-workspace) #arcStudioPanel { display: none; } + body.arc-workspace .story-workspace-only, + body.arc-workspace .drawer > :not(#arcStudioPanel) { display: none !important; } + body.arc-workspace #arcStudioPanel { display: block; margin-top: 0; } + .arc-node, .chapter-node, .moment-node { cursor: pointer; } + .arc-node circle, .chapter-node circle, .moment-node circle { transition: stroke-width 160ms ease, opacity 160ms ease; } + .arc-node.selected circle, .chapter-node.selected circle, .moment-node.selected circle { stroke: #fff3c7; stroke-width: 4; } + .arc-branch { fill: none; stroke: rgba(117, 169, 189, 0.34); stroke-width: 2; } + .arc-moment-path { fill: none; stroke: rgba(232, 202, 139, 0.26); stroke-width: 1.4; } + .arc-map-label { fill: #f7ead3; font-size: 20px; font-weight: 800; paint-order: stroke; stroke: rgba(14, 11, 8, 0.9); stroke-width: 4px; } + .arc-map-meta { fill: rgba(248, 234, 209, 0.68); font-size: 13px; paint-order: stroke; stroke: rgba(14, 11, 8, 0.9); stroke-width: 3px; } .story-cover { border: 1px solid rgba(232, 202, 139, 0.18); border-radius: 8px; @@ -1820,40 +1834,44 @@ HIDDEN_APP_HTML = r"""
@@ -1889,12 +1907,12 @@ HIDDEN_APP_HTML = r""" - - + + - - + +
@@ -1957,6 +1975,7 @@ HIDDEN_APP_HTML = r"""
+
@@ -1993,7 +2012,9 @@ HIDDEN_APP_HTML = r"""
+ +
@@ -2083,7 +2104,7 @@ HIDDEN_APP_HTML = r""" const draftKey = "hiddenNarrativeObservatoryDraft:v1"; const WORLD = { width: 4600, height: 3300, cx: 2300, cy: 1650 }; const SCREEN = { width: 1000, height: 760 }; - const state = { entries: [], stories: [], lifeArcs: [], meta: {}, mode: "graph", editorMode: "memory", selectedId: "", selectedStoryId: "", selectedArcId: "", selectedChapterId: "", editingSectionId: "", readerIndex: 0, undo: [], redo: [], draggingId: "", positions: new Map(), clusterPositions: new Map(), zoom: 0, focusCluster: "", focusKind: "", focusIds: null, focusLabel: "", history: [], explorationUndo: [], explorationRedo: [], camera: { x: 0, y: 0, k: 0.22 }, pan: null, drag: null, pendingDrag: null, renderTimer: 0 }; + const state = { entries: [], stories: [], lifeArcs: [], meta: {}, workspace: "stories", mode: "graph", editorMode: "memory", selectedId: "", selectedStoryId: "", selectedArcId: "", selectedChapterId: "", selectedMomentId: "", editingSectionId: "", readerIndex: 0, undo: [], redo: [], draggingId: "", positions: new Map(), clusterPositions: new Map(), zoom: 0, focusCluster: "", focusKind: "", focusIds: null, focusLabel: "", history: [], explorationUndo: [], explorationRedo: [], camera: { x: 0, y: 0, k: 0.22 }, pan: null, drag: null, pendingDrag: null, renderTimer: 0 }; const fields = ["title","type","contentClass","familyLayer","characters","tone","rarity","discoveryDifficulty","mysteryLevel","resonanceScore","pageLocation","triggerConditions","content","surfaces","observatoryRole","canonicalUse","symbols","narrativeArcs","tags","emotionalRole","continuationLinks","echoes","thematicLinks","symbolicLinks","triggerLinks","parentLinks","childLinks","mirroredEntries","enabled","cssClassHooks","audioSettings","animationTrigger","notes"]; const storyFields = ["storyTitle","storyDescription","storyTone","storyDiscovery","storyCharacters","storySymbols","storyMarkers","storyLayers","storyUnlock","storyHidden"]; const arcFields = ["arcTitle","arcDomain","arcVisibility","arcTone","arcThemes","arcSummary","arcOrgWeb"]; @@ -2251,7 +2272,7 @@ HIDDEN_APP_HTML = r""" $("status").textContent = message; $("autosave").textContent = message; } - function setEditorMode(mode, detail = "") { + function setEditorMode(mode, detail = "") { state.editorMode = mode === "story" ? "story" : "memory"; $("editorModebar").querySelectorAll("[data-editor-mode]").forEach((button) => button.classList.toggle("active", button.dataset.editorMode === state.editorMode)); $("storyEditorPanel").classList.toggle("active-editor", state.editorMode === "story"); @@ -2262,8 +2283,23 @@ HIDDEN_APP_HTML = r""" const entry = currentEntry(); $("editingBanner").innerHTML = state.editorMode === "story" ? `Editing Story: ${html(story?.title || "No story selected")}
${html(detail || "Shape the route, pacing, sections, and emotional sequence.")}` - : `Editing Memory: ${html(entry?.title || "No memory selected")}
${html(detail || "Craft the reusable fragment and choose where it can recur.")}`; - } + : `Editing Memory: ${html(entry?.title || "No memory selected")}
${html(detail || "Craft the reusable fragment and choose where it can recur.")}`; + } + function setWorkspace(workspace) { + state.workspace = workspace === "arcs" ? "arcs" : "stories"; + document.body.classList.toggle("arc-workspace", state.workspace === "arcs"); + $("workspaceSwitcher").querySelectorAll("[data-workspace]").forEach((button) => button.classList.toggle("active", button.dataset.workspace === state.workspace)); + if (state.workspace === "arcs") { + if (!state.selectedArcId) state.selectedArcId = state.lifeArcs[0]?.id || ""; + if (!state.selectedChapterId) state.selectedChapterId = arcById()?.chapters?.[0]?.id || ""; + fillArcForm(arcById()); + fitCameraTo({ minX: 520, minY: 420, maxX: WORLD.width - 520, maxY: WORLD.height - 420 }, true); + } else { + setEditorMode(state.editorMode); + fitCameraTo({ minX: 250, minY: 220, maxX: WORLD.width - 250, maxY: WORLD.height - 220 }, true); + } + render(); + } function cameraTransform() { return `translate(${state.camera.x} ${state.camera.y}) scale(${state.camera.k})`; } @@ -2546,6 +2582,7 @@ HIDDEN_APP_HTML = r""" state.selectedArcId = state.lifeArcs[index].id; rememberDraft(); renderArcStudio(); + if (state.workspace === "arcs") renderArcConstellation(); } function applyChapterFormToState() { const arc = arcById(); @@ -2556,6 +2593,7 @@ HIDDEN_APP_HTML = r""" state.selectedChapterId = arc.chapters[index].id; rememberDraft(); renderArcStudio(); + if (state.workspace === "arcs") renderArcConstellation(); } function fillArcForm(arc) { if (!arc) { @@ -2611,8 +2649,10 @@ HIDDEN_APP_HTML = r""" state.lifeArcs.unshift(arc); state.selectedArcId = arc.id; state.selectedChapterId = ""; + clearMomentForm(); fillArcForm(arc); rememberDraft(); + render(); } function deleteArc() { const arc = arcById(); @@ -2621,10 +2661,12 @@ HIDDEN_APP_HTML = r""" state.lifeArcs = state.lifeArcs.filter((item) => item.id !== arc.id); state.selectedArcId = state.lifeArcs[0]?.id || ""; state.selectedChapterId = arcById()?.chapters?.[0]?.id || ""; + clearMomentForm(); fillArcForm(arcById()); rememberDraft(); + render(); } - function newChapter() { + async function newChapter() { if (!arcById()) newArc(); const arc = arcById(); if (!arc) return; @@ -2641,8 +2683,11 @@ HIDDEN_APP_HTML = r""" }; arc.chapters = [chapter, ...(arc.chapters || [])]; state.selectedChapterId = chapter.id; + clearMomentForm(); fillChapterForm(chapter); rememberDraft(); + render(); + setStatus("new chapter created. Give it a title, then save the chapter."); } function deleteChapter() { const arc = arcById(); @@ -2651,12 +2696,19 @@ HIDDEN_APP_HTML = r""" snapshot(); arc.chapters = (arc.chapters || []).filter((item) => item.id !== chapter.id); state.selectedChapterId = arc.chapters[0]?.id || ""; + clearMomentForm(); fillChapterForm(chapterById()); rememberDraft(); + render(); } - function addMoment() { + async function saveChapter() { + if (!chapterById()) return setStatus("create or select a chapter first."); + applyChapterFormToState(); + await save("chapter saved."); + } + async function addMoment() { const chapter = chapterById(); - if (!chapter) return; + if (!chapter) return setStatus("create or select a chapter before adding a moment."); const text = $("momentText").value.trim(); const lesson = $("momentLesson").value.trim(); if (!text && !lesson) { @@ -2664,8 +2716,9 @@ HIDDEN_APP_HTML = r""" return; } snapshot(); - chapter.moments = [{ - id: `moment-${Date.now()}`, + const updatingMoment = Boolean(state.selectedMomentId); + const moment = { + id: state.selectedMomentId || `moment-${Date.now()}`, date: $("momentDate").value, kind: $("momentKind").value || "moment", text, @@ -2676,17 +2729,54 @@ HIDDEN_APP_HTML = r""" visibility: $("momentVisibility").value || "private", linkedStoryId: $("momentStoryLink").value, linkedEntryId: $("momentEntryLink").value, - }, ...(chapter.moments || [])]; - ["momentText","momentFeeling","momentMetric","momentLesson","momentSource"].forEach((id) => $(id).value = ""); + }; + if (state.selectedMomentId) { + const index = (chapter.moments || []).findIndex((item) => item.id === state.selectedMomentId); + if (index >= 0) chapter.moments[index] = moment; + } else { + chapter.moments = [moment, ...(chapter.moments || [])]; + } + clearMomentForm(); renderArcStudio(); + if (state.workspace === "arcs") renderArcConstellation(); rememberDraft(); + await save(updatingMoment ? "moment updated." : "moment added."); + } + function fillMomentForm(moment) { + if (!moment) return clearMomentForm(); + state.selectedMomentId = moment.id; + $("momentDate").value = moment.date || ""; + $("momentKind").value = moment.kind || "moment"; + $("momentText").value = moment.text || ""; + $("momentFeeling").value = moment.feeling || ""; + $("momentMetric").value = moment.metric || ""; + $("momentLesson").value = moment.lesson || ""; + $("momentSource").value = moment.sourcePointer || ""; + $("momentVisibility").value = moment.visibility || "private"; + $("momentStoryLink").value = moment.linkedStoryId || ""; + $("momentEntryLink").value = moment.linkedEntryId || ""; + $("addMomentBtn").textContent = "Update moment"; + $("cancelMomentBtn").classList.remove("hidden"); + renderArcStudio(); + } + function clearMomentForm() { + state.selectedMomentId = ""; + ["momentDate","momentText","momentFeeling","momentMetric","momentLesson","momentSource"].forEach((id) => $(id).value = ""); + $("momentKind").value = "moment"; + $("momentVisibility").value = "private"; + $("momentStoryLink").value = ""; + $("momentEntryLink").value = ""; + $("addMomentBtn").textContent = "Add moment"; + $("cancelMomentBtn").classList.add("hidden"); } function deleteMoment(momentId) { const chapter = chapterById(); if (!chapter) return; snapshot(); chapter.moments = (chapter.moments || []).filter((moment) => moment.id !== momentId); + if (state.selectedMomentId === momentId) clearMomentForm(); renderArcStudio(); + if (state.workspace === "arcs") renderArcConstellation(); rememberDraft(); } function renderArcStudio() { @@ -2698,18 +2788,23 @@ HIDDEN_APP_HTML = r""" $("arcList").querySelectorAll("[data-arc]").forEach((button) => button.addEventListener("click", () => { state.selectedArcId = button.dataset.arc; state.selectedChapterId = arcById()?.chapters?.[0]?.id || ""; + clearMomentForm(); fillArcForm(arcById()); + if (state.workspace === "arcs") renderArcConstellation(); })); $("chapterList").innerHTML = arc?.chapters?.length ? arc.chapters.map((item) => ``).join("") : "No chapters in this arc yet."; $("chapterList").querySelectorAll("[data-chapter]").forEach((button) => button.addEventListener("click", () => { state.selectedChapterId = button.dataset.chapter; + clearMomentForm(); fillChapterForm(chapterById()); + if (state.workspace === "arcs") renderArcConstellation(); })); $("momentList").innerHTML = chapter?.moments?.length - ? chapter.moments.map((moment) => `
${html(moment.date || moment.kind || "moment")}${html(moment.text || moment.lesson)}${[moment.feeling, moment.metric, moment.sourcePointer, moment.visibility].filter(Boolean).map(html).join(" / ")}
`).join("") + ? chapter.moments.map((moment) => `
${html(moment.date || moment.kind || "moment")}${html(moment.text || moment.lesson)}${[moment.feeling, moment.metric, moment.sourcePointer, moment.visibility].filter(Boolean).map(html).join(" / ")}
`).join("") : "Log compact moments here, then turn selected material into the chapter story draft."; + $("momentList").querySelectorAll("[data-edit-moment]").forEach((button) => button.addEventListener("click", () => fillMomentForm((chapter.moments || []).find((moment) => moment.id === button.dataset.editMoment)))); $("momentList").querySelectorAll("[data-delete-moment]").forEach((button) => button.addEventListener("click", () => deleteMoment(button.dataset.deleteMoment))); } function fillStoryForm(story) { @@ -3098,13 +3193,103 @@ HIDDEN_APP_HTML = r""" function characterColor(character) { return state.meta.characterRegistry?.[character]?.territoryColor || ""; } - function hash(value) { + function hash(value) { let h = 0; for (let i = 0; i < value.length; i += 1) h = (h * 31 + value.charCodeAt(i)) % 9999; - return h; - } - function render() { - updateSemanticZoom(); + return h; + } + function renderArcConstellation() { + const graph = $("graph"); + const arcs = state.lifeArcs; + const selectedArc = arcById(); + const selectedChapter = chapterById(); + const arcPositions = new Map(); + const radius = arcs.length > 1 ? Math.min(1180, 460 + arcs.length * 95) : 0; + arcs.forEach((arc, index) => { + const angle = -Math.PI / 2 + (Math.PI * 2 * index) / Math.max(1, arcs.length); + arcPositions.set(arc.id, { x: WORLD.cx + Math.cos(angle) * radius, y: WORLD.cy + Math.sin(angle) * radius * 0.62 }); + }); + const selectedPos = selectedArc ? arcPositions.get(selectedArc.id) : null; + const chapterPositions = new Map(); + (selectedArc?.chapters || []).forEach((chapter, index, chapters) => { + const angle = -Math.PI / 2 + (Math.PI * 2 * index) / Math.max(1, chapters.length); + chapterPositions.set(chapter.id, { x: selectedPos.x + Math.cos(angle) * 390, y: selectedPos.y + Math.sin(angle) * 270 }); + }); + const arcBranches = (selectedArc?.chapters || []).map((chapter) => { + const pos = chapterPositions.get(chapter.id); + return ``; + }).join(""); + const arcNodes = arcs.map((arc) => { + const pos = arcPositions.get(arc.id); + const selected = arc.id === state.selectedArcId; + const color = toneColors[arc.tone] || "#d3a64d"; + const size = selected ? 76 : 54; + return ` + + ${html(truncate(arc.title, 34))} + ${(arc.chapters || []).length} chapters / ${arcMomentCount(arc)} moments + `; + }).join(""); + const chapterNodes = (selectedArc?.chapters || []).map((chapter) => { + const pos = chapterPositions.get(chapter.id); + const selected = chapter.id === state.selectedChapterId; + const color = toneColors[chapter.tone] || toneColors[selectedArc.tone] || "#75a9bd"; + return ` + + ${html(truncate(chapter.title, 30))} + ${html(chapter.timeframe || "open timeframe")} + `; + }).join(""); + const momentCenter = selectedChapter ? chapterPositions.get(selectedChapter.id) : null; + const moments = selectedChapter?.moments || []; + const momentPositions = moments.map((moment, index) => { + const angle = -Math.PI / 2 + (Math.PI * 2 * index) / Math.max(1, moments.length); + const band = 145 + (index % 3) * 42; + return { moment, x: momentCenter.x + Math.cos(angle) * band, y: momentCenter.y + Math.sin(angle) * band * 0.72 }; + }); + const momentPaths = momentCenter ? momentPositions.map((item) => ``).join("") : ""; + const momentNodes = momentPositions.map(({ moment, x, y }) => { + const selected = moment.id === state.selectedMomentId; + return ` + + ${html(truncate(moment.date || moment.kind || "moment", 20))} + `; + }).join(""); + graph.innerHTML = `${arcBranches}${momentPaths}${arcNodes}${chapterNodes}${momentNodes}`; + graph.querySelectorAll("[data-arc-node]").forEach((node) => node.addEventListener("click", () => { + state.selectedArcId = node.dataset.arcNode; + state.selectedChapterId = arcById()?.chapters?.[0]?.id || ""; + clearMomentForm(); + fillArcForm(arcById()); + renderArcConstellation(); + })); + graph.querySelectorAll("[data-chapter-node]").forEach((node) => node.addEventListener("click", () => { + state.selectedChapterId = node.dataset.chapterNode; + clearMomentForm(); + fillChapterForm(chapterById()); + renderArcConstellation(); + })); + graph.querySelectorAll("[data-moment-node]").forEach((node) => node.addEventListener("click", () => { + fillMomentForm((chapterById()?.moments || []).find((moment) => moment.id === node.dataset.momentNode)); + renderArcConstellation(); + })); + $("mapTitle").textContent = selectedArc?.title || "Life Arc Constellation"; + $("mapSub").textContent = selectedChapter ? `${selectedChapter.title} / ${moments.length} moments` : "Choose an arc, then a chapter, to reveal its moments."; + $("mapNote").textContent = "Arcs hold chapters; chapters hold the moments that changed the story. Select any light to edit it."; + $("breadcrumb").innerHTML = `${selectedArc ? `` : ""}${selectedChapter ? `` : ""}`; + $("activeState").innerHTML = selectedChapter ? `Editing chapter: ${html(selectedChapter.title)}` : `Choose or create a life arc.`; + $("emptyRecovery").classList.toggle("hidden", Boolean(arcs.length)); + if (!arcs.length) $("emptyRecovery").innerHTML = `

No life arcs yet

Create the first arc from the editor on the right.

`; + $("emptyRecovery").querySelector("[data-new-arc]")?.addEventListener("click", newArc); + $("minimap").innerHTML = arcs.map((arc) => { const pos = arcPositions.get(arc.id); return ``; }).join(""); + } + function render() { + if (state.workspace === "arcs") { + renderArcStudio(); + renderArcConstellation(); + return; + } + updateSemanticZoom(); const scope = visibleScope(); const entries = scope.entries; const clusters = scope.clusters; @@ -4225,7 +4410,7 @@ HIDDEN_APP_HTML = r""" $("momentStoryLink").innerHTML = opt("", "No story link") + state.stories.map((story) => opt(story.id, story.title)).join(""); $("momentEntryLink").innerHTML = opt("", "No memory link") + state.entries.map((entry) => opt(entry.id, entry.title)).join(""); } - async function save() { + async function save(successMessage = "") { applyFormToState(); applyStoryFormToState(); applyArcFormToState(); @@ -4238,7 +4423,7 @@ HIDDEN_APP_HTML = r""" state.lifeArcs = data.lifeArcs || []; state.meta = data; localStorage.removeItem(draftKey); - setStatus(data.queuedBuild ? "constellation stored safely. asset republish queued." : (data.message || "constellation stored safely.")); + setStatus(successMessage || (data.queuedBuild ? "constellation stored safely. asset republish queued." : (data.message || "constellation stored safely."))); renderValidation(); refreshArcLinkOptions(); renderArcStudio(); @@ -4290,7 +4475,7 @@ HIDDEN_APP_HTML = r""" zoomAt(x, y, event.deltaY < 0 ? 1.12 : 1 / 1.12); }, { passive: false }); graph.addEventListener("pointerdown", (event) => { - if (event.button !== 0 || event.target.closest(".memory-node, .cluster-node")) return; + if (event.button !== 0 || event.target.closest(".memory-node, .cluster-node, .arc-node, .chapter-node, .moment-node")) return; graph.setPointerCapture(event.pointerId); graph.classList.add("is-panning"); state.pan = { id: event.pointerId, x: event.clientX, y: event.clientY, vx: 0, vy: 0, last: performance.now() }; @@ -4338,7 +4523,7 @@ HIDDEN_APP_HTML = r""" finishPan(event.pointerId); }); graph.addEventListener("dblclick", (event) => { - if (event.target.closest(".memory-node, .cluster-node")) return; + if (event.target.closest(".memory-node, .cluster-node, .arc-node, .chapter-node, .moment-node")) return; const rect = graph.getBoundingClientRect(); const x = ((event.clientX - rect.left) / rect.width) * SCREEN.width; const y = ((event.clientY - rect.top) / rect.height) * SCREEN.height; @@ -4412,12 +4597,15 @@ HIDDEN_APP_HTML = r""" $("deleteArcBtn").onclick = deleteArc; $("deleteChapterBtn").onclick = deleteChapter; $("addMomentBtn").onclick = addMoment; + $("saveChapterBtn").onclick = saveChapter; + $("cancelMomentBtn").onclick = clearMomentForm; + $("arcSaveBtn").onclick = () => save(); $("storyFromSelectionBtn").onclick = () => newStory(state.selectedId); $("addSectionBtn").onclick = addStorySection; $("saveSectionBtn").onclick = saveStorySection; $("closeSectionBtn").onclick = closeSectionEditor; $("deleteStoryBtn").onclick = deleteStory; - $("saveBtn").onclick = save; + $("saveBtn").onclick = () => save(); $("zoomOutBtn").onclick = () => zoomBy(-1); $("zoomInBtn").onclick = () => zoomBy(1); $("backBtn").onclick = goBack; @@ -4434,7 +4622,7 @@ HIDDEN_APP_HTML = r""" $("deleteBtn").onclick = deleteEntry; $("undoBtn").onclick = () => { if (!state.undo.length) return; state.redo.push(JSON.stringify({ entries: state.entries, stories: state.stories, lifeArcs: state.lifeArcs, selectedStoryId: state.selectedStoryId, selectedArcId: state.selectedArcId, selectedChapterId: state.selectedChapterId })); restore(state.undo.pop()); }; $("redoBtn").onclick = () => { if (!state.redo.length) return; state.undo.push(JSON.stringify({ entries: state.entries, stories: state.stories, lifeArcs: state.lifeArcs, selectedStoryId: state.selectedStoryId, selectedArcId: state.selectedArcId, selectedChapterId: state.selectedChapterId })); restore(state.redo.pop()); }; - $("modebar").querySelectorAll("button").forEach((button) => button.addEventListener("click", () => { + $("modebar").querySelectorAll("button").forEach((button) => button.addEventListener("click", () => { snapshotExploration(`switch to ${button.dataset.mode}`); state.mode = button.dataset.mode; state.focusCluster = ""; @@ -4442,8 +4630,9 @@ HIDDEN_APP_HTML = r""" state.focusLabel = ""; state.zoom = 0; $("modebar").querySelectorAll("button").forEach((item) => item.classList.toggle("active", item === button)); - render(); - })); + render(); + })); + $("workspaceSwitcher").querySelectorAll("[data-workspace]").forEach((button) => button.addEventListener("click", () => setWorkspace(button.dataset.workspace))); $("editorModebar").querySelectorAll("[data-editor-mode]").forEach((button) => button.addEventListener("click", () => { if (button.dataset.editorMode === "story") { setEditorMode("story");