Open a markdown document in Obsidian you want to digest via Doc Seal
Run the Templater: Open insert template modal command via the command pallet in Obsidian.
Select Doc Seal 0.9 - Digest & Seal - Current Document. For small files, after a few moments, you should see digest tags added to the end of each tile in your file and a few notice messages.
Confirm that the folder x/ds is automatically created and tiles and blockchain files are created therein. You can modify the templates and scripts to change this folder path. 2024-09-12
Templates
Add the following templates to your Templater “Template folder location”
Doc Seal 0.9 - Digest & Seal - Current Document.md
Add the following scripts in your Templater “Script files location folder”
[!] Use VS Code or another text editor because Obsidian does not handle files with .js extension
computeSHA256.js
// Import the crypto module for cryptographic operationsconst crypto = require('crypto');// Function to generate a truncated SHA-256 hash of the input contentfunction computeSHA256(content, length = 12) {// Record the start time for performance measurementconst startTime = Date.now();// Generate SHA-256 hash, convert to hex, and truncate to specified lengthconst shash = crypto.createHash('sha256').update(content).digest('hex').substring(0, length);// Log the number of characters hashed and the time takenconst endTime = Date.now();const hashtime = ((endTime - startTime) / 1000).toFixed(2);console.log(`Hashed ${content.length} characters in ${hashtime}s into #${shash}`)// Return the truncated hashreturn shash;}// Export the generateHash function for use in other modulesmodule.exports = computeSHA256;
deformatTile.js
function deformatTile(tile) { if (typeof tile !== 'string') { throw new Error('Deformat received non-string content'); } const originalLength = tile.length; // Remove Tile IDs at the end of lines tile = tile.replace(/\s*\^[a-zA-Z0-9-]+$/gm, ''); // Remove Digest Tags at the very end of the tile tile = tile.replace(/\s*#ds\/[a-f0-9]+\/\d{4}-\d{2}-\d{2}$/, ''); // Remove markdown headers tile = tile.replace(/^#{1,6}\s+/gm, ''); // Replace ordered list markers with markdown list marker tile = tile.replace(/^\s*\d+\.\s+/gm, '- '); // Remove markdown comments tile = tile.replace(//g, ''); // Collapse multiple newlines and trim tile = tile.replace(/\n+/g, '\n').trim(); const charactersRemoved = originalLength - tile.length; return { deformattedContent: tile, charactersRemoved };}module.exports = deformatTile;
ensureDirectoryExists.js
async function ensureDirectoryExists(path) { // Resolve the path, expanding ~ to the user's home directory if necessary let resolvedPath = path.startsWith('~') ? path.replace('~', app.vault.adapter.basePath) : path; // Split the path into individual directory names const dirs = resolvedPath.split('/').filter(p => p.length); // Start with the root directory let currentPath = ''; // Iterate through each directory in the path for (const dir of dirs) { currentPath += dir + '/'; if (!(await app.vault.adapter.exists(currentPath))) { try { await app.vault.createFolder(currentPath); console.log(`Created directory: ${currentPath}`); } catch (error) { console.error(`Error creating directory ${currentPath}:`, error); throw error; // Re-throw the error to be caught by the calling function } } } console.log(`Ensured existence of directory: ${resolvedPath}`);}module.exports = ensureDirectoryExists;
getPreviousBlockchainEntryDigest.js
async function getPreviousBlockchainEntryDigest(app, blockchainDirectoryPath, computeSHA256) { // Get the current date const currentDate = new Date(); const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth() + 1; // JavaScript months are 0-based // Format the current year and month as YY-MM const currentYYMM = `${String(currentYear).slice(-2)}-${String(currentMonth).padStart(2, '0')}`; let noticeMessage = `Searching for PreviousBlockchainEntryDigest:\n`; noticeMessage += `Blockchain Directory: ${blockchainDirectoryPath}\n\n`; // Function to get the last block from a file async function getLastBlockFromFile(filePath) { try { const fileContent = await app.vault.adapter.read(filePath); const blockMatches = [...fileContent.matchAll(/#ds\/block\/([a-f0-9]+)/g)]; if (blockMatches.length > 0) { const lastMatch = blockMatches[blockMatches.length - 1]; return lastMatch[1]; } return null; } catch (error) { console.error(`Error reading file ${filePath}:`, error); return null; } } try { // Get all files in the directory const { files } = await app.vault.adapter.list(blockchainDirectoryPath); // Log all files in the directory console.log(`Contents of ${blockchainDirectoryPath}:`); console.log('Files:', files); console.log(`Total files found: ${files.length}`); noticeMessage += `Total files found in directory: ${files.length}\n`; noticeMessage += `Files found:\n${files.map(file => `- ${file}`).join('\n')}\n\n`; // Sort files in reverse chronological order const sortedFiles = files.sort((a, b) => b.localeCompare(a)); console.log(`Sorted files:`, sortedFiles); noticeMessage += `Searching files in reverse chronological order:\n`; let blocksFound = false; // Iterate through sorted files to find the latest block for (const file of sortedFiles) { console.log(`Checking file: ${file}`); noticeMessage += `- Checking file: ${file}\n`; const lastBlock = await getLastBlockFromFile(file); if (lastBlock) { blocksFound = true; console.log(`Found last block in file ${file}: ${lastBlock}`); noticeMessage += ` Found last block: ${lastBlock}\n\n`; noticeMessage += `PreviousBlockchainEntryDigest chosen: ${lastBlock}\n`; noticeMessage += `Reason: This is the most recent block found in the latest file (${file}).`; new Notice(noticeMessage, 20000); // 20 seconds duration return lastBlock; } console.log(`No blocks found in file ${file}.`); noticeMessage += ` No blocks found in this file.\n`; } if (!blocksFound) { console.log('No blocks were found in any file.'); noticeMessage += '\nNo blocks were found in any file.\n'; } // If no blocks found in any file, use the hash of the current YY-MM console.log(`Using hash of current YY-MM (${currentYYMM}) as previousBlockchainEntryDigest`); const fallbackHash = computeSHA256(currentYYMM); noticeMessage += `\nPreviousBlockchainEntryDigest chosen: ${fallbackHash}\n`; noticeMessage += `Reason: No existing blocks found. Using hash of current YY-MM (${currentYYMM}) as fallback.`; new Notice(noticeMessage, 20000); // 20 seconds duration return fallbackHash; } catch (error) { console.error(`Error accessing blockchain directory ${blockchainDirectoryPath}:`, error); noticeMessage += `\nError accessing blockchain directory: ${error.message}\n`; noticeMessage += 'Using fallback method to generate PreviousBlockchainEntryDigest.'; const fallbackHash = computeSHA256(currentYYMM); noticeMessage += `\nPreviousBlockchainEntryDigest (fallback): ${fallbackHash}`; new Notice(noticeMessage, 20000); // 20 seconds duration return fallbackHash; }}module.exports = getPreviousBlockchainEntryDigest;
splitIntoTiles.js
function splitIntoTiles(content) { // Extract frontmatter if present const frontmatterMatch = content.match(/^\s*---\n[\s\S]*?\n---\s*$/m); const frontmatter = frontmatterMatch ? frontmatterMatch[0] : ''; // Remove frontmatter from the main content const contentWithoutFrontmatter = content.replace(/^\s*---\n[\s\S]*?\n---\s*\n/m, ''); // Regex to match both Markdown (### Header) and HTML (<h3>Header</h3>) headers const headerRegex = /^(?:#{1,3}\s+.*|(?:<h[1-3]>.*?<\/h[1-3]>))$/gm; const tiles = []; let lastIndex = 0; let match; // Check if there's content before the first header const firstHeaderMatch = contentWithoutFrontmatter.match(headerRegex); if (firstHeaderMatch && firstHeaderMatch.index > 0) { // Add content before first header as a tile tiles.push(contentWithoutFrontmatter.slice(0, firstHeaderMatch.index).trim()); lastIndex = firstHeaderMatch.index; } // Iterate through all header matches while ((match = headerRegex.exec(contentWithoutFrontmatter)) !== null) { if (lastIndex !== match.index) { // Add content between headers as a tile tiles.push(contentWithoutFrontmatter.slice(lastIndex, match.index).trim()); } lastIndex = match.index; } // Add any remaining content after the last header if (lastIndex < contentWithoutFrontmatter.length) { tiles.push(contentWithoutFrontmatter.slice(lastIndex).trim()); } // Count frontmatter properties const frontmatterProperties = frontmatter .split('\n') .filter(line => line.includes(':')) .length; // Log information about the splitting process console.log(`Splitting content into ${tiles.length} tiles, and preserving ${frontmatterProperties} frontmatter properties`); // Return an object containing the frontmatter and the array of tiles return { frontmatter, tiles };}// Export the function for use in other modulesmodule.exports = splitIntoTiles;
async function ensureDirectoryExists(path) { // Extract the directory path by removing the last part of the path (assumed to be a file) const dirs = path.split('/').slice(0, -1).join('/'); // Check if the directory exists if (!(await app.vault.adapter.exists(dirs))) { // If the directory does not exist, create it await app.vault.createFolder(dirs); // Log a message to the console indicating that the directory was created console.log(`Directory created: ${dirs}`); }}async function upsertToFile(filePath, header, content) { try { // Ensure the directory for the file path exists await ensureDirectoryExists(filePath); // Attempt to retrieve the file from the vault let file = app.vault.getAbstractFileByPath(filePath); let fileContent = ''; // If the file exists, read its content if (file) { fileContent = await app.vault.read(file); } // Escape special characters in the header for use in a regular expression const escapedHeader = header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Create a regex to find the section of the file starting with the header const regex = new RegExp(`${escapedHeader}[\\s\\S]*?(?=######|$)`, 'g'); // Check if the content already exists in the file under the specified header if (regex.test(fileContent)) { // If the content is already present, log and return unchanged status if (fileContent.includes(content.trim())) { console.log(`File content already up to date: ${filePath}`); return { status: 'unchanged', file: file }; } // Special handling for blockchain entries 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 { status: 'unchanged', file: file }; } } // Append new content if the header exists but content is missing fileContent += `\n\n${content}\n`; } else { // Append content if the header does not exist fileContent += `${content}\n\n`; } // Update the existing file or create a new one with the updated content if (file) { await app.vault.modify(file, fileContent); } else { file = await app.vault.create(filePath, fileContent); } console.log(`File upserted successfully: ${filePath}`); return { status: 'updated', file: file }; } catch (error) { // Log and throw any errors encountered during the process console.error(`Error upserting file ${filePath}:`, error); throw error; }}module.exports = upsertToFile;