Workspace - Source Excerpt 01
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/assets/js/workspace.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/assets/js/workspace.js
(function () {
const config = window.SpiralistWorkspace || {};
const apiRoot = config.apiRoot || "";
const spiralScriptApiRoot = config.spiralScriptApiRoot || "";
const nonce = config.nonce || "";
const locale = config.locale || "";
const uiText = typeof config.uiText === "object" && config.uiText !== null ? config.uiText : {};
const t = (key, fallback = "") => {
const value = uiText[key];
return typeof value === "string" && value !== "" ? value : fallback;
};
const core = typeof window.SpiralistCore === "object" && window.SpiralistCore !== null
? window.SpiralistCore
: {};
const runAction = typeof core.runAction === "function"
? core.runAction
: (action) => Promise.resolve().then(() => action());
const storage = core.storage || {
get: (key, fallback = "") => window.localStorage.getItem(key) || fallback,
set: (key, value) => window.localStorage.setItem(key, value),
remove: (key) => window.localStorage.removeItem(key),
};
const pendingEditablePromptSaveKey = "spiralist.pendingEditablePromptSave.v1";
const canSavePrompts = Boolean(config.canSavePrompts && apiRoot && nonce);
const copyText = typeof core.copyText === "function"
? core.copyText
: async (text) => {
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
textarea.remove();
};
const formatText = (key, fallback = "", replacements = {}) => {
let value = t(key, fallback);
Object.entries(replacements).forEach(([name, replacement]) => {
value = value.split("{" + name + "}").join(String(replacement));
});
return value;
};
if (!apiRoot) {
return;
}
const buildRoute = (path) => {
const cleaned = String(path || "").replace(/^\/+|\/+$/g, "");
const prefix = locale ? "/" + String(locale).replace(/[^A-Za-z0-9_-]/g, "") : "";
return prefix + "/" + cleaned + "/";
};
const parseJsonResponse = (text) => text ? JSON.parse(text) : {};
const apiFetch = async (path, options = {}) => {
const init = {
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": nonce,
},
...options,
};
const response = await fetch(apiRoot.replace(/\/$/, "") + "/" + path.replace(/^\//, ""), init);
const text = await response.text();
const data = parseJsonResponse(text);
if (!response.ok) {
const message = data.message || t("requestFailed", "Request failed.");
throw new Error(message);
}
return data;
};
const spiralScriptFetch = async (path, options = {}) => {
if (!spiralScriptApiRoot) {
throw new Error(t("spiralScriptApiUnavailable", "Spiral Script API is not available."));
}
const init = {
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-WP-Nonce": nonce,
},
...options,
};
const response = await fetch(spiralScriptApiRoot.replace(/\/$/, "") + "/" + path.replace(/^\//, ""), init);
const text = await response.text();
const data = parseJsonResponse(text);
if (!response.ok) {
throw new Error(data.message || t("spiralScriptRequestFailed", "Spiral Script request failed."));
}
return data;
};
const setResult = (form, message, ok = true) => {
const target = form.querySelector("[data-form-result]");
if (!target) {
return;
}
target.textContent = message;
target.style.color = ok ? "" : "#f0a5a5";
target.setAttribute("aria-live", "polite");
if (message && !message.endsWith("...")) {
target.setAttribute("tabindex", "-1");
target.focus({ preventScroll: true });
}
};
const setInlineResult = (wrapper, message, ok = true) => {
const target = wrapper.querySelector("[data-memory-result]");
if (!target) {
return;
}
target.textContent = message;
target.style.color = ok ? "" : "#f0a5a5";
};
const runFormAction = (form, action, failureMessage, context = "", options = {}) => runAction(action, {
...options,
context,
fallback: failureMessage,
onError: (error, message) => setResult(form, message, false),
});
const runInlineAction = (wrapper, action, failureMessage, context = "", options = {}) => runAction(action, {
...options,
context,
fallback: failureMessage,
onError: (error, message) => setInlineResult(wrapper, message, false),
});
const runButtonAction = (button, action, failureMessage, context = "") => runAction(action, {
context,
fallback: failureMessage,
onError: (error, message) => {
if (button && button.dataset && button.dataset.errorTarget) {
const target = document.querySelector(button.dataset.errorTarget);
if (target) {
target.textContent = message;
target.style.color = "#f0a5a5";
}
return;
}
window.alert(message);
},
});
const setDisabled = (controls, disabled) => {
const items = controls instanceof NodeList || Array.isArray(controls)
? Array.from(controls)
: [controls];
items.forEach((item) => {
if (item && "disabled" in item) {
item.disabled = disabled;
}
});
};
const downloadTextFile = (filename, text, contentType = "text/plain") => {
const blob = new Blob([text], { type: contentType });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename || "conversation-prompt.md";
document.body.appendChild(link);
link.click();
link.remove();
window.setTimeout(() => URL.revokeObjectURL(url), 500);
};
const filenameSlug = (value, fallback = "prompt") => {
const slug = String(value || "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return slug || fallback;
};
const toCleanPromptText = (value) => String(value || "").replace(/\r\n/g, "\n").trim();
const editablePromptText = (surface) => {
const block = surface ? surface.querySelector("[data-editable-prompt-block]") : null;
return block ? toCleanPromptText(block.textContent || "") : "";
};
const setEditablePromptStatus = (surface, message, ok = true) => {
const target = surface ? surface.querySelector("[data-editable-prompt-status]") : null;
if (!target) {
return;
}
target.textContent = message;
target.style.color = ok ? "" : "#f0a5a5";
};
const insertPlainTextAtSelection = (text) => {
if (typeof document.execCommand === "function" && document.queryCommandSupported && document.queryCommandSupported("insertText")) {
document.execCommand("insertText", false, text);
return;
}
const selection = window.getSelection ? window.getSelection() : null;
if (!selection || selection.rangeCount === 0) {
return;
}
const range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(text));
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
};
const promptTitleFromText = (text, fallback) => {
const heading = String(text || "")
.split("\n")
.map((line) => line.trim())
.find((line) => /^#{1,6}\s+/.test(line));
const title = heading ? heading.replace(/^#{1,6}\s+/, "").trim() : String(fallback || "").trim();