Digested 2024-09-24 Authors:: Unknown License:: Unspecified Doc Seal:: bbb486f942f0

MarkdownTile

<%* // DS Tagger Chain for Obsidian Templater // Version 7.8 /* Fixes for 7.9

  • use tp.frontmatter to add DigestTag to tile file
  • make sure blockchain file follows the Blockchain Logic in the comments
  • update the Title Structure as per Specs
  • include Templater version in Notice and console.log message / / # Preserve these Tile and Blockchain Specs # Tile:
  • Structure ###### #Digested #ds/{deformat-digest}/{YYYY-MM-DD}\nAuthors:: {authors}\nLicense:: {license}\nDoc Seal:: {doc-seal-hash}\n###### #MarkdownTile\n{tile}\n###### #DeformattedTile\n{deformatted-tile}
  • Location: Notes/ds/tile/{deformat-digest}.md
  • Upsert Logic: Skip (with Notice message) if #ds/{deformat-digest}/{YYYY-MM-DD} exists, append otherwise Blockchain:
  • Structure: ### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${YYYY-MM-DDTHH:mm} to #ds/block/${previousBlockchainEntryDigest}\n${digestTags.join(' ')}\n#ds/block/{currentBlockchainEntryDigest}
  • Location: Notes/ds/blockchains/bc-{YY-MM}.md
  • Blockchain Logic: {previousBlockchainEntryDigest} is a hash of {YY-MM} for initial block or the BlockchainEntryDigest of the previous entry */

const crypto = require(‘crypto’);

// Helper functions

function generateHash(content, length = 12) { return crypto.createHash(‘sha256’).update(content).digest(‘hex’).substring(0, length); }

function deformatContent(content) { if (typeof content !== ‘string’) { throw new Error(‘Deformat received non-string content’); }

const originalLength = content.length;

// Remove Digest Tags and Tile IDs
content = content.replace(/\s*#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/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 };

}

function createDigestTag(hash, date) { return #ds/${hash}/${date}; }

function createTileID(digestTag, date) { const tileHash = generateHash(digestTag + date); return ^ds-${tileHash}; }

function splitIntoTiles(content) { // Remove frontmatter content = content.replace(/^---\n[\s\S]*?\n---\n/, ”);

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

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

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

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

return tiles;

}

function createDocSealHash(digestTags) { return generateHash(digestTags.join(’ ’), 12); }

async function ensureDirectoryExists(path) { const dirs = path.split(’/‘).slice(0, -1).join(’/’); if (!(await app.vault.adapter.exists(dirs))) { await app.vault.createFolder(dirs); } }

async function upsertToFile(filePath, header, content) { await ensureDirectoryExists(filePath);

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

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

const escapedHeader = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`${escapedHeader}[\\s\\S]*?(?=###|$)`, 'g');

if (regex.test(fileContent)) {
    if (fileContent.includes(content.trim())) {
        console.log(`File content already up to date: ${filePath}`);
        return 'unchanged';
    }
    // For blockchain entries, check if the entry already exists
    if (header.startsWith('### [[') && header.includes('🔒 #ds/seal/')) {
        if (fileContent.includes(header)) {
            console.log(`Blockchain entry already exists: ${filePath}`);
            new Notice(`Blockchain entry already exists: ${filePath}`, 5000);
            return 'unchanged';
        }
    }
    fileContent += `\n\n${content}\n`;
} else {
    fileContent += `${content}\n\n`;
}

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

console.log(`File upserted successfully: ${filePath}`);
return 'updated';

}

// Main function async function dsTaggerChain() { const version = “7.8”; const startTime = Date.now(); const currentDate = new Date().toISOString().split(‘T’)[0]; const currentTimestamp = new Date().toISOString(); console.log([${currentDate}] DS Tagger Chain v${version} started);

try {
    let content = tp.file.content;
    const fileName = tp.file.title;

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

    const authors = tp.frontmatter.authors || 'Unknown';
    const license = tp.frontmatter.license || 'Unspecified';
    console.log(`[${currentDate}] Authors extracted: ${authors}`);
    console.log(`[${currentDate}] License extracted: ${license}`);

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

    let tiles = splitIntoTiles(content);
    console.log(`[${currentDate}] Number of tiles: ${tiles.length}`);

    let processedTiles = [];
    let digestedTiles = 0;
    let nonDigestedTiles = 0;
    let updatedTiles = 0;
    let digestTags = [];
    let tileFilesUpdated = {};

    for (let i = 0; i < tiles.length; i++) {
        let tile = tiles[i];
        const tileHash = generateHash(tile);
        console.log(`\n[${currentDate}] Processing tile ${i + 1} (${tileHash})`);

        let existingDigestMatch = tile.match(/\s*(#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2})/);
        let existingTileIDMatch = tile.match(/\s*(\^ds-[a-f0-9]+)/);
        let existingDigest = existingDigestMatch ? existingDigestMatch[1] : null;
        let existingTileID = existingTileIDMatch ? existingTileIDMatch[1] : null;

        console.log(`Existing Digest Tag: ${existingDigest || 'None'}`);
        console.log(`Existing Tile ID: ${existingTileID || 'None'}`);

        let cleanTile = tile.replace(/\s*#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/, '')
                            .replace(/\s*\^ds-[a-f0-9]+/, '')
                            .trim();

        let { deformattedContent, charactersRemoved } = deformatContent(cleanTile);
        let digest = generateHash(deformattedContent);
        let digestTag = createDigestTag(digest, currentDate);
        let tileID = createTileID(digestTag, currentDate);

        console.log(`New Digest Tag: ${digestTag}`);
        console.log(`New Tile ID: ${tileID}`);

        if (existingDigest && existingDigest.includes(digest) && existingDigest.includes(currentDate)) {
            console.log(`Content unchanged for tile ${tileHash}. Keeping existing Digest Tag and Tile ID.`);
            digestTag = existingDigest;
            tileID = existingTileID || createTileID(digestTag, currentDate);
            nonDigestedTiles++;
        } else {
            console.log(`Content changed or new for tile ${tileHash}. Updating Digest Tag and Tile ID.`);
            if (existingDigest) {
                updatedTiles++;
            } else {
                digestedTiles++;
            }
        }

        let processedTile = `${cleanTile} ${digestTag} ${tileID}`;
        processedTiles.push(processedTile);
        digestTags.push(digestTag);

        console.log(`Tile ${tileHash} stats: Characters removed: ${charactersRemoved}`);

        const tileFilePath = `Notes/ds/tile/${digest}.md`;
        const docSealHash = createDocSealHash(digestTags);
        const tileFileContent = `*Digested* ${digestTag}

Authors:: {license} Doc Seal:: ${docSealHash}

MarkdownTile

${cleanTile}

DeformattedTile

{digestTag}`, tileFileContent); tileFilesUpdated[tileFilePath] = tileUpdateStatus; }

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

    let docSealHash = createDocSealHash(digestTags);
    console.log(`\n[${currentDate}] Doc seal hash created: ${docSealHash}`);

    let frontmatter = content.match(/^---\n[\s\S]*?\n---\n/);
    const docSealTimestamp = new Date().toISOString();
    if (frontmatter) {
        let updatedFrontmatter = frontmatter[0]
            .replace(/doc-seal-hash:.*\n/, '')
            .replace(/doc-seal-timestamp:.*\n/, '');
        updatedFrontmatter = updatedFrontmatter.replace(/---\n$/, `doc-seal-hash: "${docSealHash}"\ndoc-seal-timestamp: "${docSealTimestamp}"\n---\n`);
        result = updatedFrontmatter + result;
    } else {
        result = `---\ndoc-seal-hash: "${docSealHash}"\ndoc-seal-timestamp: "${docSealTimestamp}"\n---\n\n${result}`;
    }

    // Update blockchain file
    const blockchainFilePath = `Notes/ds/blockchains/bc-${currentDate.slice(2, 7)}.md`;
    let previousBlockchainEntryDigest = '';
    const blockchainFile = app.vault.getAbstractFileByPath(blockchainFilePath);
    
    if (blockchainFile) {
        const blockchainFileContent = await app.vault.read(blockchainFile);
        const lastEntryMatch = blockchainFileContent.match(/#ds\/block\/([a-f0-9]+)$/m);
        previousBlockchainEntryDigest = lastEntryMatch ? lastEntryMatch[1] : '';
    } else {
        previousBlockchainEntryDigest = generateHash(currentDate.substring(0, 7));
    }

    // Create the blockchain content without the final hash
    const blockchainContentWithoutHash = `### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${currentTimestamp} to #ds/block/${previousBlockchainEntryDigest}

${digestTags.join(’ ’)}`.trim();

    // Generate the hash for the blockchain entry
    const blockchainEntryHash = generateHash(blockchainContentWithoutHash);

    // Create the final blockchain content
    const blockchainFileContent = `${blockchainContentWithoutHash}

block/${blockchainEntryHash}`;

    const blockchainUpdateStatus = await upsertToFile(blockchainFilePath, `### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${currentTimestamp}`, blockchainFileContent);
    if (blockchainUpdateStatus === 'updated') {
        console.log(`Updated blockchain file: ${blockchainFilePath}`);
    } else {
        console.log(`Blockchain file already up to date: ${blockchainFilePath}`);
    }

    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}] DS Tagger Chain process completed successfully`);
    
    const statusMessage = `DS Tagger Chain v${version} completed:

Runtime: {docSealHash} Tiles processed: {digestedTiles} Tiles not digested: {updatedTiles} Date: ${currentDate}`;

    const tileFilesMessage = `Tile files updated:

{Object.entries(tileFilesUpdated).map(([file, status]) => `{file}: ${status}).join('\n')};

    const blockchainEntryMessage = `Blockchain entry (${blockchainUpdateStatus}):

${blockchainFileContent}`;

    console.log(statusMessage);
    console.log(tileFilesMessage);
    console.log(blockchainEntryMessage);

    new Notice(statusMessage, 10000);
    new Notice(tileFilesMessage, 10000);
    new Notice(blockchainEntryMessage, 10000);

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

}

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

DeformattedTile

<% // DS Tagger Chain for Obsidian Templater // Version 7.8 / Fixes for 7.9 use tp.frontmatter to add DigestTag to tile file make sure blockchain file follows the Blockchain Logic in the comments update the Title Structure as per Specs include Templater version in Notice and console.log message / / # Preserve these Tile and Blockchain Specs # Tile: Structure ###### #Digested #ds/{deformat-digest}/{YYYY-MM-DD}\nAuthors:: {authors}\nLicense:: {license}\nDoc Seal:: {doc-seal-hash}\n###### #MarkdownTile\n{tile}\n###### #DeformattedTile\n{deformatted-tile} Location: Notes/ds/tile/{deformat-digest}.md Upsert Logic: Skip (with Notice message) if #ds/{deformat-digest}/{YYYY-MM-DD} exists, append otherwise Blockchain: Structure: ### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${YYYY-MM-DDTHH:mm} to #ds/block/${previousBlockchainEntryDigest}\n${digestTags.join(' ')}\n#ds/block/{currentBlockchainEntryDigest} Location: Notes/ds/blockchains/bc-{YY-MM}.md Blockchain Logic: {previousBlockchainEntryDigest} is a hash of {YY-MM} for initial block or the BlockchainEntryDigest of the previous entry / const crypto = require(‘crypto’); // Helper functions function generateHash(content, length = 12) { return crypto.createHash(‘sha256’).update(content).digest(‘hex’).substring(0, length); } function deformatContent(content) { if (typeof content ! ‘string’) { throw new Error(‘Deformat received non-string content’); }

const originalLength = content.length;

// Remove Digest Tags and Tile IDs
content = content.replace(/\s#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/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 };

} function createDigestTag(hash, date) { return #ds/${hash}/${date}; } function createTileID(digestTag, date) { const tileHash = generateHash(digestTag + date); return ^ds-${tileHash}; } function splitIntoTiles(content) { // Remove frontmatter content = content.replace(/^\n[\s\S]?\n\n/, ”); const headerRegex = /^#{1,3}\s+./gm; const tiles = []; let lastIndex = 0; let match; // Check if there's content before the first header const firstHeaderMatch = content.match(headerRegex); if (firstHeaderMatch && firstHeaderMatch.index 0) { tiles.push(content.slice(0, firstHeaderMatch.index).trim()); lastIndex = firstHeaderMatch.index; } while ((match = headerRegex.exec(content)) ! null) { if (lastIndex ! match.index) { tiles.push(content.slice(lastIndex, match.index).trim()); } lastIndex = match.index; } if (lastIndex < content.length) { tiles.push(content.slice(lastIndex).trim()); } return tiles; } function createDocSealHash(digestTags) { return generateHash(digestTags.join(' '), 12); } async function ensureDirectoryExists(path) { const dirs = path.split('/').slice(0, -1).join('/'); if (!(await app.vault.adapter.exists(dirs))) { await app.vault.createFolder(dirs); } } async function upsertToFile(filePath, header, content) { await ensureDirectoryExists(filePath); let file = app.vault.getAbstractFileByPath(filePath); let fileContent = ''; if (file) { fileContent = await app.vault.read(file); } const escapedHeader = header.replace(/[.+?^{}()|[]\]/g, ’\&'); const regex = new RegExp(`{escapedHeader}[\s\S]?(?=###|)`, 'g'); if (regex.test(fileContent)) { if (fileContent.includes(content.trim())) { console.log(`File content already up to date: {filePath}); return 'unchanged'; } // For blockchain entries, check if the entry already exists if (header.startsWith('### [[') && header.includes('🔒 #ds/seal/')) { if (fileContent.includes(header)) { console.log(Blockchain entry already exists: {filePath}, 5000); return 'unchanged'; } } fileContent += \n\n{content}\n`; } else { fileContent += `{content}\n\n; } if (file) { await app.vault.modify(file, fileContent); } else { await app.vault.create(filePath, fileContent); } console.log(File upserted successfully: {filePath}`); return 'updated'; } // Main function async function dsTaggerChain() { const version = "7.8"; const startTime = Date.now(); const currentDate = new Date().toISOString().split('T')[0]; const currentTimestamp = new Date().toISOString(); console.log(`[{currentDate}] DS Tagger Chain v{version} started`); try { let content = tp.file.content; const fileName = tp.file.title; if (typeof content ! 'string') { throw new Error('Received non-string content'); } const authors = tp.frontmatter.authors || 'Unknown'; const license = tp.frontmatter.license || 'Unspecified'; console.log(`[{currentDate}] Authors extracted: {currentDate}] License extracted: {currentDate}] File content length: {currentDate}] Number of tiles: {tiles.length}`); let processedTiles = []; let digestedTiles = 0; let nonDigestedTiles = 0; let updatedTiles = 0; let digestTags = []; let tileFilesUpdated = {}; for (let i = 0; i < tiles.length; i++) { let tile = tiles[i]; const tileHash = generateHash(tile); console.log(`\n[{currentDate}] Processing tile {tileHash})); let existingDigestMatch = tile.match(/\s(#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2})/); let existingTileIDMatch = tile.match(/\s(\^ds-[a-f0-9]+)/); let existingDigest = existingDigestMatch ? existingDigestMatch[1] : null; let existingTileID = existingTileIDMatch ? existingTileIDMatch[1] : null; console.log(Existing Digest Tag: {existingTileID || ‘None’}); let cleanTile = tile.replace(/\s#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/, '') .replace(/\s\^ds-[a-f0-9]+/, '') .trim(); let { deformattedContent, charactersRemoved } = deformatContent(cleanTile); let digest = generateHash(deformattedContent); let digestTag = createDigestTag(digest, currentDate); let tileID = createTileID(digestTag, currentDate); console.log(New Digest Tag: {tileID}); if (existingDigest && existingDigest.includes(digest) && existingDigest.includes(currentDate)) { console.log(Content unchanged for tile {tileHash}. Keeping existing Digest Tag and Tile ID.`); digestTag = existingDigest; tileID = existingTileID || createTileID(digestTag, currentDate); nonDigestedTiles++; } else { console.log(`Content changed or new for tile {tileHash}. Updating Digest Tag and Tile ID.); if (existingDigest) { updatedTiles++; } else { digestedTiles++; } } let processedTile = {digestTag} {tileHash} stats: Characters removed: {digest}.md; const docSealHash = createDocSealHash(digestTags); const tileFileContent = Digested {authors} License:: {docSealHash} #MarkdownTile {cleanTile} #DeformattedTile {deformattedContent} ; const tileUpdateStatus = await upsertToFile(tileFilePath, Digested {digestTag}`, tileFileContent); tileFilesUpdated[tileFilePath] = tileUpdateStatus; } let result = processedTiles.join('\n\n'); let docSealHash = createDocSealHash(digestTags); console.log(`\n[{currentDate}] Doc seal hash created: {docSealHash}`); let frontmatter = content.match(/^\n[\s\S]?\n\n/); const docSealTimestamp = new Date().toISOString(); if (frontmatter) { let updatedFrontmatter = frontmatter[0] .replace(/doc-seal-hash:.\n/, '') .replace(/doc-seal-timestamp:.\n/, ''); updatedFrontmatter = updatedFrontmatter.replace(/\n/, doc-seal-hash: "${docSealHash}"\ndoc-seal-timestamp: "${docSealTimestamp}"\n\n); result = updatedFrontmatter + result; } else { result = \ndoc-seal-hash: "${docSealHash}"\ndoc-seal-timestamp: "${docSealTimestamp}"\n\n\n${result}; } // Update blockchain file const blockchainFilePath = Notes/ds/blockchains/bc-${currentDate.slice(2, 7)}.md; let previousBlockchainEntryDigest = ”; const blockchainFile = app.vault.getAbstractFileByPath(blockchainFilePath);

    if (blockchainFile) {
        const blockchainFileContent = await app.vault.read(blockchainFile);
        const lastEntryMatch = blockchainFileContent.match(/#ds\/block\/([a-f0-9]+)$/m);
        previousBlockchainEntryDigest = lastEntryMatch ? lastEntryMatch[1] : '';
    } else {
        previousBlockchainEntryDigest = generateHash(currentDate.substring(0, 7));
    }
    // Create the blockchain content without the final hash
    const blockchainContentWithoutHash = `### [[${fileName}]] 🔒 #ds/seal/${docSealHash} chained ${currentTimestamp} to #ds/block/${previousBlockchainEntryDigest}

{digestTags.join(' ')}`.trim(); // Generate the hash for the blockchain entry const blockchainEntryHash = generateHash(blockchainContentWithoutHash); // Create the final blockchain content const blockchainFileContent = `{blockchainContentWithoutHash} #ds/block/{blockchainEntryHash}`; const blockchainUpdateStatus = await upsertToFile(blockchainFilePath, `### [[{fileName}]] 🔒 seal/{currentTimestamp}, blockchainFileContent); if (blockchainUpdateStatus = 'updated') { console.log(Updated blockchain file: {blockchainFilePath}`); } else { console.log(`Blockchain file already up to date: {blockchainFilePath}); } 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}] DS Tagger Chain process completed successfully`);

    const statusMessage = `DS Tagger Chain v${version} completed:

Runtime: {docSealHash} Tiles processed: {digestedTiles} Tiles not digested: {updatedTiles} Date: {Object.entries(tileFilesUpdated).map(([file, status]) = ${file}: ${status}).join(‘\n’)}; const blockchainEntryMessage = Blockchain entry ({blockchainFileContent}; console.log(statusMessage); console.log(tileFilesMessage); console.log(blockchainEntryMessage); new Notice(statusMessage, 10000); new Notice(tileFilesMessage, 10000); new Notice(blockchainEntryMessage, 10000); } catch (error) { console.error([{version}: ${error.message}. Check console for details.`, 10000); } } // Run the script and output the result await dsTaggerChain(); %