Skip to content

Latest commit

 

History

History
179 lines (161 loc) · 8.63 KB

File metadata and controls

179 lines (161 loc) · 8.63 KB

kit:: insertToPurchases

  • /**
     * @file insertToPurchases.md
     * @summary A logseq kit that inserts a block to today's purchase holder container. If the container is not yet present, the purchase holder container is added.
     *
     * @requires logseq {@link https://github.com/logseq/logseq}
     * @requires {@link https://discuss.logseq.com/t/edit-and-run-javascript-code-inside-logseq-itself/20763 Logseq Kits}
     * @requires {@link https://github.com/DeadBranches/logseq-queries-and-scripts/blob/main/kitButton.md kitButton}
     *
     * @function insertToPurchases
     * @param {HTMLElement} el - The div element triggering this script
     *
     * @usage This script should be invoked via the {{kitButton}} macro.
     * This macro should include parameters to specify the type of content to insert ('grocery' or 'shopping') and whether to focus the editor after insertion. The `focus` parameter is optional and defaults to `false`. If any value for `focus` is provided, it is treated as `true`.
     *
     * Expected syntax for the {{kitButton}} macro:
     * - `template='grocery'` (inserts a grocery block, no focus)
     * - `template='shopping'` (inserts a shopping block, no focus)
     * - `template='grocery' focus='true'` (inserts a grocery block and focuses the editor)
     * - `template='shopping' focus='true'` (inserts a shopping block and focuses the editor)
     *
     * @example In a logseq block add the following kitButton macro:
     * {{kitButton add and edit,insertToPurchases,eb0b eb25,squat dark-gray gray-border,template='shopping' focus='true'}}
     *
     * {{kitButton add,insertToPurchases,eb0b eb25,squat dark-gray gray-border,template='shopping'}}
     *
     * @note
     * 1. Create a page named "insertToPurchases" in your Logseq graph.
     * 2. Add this entire script within a JavaScript markdown code block on that page.
     * 3. No specific macro needs to be added to the Logseq config.edn file for this script.
     *
     * @author DeadBranch
     * @version 1.0.0
     * @date October 3, 2024
     */
    
    /**
     * @function getTemplateContent
     * @description Retrieves the content of a specified template. This function is used to fetch a template by name and then applies a function to get the desired content.
     *
     * The composition of this function allows flexibility, as it separates fetching the template metadata from extracting the content, enabling more reusable and modular code.
     *
     * @param {string} templateName - The name of the template to retrieve from Logseq.
     * @param {function} getContentFunction - A callback function that takes the template as input and returns the specific content. This function is typically used to extract the child blocks or process the template as needed.
     * @returns {Promise<string>} A promise that resolves with the content of the requested template.
     */
    const getTemplateContent = async (templateName, getContentFunction) => {
      if (!templateName) throw new Error("Source template name is required");
    
      const template = await logseq.api.get_template(templateName);
      const templateContent = await getContentFunction(template);
      return templateContent;
    };
    
    /**
     * @function getBlockChildContent
     * @description Retrieves the content of a specific child block given its parent block and the index of the child.
     *
     * This function is used to navigate Logseq's block hierarchy by obtaining the child block content based on a parent block. By making this function reusable, it can be used across different areas of the code where child content extraction is required.
     *
     * @param {object} block - The parent block object that contains child blocks.
     * @param {number} childIndex - The index of the child block whose content needs to be fetched.
     * @returns {Promise<string>} A promise that resolves with the content of the specified child block.
     */
    const getBlockChildContent = async (block, childIndex) => {
      if (!block) throw new Error("block is required");
    
      const blockUUID = block.uuid;
      const blockAndChildren = await logseq.api.get_block(blockUUID, {
        includeChildren: true,
      });
      return blockAndChildren.children[childIndex].content;
    };
    
    logseq.kits.insertToPurchases = insertToPurchases;
    /**
     * Main function to insert a new block into today's purchase holder container.
     * @function insertToPurchases
     * @async
     * @description This function inserts a block into the purchase holder container of today's journal page in Logseq. If the purchase holder container is not yet present, it creates one.
     *
     * The function is composed of two main parts: locating or adding the purchase holder, and inserting a new block based on the provided template. The composition enables modularity and readability, with the holder creation logic abstracted into a separate async function.
     *
     * @param {Element} el - The DOM element that triggered the function.
     * @returns {Promise<void>} A promise that resolves when the block has been successfully inserted.
     * @note This function is part of the Logseq automation workflow and is invoked through a kitButton macro to allow quick additions to a daily purchase tracker.
     *
     * @constant {string} INSERTION_IDENTIFIER - A logseq macro name identifying a sister target for the journal container
     * @constant {string} PURCHASE_HOLDER_IDENTIFIER - A logseq macro name accompanying the purchase holder container. Assists in identifying the presense or lack there of the purchase holder.
     * @constant {string} PURCHASE_HOLDER_TEMPLATE - The :template block name containing the purchase holder (as a child block) in the logseq graph.
     */
    async function insertToPurchases(el) {
      const INSERTION_IDENTIFIER = "journal-container-insertion-point";
      const PURCHASE_HOLDER_IDENTIFIER = "purchase-holder";
      const PURCHASE_HOLDER_TEMPLATE = "logseq, purchase-holder";
    
      const element = event.target;
      const me = event.target.closest(".ls-block");
      const journalRoot = me.closest(".journal-item.content");
    
      /**
       * Purchase holder container logic
       */
      let holderElement = journalRoot.querySelector(
        `[data-macro-name="${PURCHASE_HOLDER_IDENTIFIER}"]`
      );
      let holderBlockUUID;
    
      holderBlockUUID = await (async (
        element = holderElement,
        todaysJournal = journalRoot,
        purchaseHolderInsertionPoint = INSERTION_IDENTIFIER,
        holderTemplateName = PURCHASE_HOLDER_TEMPLATE,
        templateGetter = getTemplateContent,
        contentGetter = getBlockChildContent
      ) => {
        if (element) {
          console.info("Using existing purchase holder UUID.");
          return element.closest(".ls-block").getAttribute("blockId");
        }
    
        console.info("A purchase holder will be added to today's journal.");
    
        const insertionLocation = todaysJournal
          .querySelector(`[data-macro-name="${purchaseHolderInsertionPoint}"]`)
          .closest(".ls-block");
        if (!insertionLocation) {
          throw new Error(
            `Purchase holder could not be found, and holder insertion point was missing.\n 
            Could not find block with macro {{${purchaseHolderInsertionPoint}}}.`
          );
        }
        const insertionLocationUUID = insertionLocation.getAttribute("blockId");
    
        const holderTemplateBlock = await templateGetter(holderTemplateName, contentGetter);
        const insertionResults = await logseq.api.insert_block(
          insertionLocationUUID,
          holderTemplateBlock,
          {
            before: false,
            focus: false,
            sibling: true,
          }
        );
        if (insertionResults) {
          console.info("Purchase holder added successfully.");
          return insertionResults.uuid;
        }
      })();
    
      const templateType = element.getAttribute("data-template");
    
      // Define the content for the new block based on the template
      const whitespace = "\u{0020}";
      const templates = {
        grocery: `TODO {{grocery}}${whitespace}`,
        shopping: `TODO {{buy}}${whitespace}`,
      };
    
      const content = templates[templateType];
      if (!content) throw new Error(`Invalid template type provided: ${templateType}`);
    
      const focusBoolean = element.hasAttribute("data-focus");
      const focusEditor = focusBoolean ? true : false;
      await logseq.api.insert_block(holderBlockUUID, content, {
        sibling: false,
        focus: focusEditor,
      });
    }
    
    insertToPurchases();
    // Example of a kitButton macro to use this new functionality
    // - You can adjust the 'template' and 'focus' options as needed
    //    {{kitButton 'Add Grocery to Purchases' insertToPurchases '' squat dark-gray gray-border template_type='grocery' focus='true'}}
    //    {{kitButton 'Add Shopping to Purchases' insertToPurchases '' squat dark-gray gray-border template_type='shopping' focus='false'}}