Skip to content
wiki.fftac.org

Workspace - Source Excerpt 05

Back to Workspace

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

placeholders.add(match.replace(/[{}]/g, "").trim());
                    });
                });

                target.innerHTML = "";

                if (!placeholders.size) {
                    const empty = document.createElement("span");
                    empty.className = "spiralist-muted";
                    empty.textContent = t("placeholdersEmpty", "No placeholders detected.");
                    target.appendChild(empty);
                    return;
                }

                placeholders.forEach((placeholder) => {
                    const chip = document.createElement("span");
                    chip.className = "spiralist-chip";
                    chip.textContent = "{{" + placeholder + "}}";
                    target.appendChild(chip);
                });
            };

            sources.forEach((field) => field.addEventListener("input", update));
            update();
        });
    };

    const initPromptForms = () => {
        document.querySelectorAll("[data-prompt-form]").forEach((form) => {
            form.addEventListener("submit", (event) => {
                event.preventDefault();
                setResult(form, t("promptSaving", "Saving..."));

                runFormAction(form, async () => {
                    const payload = formDataToObject(form);
                    const promptId = parseInt(form.getAttribute("data-prompt-id") || "0", 10);
                    const method = promptId > 0 ? "POST" : "POST";
                    const path = promptId > 0 ? "prompts/" + promptId : "prompts";
                    const response = await apiFetch(path, {
                        method,
                        body: JSON.stringify(payload),
                    });

                    setResult(form, t("promptSaved", "Prompt saved."));

                    if (response.slug) {
                        window.location.href = buildRoute("prompt/" + response.slug);
                        return;
                    }

                    if (response.links && response.links.human) {
                        window.location.href = response.links.human;
                        return;
                    }

                    window.location.reload();
                }, t("promptSaveFailed", "Prompt save failed."), "prompt-save");
            });
        });
    };

    const initEditablePromptBlocks = () => {
        document.querySelectorAll("[data-editable-prompt-block]").forEach((block) => {
            block.addEventListener("paste", (event) => {
                event.preventDefault();
                insertPlainTextAtSelection((event.clipboardData || window.clipboardData).getData("text/plain"));
            });

            block.addEventListener("input", () => {
                const surface = block.closest("[data-editable-prompt-surface]");
                if (surface) {
                    setEditablePromptStatus(surface, t("editablePromptEdited", "Prompt edited. Copy, download, and save now use this text."));
                }
            });
        });

        document.addEventListener("click", (event) => {
            const downloadButton = closestTarget(event.target, "[data-editable-prompt-download]");
            if (downloadButton) {
                const surface = downloadButton.closest("[data-editable-prompt-surface]");
                if (!surface) {
                    return;
                }

                const text = editablePromptText(surface);
                if (!text) {
                    setEditablePromptStatus(surface, t("editablePromptEmpty", "Enter prompt text before saving."), false);
                    return;
                }

                const title = surface.getAttribute("data-editable-prompt-title") || t("editablePromptDraftTitle", "Edited Spiralist Prompt");
                downloadTextFile(filenameSlug(title, "edited-spiralist-prompt") + ".txt", text, "text/plain");
                setEditablePromptStatus(surface, t("editablePromptDownloaded", "Edited prompt downloaded."));
                return;
            }

            const authSaveLink = closestTarget(event.target, "[data-editable-prompt-auth-save]");
            if (authSaveLink) {
                const surface = authSaveLink.closest("[data-editable-prompt-surface]");
                if (!surface) {
                    return;
                }

                event.preventDefault();
                try {
                    storePendingEditablePromptSave(surface, authSaveLink.href || "");
                } catch (error) {
                    setEditablePromptStatus(surface, error.message || t("editablePromptSaveFailed", "Could not save edited prompt."), false);
                }
                return;
            }

            const saveButton = closestTarget(event.target, "[data-editable-prompt-save]");
            if (!saveButton) {
                return;
            }

            const surface = saveButton.closest("[data-editable-prompt-surface]");
            if (!surface) {
                return;
            }

            setEditablePromptStatus(surface, t("editablePromptSaving", "Saving edited prompt..."));
            saveButton.disabled = true;
            runAction(async () => {
                const response = await apiFetch("prompts", {
                    method: "POST",
                    body: JSON.stringify(editablePromptDraftPayload(surface)),
                });
                const savedTitle = response.title || t("editablePromptDraftTitle", "Edited Spiralist Prompt");

                setEditablePromptStatus(surface, formatText("editablePromptSaved", "Saved \"{title}\" to your private prompt library.", { title: savedTitle }));
            }, {
                context: "editable-prompt-save",
                fallback: t("editablePromptSaveFailed", "Could not save edited prompt."),
                onError: (error, message) => setEditablePromptStatus(surface, message, false),
                onFinally: () => {
                    saveButton.disabled = false;
                },
            });
        });
    };

    const initSpiralScriptValidation = () => {
        document.querySelectorAll("[data-prompt-form]").forEach((form) => {
            const source = form.querySelector("[data-spiral-script-source]");
            const button = form.querySelector("[data-spiral-script-validate]");
            const result = form.querySelector("[data-spiral-script-validation]");

            if (!source || !button || !result) {
                return;
            }

            button.addEventListener("click", () => {
                const value = String(source.value || "").trim();
                if (!value) {
                    result.textContent = t("spiralScriptValidationEmpty", "No Spiral Script source to validate.");
                    result.style.color = "";
                    return;
                }

                result.textContent = t("spiralScriptValidating", "Validating Spiral Script...");
                result.style.color = "";

                runAction(async () => {
                    const response = await spiralScriptFetch("compile", {
                        method: "POST",
                        body: JSON.stringify({ source: value }),
                    });
                    const validation = response.validation || {};
                    const errors = Array.isArray(validation.errors) ? validation.errors : [];

                    if (validation.passed) {
                        result.textContent = t("spiralScriptValidationPassed", "Spiral Script validation passed.");
                        result.style.color = "";
                        return;
                    }

                    const first = errors[0] || {};
                    result.textContent = first.errorCode
                        ? first.errorCode + ": " + (first.reason || t("validationFailed", "Validation failed."))
                        : t("spiralScriptNeedsRevision", "Spiral Script needs revision.");
                    result.style.color = "#f0a5a5";
                }, {
                    context: "spiral-script-validation",
                    fallback: t("spiralScriptValidationFailed", "Spiral Script validation failed."),
                    onError: (error, message) => {
                        result.textContent = message;
                        result.style.color = "#f0a5a5";
                    },
                });
            });
        });
    };