documentatie
This commit is contained in:
@@ -48,6 +48,12 @@ export class CmapConceptController {
|
||||
this.getDialog()?.openNew(context, this.dialogResources());
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Apply one validated concept-dialog submission as one editor history step.
|
||||
* pre : record identifies an existing placement, or createContext describes a new placement.
|
||||
* post : Shared concept content and placement presentation are updated and autosave is scheduled.
|
||||
* result : False when the editor or required create context is unavailable.
|
||||
*/
|
||||
async saveConcept(record, createContext, values) {
|
||||
const editor = this.getEditor();
|
||||
if ((!record && !createContext) || !editor) return false;
|
||||
@@ -101,6 +107,7 @@ export class CmapConceptController {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Update selection-dependent layout and concept action availability in the toolbar. */
|
||||
updateSelectionToolbar(editor, selectedRecords = [], connector = null) {
|
||||
const toolbar = document.getElementById("cmap-selection-toolbar");
|
||||
const selected = Array.isArray(selectedRecords) ? selectedRecords : [];
|
||||
@@ -127,6 +134,12 @@ export class CmapConceptController {
|
||||
parts.slug.toLocaleLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Move selected concept description pages into the active CMap namespace.
|
||||
* pre : An active CMap namespace exists and selected records may have description references.
|
||||
* post : Existing pages are renamed, editor references are changed and the map is autosaved.
|
||||
* result : False when no applicable selection exists or a target-page conflict is found.
|
||||
*/
|
||||
async moveSelectedDescriptionsToNamespace() {
|
||||
const editor = this.getEditor();
|
||||
const namespace = this.getNamespace();
|
||||
@@ -187,6 +200,7 @@ export class CmapConceptController {
|
||||
return options;
|
||||
}
|
||||
|
||||
/** Open inline phrase editing or the concept dialog for the selected editor record. */
|
||||
editSelected(record = null) {
|
||||
const editor = this.getEditor();
|
||||
const selectedRecord = record || editor?.selected();
|
||||
@@ -201,6 +215,7 @@ export class CmapConceptController {
|
||||
this.openDialog(selectedRecord);
|
||||
}
|
||||
|
||||
/** Group the current editor selection into a named inline submap. */
|
||||
groupSelection() {
|
||||
const editor = this.getEditor();
|
||||
if (!editor || !editor.canGroupSelection()) return false;
|
||||
@@ -216,6 +231,7 @@ export class CmapConceptController {
|
||||
});
|
||||
}
|
||||
|
||||
/** Populate a newly opened sample submap with its initial child records. */
|
||||
populateSubmap(record, editor) {
|
||||
const baseX = Number(record.node.attr("x"));
|
||||
const baseY = Number(record.node.attr("y"));
|
||||
|
||||
@@ -12,6 +12,11 @@ export class CmapEditorHost {
|
||||
this.editor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Replace any active editor with a newly configured editor instance.
|
||||
* post : The previous editor is destroyed; an optional model is loaded before onCreated runs.
|
||||
* result : The active editor, or null when no factory is configured.
|
||||
*/
|
||||
create(options = null, model = null) {
|
||||
this.destroy();
|
||||
if (!this.factory) return null;
|
||||
@@ -23,6 +28,7 @@ export class CmapEditorHost {
|
||||
return this.editor;
|
||||
}
|
||||
|
||||
/** Destroy the active editor once and release the host's reference to it. */
|
||||
destroy() {
|
||||
if (!this.editor) return;
|
||||
this.editor.destroy();
|
||||
|
||||
@@ -17,6 +17,10 @@ export class CmapEditorUiController {
|
||||
this.listeners.push(() => element.removeEventListener(event, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind every standard CMap toolbar action once.
|
||||
* result : This controller, so the host may retain the initialized instance.
|
||||
*/
|
||||
initialize() {
|
||||
this.bind("#cmap-save-map", "click", () => this.actions.save?.());
|
||||
this.bind("#cmap-undo", "click", (_event, editor) => editor?.undo());
|
||||
@@ -47,6 +51,7 @@ export class CmapEditorUiController {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Remove every listener installed by initialize before the host is disposed. */
|
||||
destroy() {
|
||||
for (const remove of this.listeners) remove();
|
||||
this.listeners = [];
|
||||
|
||||
@@ -29,6 +29,11 @@ export class CmapMapController {
|
||||
this.refreshPageConnections = refreshPageConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Refresh the CMap catalogue and its lightweight global reference index.
|
||||
* post : Workspace CMap lists, selector and usage indicators reflect the repository.
|
||||
* result : The current list of stored CMaps.
|
||||
*/
|
||||
async loadMaps() {
|
||||
const [conceptMaps, placements] = await Promise.all([
|
||||
this.repository.list(),
|
||||
@@ -65,6 +70,11 @@ export class CmapMapController {
|
||||
this.renderSelector();
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Open a stored CMap or its source map when it is a derived submap view.
|
||||
* pre : slug identifies a stored CMap; a later request supersedes this load safely.
|
||||
* post : Active map state, editor document and saved snapshot are synchronized.
|
||||
*/
|
||||
async open(slug) {
|
||||
if (!slug) return;
|
||||
const loadSequence = ++this.state.cmapLoadSequence;
|
||||
@@ -103,6 +113,7 @@ export class CmapMapController {
|
||||
this.status(this.translate("concept-map-loaded", "CMap loaded"), true);
|
||||
}
|
||||
|
||||
/** Restore the source submap context for a stored derived CMap. */
|
||||
openDerivedView(conceptMap, derivedView) {
|
||||
const editor = this.getEditor();
|
||||
const root = editor.itemRecord(derivedView.rootItemId);
|
||||
@@ -124,6 +135,10 @@ export class CmapMapController {
|
||||
editor.openSubmapMap(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Load one immutable CMap version for review or subsequent explicit save.
|
||||
* post : The editor shows the historical model while the current map remains dirty.
|
||||
*/
|
||||
async loadHistoricalVersion(version) {
|
||||
const conceptMap = this.getCurrentStorageMap();
|
||||
if (!conceptMap) return;
|
||||
@@ -145,6 +160,11 @@ export class CmapMapController {
|
||||
location.hash = this.cmapRoute(conceptMap.slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Extract the selected inline submap into a complete stored CMap.
|
||||
* post : Parent and child map are saved consistently; cross-boundary relations remain on the parent.
|
||||
* result : Whether extraction completed or was deliberately cancelled.
|
||||
*/
|
||||
async promoteSelectedSubmap() {
|
||||
const editor = this.getEditor();
|
||||
const record = editor ? editor.selected() : null;
|
||||
@@ -263,6 +283,11 @@ export class CmapMapController {
|
||||
{ tags: [], summary: "", explanationPageSlug: "" };
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Persist map metadata through either the source editor or a derived map model.
|
||||
* post : Namespace, tags, summary and explanation-page reference are stored together.
|
||||
* result : False when there is no editable map or saving fails.
|
||||
*/
|
||||
async saveMetadata(metadata) {
|
||||
const conceptMap = this.state.currentConceptMap;
|
||||
const editor = this.getEditor();
|
||||
@@ -290,6 +315,11 @@ export class CmapMapController {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Archive the active CMap after explicit title confirmation.
|
||||
* post : Active map state is cleared and navigation continues to another CMap or overview.
|
||||
* result : False when confirmation is cancelled, mismatched or persistence fails.
|
||||
*/
|
||||
async archive() {
|
||||
this.cancelAutosave();
|
||||
const conceptMap = this.state.currentConceptMap;
|
||||
@@ -324,6 +354,7 @@ export class CmapMapController {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create an explicit, named immutable snapshot of the active stored CMap. */
|
||||
async createSnapshot() {
|
||||
if (!this.getCurrentStorageMap()) return false;
|
||||
const description = window.prompt(this.translate("snapshot-description", "Snapshot description"), "");
|
||||
|
||||
@@ -14,10 +14,12 @@ export class CmapNavigationController {
|
||||
this.location = locationObject;
|
||||
}
|
||||
|
||||
/** Open a stored CMap through the central hash-navigation contract. */
|
||||
open(slug) {
|
||||
return this.navigateToHash(this.cmapRoute(slug));
|
||||
}
|
||||
|
||||
/** Remember the active separate-submap root before following a linked CMap. */
|
||||
openLinked(record) {
|
||||
if (!record?.cmapSlug) return false;
|
||||
this.rememberContext();
|
||||
@@ -25,6 +27,7 @@ export class CmapNavigationController {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Return to a source CMap when present, otherwise leave one inline submap level. */
|
||||
openParent() {
|
||||
const source = this.getParentMap();
|
||||
if (source?.slug) {
|
||||
@@ -39,6 +42,7 @@ export class CmapNavigationController {
|
||||
return this.storage.requestTransition(action);
|
||||
}
|
||||
|
||||
/** Store the active separate-submap context in the current browser history entry. */
|
||||
rememberContext() {
|
||||
const editor = this.getEditor();
|
||||
const current = this.getCurrentMap();
|
||||
@@ -50,6 +54,10 @@ export class CmapNavigationController {
|
||||
} }, "", this.location.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a remembered separate-submap root after browser navigation.
|
||||
* post : A consumed context is removed from history state to prevent repeated restoration.
|
||||
*/
|
||||
async restoreContext(slug) {
|
||||
const context = this.history.state?.cmapContext;
|
||||
if (!context || context.mapSlug !== slug) return false;
|
||||
|
||||
@@ -49,6 +49,10 @@ export class CmapStorageController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Queue one delayed autosave for the current dirty CMap.
|
||||
* post : Any preceding autosave timer is replaced; non-editable or clean maps are not queued.
|
||||
*/
|
||||
scheduleAutosave() {
|
||||
this.cancelAutosave();
|
||||
if (!this.canEdit() || !this.getCurrentMap() || !this.hasUnsavedChanges()) return;
|
||||
@@ -59,6 +63,10 @@ export class CmapStorageController {
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Guard a route or view transition while unsaved CMap changes exist.
|
||||
* result : True only when the action ran after saving, discarding or finding no changes.
|
||||
*/
|
||||
async requestTransition(action) {
|
||||
if (!this.hasUnsavedChanges()) {
|
||||
await action();
|
||||
@@ -93,6 +101,11 @@ export class CmapStorageController {
|
||||
if (this.savePromise) await this.savePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Persist the active editor model and its derived SVG render in one ordered workflow.
|
||||
* post : A successful save updates the current map, saved snapshot and CMap catalogue.
|
||||
* result : False for cancellation, unavailable editor or persistence failure.
|
||||
*/
|
||||
async save({ automatic = false, force = false, summary = null,
|
||||
snapshotVersion = false, historyMode = null } = {}) {
|
||||
const editor = this.getEditor();
|
||||
|
||||
@@ -12,6 +12,7 @@ export class CmapTransferController {
|
||||
});
|
||||
}
|
||||
|
||||
/** Save pending changes, then create the requested readable Markdown export. */
|
||||
async exportMarkdown(options) {
|
||||
const map = this.getCurrentMap();
|
||||
if (!map) throw new Error(this.translate("cmap-export-unavailable", "CMap export is unavailable."));
|
||||
@@ -19,6 +20,7 @@ export class CmapTransferController {
|
||||
return this.markdownExporter.export(map, options);
|
||||
}
|
||||
|
||||
/** Save pending changes, then create the requested portable JSON bundle. */
|
||||
async exportJson(depth) {
|
||||
const map = this.getCurrentMap();
|
||||
if (!map) throw new Error(this.translate("cmap-json-unavailable", "CMap JSON export is unavailable."));
|
||||
@@ -26,6 +28,11 @@ export class CmapTransferController {
|
||||
return this.jsonExporter.export(map, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* goal : Import a validated CMap bundle after presenting replacement conflicts.
|
||||
* post : Page and CMap catalogues are refreshed and navigation opens the imported root CMap.
|
||||
* result : The importer summary.
|
||||
*/
|
||||
async importFile(file) {
|
||||
const bundle = await this.jsonImporter.read(file);
|
||||
await this.saveBeforeTransfer();
|
||||
|
||||
@@ -292,6 +292,19 @@ export class CmapWorkspaceController {
|
||||
return text.length > 150 ? `${text.slice(0, 147)}…` : text;
|
||||
}
|
||||
|
||||
function showCmapDescriptionTooltip(button) {
|
||||
const item = button.closest("[data-rw-cmap-item-id]");
|
||||
const editor = cmapPrototypeState().editor;
|
||||
const record = item && editor ? editor.itemRecord(Number(item.dataset.rwCmapItemId)) : null;
|
||||
if (record?.descriptionPageSlug) {
|
||||
cmapDescriptionPreview.show(button, record.descriptionPageSlug, tr("loading", "Loading…"));
|
||||
}
|
||||
}
|
||||
|
||||
function hideCmapDescriptionTooltip() {
|
||||
cmapDescriptionPreview.hide();
|
||||
}
|
||||
|
||||
function addCmapPrototypeNode(options = {}) {
|
||||
return conceptController.addNode(options);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user