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();
%