aliases: 2024-09-24 file-seal-hash: 6bb550929323
DS Tagger Chain 7.5 | digested 2024-09-24
<%*
// DS Tagger Chain for Obsidian Templater
// Version 7.5
// Fixes for 7.6:
// use the word tile to describe a block of content either after the frontmatter or after a H1-H3 header
// use the word Tile ID to replace Seal Mark
// write the {deformat-digest}.md “blockFile” files to “Notes/ds/tile/{deformat-digest}.md”
// writing to files should happen at the end and provide notice and console.log of 1) list of all tile files updated, 2) block written to the blockchain file if success or explaining errors otherwise, 3)
// the Notes/ds/blocks/{deformat-digest}.md files should only check if the key #ds/{deformat-digest}/{YYYY-MM-DD} #ds/seal/{file-seal-hash} for today exists and if not, append a new block with this structure: #ds/{deformat-digest}/{YYYY-MM-DD}\n{deformatted-tile}\n*️⃣*️⃣*️⃣\n{tile}\n
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 Seal Marks
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 createSealMark(digestTag, date) {
const sealHash = generateHash(digestTag + date);
return ^ds-${sealHash};
}
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;
}
function createFileSealHash(digestTags) { return generateHash(digestTags.join(’ ’), 12); }
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); } } }
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 escapedHeader = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`${escapedHeader}[\\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);
}
new Notice(`File upserted successfully: ${filePath}`, 5000);
}
function createChainEntry(fileName, fileSealHash, digestTags, timestamp, previousBlockchainEntryDigest) {
const chainEntryContent = ### [[${fileName}]] 🔒 #ds/seal/${fileSealHash} chained ${timestamp} to #ds/${previousBlockchainEntryDigest} ${digestTags.join(' ')};
const newBlockchainEntryDigest = generateHash(chainEntryContent);
return ${chainEntryContent} #ds/${newBlockchainEntryDigest};
}
// Main function
async function dsTaggerChain() {
const version = “7.5”;
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';
console.log(`[${currentDate}] Authors extracted: ${authors}`);
console.log(`[${currentDate}] File content length: ${content.length} characters`);
let blocks = splitIntoBlocks(content);
console.log(`[${currentDate}] Number of blocks: ${blocks.length}`);
let processedBlocks = [];
let digestedBlocks = 0;
let nonDigestedBlocks = 0;
let updatedBlocks = 0;
let digestTags = [];
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})`);
let existingDigestMatch = block.match(/\s*(#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2})/);
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 Tag: ${existingDigest || 'None'}`);
console.log(`Existing Seal Mark: ${existingSeal || 'None'}`);
let cleanBlock = block.replace(/\s*#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}/, '')
.replace(/\s*\^ds-[a-f0-9]+/, '')
.trim();
let { deformattedContent, charactersRemoved } = deformatContent(cleanBlock);
let digest = generateHash(deformattedContent);
let digestTag = createDigestTag(digest, currentDate);
let sealMark = createSealMark(digestTag, currentDate);
console.log(`New Digest Tag: ${digestTag}`);
console.log(`New Seal Mark: ${sealMark}`);
if (existingDigest && existingDigest.includes(digest)) {
console.log(`Content unchanged for block ${blockHash}. Keeping existing Digest Tag and Seal Mark.`);
digestTag = existingDigest;
sealMark = existingSeal || createSealMark(digestTag, currentDate);
nonDigestedBlocks++;
} else {
console.log(`Content changed or new for block ${blockHash}. Updating Digest Tag and Seal Mark.`);
if (existingDigest) {
updatedBlocks++;
} else {
digestedBlocks++;
}
}
let processedBlock = `${cleanBlock} ${digestTag} ${sealMark}`;
processedBlocks.push(processedBlock);
digestTags.push(digestTag);
console.log(`Block ${blockHash} stats: Characters removed: ${charactersRemoved}`);
const blockFilePath = `Notes/ds/blocks/${digest}.md`;
const blockFileContent = `---