### Configure HackMD Plugin Settings (TypeScript) Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Provides the UI for configuring HackMD authentication and default permissions for new notes. It uses Obsidian's Setting API to create input fields for access tokens and dropdowns for read, write, and comment permissions. Changes are saved to plugin settings and the HackMD client is re-initialized. ```typescript import { App, PluginSettingTab, Setting } from 'obsidian'; import { NotePermissionRole, CommentPermissionType } from '@hackmd/api/dist/type'; // Settings configuration const DEFAULT_SETTINGS = { accessToken: '', defaultReadPermission: NotePermissionRole.OWNER, defaultWritePermission: NotePermissionRole.OWNER, defaultCommentPermission: CommentPermissionType.DISABLED, noteIdMap: {}, lastSyncTimestamps: {} }; class HackMDSettingTab extends PluginSettingTab { display(): void { const { containerEl } = this; containerEl.empty(); // Access token setting new Setting(containerEl) .setName('Token') .setDesc('HackMD API access token (from hackmd.io → Settings → API)') .addText(text => text .setPlaceholder('Enter your HackMD access token') .setValue(this.plugin.settings.accessToken || '') .onChange(async (value) => { this.plugin.settings.accessToken = value; await this.plugin.saveData(this.plugin.settings); await this.plugin.initializeClient(); })); // Read permission setting new Setting(containerEl) .setName('Read permission') .setDesc('Default read permission for new notes') .addDropdown(dropdown => dropdown .addOption(NotePermissionRole.OWNER, 'Owner') .addOption(NotePermissionRole.SIGNED_IN, 'Signed In Users') .addOption(NotePermissionRole.GUEST, 'Everyone') .setValue(this.plugin.settings.defaultReadPermission) .onChange(async (value) => { this.plugin.settings.defaultReadPermission = value; await this.plugin.saveData(this.plugin.settings); })); // Write permission setting new Setting(containerEl) .setName('Write permission') .setDesc('Default write permission for new notes') .addDropdown(dropdown => dropdown .addOption(NotePermissionRole.OWNER, 'Owner') .addOption(NotePermissionRole.SIGNED_IN, 'Signed In Users') .addOption(NotePermissionRole.GUEST, 'Everyone') .setValue(this.plugin.settings.defaultWritePermission) .onChange(async (value) => { this.plugin.settings.defaultWritePermission = value; await this.plugin.saveData(this.plugin.settings); })); } } ``` -------------------------------- ### HackMDClient.createNote - Create New Note on HackMD Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Creates a new note on HackMD with specified content, title, and permission settings. Returns the created note object containing the note ID, URL, and metadata. ```APIDOC ## POST /notes ### Description Creates a new note on HackMD with specified content, title, and permission settings. ### Method POST ### Endpoint /notes ### Parameters #### Request Body - **title** (string) - Required - The title of the note. - **content** (string) - Required - The markdown content of the note. - **readPermission** (NotePermissionRole) - Optional - The permission level for reading the note (e.g., GUEST, SIGNED_IN, PRIVATE). - **writePermission** (NotePermissionRole) - Optional - The permission level for writing to the note (e.g., SIGNED_IN, PRIVATE). - **commentPermission** (CommentPermissionType) - Optional - The permission level for commenting on the note (e.g., EVERYONE, SIGNED_IN, NONE). ### Request Example ```json { "title": "My Collaborative Document", "content": "# Hello World\n\nThis is a sample note created from Obsidian.", "readPermission": "GUEST", "writePermission": "SIGNED_IN", "commentPermission": "EVERYONE" } ``` ### Response #### Success Response (200) - **id** (string) - The unique identifier of the created note. - **title** (string) - The title of the note. - **content** (string) - The content of the note. - **createdAt** (string) - The timestamp when the note was created. - **lastChangedAt** (string) - The timestamp when the note was last modified. - **readPermission** (NotePermissionRole) - The read permission setting. - **writePermission** (NotePermissionRole) - The write permission setting. - **commentPermission** (CommentPermissionType) - The comment permission setting. #### Response Example ```json { "id": "aBcDeFgHiJkLmNoPqRsT", "title": "My Collaborative Document", "content": "# Hello World\n\nThis is a sample note created from Obsidian.", "createdAt": "2023-10-27T10:00:00Z", "lastChangedAt": "2023-10-27T10:00:00Z", "readPermission": "GUEST", "writePermission": "SIGNED_IN", "commentPermission": "EVERYONE" } ``` ``` -------------------------------- ### Handle HackMD API Errors in TypeScript Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Demonstrates how to catch and handle specific HackMDError types, such as authentication failures, permission issues, and network errors, within an asynchronous API call. This ensures graceful failure and user feedback. ```typescript import { HackMDError, HackMDErrorType } from './types'; async function robustApiCall() { const client = new HackMDClient('your-access-token'); try { const note = await client.getNote('some-note-id'); return note; } catch (error) { if (error instanceof HackMDError) { switch (error.type) { case HackMDErrorType.AUTH_FAILED: console.error('Authentication failed - check your access token'); // Prompt user to update token in settings break; case HackMDErrorType.NOT_FOUND: console.error('Note not found - it may have been deleted'); // Clean up local metadata break; case HackMDErrorType.PERMISSION_DENIED: console.error('Permission denied - you cannot access this note'); // Inform user about permission requirements break; case HackMDErrorType.SYNC_CONFLICT: console.error('Sync conflict detected'); // Suggest force sync or manual resolution break; case HackMDErrorType.NETWORK_ERROR: console.error('Network error - check your connection'); // Retry logic or offline mode break; default: console.error('Unknown error:', error.message); } } throw error; } } ``` -------------------------------- ### HackMDClient.createNote - Create New Note on HackMD (TypeScript) Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Creates a new note on HackMD using the HackMDClient. This function requires an access token for authentication and allows specifying the note's title, content, and various permission settings for reading, writing, and commenting. It returns the created note object, including its ID and URL. ```typescript import { HackMDClient } from './client'; import { NotePermissionRole, CommentPermissionType } from '@hackmd/api/dist/type'; // Initialize client with access token const client = new HackMDClient('your-hackmd-access-token'); // Create a new note with custom permissions async function createNote() { try { const note = await client.createNote({ title: 'My Collaborative Document', content: '# Hello World\n\nThis is a sample note created from Obsidian.', readPermission: NotePermissionRole.GUEST, // Everyone can read writePermission: NotePermissionRole.SIGNED_IN, // Only signed-in users can write commentPermission: CommentPermissionType.EVERYONE }); console.log('Created note:', note.id); console.log('Note URL:', `https://hackmd.io/${note.id}`); return note; } catch (error) { console.error('Failed to create note:', error.message); throw error; } } ``` -------------------------------- ### Retrieve HackMD Note Details Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Fetches comprehensive details for a specific HackMD note using its ID. This includes content, metadata, and timestamps. It handles potential errors like 'Note not found' (404) or 'Access denied' (403). ```typescript async function getNoteDetails(noteId: string) { const client = new HackMDClient('your-hackmd-access-token'); try { const note = await client.getNote(noteId); console.log('Note details:'); console.log(' ID:', note.id); console.log(' Title:', note.title); console.log(' Content length:', note.content.length); console.log(' Created:', new Date(note.createdAt).toLocaleString()); console.log(' Last changed:', new Date(note.lastChangedAt).toLocaleString()); console.log(' Read permission:', note.readPermission); console.log(' Write permission:', note.writePermission); return note; } catch (error) { if (error.statusCode === 404) { console.error('Note not found - it may have been deleted'); } else if (error.statusCode === 403) { console.error('Access denied - you do not have permission to view this note'); } throw error; } } ``` -------------------------------- ### HackMDClient.updateNote - Update Existing Note Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Updates an existing HackMD note's content and title. The method handles asynchronous processing by the HackMD API, waiting for updates to complete before returning. ```APIDOC ## PUT /notes/{noteId} ### Description Updates an existing HackMD note's content and title. ### Method PUT ### Endpoint /notes/{noteId} ### Parameters #### Path Parameters - **noteId** (string) - Required - The unique identifier of the note to update. #### Request Body - **title** (string) - Optional - The new title for the note. - **content** (string) - Optional - The new markdown content for the note. ### Request Example ```json { "title": "Updated Title", "content": "# Updated Content\n\nThis note has been modified from Obsidian." } ``` ### Response #### Success Response (200) - **id** (string) - The unique identifier of the updated note. - **title** (string) - The updated title of the note. - **content** (string) - The updated content of the note. - **createdAt** (string) - The timestamp when the note was created. - **lastChangedAt** (string) - The timestamp when the note was last modified. #### Response Example ```json { "id": "aBcDeFgHiJkLmNoPqRsT", "title": "Updated Title", "content": "# Updated Content\n\nThis note has been modified from Obsidian.", "createdAt": "2023-10-27T10:00:00Z", "lastChangedAt": "2023-10-27T11:00:00Z" } ``` #### Error Response (404) - **message** (string) - 'Note not found' #### Error Response (403) - **message** (string) - 'Permission denied' ``` -------------------------------- ### Push Local Note to HackMD - TypeScript Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Synchronizes a local Obsidian note to HackMD. It creates a new remote note if it doesn't exist or updates an existing one. Includes conflict detection to prevent overwriting remote changes. This function requires access to the Obsidian editor, file system, and a HackMD client. ```typescript async function pushToHackMD(editor: Editor, file: TFile) { const plugin = this; // HackMDPlugin instance // Get current note content const content = editor.getValue(); // Check if note already exists on HackMD const frontmatter = plugin.getFrontmatter(content); const noteId = frontmatter?.hackmd?.url ? getIdFromUrl(frontmatter.hackmd.url) : null; try { if (noteId) { // Update existing note - check for conflicts first const remoteNote = await plugin.client.getNote(noteId); const lastSync = plugin.settings.lastSyncTimestamps[file.path] || 0; const remoteModTime = new Date(remoteNote.lastChangedAt).getTime(); if (remoteModTime > lastSync) { throw new Error('Remote note modified. Use force push to overwrite.'); } await plugin.client.updateNote(noteId, { content: content, title: file.basename }); } else { // Create new note const newNote = await plugin.client.createNote({ content: content, title: file.basename, readPermission: plugin.settings.defaultReadPermission, writePermission: plugin.settings.defaultWritePermission, commentPermission: plugin.settings.defaultCommentPermission }); // Update frontmatter with HackMD metadata const metadata = { url: `https://hackmd.io/${newNote.id}`, title: newNote.title, lastSync: new Date().toISOString() }; const updatedContent = `---\nhackmd:\n url: ${metadata.url}\n title: ${metadata.title}\n lastSync: ${metadata.lastSync}\n---\n${content}`; editor.setValue(updatedContent); } // Update sync timestamp plugin.settings.lastSyncTimestamps[file.path] = Date.now(); await plugin.saveData(plugin.settings); } catch (error) { console.error('Push failed:', error); throw error; } } ``` -------------------------------- ### Pull Remote Changes to Local - TypeScript Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Fetches the latest content from HackMD and updates the local Obsidian note. It includes conflict detection to prevent overwriting local changes. This function requires the Obsidian editor, file system access, and the note's HackMD ID. ```typescript async function pullFromHackMD(editor: Editor, file: TFile) { const plugin = this; // HackMDPlugin instance try { // Extract note ID from frontmatter const content = editor.getValue(); const frontmatter = plugin.getFrontmatter(content); const noteId = frontmatter?.hackmd?.url ? getIdFromUrl(frontmatter.hackmd.url) : null; if (!noteId) { throw new Error('This file has not been pushed to HackMD yet.'); } // Check for local modifications const lastSync = plugin.settings.lastSyncTimestamps[file.path] || 0; const localModTime = file.stat.mtime; if (localModTime > lastSync) { throw new Error('Local note modified. Use force pull to overwrite.'); } // Fetch remote note const remoteNote = await plugin.client.getNote(noteId); // Update local content with remote changes const metadata = { url: `https://hackmd.io/${remoteNote.id}`, title: remoteNote.title, lastSync: new Date().toISOString() }; const updatedContent = `---\nhackmd:\n url: ${metadata.url}\n title: ${metadata.title}\n lastSync: ${metadata.lastSync}\n---\n${remoteNote.content}`; editor.setValue(updatedContent); // Update sync timestamp plugin.settings.lastSyncTimestamps[file.path] = Date.now(); await plugin.saveData(plugin.settings); console.log('Successfully pulled from HackMD'); } catch (error) { console.error('Pull failed:', error); throw error; } } ``` -------------------------------- ### Force Pull from HackMD (TypeScript) Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Overwrites local Obsidian content regardless of local changes. This function retrieves the note ID and fetches the remote note. It then reconstructs the content with updated metadata in the frontmatter and sets the editor's value. Finally, it updates the last sync timestamp and saves settings. ```typescript // Force pull - overwrite local regardless of local changes // Command: Ctrl+P -> "HackMD Sync: Force pull" async function forcePullExample(editor: Editor, file: TFile) { const plugin = this; const content = editor.getValue(); const frontmatter = plugin.getFrontmatter(content); const noteId = frontmatter?.hackmd?.url ? getIdFromUrl(frontmatter.hackmd.url) : null; if (!noteId) { throw new Error('This file has not been pushed to HackMD yet.'); } // Skip local conflict detection - directly fetch and overwrite const remoteNote = await plugin.client.getNote(noteId); const metadata = { url: `https://hackmd.io/${remoteNote.id}`, title: remoteNote.title, lastSync: new Date().toISOString() }; const updatedContent = `--- hackmd: url: ${metadata.url} title: ${metadata.title} lastSync: ${metadata.lastSync} --- ${remoteNote.content}`; editor.setValue(updatedContent); plugin.settings.lastSyncTimestamps[file.path] = Date.now(); await plugin.saveData(plugin.settings); } ``` -------------------------------- ### Force Push to HackMD (TypeScript) Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Overwrites remote HackMD content regardless of remote changes. This function retrieves the note ID from frontmatter, and if found, updates the note via the HackMD API. If no note ID exists, it creates a new note. It then updates the last sync timestamp and saves settings. ```typescript // Force push - overwrite remote regardless of remote changes // Command: Ctrl+P -> "HackMD Sync: Force push" async function forcePushExample(editor: Editor, file: TFile) { const plugin = this; const content = editor.getValue(); const frontmatter = plugin.getFrontmatter(content); const noteId = frontmatter?.hackmd?.url ? getIdFromUrl(frontmatter.hackmd.url) : null; if (noteId) { // Skip conflict detection - directly update await plugin.client.updateNote(noteId, { content: content, title: file.basename }); } else { // Create new note const note = await plugin.client.createNote({ content: content, title: file.basename, readPermission: plugin.settings.defaultReadPermission, writePermission: plugin.settings.defaultWritePermission, commentPermission: plugin.settings.defaultCommentPermission }); } plugin.settings.lastSyncTimestamps[file.path] = Date.now(); await plugin.saveData(plugin.settings); } ``` -------------------------------- ### HackMDClient.updateNote - Update Existing Note (TypeScript) Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Updates an existing HackMD note's content and title using the HackMDClient. This method takes the note ID and an object containing the new title and content. It handles asynchronous API processing and provides error handling for cases like a non-existent note or insufficient permissions. The function returns the updated note object, including its last changed timestamp. ```typescript // Update an existing note async function updateNote(noteId: string) { const client = new HackMDClient('your-hackmd-access-token'); try { const updatedNote = await client.updateNote(noteId, { title: 'Updated Title', content: '# Updated Content\n\nThis note has been modified from Obsidian.' }); console.log('Note updated successfully'); console.log('Last changed:', updatedNote.lastChangedAt); return updatedNote; } catch (error) { if (error.type === 'not_found') { console.error('Note does not exist'); } else if (error.type === 'permission_denied') { console.error('You do not have permission to edit this note'); } throw error; } } ``` -------------------------------- ### Delete Remote HackMD Note with Confirmation Source: https://context7.com/thor314/hackmd-obsidian/llms.txt Deletes a note from HackMD and removes associated local metadata. It first extracts the note ID from the file's frontmatter and then presents a confirmation modal to the user before proceeding with the deletion. If successful, it cleans up local settings and removes the HackMD frontmatter. ```typescript async function deleteHackMDNote(editor: Editor, file: TFile) { const plugin = this; // HackMDPlugin instance try { // Get note ID from frontmatter const content = editor.getValue(); const frontmatter = plugin.getFrontmatter(content); const noteId = frontmatter?.hackmd?.url ? getIdFromUrl(frontmatter.hackmd.url) : null; if (!noteId) { throw new Error('This file is not linked to a HackMD note.'); } // Create confirmation modal const modal = new DeleteConfirmModal( plugin.app, file.basename, async () => { // Delete from HackMD await plugin.client.deleteNote(noteId); // Clean up local metadata delete plugin.settings.noteIdMap[file.path]; delete plugin.settings.lastSyncTimestamps[file.path]; await plugin.saveData(plugin.settings); // Remove frontmatter if (frontmatter) { delete frontmatter.hackmd; if (Object.keys(frontmatter).length > 0) { const yamlStr = stringifyYaml(frontmatter).trim(); const restContent = content.replace(/^---\n[\s\S]*?\n---\n/, ''); editor.setValue(`---\n${yamlStr}\n---\n${restContent}`); } else { const restContent = content.replace(/^---\n[\s\S]*?\n---\n/, ''); editor.setValue(restContent.trim()); } } console.log('Successfully unlinked note from HackMD'); } ); modal.open(); } catch (error) { console.error('Delete failed:', error); throw error; } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.