verdere refactoring
This commit is contained in:
@@ -25,6 +25,11 @@ import { CmapHistoryDialog } from "./dialogs/history-dialog.js";
|
||||
import { CmapMetadataDialog } from "./dialogs/metadata-dialog.js";
|
||||
import { CmapPeopleDialog } from "./dialogs/people-dialog.js";
|
||||
import { CmapUnsavedDialog } from "./dialogs/unsaved-dialog.js";
|
||||
import { CmapStorageController } from "./cmap-storage-controller.js";
|
||||
import { CmapNavigationController } from "./cmap-navigation-controller.js";
|
||||
import { CmapTransferController } from "./cmap-transfer-controller.js";
|
||||
import { CmapEditorUiController } from "./cmap-editor-ui-controller.js";
|
||||
import { CmapEditorHost } from "./cmap-editor-host.js";
|
||||
import { CmapEmbedView } from "./cmap-embed-view.js";
|
||||
import { CmapEditorPresentation } from "./cmap-editor-presentation.js";
|
||||
|
||||
@@ -117,10 +122,84 @@ export class CmapWorkspaceController {
|
||||
const exportDialog = new CmapExportDialog(
|
||||
$("cmap-export-dialog"), tr, buildCurrentCmapMarkdownExport, buildCurrentCmapJsonExport);
|
||||
const unsavedDialog = new CmapUnsavedDialog($("cmap-unsaved-dialog"));
|
||||
const storage = new CmapStorageController({
|
||||
repository: cmapRepository,
|
||||
getEditor: () => cmapPrototypeState().editor,
|
||||
getCurrentMap: currentCmapStorageMap,
|
||||
getViewVisible: () => !$("cmap-view").classList.contains("hidden"),
|
||||
renderSvg: currentCmapRenderedSvg,
|
||||
reloadMaps: loadConceptMaps,
|
||||
status: showCmapStatus,
|
||||
translate: tr,
|
||||
unsavedDialog,
|
||||
canEdit: () => can("editor"),
|
||||
onMapSaved: (saved) => {
|
||||
if (!state.currentConceptMap && !state.currentConceptMapSource) {
|
||||
state.currentConceptMap = saved;
|
||||
const savedRoute = cmapRoute(saved.slug);
|
||||
history.replaceState(history.state, "", `${location.pathname}${location.search}${savedRoute}`);
|
||||
state.cmapGuardHash = savedRoute;
|
||||
} else if (state.currentConceptMapSource?.slug === saved.slug) {
|
||||
state.currentConceptMapSource = saved;
|
||||
} else if (state.currentConceptMap?.slug === saved.slug) {
|
||||
state.currentConceptMap = saved;
|
||||
}
|
||||
updateStoredConceptMapSummary(saved);
|
||||
}
|
||||
});
|
||||
const navigation = new CmapNavigationController({
|
||||
navigateToHash,
|
||||
cmapRoute,
|
||||
getEditor: () => cmapPrototypeState().editor,
|
||||
getCurrentMap: currentCmapStorageMap,
|
||||
getParentMap: () => state.currentConceptMapSource,
|
||||
storage
|
||||
});
|
||||
const transfer = new CmapTransferController({
|
||||
repository: cmapRepository,
|
||||
jsonImporter,
|
||||
jsonExporter,
|
||||
markdownExporter,
|
||||
getCurrentMap: () => state.currentConceptMap,
|
||||
getPages: () => state.pages,
|
||||
getConceptMaps: () => state.conceptMaps,
|
||||
loadPages,
|
||||
loadConceptMaps,
|
||||
navigateToMap: (slug) => navigation.open(slug),
|
||||
saveBeforeTransfer: () => cmapHasUnsavedChanges() ?
|
||||
saveStoredConceptMap({ automatic: true, force: true, historyMode: "autosave" }) : true,
|
||||
confirm: (message) => window.confirm(message),
|
||||
status: showCmapStatus,
|
||||
translate: tr
|
||||
});
|
||||
const editorUi = new CmapEditorUiController({
|
||||
root: document,
|
||||
getEditor: () => cmapPrototypeState().editor,
|
||||
actions: {
|
||||
save: () => saveStoredConceptMap(),
|
||||
edit: () => editSelectedCmapNode(),
|
||||
group: () => groupSelectedCmapItems(),
|
||||
zoom: (value, absolute = false) => setCmapZoom(
|
||||
absolute ? Number(value) : Number($("cmap-zoom-percent").value) + value),
|
||||
togglePageGuides: () => {
|
||||
const visible = $("cmap-toggle-page-guides").getAttribute("aria-checked") !== "true";
|
||||
setCmapPageGuides(visible);
|
||||
settingsRepository.setPageGuidesVisible(visible).catch((error) => showCmapStatus(error.message));
|
||||
},
|
||||
selection: (action, editor) => {
|
||||
if (action === "edit") editSelectedCmapNode();
|
||||
if (action === "group") groupSelectedCmapItems();
|
||||
if (action === "ungroup") editor.ungroupSelection();
|
||||
if (action === "hide") editor.hideSelectionInCurrentContext();
|
||||
}
|
||||
}
|
||||
});
|
||||
const editorHost = new CmapEditorHost({
|
||||
canvas: $("cmap-canvas"),
|
||||
factory: (canvas, options) => window.RacketWikiCmap.createEditor(canvas, options),
|
||||
createOptions: () => ({})
|
||||
});
|
||||
|
||||
let pendingCmapTransition = null;
|
||||
let cmapAutosaveTimer = null;
|
||||
let cmapSavePromise = null;
|
||||
let cmapEmbedHydrationTimer = null;
|
||||
const CMAP_AUTOSAVE_DELAY = 1500;
|
||||
|
||||
@@ -211,45 +290,19 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
function openLinkedCmap(record) {
|
||||
if (!record || !record.cmapSlug) return false;
|
||||
rememberActiveCmapContext();
|
||||
navigateToHash(cmapRoute(record.cmapSlug)).catch((error) => console.error(error));
|
||||
return true;
|
||||
return navigation.openLinked(record);
|
||||
}
|
||||
|
||||
function rememberActiveCmapContext() {
|
||||
const prototype = cmapPrototypeState();
|
||||
const editor = prototype.editor;
|
||||
const mapSlug = state.currentConceptMap?.slug;
|
||||
const root = editor?.activeMapRoot;
|
||||
if (!mapSlug || !root) return;
|
||||
const context = {
|
||||
mapSlug,
|
||||
rootItemId: Number(root.id)
|
||||
};
|
||||
history.replaceState({ ...history.state, cmapContext: context }, "", location.href);
|
||||
return navigation.rememberContext();
|
||||
}
|
||||
|
||||
async function restoreActiveCmapContext(slug) {
|
||||
const context = history.state?.cmapContext;
|
||||
if (!context || context.mapSlug !== slug) return false;
|
||||
const prototype = cmapPrototypeState();
|
||||
const root = prototype.editor?.itemRecord(context.rootItemId);
|
||||
if (root && root.kind === "submap" && root.separateMap) {
|
||||
prototype.editor.openSubmapMap(root);
|
||||
}
|
||||
history.replaceState({ ...history.state, cmapContext: null }, "", location.href);
|
||||
return true;
|
||||
return navigation.restoreContext(slug);
|
||||
}
|
||||
|
||||
function openParentCmap() {
|
||||
const source = state.currentConceptMapSource;
|
||||
if (source && source.slug) {
|
||||
navigateToHash(cmapRoute(source.slug)).catch((error) => console.error(error));
|
||||
return true;
|
||||
}
|
||||
const prototype = cmapPrototypeState();
|
||||
return Boolean(prototype.editor && prototype.editor.openParentMap());
|
||||
return navigation.openParent();
|
||||
}
|
||||
|
||||
function titledCmapComboboxEntry(record) {
|
||||
@@ -441,6 +494,68 @@ export class CmapWorkspaceController {
|
||||
actionButtons.get("group").disabled = !editor || !editor.canGroupSelection();
|
||||
actionButtons.get("ungroup").disabled = !editor || !editor.canUngroupSelection();
|
||||
actionButtons.get("hide").disabled = !editor || !editor.canHideSelectionInCurrentContext();
|
||||
const namespaceButton = $("cmap-move-selected-to-namespace");
|
||||
namespaceButton.disabled = !editor || !diagramNamespace() ||
|
||||
!selected.some((item) => item.kind !== "phrase");
|
||||
}
|
||||
|
||||
function diagramNamespace() {
|
||||
const model = state.currentConceptMapSource?.model || state.currentConceptMap?.model;
|
||||
return model?.metadata()?.namespace || "";
|
||||
}
|
||||
|
||||
function pageMatchesReference(page, reference) {
|
||||
if (page.slug === reference) return true;
|
||||
const parts = splitPageReference(reference);
|
||||
return String(page.namespace || "").toLocaleLowerCase() === parts.namespace.toLocaleLowerCase() &&
|
||||
String(page.pageSlug || splitPageReference(page.slug).slug).toLocaleLowerCase() ===
|
||||
parts.slug.toLocaleLowerCase();
|
||||
}
|
||||
|
||||
async function moveSelectedDescriptionsToNamespace() {
|
||||
const editor = cmapPrototypeState().editor;
|
||||
const namespace = diagramNamespace();
|
||||
if (!editor || !namespace) return false;
|
||||
const records = editor.selectedAll().filter((record) => record.kind !== "phrase");
|
||||
if (!records.length) return false;
|
||||
const changes = [];
|
||||
for (const record of records) {
|
||||
const current = record.descriptionPageSlug;
|
||||
if (!current) continue;
|
||||
const parts = splitPageReference(current);
|
||||
const target = pageReference(namespace, parts.slug);
|
||||
if (target === current) continue;
|
||||
const existing = state.pages.find((page) => pageMatchesReference(page, current));
|
||||
const conflict = state.pages.find((page) => pageMatchesReference(page, target));
|
||||
if (existing && conflict && existing !== conflict) {
|
||||
showCmapStatus(tr("namespace-move-conflict", "Cannot move {page}: the target page already exists.")
|
||||
.replace("{page}", target));
|
||||
return false;
|
||||
}
|
||||
changes.push({ record, current, target, existing });
|
||||
}
|
||||
if (!changes.length) return false;
|
||||
for (const change of changes) {
|
||||
if (change.existing) {
|
||||
const page = await api(`/api/pages/${encodeURIComponent(change.current)}/rename`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
title: change.existing.title,
|
||||
namespace,
|
||||
slug: splitPageReference(change.current).slug,
|
||||
summary: tr("move-description-page-summary", "Moved description page to CMap namespace")
|
||||
})
|
||||
});
|
||||
change.target = page.slug;
|
||||
}
|
||||
editor.updateItem(change.record, { descriptionPageSlug: change.target });
|
||||
}
|
||||
await loadPages();
|
||||
editor.commitHistory();
|
||||
if (currentCmapStorageMap()) await saveStoredConceptMap({ automatic: true });
|
||||
showCmapStatus(tr("descriptions-moved-to-namespace", "Selected descriptions moved to {namespace}.")
|
||||
.replace("{namespace}", namespace), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
function openCmapContextMenu(clientX, clientY, createContext = {}) {
|
||||
@@ -623,10 +738,7 @@ export class CmapWorkspaceController {
|
||||
closeCmapContextMenu();
|
||||
$("cmap-map-navigation").classList.add("hidden");
|
||||
$("cmap-active-map-title").textContent = "";
|
||||
if (state.cmapPrototype && state.cmapPrototype.editor &&
|
||||
typeof state.cmapPrototype.editor.destroy === "function") {
|
||||
state.cmapPrototype.editor.destroy();
|
||||
}
|
||||
editorHost.destroy();
|
||||
canvas.replaceChildren();
|
||||
state.cmapPrototype = null;
|
||||
updateCmapSelectionToolbar(null, [], null);
|
||||
@@ -648,7 +760,7 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
const prototype = cmapPrototypeState();
|
||||
prototype.editor = window.RacketWikiCmap.createEditor(canvas, {
|
||||
prototype.editor = editorHost.create({
|
||||
renderItem: (record) => cmapNodeHtml(record),
|
||||
boundaryReferenceMapTitle: state.currentConceptMapSource?.title ||
|
||||
state.currentConceptMap?.title || "",
|
||||
@@ -658,7 +770,9 @@ export class CmapWorkspaceController {
|
||||
state.currentConceptMap?.model?.metadata()?.namespace || "";
|
||||
const isDescriptionReference = record.descriptionPageSlug === record.pageSlug;
|
||||
const page = isDescriptionReference ? splitPageReference(record.pageSlug) : null;
|
||||
const target = namespace && page?.namespace === "cmap" ?
|
||||
const existingPage = state.pages.some((pageRecord) =>
|
||||
pageRecord.slug === record.pageSlug);
|
||||
const target = !existingPage && namespace && page?.namespace === "cmap" ?
|
||||
pageReference(namespace, page.slug) : record.pageSlug;
|
||||
if (isDescriptionReference && target !== record.pageSlug) {
|
||||
record.pageSlug = target;
|
||||
@@ -855,9 +969,7 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
function currentCmapSnapshot() {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (!prototype.editor) return null;
|
||||
return JSON.stringify(prototype.editor.toDocument());
|
||||
return storage.currentSnapshot();
|
||||
}
|
||||
|
||||
function currentCmapRenderedSvg() {
|
||||
@@ -876,37 +988,20 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
function markCurrentCmapSaved(snapshot = currentCmapSnapshot()) {
|
||||
storage.markSaved(snapshot);
|
||||
state.cmapSavedSnapshot = snapshot;
|
||||
}
|
||||
|
||||
function cmapHasUnsavedChanges() {
|
||||
if ($("cmap-view").classList.contains("hidden")) return false;
|
||||
const currentSnapshot = currentCmapSnapshot();
|
||||
if (currentSnapshot === null || state.cmapSavedSnapshot === null) return false;
|
||||
if (currentSnapshot === state.cmapSavedSnapshot) return false;
|
||||
const editor = cmapPrototypeState().editor;
|
||||
if (editor && !editor.canUndo() && !editor.history.hasPendingCommit()) {
|
||||
state.cmapSavedSnapshot = currentSnapshot;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return storage.hasUnsavedChanges();
|
||||
}
|
||||
|
||||
function cancelCmapAutosave() {
|
||||
if (cmapAutosaveTimer !== null) {
|
||||
window.clearTimeout(cmapAutosaveTimer);
|
||||
cmapAutosaveTimer = null;
|
||||
}
|
||||
storage.cancelAutosave();
|
||||
}
|
||||
|
||||
function scheduleCmapAutosave() {
|
||||
cancelCmapAutosave();
|
||||
if (!can("editor") || !state.currentConceptMap || !cmapHasUnsavedChanges()) return;
|
||||
showCmapStatus(tr("autosave-pending", "Changes waiting to be saved"));
|
||||
cmapAutosaveTimer = window.setTimeout(() => {
|
||||
cmapAutosaveTimer = null;
|
||||
saveStoredConceptMap({ automatic: true }).catch((error) => console.error(error));
|
||||
}, CMAP_AUTOSAVE_DELAY);
|
||||
storage.scheduleAutosave();
|
||||
}
|
||||
|
||||
function updateStoredConceptMapSummary(conceptMap) {
|
||||
@@ -922,29 +1017,7 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
async function requestCmapTransition(action) {
|
||||
if (!cmapHasUnsavedChanges()) {
|
||||
await action();
|
||||
return true;
|
||||
}
|
||||
if (pendingCmapTransition) return false;
|
||||
|
||||
const transition = (async () => {
|
||||
const choice = await unsavedDialog.choose();
|
||||
if (choice === "save" && !await saveStoredConceptMap()) return false;
|
||||
if (choice === "discard") markCurrentCmapSaved();
|
||||
if (choice === "cancel") {
|
||||
renderConceptMapSelector();
|
||||
return false;
|
||||
}
|
||||
await action();
|
||||
return true;
|
||||
})();
|
||||
pendingCmapTransition = transition;
|
||||
try {
|
||||
return await transition;
|
||||
} finally {
|
||||
if (pendingCmapTransition === transition) pendingCmapTransition = null;
|
||||
}
|
||||
return navigation.requestTransition(action);
|
||||
}
|
||||
|
||||
async function openStoredConceptMap(slug) {
|
||||
@@ -1192,14 +1265,7 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
async function buildCurrentCmapMarkdownExport({ depth, includeWikiPages }) {
|
||||
if (!state.currentConceptMap) {
|
||||
throw new Error(tr("cmap-export-unavailable", "CMap export is unavailable."));
|
||||
}
|
||||
if (cmapHasUnsavedChanges() && can("editor")) {
|
||||
const saved = await saveStoredConceptMap({ automatic: true, force: true, historyMode: "autosave" });
|
||||
if (!saved) throw new Error(tr("save-before-export-failed", "The CMap could not be saved before export."));
|
||||
}
|
||||
return markdownExporter.export(state.currentConceptMap, {
|
||||
return transfer.exportMarkdown({
|
||||
maxDepth: depth,
|
||||
includeWikiPages,
|
||||
language: state.language
|
||||
@@ -1207,43 +1273,11 @@ export class CmapWorkspaceController {
|
||||
}
|
||||
|
||||
async function buildCurrentCmapJsonExport(depth) {
|
||||
if (!state.currentConceptMap) {
|
||||
throw new Error(tr("cmap-json-unavailable", "CMap JSON export is unavailable."));
|
||||
}
|
||||
if (cmapHasUnsavedChanges() && can("editor")) {
|
||||
const saved = await saveStoredConceptMap({ automatic: true, force: true, historyMode: "autosave" });
|
||||
if (!saved) throw new Error(tr("save-before-export-failed", "The CMap could not be saved before export."));
|
||||
}
|
||||
return jsonExporter.export(state.currentConceptMap, depth);
|
||||
return transfer.exportJson(depth);
|
||||
}
|
||||
|
||||
async function importCmapBundleFile(file) {
|
||||
const bundle = await jsonImporter.read(file);
|
||||
if (cmapHasUnsavedChanges()) {
|
||||
const saved = await saveStoredConceptMap({ automatic: true, force: true, historyMode: "autosave" });
|
||||
if (!saved) throw new Error(tr("save-before-import-failed", "The current CMap could not be saved before import."));
|
||||
}
|
||||
|
||||
const conflicts = jsonImporter.conflicts(bundle, state.pages, state.conceptMaps);
|
||||
let replaceExisting = false;
|
||||
if (conflicts.pages.length || conflicts.conceptMaps.length) {
|
||||
replaceExisting = window.confirm(tr(
|
||||
"cmap-import-conflicts",
|
||||
"The import contains {maps} existing CMaps and {pages} existing pages. Choose OK to replace them with the imported content, or Cancel to keep them and import only new records.")
|
||||
.replace("{maps}", conflicts.conceptMaps.length)
|
||||
.replace("{pages}", conflicts.pages.length));
|
||||
}
|
||||
|
||||
const result = await jsonImporter.import(bundle, {
|
||||
pages: state.pages,
|
||||
conceptMaps: state.conceptMaps,
|
||||
replaceExisting,
|
||||
summary: tr("imported-from-cmap-json", "Imported from CMap JSON")
|
||||
});
|
||||
await loadPages();
|
||||
await loadConceptMaps();
|
||||
await navigateToHash(cmapRoute(bundle.rootCmapSlug));
|
||||
return result;
|
||||
return transfer.importFile(file);
|
||||
}
|
||||
|
||||
async function handleCmapImportFile(file) {
|
||||
@@ -1266,7 +1300,7 @@ export class CmapWorkspaceController {
|
||||
|
||||
async function deleteStoredConceptMap() {
|
||||
cancelCmapAutosave();
|
||||
if (cmapSavePromise) await cmapSavePromise;
|
||||
await storage.waitForSave();
|
||||
const conceptMap = state.currentConceptMap;
|
||||
if (!conceptMap) return false;
|
||||
const question = tr(
|
||||
@@ -1304,85 +1338,7 @@ export class CmapWorkspaceController {
|
||||
async function saveStoredConceptMap({ automatic = false, force = false,
|
||||
summary = null, snapshotVersion = false,
|
||||
historyMode = null } = {}) {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (!prototype.editor) return false;
|
||||
cancelCmapAutosave();
|
||||
if (automatic && !currentCmapStorageMap()) return false;
|
||||
const effectiveHistoryMode = historyMode ||
|
||||
(snapshotVersion ? "snapshot" : (automatic ? "autosave" : "manual"));
|
||||
if (cmapSavePromise) {
|
||||
const firstSaveSucceeded = await cmapSavePromise;
|
||||
if (!firstSaveSucceeded) return false;
|
||||
if (force || cmapHasUnsavedChanges() || effectiveHistoryMode !== "autosave") {
|
||||
return saveStoredConceptMap({ automatic, force, summary, snapshotVersion, historyMode });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const snapshot = currentCmapSnapshot();
|
||||
if (snapshot === null) return false;
|
||||
if (!force && snapshot === state.cmapSavedSnapshot && effectiveHistoryMode === "autosave") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const model = prototype.editor.currentModel();
|
||||
const renderedSvg = currentCmapRenderedSvg();
|
||||
const conceptMapAtStart = currentCmapStorageMap();
|
||||
let saveSucceeded = false;
|
||||
showCmapStatus(snapshotVersion ? tr("creating-snapshot", "Creating snapshot…") :
|
||||
(automatic ? tr("autosaving", "Saving automatically…") : tr("saving", "Saving…")));
|
||||
cmapSavePromise = (async () => {
|
||||
try {
|
||||
if (!conceptMapAtStart) {
|
||||
const title = window.prompt(tr("concept-map-name", "Concept map name"), "");
|
||||
if (!title || !title.trim()) {
|
||||
showCmapStatus("");
|
||||
return false;
|
||||
}
|
||||
state.currentConceptMap = await cmapRepository.create(title.trim(), model, null, { renderedSvg });
|
||||
const savedRoute = cmapRoute(state.currentConceptMap.slug);
|
||||
history.replaceState(history.state, "", `${location.pathname}${location.search}${savedRoute}`);
|
||||
state.cmapGuardHash = savedRoute;
|
||||
await loadConceptMaps();
|
||||
} else {
|
||||
const savedConceptMap = await cmapRepository.save(
|
||||
conceptMapAtStart,
|
||||
model,
|
||||
{
|
||||
summary: summary || (automatic ?
|
||||
tr("automatic-save", "Automatic save") :
|
||||
tr("manual-save", "Manual save")),
|
||||
snapshot: snapshotVersion,
|
||||
saveKind: effectiveHistoryMode,
|
||||
renderedSvg
|
||||
});
|
||||
if (state.currentConceptMapSource &&
|
||||
state.currentConceptMapSource.slug === conceptMapAtStart.slug) {
|
||||
state.currentConceptMapSource = savedConceptMap;
|
||||
updateStoredConceptMapSummary(savedConceptMap);
|
||||
} else if (state.currentConceptMap &&
|
||||
state.currentConceptMap.slug === conceptMapAtStart.slug) {
|
||||
state.currentConceptMap = savedConceptMap;
|
||||
updateStoredConceptMapSummary(savedConceptMap);
|
||||
}
|
||||
}
|
||||
markCurrentCmapSaved(snapshot);
|
||||
await loadConceptMaps();
|
||||
showCmapStatus(
|
||||
snapshotVersion ? tr("snapshot-created", "Snapshot created") :
|
||||
(automatic ? tr("concept-map-autosaved", "CMap saved automatically") : tr("concept-map-saved", "CMap saved")),
|
||||
true);
|
||||
saveSucceeded = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
showCmapStatus(error.message);
|
||||
return false;
|
||||
}
|
||||
})().finally(() => {
|
||||
cmapSavePromise = null;
|
||||
if (saveSucceeded && cmapHasUnsavedChanges()) scheduleCmapAutosave();
|
||||
});
|
||||
return cmapSavePromise;
|
||||
return storage.save({ automatic, force, summary, snapshotVersion, historyMode });
|
||||
}
|
||||
|
||||
async function createConceptMapSnapshot() {
|
||||
@@ -1479,7 +1435,6 @@ export class CmapWorkspaceController {
|
||||
showCmapStatus(error.message);
|
||||
});
|
||||
});
|
||||
$("cmap-save-map").addEventListener("click", () => saveStoredConceptMap());
|
||||
$("cmap-rename-map").addEventListener("click", () => renameStoredConceptMap());
|
||||
$("cmap-edit-metadata").addEventListener("click", openCmapMetadataDialog);
|
||||
$("cmap-export-markdown").addEventListener("click", openCmapExportDialog);
|
||||
@@ -1491,6 +1446,10 @@ export class CmapWorkspaceController {
|
||||
$("cmap-manage-people").addEventListener("click", () => {
|
||||
peopleDialog.open().catch((error) => showCmapStatus(error.message));
|
||||
});
|
||||
$("cmap-move-selected-to-namespace").addEventListener("click", () => {
|
||||
moveSelectedDescriptionsToNamespace()
|
||||
.catch((error) => showCmapStatus(error.message));
|
||||
});
|
||||
$("cmap-delete-map").addEventListener("click", () => deleteStoredConceptMap());
|
||||
$("cmap-create-snapshot").addEventListener("click", () => {
|
||||
createConceptMapSnapshot().catch((error) => showCmapStatus(error.message));
|
||||
@@ -1521,83 +1480,10 @@ export class CmapWorkspaceController {
|
||||
});
|
||||
});
|
||||
}
|
||||
$("cmap-edit-selected").addEventListener("click", () => editSelectedCmapNode());
|
||||
$("cmap-cut-selected").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.cutSelectionReferences();
|
||||
});
|
||||
$("cmap-copy-selected").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.copySelectionReferences();
|
||||
});
|
||||
$("cmap-paste-concepts").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.pasteConceptReferences();
|
||||
});
|
||||
$("cmap-undo").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.undo();
|
||||
});
|
||||
$("cmap-redo").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.redo();
|
||||
});
|
||||
$("cmap-select-all").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.selectAll();
|
||||
});
|
||||
$("cmap-group-selected").addEventListener("click", () => {
|
||||
groupSelectedCmapItems();
|
||||
});
|
||||
$("cmap-ungroup-selected").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.ungroupSelection();
|
||||
});
|
||||
$("cmap-hide-selected").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.hideSelectionInCurrentContext();
|
||||
});
|
||||
$("cmap-delete-selected").addEventListener("click", () => {
|
||||
const prototype = cmapPrototypeState();
|
||||
if (prototype.editor) prototype.editor.deleteSelection();
|
||||
});
|
||||
$("cmap-selection-toolbar").addEventListener("click", (event) => {
|
||||
const button = event.target.closest("button");
|
||||
if (!button || button.disabled) return;
|
||||
const prototype = cmapPrototypeState();
|
||||
const editor = prototype.editor;
|
||||
if (!editor) return;
|
||||
const layoutCommand = button.dataset.cmapLayout;
|
||||
if (layoutCommand) {
|
||||
editor.applySelectionLayout(layoutCommand);
|
||||
return;
|
||||
}
|
||||
switch (button.dataset.cmapSelectionAction) {
|
||||
case "edit":
|
||||
editSelectedCmapNode();
|
||||
break;
|
||||
case "group":
|
||||
groupSelectedCmapItems();
|
||||
break;
|
||||
case "ungroup":
|
||||
editor.ungroupSelection();
|
||||
break;
|
||||
case "hide":
|
||||
editor.hideSelectionInCurrentContext();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
$("cmap-toggle-page-guides").addEventListener("click", () => {
|
||||
const visible = $("cmap-toggle-page-guides").getAttribute("aria-checked") !== "true";
|
||||
setCmapPageGuides(visible);
|
||||
settingsRepository.setPageGuidesVisible(visible)
|
||||
.catch((error) => {
|
||||
setCmapPageGuides(settingsRepository.pageGuidesVisible);
|
||||
showCmapStatus(error.message);
|
||||
});
|
||||
});
|
||||
$("cmap-reset").addEventListener("click", () => {
|
||||
if (state.currentConceptMap) {
|
||||
requestCmapTransition(() => openStoredConceptMap(state.currentConceptMap.slug))
|
||||
@@ -1649,10 +1535,6 @@ export class CmapWorkspaceController {
|
||||
parentSubmap: prototype.editor.submapAtPoint(point)
|
||||
});
|
||||
});
|
||||
$("cmap-zoom-out").addEventListener("click", () => setCmapZoom(Number($("cmap-zoom-percent").value) - 10));
|
||||
$("cmap-zoom-in").addEventListener("click", () => setCmapZoom(Number($("cmap-zoom-percent").value) + 10));
|
||||
$("cmap-zoom-reset").addEventListener("click", () => setCmapZoom(100));
|
||||
$("cmap-zoom-percent").addEventListener("change", (event) => setCmapZoom(event.target.value));
|
||||
$("cmap-import-file").addEventListener("change", (event) => {
|
||||
const file = event.currentTarget.files && event.currentTarget.files[0];
|
||||
if (file) handleCmapImportFile(file);
|
||||
@@ -1662,7 +1544,7 @@ export class CmapWorkspaceController {
|
||||
!$("cmap-view").classList.contains("hidden")) {
|
||||
event.preventDefault();
|
||||
if (can("editor")) {
|
||||
if (pendingCmapTransition) {
|
||||
if (storage.hasPendingTransition()) {
|
||||
unsavedDialog.chooseSave();
|
||||
return;
|
||||
}
|
||||
@@ -1674,6 +1556,8 @@ export class CmapWorkspaceController {
|
||||
handleCmapKeyboardShortcut(event);
|
||||
});
|
||||
|
||||
editorUi.initialize();
|
||||
|
||||
/**
|
||||
* Load persistent CMap preferences and the data needed by the workspace.
|
||||
*/
|
||||
@@ -1702,6 +1586,9 @@ export class CmapWorkspaceController {
|
||||
this.normalizeExternalUrl = normalizedCmapExternalUrl;
|
||||
this.clearDescriptionPreviews = () => cmapDescriptionPreview.clear();
|
||||
this.requestTransition = requestCmapTransition;
|
||||
this.navigation = navigation;
|
||||
this.transfer = transfer;
|
||||
this.storage = storage;
|
||||
this.show = showCmapPrototype;
|
||||
this.hasUnsavedChanges = cmapHasUnsavedChanges;
|
||||
this.startSlug = startCmapSlug;
|
||||
|
||||
Reference in New Issue
Block a user