<%* // Record start time for entire template runtime const startTime = performance.now();
// Import necessary modules const crypto = require(‘crypto’); const fs = require(‘fs’);
// Function to normalize the text block and track removals
function normalizeText(block) {
console.log([${new Date().toISOString()}] Normalizing text block);
const removals = {
headers: 0,
boldItalic: 0,
unorderedLists: 0,
orderedLists: 0,
todos: 0,
digestLink: 0,
sealLink: 0
};
// Strip markdown prefixes (e.g., headers, bold, italic)
block = block.replace(/^#+\s+/gm, () => { removals.headers++; return ''; }); // Headers
block = block.replace(/[*_]{1,3}([^*_]+)[*_]{1,3}/g, (_, p1) => { removals.boldItalic++; return p1; }); // Bold and Italics
// Remove ordered lists, lists, and todos
block = block.replace(/^\s*[-*+]\s+/gm, () => { removals.unorderedLists++; return ''; }); // Unordered lists
block = block.replace(/^\s*\d+\.\s+/gm, () => { removals.orderedLists++; return ''; }); // Ordered lists
block = block.replace(/^\s*[-*+]\s+\[.\]\s+/gm, () => { removals.todos++; return ''; }); // Todos
// Remove Digest Link
block = block.replace(/\[\^ds\/[a-f0-9]{12}\/\d{4}-\d{2}-\d{2}\^\]/g, () => { removals.digestLink++; return ''; });
// Remove Seal Link
block = block.replace(/\s\^ds-\d{4}-\d{2}-\d{2}-[a-f0-9]{12}/g, () => { removals.sealLink++; return ''; });
return { normalizedText: block.trim(), removals };
}
// Function to compute a 12-character SHA-256 hash function computeHash(text) { const fullHash = crypto.createHash(‘sha256’).update(text).digest(‘hex’); return fullHash.slice(0, 12); }
// Function to upsert content to a file function upsertToFile(filePath, header, content) { let fileContent = ”; if (fs.existsSync(filePath)) { fileContent = fs.readFileSync(filePath, ‘utf8’); }
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;
}
fs.writeFileSync(filePath, fileContent);
}
// Function to verify if content has already been digested function verifyExistingDigest(content, existingDigest) { const { normalizedText } = normalizeText(content); const computedDigest = computeHash(normalizedText); return computedDigest === existingDigest; }
// Get the currently selected text in Obsidian
console.log([${new Date().toISOString()}] Getting selected text);
let selection = app.workspace.activeEditor.getSelection();
// Check if no text is selected
if (!selection || selection.trim() === ”) {
const noSelectionError = “No text selected. Please select the text you want to digest.”;
new Notice(noSelectionError);
console.error([${new Date().toISOString()}] ${noSelectionError});
throw new Error(noSelectionError);
}
// Check for existing Digest Links and Seal Links const digestLinkMatches = […selection.matchAll(/[^ds/([a-f0-9]{12})/(\d{4}-\d{2}-\d{2})^]/g)]; const sealLinkMatches = […selection.matchAll(/\s(^ds-(\d{4}-\d{2}-\d{2})-([a-f0-9]{12}))/g)];
if (digestLinkMatches.length > 1) {
const warningMessage = Warning: Multiple Digest Links found (${digestLinkMatches.length}). Only the last one will be considered.;
console.warn([${new Date().toISOString()}] ${warningMessage});
new Notice(warningMessage);
}
if (sealLinkMatches.length > 1) {
const warningMessage = Warning: Multiple Seal Links found (${sealLinkMatches.length}). Only the last one will be considered.;
console.warn([${new Date().toISOString()}] ${warningMessage});
new Notice(warningMessage);
}
let existingDigestLink = digestLinkMatches.length > 0 ? digestLinkMatches[digestLinkMatches.length - 1][0] : ”; let existingDigest = digestLinkMatches.length > 0 ? digestLinkMatches[digestLinkMatches.length - 1][1] : ”; let existingDigestDate = digestLinkMatches.length > 0 ? digestLinkMatches[digestLinkMatches.length - 1][2] : ”; let existingSealLink = sealLinkMatches.length > 0 ? sealLinkMatches[sealLinkMatches.length - 1][1] : ”; let existingSealDate = sealLinkMatches.length > 0 ? sealLinkMatches[sealLinkMatches.length - 1][2] : ”; let existingSealHash = sealLinkMatches.length > 0 ? sealLinkMatches[sealLinkMatches.length - 1][3] : ”;
let selectionWithoutLinks = selection; if (existingDigestLink) { selectionWithoutLinks = selectionWithoutLinks.replace(existingDigestLink, ”).trim(); } if (existingSealLink) { selectionWithoutLinks = selectionWithoutLinks.replace(existingSealLink, ”).trim(); }
// Normalize the selected text
const { normalizedText: normalizedSelection, removals } = normalizeText(selectionWithoutLinks);
console.log([${new Date().toISOString()}] Text normalized);
console.log([${new Date().toISOString()}] Normalized Content:\n${normalizedSelection});
// Compute the normal hash of the normalized selection
const normalHash = computeHash(normalizedSelection);
console.log([${new Date().toISOString()}] Normal Hash computed: ${normalHash});
let digestVerificationResult = ”;
if (existingDigestLink) {
if (verifyExistingDigest(selectionWithoutLinks, existingDigest)) {
const unchangedMessage = “Content unchanged. No re-digestion needed.”;
new Notice(unchangedMessage);
console.log([${new Date().toISOString()}] ${unchangedMessage});
digestVerificationResult = Existing Digest verified successfully. Date: ${existingDigestDate};
return;
} else {
digestVerificationResult = “Existing Digest verification failed. Content will be re-digested.”;
console.log([${new Date().toISOString()}] ${digestVerificationResult});
}
}
// Get the current date in YYYY-MM-DD format
const digestDate = new Date().toISOString().split(‘T’)[0];
console.log([${new Date().toISOString()}] Digest date: ${digestDate});
// Calculate word and character counts const wordCount = normalizedSelection.split(/\s+/).length; const charCount = selectionWithoutLinks.length; const lineCount = selectionWithoutLinks.split(‘\n’).length;
// Get additional metadata const currentFile = app.workspace.getActiveFile(); const filePath = currentFile ? currentFile.path : “Unknown”; const frontmatter = app.metadataCache.getFileCache(currentFile).frontmatter; const authors = frontmatter && frontmatter.authors ? frontmatter.authors.join(”, ”) : “Unknown”;
// Ensure the directory exists before writing
const digestsContentPath = ${app.vault.adapter.basePath}/Directory/Digests/content/;
fs.mkdirSync(digestsContentPath, { recursive: true });
// Append to content digest file
const contentDigestPath = ${digestsContentPath}${normalHash}.md;
const contentDigestHeader = ### #${normalHash}/${digestDate};
const contentDigestEntry = ${selectionWithoutLinks} #cbc \n${normalizedSelection} #nbc \nFile: ${filePath}\nAuthors: ${authors};
upsertToFile(contentDigestPath, contentDigestHeader, contentDigestEntry);
console.log([${new Date().toISOString()}] Upserted to content digest file: ${contentDigestPath});
// Calculate hashtime const endTime = performance.now(); const hashtime = ((endTime - startTime) / 1000).toFixed(6);
// Create the removal summary
const removalSummary = Object.entries(removals)
.filter(([_, count]) ⇒ count > 0)
.map(([type, count]) ⇒ ${type}: ${count})
.join(‘\n’);
// Create the notice and log message const message = `Date Digest v1.5: Normal Hash: {wordCount} Character Count: {lineCount} Normal Character Count: {charCount - normalizedSelection.length} Hashtime Seconds: {digestDate} Digest File: {filePath} Authors: {existingSealLink ? ‘Existing Seal Link preserved: ’ + existingSealLink : ”} ${digestVerificationResult}
Removals during normalization: ${removalSummary}`;
new Notice(message);
console.log([${new Date().toISOString()}] Date Digest v1.5: ${message});
// Update the selection with the new digest information
const newDigestLink = [^ds/${normalHash}/${digestDate}^];
const updatedSelection = ${selectionWithoutLinks} ${newDigestLink}${existingSealLink ? ' ' + existingSealLink : ''};
app.workspace.activeEditor.editor.replaceSelection(updatedSelection);
// Create the footnote
const footnote = [^ds/${normalHash}/${digestDate}^]: ${new Date().toISOString()} WC:${wordCount}, CC:${charCount}, LC:${lineCount}, DF:[[${contentDigestPath.split('/').pop()}]], File: ${filePath}, Authors: ${authors};
// Upsert the footnote under the ”### References” header const fileContent = await app.vault.read(currentFile); let updatedContent = fileContent;
const referencesHeader = ”### References”;
const footnoteRegex = new RegExp(\\[\\^ds\\/[a-f0-9]{12}\\/\\d{4}-\\d{2}-\\d{2}\\^\\]:.*, ‘g’);
if (fileContent.includes(referencesHeader)) {
const parts = fileContent.split(referencesHeader);
if (footnoteRegex.test(parts[1])) {
parts[1] = parts[1].replace(footnoteRegex, footnote);
} else {
parts[1] = \n${footnote}${parts[1]};
}
updatedContent = parts.join(referencesHeader);
} else {
updatedContent += \n\n${referencesHeader}\n${footnote}\n;
}
await app.vault.modify(currentFile, updatedContent);
console.log([${new Date().toISOString()}] Footnote added or updated under References);
%>