2024-09-24

<%* // WORKING Cool-Digest Script v5.1 for Obsidian Templater // Fixes: // - upsert a block to the file “Notes/ds/chains/{YYYY-MM}.md” with this structure: ### [[{file-name}]] | #ds/seal/{file-seal-hash} chained {timestamp} to #ds/{previous-deformatted-block-digest}\n{comma-separated-digest-tags-within-file}\n#ds/{this-deformatted-block-digest} // upsert to “Notes/ds/blocks/{deformatted-digest}.md” with frontmatter (aliases: {digest-tag}, file-seal-hash: {file-seal-hash}) and structure as # [[{file-name}]] | #ds/seal/{file-seal-hash} digested {timestamp}\n{content} // rename Digest Mark to Digest Tag // use #ds/{digest-hash}/{date} for Digest Tag format // - set Notice messages to 10 seconds // - make sure to create the directory and file if it does not exist, all operations should me idempotent and error resilient // rename script to DS Tagger Chain

// Import crypto module const crypto = require(‘crypto’);

// Helper functions

/**

  • Generates a SHA-256 hash of the content
  • @param {string} content - The content to hash
  • @param {number} length - Length of the hash to return
  • @returns {string} SHA-256 hash of specified length */ function generateHash(content, length = 12) { return crypto.createHash(‘sha256’).update(content).digest(‘hex’).substring(0, length); }

/**

  • Removes markdown formatting, Digest Marks, and Seal Marks from the content

  • @param {string} content - The content to deformat

  • @returns {Object} Deformatted content and number of characters removed */ function deformatContent(content) { if (typeof content !== ‘string’) { throw new Error(‘Deformat received non-string content’); }

    const originalLength = content.length;

    // Remove Digest and Seal Marks content = content.replace(/\s*[✉️](https://WikiWe.org/Notes/ds/block/[^)]+)/g, ”); content = content.replace(/\s*^ds-[a-f0-9]+/g, ”);

    // Remove headers, emphasis, bold, strikethrough, blockquotes, horizontal rules content = content.replace(/^#{1,6}\s+|[*~>]|-{3,}|*{3,}|{3,}/gm, ”);

    // Remove list markers and task list markers content = content.replace(/^[\s][-+]\s|^\s*\d+.\s|^- x\s/gm, ”);

    // Remove highlight markers content = content.replace(/==/g, ”);

    // Collapse multiple newlines and trim content = content.replace(/\n+/g, ‘\n’).trim();

    const charactersRemoved = originalLength - content.length;

    return { deformattedContent: content, charactersRemoved }; }

/**

  • Creates a Digest Mark
  • @param {string} hash - The generated hash
  • @param {string} date - Current date
  • @param {string} authors - Content authors
  • @param {number} cc - Character count
  • @param {number} wc - Word count
  • @param {number} lc - Line count
  • @returns {string} Formatted Digest Mark */ function createDigestMark(hash, date, authors, cc, wc, lc) { const encodedAuthors = encodeURIComponent(authors); return “; }

/**

  • Creates a Seal Mark
  • @param {string} digestMark - The Digest Mark
  • @param {string} date - Current date
  • @returns {string} Formatted Seal Mark */ function createSealMark(digestMark, date) { const sealHash = generateHash(digestMark + date); return ^ds-${sealHash}; }

/**

  • Splits content into header-based blocks, ignoring frontmatter

  • @param {string} content - The content to split

  • @returns {Array} Array of content blocks / function splitIntoBlocks(content) { // Remove frontmatter content = content.replace(/^---\n[\s\S]?\n---\n/, ”);

    const headerRegex = /^#{1,3}\s+.*$/gm; const blocks = []; let lastIndex = 0; let match;

    // Check if there’s content before the first header const firstHeaderMatch = content.match(headerRegex); if (firstHeaderMatch && firstHeaderMatch.index > 0) { blocks.push(content.slice(0, firstHeaderMatch.index).trim()); lastIndex = firstHeaderMatch.index; }

    while ((match = headerRegex.exec(content)) ! null) { if (lastIndex ! match.index) { blocks.push(content.slice(lastIndex, match.index).trim()); } lastIndex = match.index; }

    if (lastIndex < content.length) { blocks.push(content.slice(lastIndex).trim()); }

    return blocks; }

/**

  • Creates a file seal hash from all Digest Marks and Seal Marks
  • @param {string} content - The full content with Digest Marks and Seal Marks
  • @returns {string} File seal hash */ function createFileSealHash(content) { const digestMarks = content.match(/[✉️](https://WikiWe.org/Notes/ds/block/[^)]+)/g) || []; const sealMarks = content.match(/^ds-[a-f0-9]+/g) || []; return generateHash(digestMarks.join(”) + sealMarks.join(”), 12); }

/**

  • Extracts hash and date from a Digest Mark
  • @param {string} digestMark - The Digest Mark
  • @returns {Object} Object containing hash and date */ function extractDigestInfo(digestMark) { const match = digestMark.match(/[✉️](https://WikiWe.org/Notes/ds/block/([a-f0-9]+)?ts=([^&]+)/); if (match) { return { hash: match[1], date: match[2] }; } return null; }

/**

  • Validates the Digest and Seal Marks

  • @param {string} digestMark - The Digest Mark

  • @param {string} sealMark - The Seal Mark

  • @param {string} content - The content to validate against

  • @returns {Object} Validation result with details */ function validateMarks(digestMark, sealMark, content) { const digestInfo = extractDigestInfo(digestMark); if (!digestInfo) return { isValid: false, reason: “Invalid Digest Mark format” };

    const { deformattedContent } = deformatContent(content); const newDigest = generateHash(deformattedContent); const newSeal = createSealMark(digestMark, digestInfo.date);

    const digestValid = newDigest === digestInfo.hash; const sealValid = newSeal === sealMark;

    return { isValid: digestValid && sealValid, digestValid, sealValid, oldDigest: digestInfo.hash, newDigest, oldSeal: sealMark, newSeal, oldDate: digestInfo.date }; }

/**

  • Ensures a directory exists, creating it if necessary
  • @param {string} path - The directory path */ async function ensureDirectoryExists(path) { const dirs = path.split(’/’); let currentPath = ”; for (const dir of dirs) { currentPath += dir + ’/’; if (!(await app.vault.adapter.exists(currentPath))) { await app.vault.createFolder(currentPath); } } }

/**

  • Upserts content to a file under a specific header

  • @param {string} filePath - The path to the file

  • @param {string} header - The header to search for or add

  • @param {string} content - The content to add or update */ async function upsertToFile(filePath, header, content) { await ensureDirectoryExists(filePath.split(’/‘).slice(0, -1).join(’/’));

    let file = app.vault.getAbstractFileByPath(filePath); let fileContent = ”;

    if (file) { fileContent = await app.vault.read(file); }

    const regex = new RegExp(${header}[\\s\\S]*?(?=###|$), ‘g’); const replacement = ${header}\n${content}\n\n;

    if (regex.test(fileContent)) { fileContent = fileContent.replace(regex, replacement); } else { fileContent += replacement; }

    if (file) { await app.vault.modify(file, fileContent); } else { await app.vault.create(filePath, fileContent); } }

// Main function async function coolDigest() { const version = “5.3”; const startTime = Date.now(); const currentDate = new Date().toISOString().split(‘T’)[0]; console.log([${currentDate}] Cool-Digest Script v${version} started);

try {
    // Get entire file content
    let content = tp.file.content;

    if (typeof content !== 'string') {
        throw new Error('Received non-string content');
    }

    // Extract authors using tp.frontmatter
    const authors = tp.frontmatter["authors"] || 'Unknown';
    console.log(`[${currentDate}] Authors extracted: ${authors}`);

    console.log(`[${currentDate}] File content length: ${content.length} characters`);

    // Split content into blocks
    let blocks = splitIntoBlocks(content);
    console.log(`[${currentDate}] Number of blocks: ${blocks.length}`);

    let processedBlocks = [];
    let digestedBlocks = 0;
    let nonDigestedBlocks = 0;
    let updatedBlocks = 0;
    for (let i = 0; i < blocks.length; i++) {
        let block = blocks[i];
        const blockHash = generateHash(block);
        console.log(`\n[${currentDate}] Processing block ${i + 1} (${blockHash})`);

        // Extract existing Digest and Seal Marks
        let existingDigestMatch = block.match(/\s*(\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/[^\)]+\))/);
        let existingSealMatch = block.match(/\s*(\^ds-[a-f0-9]+)/);
        let existingDigest = existingDigestMatch ? existingDigestMatch[1] : null;
        let existingSeal = existingSealMatch ? existingSealMatch[1] : null;

        console.log(`Existing Digest Mark: ${existingDigest || 'None'}`);
        console.log(`Existing Seal Mark: ${existingSeal || 'None'}`);

        // Remove existing Digest and Seal Marks for processing
        let cleanBlock = block.replace(/\s*\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/[^\)]+\)/, '')
                              .replace(/\s*\^ds-[a-f0-9]+/, '')
                              .trim();

        // Deformat content and generate hash
        let { deformattedContent, charactersRemoved } = deformatContent(cleanBlock);
        let digest = generateHash(deformattedContent);
        let cc = deformattedContent.length;
        let wc = deformattedContent.split(/\s+/).length;
        let lc = deformattedContent.split('\n').length;

        let digestMark = createDigestMark(digest, currentDate, authors, cc, wc, lc);
        let sealMark = createSealMark(digestMark, currentDate);

        console.log(`New Digest Mark: ${digestMark}`);
        console.log(`New Seal Mark: ${sealMark}`);

        // Validate existing marks if both exist
        if (existingDigest && existingSeal) {
            const validationResult = validateMarks(existingDigest, existingSeal, cleanBlock);
            console.log('Validation Result:', JSON.stringify(validationResult, null, 2));

            if (validationResult.isValid) {
                console.log(`Content unchanged for block ${blockHash}. Keeping existing Digest and Seal Marks.`);
                digestMark = existingDigest;
                sealMark = existingSeal;
                nonDigestedBlocks++;
            } else {
                console.log(`Content or marks changed for block ${blockHash}. Updating Digest and Seal Marks.`);
                updatedBlocks++;
                new Notice(`Block updated:

Old digest: {validationResult.newDigest} Old seal: {validationResult.newSeal} Old date: {currentDate}, 10000); } } else if (existingDigest || existingSeal) { console.log(Incomplete marks for block {blockHash}. Updating Digest and Seal Marks.`); updatedBlocks++; } else { console.log(`New block {blockHash}. Creating Digest and Seal Marks.`); digestedBlocks++; }

        // Append Digest Mark and Seal Mark to the block with a single space
        let processedBlock = `${cleanBlock} ${digestMark} ${sealMark}`;
        processedBlocks.push(processedBlock);

        console.log(`Block ${blockHash} stats: WC=${wc}, CC=${cc}, LC=${lc}, Characters removed: ${charactersRemoved}`);

        // Upsert block information to file
        const filePath = `Notes/ds/blocks/${digest}.md`;
        const header = `### ${currentDate}`;
        const blockContent = `${cleanBlock}\n\n${deformattedContent}`;
        await upsertToFile(filePath, header, blockContent);
    }

    let result = processedBlocks.join('\n\n');

    // Create file seal hash
    let fileSealHash = createFileSealHash(result);
    console.log(`\n[${currentDate}] File seal hash created: ${fileSealHash}`);

    // Add file seal hash and timestamp to frontmatter
    let frontmatter = content.match(/^---\n[\s\S]*?\n---\n/);
    const fileSealTimestamp = new Date().toISOString();
    if (frontmatter) {
        let updatedFrontmatter = frontmatter[0]
            .replace(/file-seal-hash:.*\n/, '')
            .replace(/file-seal-timestamp:.*\n/, '');
        updatedFrontmatter = updatedFrontmatter.replace(/---\n$/, `file-seal-hash: "${fileSealHash}"\nfile-seal-timestamp: "${fileSealTimestamp}"\n---\n`);
        result = updatedFrontmatter + result;
    } else {
        result = `---\nfile-seal-hash: "${fileSealHash}"\nfile-seal-timestamp: "${fileSealTimestamp}"\n---\n\n${result}`;
    }

    // Update the file content using app.vault.modify
    await app.vault.modify(tp.file.find_tfile(tp.file.title), result);

    const endTime = Date.now();
    const runtime = ((endTime - startTime) / 1000).toFixed(2);
    console.log(`\n[${currentDate}] Cool-Digest process completed successfully`);
    new Notice(`Cool-Digest v${version} completed:

Runtime: {fileSealHash} Blocks processed: {digestedBlocks} Blocks not digested: {updatedBlocks} Date: ${currentDate}`, 10000);

} catch (error) {
    console.error(`[${currentDate}] An error occurred:`, error);
    new Notice(`Error: ${error.message}. Check console for details.`, 10000);
}

}

// Run the script and output the result await coolDigest(); %>

<% // WORKING Cool-Digest Script v5.1 for Obsidian Templater // Fixes: // - upsert a block to the file “Notes/ds/chains/{YYYY-MM}.md” with this structure: ### [[{file-name}]] | #ds/seal/{file-seal-hash} chained {timestamp} to #ds/{previous-deformatted-block-digest}\n{comma-separated-digest-tags-within-file}\n#ds/{this-deformatted-block-digest} // upsert to “Notes/ds/blocks/{deformatted-digest}.md” with frontmatter (aliases: {digest-tag}, file-seal-hash: {file-seal-hash}) and structure as # [[{file-name}]] | #ds/seal/{file-seal-hash} digested {timestamp}\n{content} // rename Digest Mark to Digest Tag // use #ds/{digest-hash}/{date} for Digest Tag format // - set Notice messages to 10 seconds // - make sure to create the directory and file if it does not exist, all operations should me idempotent and error resilient // rename script to DS Tagger Chain // Import crypto module const crypto = require(‘crypto’); // Helper functions / Generates a SHA-256 hash of the content @param {string} content - The content to hash @param {number} length - Length of the hash to return @returns {string} SHA-256 hash of specified length / function generateHash(content, length = 12) { return crypto.createHash(‘sha256’).update(content).digest(‘hex’).substring(0, length); } / Removes markdown formatting, Digest Marks, and Seal Marks from the content @param {string} content - The content to deformat @returns {Object} Deformatted content and number of characters removed / function deformatContent(content) { if (typeof content ! ‘string’) { throw new Error(‘Deformat received non-string content’); }

const originalLength = content.length;

// Remove Digest and Seal Marks
content = content.replace(/\s\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/[^\)]+\)/g, '');
content = content.replace(/\s\^ds-[a-f0-9]+/g, '');

// Remove headers, emphasis, bold, strikethrough, blockquotes, horizontal rules
content = content.replace(/^#{1,6}\s+|[]|\-{3,}|\{3,}|{3,}/gm, '');

// Remove list markers and task list markers
content = content.replace(/^[\s][-+]\s|^\s\d+\.\s|^- \[[ x]\]\s/gm, '');

// Remove highlight markers
content = content.replace(//g, '');

// Collapse multiple newlines and trim
content = content.replace(/\n+/g, '\n').trim();

const charactersRemoved = originalLength - content.length;

return { deformattedContent: content, charactersRemoved };

} / Creates a Digest Mark @param {string} hash - The generated hash @param {string} date - Current date @param {string} authors - Content authors @param {number} cc - Character count @param {number} wc - Word count @param {number} lc - Line count @returns {string} Formatted Digest Mark / function createDigestMark(hash, date, authors, cc, wc, lc) { const encodedAuthors = encodeURIComponent(authors); return “; } / Creates a Seal Mark @param {string} digestMark - The Digest Mark @param {string} date - Current date @returns {string} Formatted Seal Mark / function createSealMark(digestMark, date) { const sealHash = generateHash(digestMark + date); return ^ds-${sealHash}; } / Splits content into header-based blocks, ignoring frontmatter @param {string} content - The content to split @returns {Array} Array of content blocks / function splitIntoBlocks(content) { // Remove frontmatter content = content.replace(/^\n[\s\S]?\n\n/, ”); const headerRegex = /^#{1,3}\s+./gm; const blocks = []; let lastIndex = 0; let match; // Check if there's content before the first header const firstHeaderMatch = content.match(headerRegex); if (firstHeaderMatch && firstHeaderMatch.index 0) { blocks.push(content.slice(0, firstHeaderMatch.index).trim()); lastIndex = firstHeaderMatch.index; } while ((match = headerRegex.exec(content)) ! null) { if (lastIndex ! match.index) { blocks.push(content.slice(lastIndex, match.index).trim()); } lastIndex = match.index; } if (lastIndex < content.length) { blocks.push(content.slice(lastIndex).trim()); } return blocks; } / Creates a file seal hash from all Digest Marks and Seal Marks @param {string} content - The full content with Digest Marks and Seal Marks @returns {string} File seal hash / function createFileSealHash(content) { const digestMarks = content.match(/\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/[^\)]+\)/g) || []; const sealMarks = content.match(/\^ds-[a-f0-9]+/g) || []; return generateHash(digestMarks.join('') + sealMarks.join(''), 12); } / Extracts hash and date from a Digest Mark @param {string} digestMark - The Digest Mark @returns {Object} Object containing hash and date / function extractDigestInfo(digestMark) { const match = digestMark.match(/\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/([a-f0-9]+)\?ts=([^&]+)/); if (match) { return { hash: match[1], date: match[2] }; } return null; } / Validates the Digest and Seal Marks @param {string} digestMark - The Digest Mark @param {string} sealMark - The Seal Mark @param {string} content - The content to validate against @returns {Object} Validation result with details / function validateMarks(digestMark, sealMark, content) { const digestInfo = extractDigestInfo(digestMark); if (!digestInfo) return { isValid: false, reason: "Invalid Digest Mark format" }; const { deformattedContent } = deformatContent(content); const newDigest = generateHash(deformattedContent); const newSeal = createSealMark(digestMark, digestInfo.date); const digestValid = newDigest = digestInfo.hash; const sealValid = newSeal = sealMark; return { isValid: digestValid && sealValid, digestValid, sealValid, oldDigest: digestInfo.hash, newDigest, oldSeal: sealMark, newSeal, oldDate: digestInfo.date }; } / Ensures a directory exists, creating it if necessary @param {string} path - The directory path / async function ensureDirectoryExists(path) { const dirs = path.split('/'); let currentPath = ''; for (const dir of dirs) { currentPath += dir + '/'; if (!(await app.vault.adapter.exists(currentPath))) { await app.vault.createFolder(currentPath); } } } / Upserts content to a file under a specific header @param {string} filePath - The path to the file @param {string} header - The header to search for or add @param {string} content - The content to add or update / async function upsertToFile(filePath, header, content) { await ensureDirectoryExists(filePath.split('/').slice(0, -1).join('/')); let file = app.vault.getAbstractFileByPath(filePath); let fileContent = ''; if (file) { fileContent = await app.vault.read(file); } const regex = new RegExp(`{header}[\s\S]?(?=###|{header}\n{content}\n\n`; if (regex.test(fileContent)) { fileContent = fileContent.replace(regex, replacement); } else { fileContent += replacement; } if (file) { await app.vault.modify(file, fileContent); } else { await app.vault.create(filePath, fileContent); } } // Main function async function coolDigest() { const version = "5.3"; const startTime = Date.now(); const currentDate = new Date().toISOString().split('T')[0]; console.log(`[{currentDate}] Cool-Digest Script v{version} started`); try { // Get entire file content let content = tp.file.content; if (typeof content ! 'string') { throw new Error('Received non-string content'); } // Extract authors using tp.frontmatter const authors = tp.frontmatter["authors"] || 'Unknown'; console.log(`[{currentDate}] Authors extracted: {currentDate}] File content length: {currentDate}] Number of blocks: {blocks.length}`); let processedBlocks = []; let digestedBlocks = 0; let nonDigestedBlocks = 0; let updatedBlocks = 0; for (let i = 0; i < blocks.length; i++) { let block = blocks[i]; const blockHash = generateHash(block); console.log(`\n[{currentDate}] Processing block {blockHash})); // Extract existing Digest and Seal Marks let existingDigestMatch = block.match(/\s(\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/[^\)]+\))/); let existingSealMatch = block.match(/\s(\^ds-[a-f0-9]+)/); let existingDigest = existingDigestMatch ? existingDigestMatch[1] : null; let existingSeal = existingSealMatch ? existingSealMatch[1] : null; console.log(Existing Digest Mark: {existingSeal || ‘None’}); // Remove existing Digest and Seal Marks for processing let cleanBlock = block.replace(/\s\[✉️\]\(https:\/\/WikiWe\.org\/Notes\/ds\/block\/[^\)]+\)/, '') .replace(/\s\^ds-[a-f0-9]+/, '') .trim(); // Deformat content and generate hash let { deformattedContent, charactersRemoved } = deformatContent(cleanBlock); let digest = generateHash(deformattedContent); let cc = deformattedContent.length; let wc = deformattedContent.split(/\s+/).length; let lc = deformattedContent.split('\n').length; let digestMark = createDigestMark(digest, currentDate, authors, cc, wc, lc); let sealMark = createSealMark(digestMark, currentDate); console.log(New Digest Mark: {sealMark}); // Validate existing marks if both exist if (existingDigest && existingSeal) { const validationResult = validateMarks(existingDigest, existingSeal, cleanBlock); console.log('Validation Result:', JSON.stringify(validationResult, null, 2)); if (validationResult.isValid) { console.log(Content unchanged for block {blockHash}. Keeping existing Digest and Seal Marks.`); digestMark = existingDigest; sealMark = existingSeal; nonDigestedBlocks++; } else { console.log(`Content or marks changed for block {blockHash}. Updating Digest and Seal Marks.); updatedBlocks++; new Notice(Block updated: Old digest: {validationResult.newDigest} Old seal: {validationResult.newSeal} Old date: {currentDate}, 10000); } } else if (existingDigest || existingSeal) { console.log(Incomplete marks for block {blockHash}. Updating Digest and Seal Marks.`); updatedBlocks++; } else { console.log(`New block {blockHash}. Creating Digest and Seal Marks.); digestedBlocks++; } // Append Digest Mark and Seal Mark to the block with a single space let processedBlock = {digestMark} {blockHash} stats: WC={cc}, LC={charactersRemoved}); // Upsert block information to file const filePath = Notes/ds/blocks/{digest}.md`; const header = `### {currentDate}; const blockContent = {deformattedContent}; await upsertToFile(filePath, header, blockContent); } let result = processedBlocks.join('\n\n'); // Create file seal hash let fileSealHash = createFileSealHash(result); console.log(\n[{fileSealHash}); // Add file seal hash and timestamp to frontmatter let frontmatter = content.match(/^\n[\s\S]?\n\n/); const fileSealTimestamp = new Date().toISOString(); if (frontmatter) { let updatedFrontmatter = frontmatter[0] .replace(/file-seal-hash:.\n/, '') .replace(/file-seal-timestamp:.\n/, ''); updatedFrontmatter = updatedFrontmatter.replace(/\n$/, file-seal-hash: “{fileSealTimestamp}“\n\n); result = updatedFrontmatter + result; } else { result = \nfile-seal-hash: “{fileSealTimestamp}“\n\n\n{result}`; } // Update the file content using app.vault.modify await app.vault.modify(tp.file.findtfile(tp.file.title), result); const endTime = Date.now(); const runtime = ((endTime - startTime) / 1000).toFixed(2); console.log(`\n[{currentDate}] Cool-Digest process completed successfully); new Notice(Cool-Digest v{runtime}s File-seal-hash: {blocks.length} Blocks digested: {nonDigestedBlocks} Blocks updated: {currentDate}, 10000); } catch (error) { console.error([{error.message}. Check console for details.`, 10000); } } // Run the script and output the result await coolDigest(); %