// Settings beyond the few that have their own field on the store (theme, text size, ...). One object, saved as `prefs`. // Every key has a default; cleanPrefs() fills gaps and drops anything of the wrong kind from a file written by hand. export const DEFAULTS = { // appearance uiFont: 'inter', // 'inter' | 'system' | 'mono' density: 'default', // sidebar rows and spacing: 'compact' | 'default' | 'comfortable' sidebarWidth: 268, // px showCover: true, showIcon: true, showBacklinks: true, codeSize: 100, // % of the page's code size codeWrap: false, // wrap long lines in code blocks instead of scrolling customPageFont: '', // an installed font's name for page text; '' uses the Page font choice customCodeFont: '', // same, for code // editor autoPair: true, // ( [ { ` close themselves smartTypography: false, // -- to an em dash, ... to an ellipsis, curly quotes pasteRich: true, // pasted web / rich text keeps headings, lists, links and bold as Markdown mdShortcuts: true, // "# " and "- " turn a line into a heading or a list typewriter: false, // keep the line being typed in the middle of the screen hiddenKinds: [], // block kinds left out of the slash menu autosaveDelay: 600, // ms after the last keystroke // notes dateFormat: 'YYYY-MM-DD', // daily note names dailyTemplate: '', // text a new daily note starts with; {{date}} and {{weekday}} are filled in newPageIcon: 'random', // 'random' | 'none' newPageCover: 'random', // 'random' | 'none' weekStart: 'monday', // calendar views: 'monday' | 'sunday' locale: '', // date language; '' follows the system exportLinks: 'wiki', // [[Page]] links in Markdown exports: 'wiki' | 'markdown' confirmTrash: false, // ask before moving a page to the trash trashDays: 30, // days in the trash before it is emptied; 0 never historyKeep: 100, // versions kept per page historyGapMin: 5, // at most one version per this many minutes of editing // canvas snapToGrid: true, cardColor: '', // color of new canvas cards: '' (none) or a preset 1-6 // window alwaysOnTop: false, startMinimized: false, // keyboard shortcuts: {}, // { action id: 'Mod+Shift+K' } for the shortcuts that were changed (src/lib/shortcuts.js) // quick capture captureShortcut: 'CommandOrControl+Alt+N', // global shortcut for the capture window; '' turns it off captureTo: 'daily', // 'daily' (today's note) | 'inbox' // backups backupEvery: 'off', // 'off' | 'daily' | 'weekly' backupKeep: 5, // zips kept backupFolder: '' // '' = /backups }; export const DATE_FORMATS = ['YYYY-MM-DD', 'DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY.MM.DD', 'YYYY-MM-DD dddd', 'MMMM D, YYYY']; const CHOICES = { uiFont: ['inter', 'system', 'mono'], density: ['compact', 'default', 'comfortable'], newPageIcon: ['random', 'none'], newPageCover: ['random', 'none'], weekStart: ['monday', 'sunday'], exportLinks: ['wiki', 'markdown'], backupEvery: ['off', 'daily', 'weekly'], captureTo: ['daily', 'inbox'], dateFormat: DATE_FORMATS, cardColor: ['', '1', '2', '3', '4', '5', '6'] }; const RANGES = { sidebarWidth: [220, 420], codeSize: [80, 140], autosaveDelay: [200, 5000], trashDays: [0, 365], historyKeep: [5, 1000], historyGapMin: [1, 120], backupKeep: [1, 50] }; export function cleanPrefs(raw) { const src = raw && typeof raw === 'object' ? raw : {}; const out = {}; for (const [k, def] of Object.entries(DEFAULTS)) { const v = src[k]; if (k === 'shortcuts') { out[k] = {}; if (v && typeof v === 'object' && !Array.isArray(v)) for (const [id, c] of Object.entries(v)) if (typeof c === 'string' && /^Mod(\+(Alt|Shift))*\+.+$/.test(c)) out[k][id] = c; continue; } if (CHOICES[k]) out[k] = CHOICES[k].includes(v) ? v : def; else if (RANGES[k]) { const n = Math.round(Number(v)); out[k] = Number.isFinite(n) && v !== '' && v !== null ? Math.min(RANGES[k][1], Math.max(RANGES[k][0], n)) : def; } else if (typeof def === 'boolean') out[k] = typeof v === 'boolean' ? v : def; else if (Array.isArray(def)) out[k] = Array.isArray(v) ? v.filter((x) => typeof x === 'string').slice(0, 64) : []; else out[k] = typeof v === 'string' ? v.slice(0, 4000) : def; } return out; } // [[Page]] -> [Page](Page.md), [[Page|label]] -> [label](Page.md), ![[photo.png]] -> ![](photo.png); a #heading goes // into the address. Links inside code are left alone. export function wikiToMarkdownLinks(md) { const enc = (t) => encodeURI(t).replace(/\(/g, '%28').replace(/\)/g, '%29'); const link = (embed, target, alias) => { const [name, heading] = target.split('#'); const file = /\.[a-z0-9]{2,5}$/i.test(name) ? name : name + '.md'; const label = alias || target; if (embed && /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(name)) return `![${alias || ''}](${enc(name)})`; return `${embed ? '!' : ''}[${label}](${enc(file)}${heading ? '#' + enc(heading.toLowerCase().replace(/\s+/g, '-')) : ''})`; }; let fenced = false; return md .split('\n') .map((line) => { if (/^\s*(```|~~~)/.test(line)) { fenced = !fenced; return line; } if (fenced) return line; return line .split(/(`[^`]*`)/) .map((part, i) => (i % 2 ? part : part.replace(/(!?)\[\[([^\]|]+)(?:\|([^\]]*))?\]\]/g, (_, bang, target, alias) => link(!!bang, target.trim(), alias && alias.trim())))) .join(''); }) .join('\n'); }