Action

Craft Imagine Smart Add

Posted by @FlohGro, Last update about 9 hours ago

created by @FlohGro / more on my Blog

This Action connects Drafts with Craft Imagine.

Seamlessly send drafts to any Craft document via the Multi-Document API. Intelligently detects collections and dynamically prompts for properties based on the collection schema.

Features:

  • Lists all accessible Craft documents and lets you choose the destination
  • Automatically detects collections in the selected document
  • For collections: uses draft title as item title and generates dynamic prompts for all properties (text, dates, single/multi-select, etc.); you can also choose to append it as text
  • For documents without collections: appends content as formatted blocks
  • Draft body is added as content inside collection items

Requirements: Craft account with API access via Craft Imagine

Setup:

  1. Create an API Connection via the Imagine tab in Craft (and add the documents you want to use with the API)
  2. Copy the API base URL (I also recommend to set a token)
  3. Run the action and paste the base URL (and token) into the “Confiure Action” pop-up;
  4. Start using the Action with real content 😊

If you find this useful and want to support me you can donate or buy me a coffee

Steps

  • configurationKey

    name
    Craft API Base Url
    key
    apiBaseUrl
  • configurationKey

    name
    Craft API Token
    key
    apiToken
  • script

    // created by @FlohGro@social.lol
    
    //const apiBaseUrl = "https://connect.craft.do/links/ChW7CJdljaU/api/v1";
    
    const apiBaseUrl = action.configuredValues["apiBaseUrl"]
    
    //const apiToken = "pdk_e3051c73-d61f-3982-48d8-cdf2de01f4b8";
    
    const apiToken = action.configuredValues["apiToken"]
    
    
    // Add draft body as content inside collection items?
    const includeBodyAsContent = true;
    
    // Validate configuration
    if (apiBaseUrl === "YOUR_CRAFT_MULTI_DOC_API_BASE_URL" || !apiBaseUrl.startsWith("https://")) {
      app.displayErrorMessage("Please configure your Craft API base URL in the action script.");
      context.fail();
    }
    
    // Get draft content
    const draftContent = draft.content.trim();
    const draftTitle = draft.displayTitle;
    const draftBody = draft.processTemplate("[[body]]").trim();
    
    if (!draftContent) {
      app.displayErrorMessage("Draft is empty.");
      context.fail();
    }
    
    // API Helper Functions
    
    const http = HTTP.create();
    
    function apiRequest(method, endpoint, data = null) {
      const options = {
        "url": `${apiBaseUrl}${endpoint}`,
        "method": method,
        "headers": {
          "Authorization": `Bearer ${apiToken}`,
          "Accept": "application/json",
          "Content-Type": "application/json"
        }
      };
    
    
      if (data) {
        options.encoding = "json";
        options.data = data;
      }
    
      const response = http.request(options);
    
      let parsedData = null;
      if (response.responseText) {
        try {
          parsedData = JSON.parse(response.responseText);
        } catch (e) {
          parsedData = { raw: response.responseText };
        }
      }
    
      return {
        success: response.success && response.statusCode >= 200 && response.statusCode < 300,
        statusCode: response.statusCode,
        data: parsedData,
        error: response.success ? null : response.responseText
      };
    
    
    }
    
    function formatDateForAPI(date) {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      return `${year}-${month}-${day}`;
    }
    
    function showCancelMessage(text) {
      const message = text + " cancelled."
      app.displayInfoMessage(message)
    }
    
    // Fetch and Select Document
    
    function f() {
      const docsResult = apiRequest("GET", "/documents");
    
      if (!docsResult.success) {
        app.displayErrorMessage(`Failed to fetch documents: ${docsResult.error || docsResult.statusCode}`);
        context.fail();
      }
    
      const documents = (docsResult.data.items || []).filter(doc => !doc.isDeleted);
    
      if (documents.length === 0) {
        app.displayErrorMessage("No documents found in your Craft connection.");
        context.fail();
      }
    
      // Show document selection
      const docPrompt = Prompt.create();
      docPrompt.title = "Select Document";
      docPrompt.message = `Choose a Craft document:\n\nDraft: "${draftTitle}"`;
    
      for (const doc of documents) {
        docPrompt.addButton(doc.title, doc.id)
      }
    
      if (!docPrompt.show()) {
        context.cancel();
        showCancelMessage("Selecting Document")
        return
      }
    
      const selectedDocId = docPrompt.buttonPressed
    
      const docTitle = documents.filter(doc => doc.id == selectedDocId)[0].title
    
      // Check for Collections in Document
    
      const collectionsResult = apiRequest("GET", `/collections?documentIds[]=${selectedDocId}`);
    
      let collections = [];
      if (collectionsResult.success && collectionsResult.data.items) {
        collections = collectionsResult.data.items;
      }
    
      // Handle Based on Collections
    
      if (collections.length === 0) {
        // No collections - just append content as blocks
        const insertResult = apiRequest("POST", "/blocks", {
          "markdown": draftContent,
          "position": {
            "position": "end",
            "pageId": selectedDocId
          }
        });
    
    
        if (insertResult.success) {
          app.displaySuccessMessage("āœ“ Added to " + docTitle);
        } else {
          app.displayErrorMessage(`Failed to add content: ${insertResult.error || insertResult.statusCode}`);
          context.fail();
          return
        }
    
      } else {
        // Collections found - ask user what to do
        const modePrompt = Prompt.create();
        modePrompt.title = "Document has Collections";
        modePrompt.message = `"${docTitle}" contains ${collections.length} collection(s):\n${collections.map(col => "\"" + col.name + "\"" || "Untitled")}\n\nWhat would you like to do?`;
        modePrompt.addButton("Add to Collection");
        modePrompt.addButton("Append as Text");
    
    
        if (!modePrompt.show()) {
          context.cancel();
          showCancelMessage("Selecting Append Type")
          return
    
        }
    
        if (modePrompt.buttonPressed === "Append as Text") {
    
          let paragraphs = draft.content.split(/\n\n+/).filter(p => p.trim());
          paragraphs.unshift(draftTitle)
          const blocks = paragraphs.map(p => ({
            "type": "text",
            "markdown": p.trim()
          }));
    
          const insertResult = apiRequest("POST", "/blocks", {
            "blocks": blocks,
            "position": {
              "position": "end",
              "pageId": selectedDocId
            }
          });
    
          if (insertResult.success) {
            app.displaySuccessMessage(`āœ“ Added to "${docTitle}"`);
          } else {
            app.displayErrorMessage(`Failed to add content: ${insertResult.error || insertResult.statusCode}`);
            context.fail();
            return
          }
    
        } else {
          // Add to collection
          let selectedCollection;
    
          if (collections.length === 1) {
            selectedCollection = collections[0];
          } else {
            const colPrompt = Prompt.create();
            colPrompt.title = "Select Collection";
            colPrompt.message = "Choose which collection to add to:";
    
            for (const col of collections) {
              colPrompt.addButton(col.name || "Untitled Collection", col)
            }
    
            if (!colPrompt.show()) {
              context.cancel();
              showCancelMessage("Selecting Collection")
              return
    
            }
    
            selectedCollection = colPrompt.buttonPressed
          }
    
    
          // Fetch Collection Schema
    
          const schemaResult = apiRequest("GET", `/collections/${selectedCollection.id}/schema?format=schema`);
    
          if (!schemaResult.success) {
            app.displayErrorMessage(`Failed to fetch schema: ${schemaResult.error || schemaResult.statusCode}`);
            context.fail();
            return
          }
    
          const schema = schemaResult.data;
          const properties = schema.properties || [];
    
          // Build Dynamic Prompt for Properties
    
          const itemTitle = draftTitle || draftContent.split('\n')[0];
    
          const propPrompt = Prompt.create();
          propPrompt.title = `New ${schema.name || 'Collection'} Item`;
    
          let promptMessage = `Title: "${itemTitle}"`;
          if (draftBody && includeBodyAsContent) {
            promptMessage += `\n\nšŸ“ Body will be added as content inside the item.`;
          }
          promptMessage += `\n\nSet properties (leave empty to skip):`;
          propPrompt.message = promptMessage;
    
          const fieldMap = [];
    
          for (const prop of properties) {
            const fieldKey = `prop_${prop.key || prop.name}`;
            const label = prop.name;
    
            switch (prop.type) {
              case "text":
                propPrompt.addTextField(fieldKey, label, "");
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "text", name: prop.name });
                break;
    
              case "number":
                propPrompt.addTextField(fieldKey, `${label} (number)`, 0, true);
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "number", name: prop.name, config: prop.config });
                break;
    
              case "date":
                propPrompt.addDatePicker(fieldKey, label, new Date(), { "mode": "date" });
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "date", name: prop.name });
                break;
    
              case "boolean":
                propPrompt.addSwitch(fieldKey, label, false);
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "boolean", name: prop.name });
                break;
    
              case "url":
                propPrompt.addTextField(fieldKey, `${label} (URL)`, "");
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "url", name: prop.name });
                break;
    
              case "email":
                propPrompt.addTextField(fieldKey, `${label} (Email)`, "");
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "email", name: prop.name });
                break;
    
              case "phone":
                propPrompt.addTextField(fieldKey, `${label} (Phone)`, "");
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "phone", name: prop.name });
                break;
    
              case "singleSelect":
                if (prop.options && prop.options.length > 0) {
                  const optionNames = prop.options.map(opt => opt.name);
                  const optionsWithEmpty = ["— Select —", ...optionNames];
                  propPrompt.addPicker(fieldKey, label, [optionsWithEmpty], [0]);
                  fieldMap.push({
                    key: prop.key || prop.name,
                    fieldKey,
                    type: "singleSelect",
                    name: prop.name,
                    options: optionsWithEmpty,
                    originalOptions: optionNames
                  });
                }
                break;
    
              case "multiSelect":
                if (prop.options && prop.options.length > 0) {
                  const optionNames = prop.options.map(opt => opt.name);
                  propPrompt.addSelect(fieldKey, `${label} (multi)`, optionNames, [], true);
                  fieldMap.push({
                    key: prop.key || prop.name,
                    fieldKey,
                    type: "multiSelect",
                    name: prop.name,
                    options: optionNames
                  });
                }
                break;
    
              case "relation":
                // Skip relations - too complex for basic prompt
                break;
    
              case "blockLink":
                // Skip block links
                break;
    
              default:
                propPrompt.addTextField(fieldKey, label, "");
                fieldMap.push({ key: prop.key || prop.name, fieldKey, type: "text", name: prop.name });
            }
          }
    
          propPrompt.addButton("Create Item");
    
          if (!propPrompt.show()) {
            context.cancel();
            showCancelMessage("Creating Item")
            return
    
          }
    
          // Build Item Data from Prompt Values
    
          const itemProperties = {};
    
          for (const field of fieldMap) {
            const value = propPrompt.fieldValues[field.fieldKey];
    
            if (value === undefined || value === null) continue;
    
            switch (field.type) {
              case "text":
              case "url":
              case "email":
              case "phone":
                if (value && String(value).trim()) {
                  itemProperties[field.key] = String(value).trim();
                }
                break;
    
              case "number":
                if (value && String(value).trim()) {
                  const num = parseFloat(String(value).trim());
                  if (!isNaN(num)) {
                    itemProperties[field.key] = num;
                  }
                }
                break;
    
              case "date":
                if (value instanceof Date) {
                  itemProperties[field.key] = formatDateForAPI(value);
                }
                break;
    
              case "boolean":
                // Always include boolean values
                itemProperties[field.key] = Boolean(value);
                break;
    
              case "singleSelect":
                const selectedIdx = value[0];
                if (selectedIdx > 0) {
                  itemProperties[field.key] = field.originalOptions[selectedIdx - 1];
                }
                break;
    
              case "multiSelect":
                if (Array.isArray(value) && value.length > 0) {
                  itemProperties[field.key] = value;
                }
                break;
            }
          }
    
          // Create Collection Item
    
          const newItem = {
            "title": itemTitle,
            "properties": itemProperties
          };
    
          const createResult = apiRequest("POST", `/collections/${selectedCollection.id}/items`, {
            "items": [newItem]
          });
    
          if (!createResult.success) {
            let errorMsg = `Failed to create item: ${createResult.statusCode}`;
            if (createResult.data && createResult.data.message) {
              errorMsg = createResult.data.message;
            }
            app.displayErrorMessage(errorMsg);
            console.log("Create item error:", JSON.stringify(createResult.data || createResult.error));
            context.fail();
            return
          }
    
          // Step 8: Add Body Content to Item (if enabled)
    
          let contentAdded = false;
    
          if (includeBodyAsContent && draftBody && createResult.data && createResult.data.items && createResult.data.items.length > 0) {
            const createdItem = createResult.data.items[0];
            const itemId = createdItem.id;
    
            if (itemId) {
              // Split body into paragraphs and create separate blocks
              const paragraphs = draftBody.split(/\n\n+/).filter(p => p.trim());
              const blocks = paragraphs.map(p => ({
                "type": "text",
                "markdown": p.trim()
              }));
    
              const contentResult = apiRequest("POST", "/blocks", {
                "blocks": blocks,
                "position": {
                  "position": "end",
                  "pageId": itemId
                }
              });
    
              contentAdded = contentResult.success;
    
              if (!contentAdded) {
                console.log("failed to add body content:", json.stringify(contentresult.data || contentresult.error));
              }
            }
          }
    
          let message = `āœ“ created "${itemTitle}"`;
          app.displaySuccessMessage(message);
        }
    
      }
    }
    
    f();
    

Options

  • After Success Default
    Notification None
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.