Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 6x 6x 6x 27x 39x 17x 17x 15x 17x 3x 17x 30x 6x 9x 9x 8x 2x 2x 6x 6x 6x 6x 7x 5x 7x 7x 6x 3x 9x 6x 6x 12x 5x 7x 6x 154x 92x 92x 88x 88x 88x 8x 8x 8x 10x 10x 10x 10x 8x 127x 127x 127x 127x 127x 57x | // Wikilink placeholder tokens for markdown round-trip
const WL_START = '\u2039WIKILINK:'
const WL_END = '\u203A'
const WL_RE = new RegExp(`${WL_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^${WL_END}]+)${WL_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'g')
/** Pre-process markdown: replace [[target]] with placeholder tokens */
export function preProcessWikilinks(md: string): string {
return md.replace(/\[\[([^\]]+)\]\]/g, (_m, target) => `${WL_START}${target}${WL_END}`)
}
// Minimal shape of a BlockNote block for wikilink processing
interface BlockLike {
content?: InlineItem[]
children?: BlockLike[]
[key: string]: unknown
}
interface InlineItem {
type: string
text?: string
props?: Record<string, string>
content?: unknown
[key: string]: unknown
}
type ContentTransform = (content: InlineItem[]) => InlineItem[]
/** Walk blocks recursively, applying a transform to each block's inline content */
function walkBlocks(blocks: unknown[], transform: ContentTransform, clone = false): unknown[] {
return (blocks as BlockLike[]).map(block => {
const b = clone ? { ...block } : block
if (b.content && Array.isArray(b.content)) {
b.content = transform(b.content)
}
if (b.children && Array.isArray(b.children)) {
b.children = walkBlocks(b.children, transform, clone) as BlockLike[]
}
return b
})
}
/** Walk blocks and replace placeholder text with wikilink inline content */
export function injectWikilinks(blocks: unknown[]): unknown[] {
return walkBlocks(blocks, expandWikilinksInContent)
}
/**
* Deep-clone blocks and convert wikilink inline content back to [[target]] text.
* This is the reverse of injectWikilinks — used before blocksToMarkdownLossy
* so that wikilinks survive the markdown round-trip.
*/
export function restoreWikilinksInBlocks(blocks: unknown[]): unknown[] {
return walkBlocks(blocks, collapseWikilinksInContent, true)
}
function expandWikilinksInContent(content: InlineItem[]): InlineItem[] {
const result: InlineItem[] = []
for (const item of content) {
if (item.type !== 'text' || typeof item.text !== 'string' || !item.text.includes(WL_START)) {
result.push(item)
continue
}
const text = item.text as string
let lastIndex = 0
WL_RE.lastIndex = 0
let match
while ((match = WL_RE.exec(text)) !== null) {
if (match.index > lastIndex) {
result.push({ ...item, text: text.slice(lastIndex, match.index) })
}
result.push({
type: 'wikilink',
props: { target: match[1] },
content: undefined,
})
lastIndex = match.index + match[0].length
}
if (lastIndex < text.length) {
result.push({ ...item, text: text.slice(lastIndex) })
}
}
return result
}
function collapseWikilinksInContent(content: InlineItem[]): InlineItem[] {
const result: InlineItem[] = []
for (const item of content) {
if (item.type === 'wikilink' && item.props?.target) {
result.push({ type: 'text', text: `[[${item.props.target}]]` })
} else {
result.push(item)
}
}
return result
}
/** Strip YAML frontmatter from markdown, returning [frontmatter, body] */
export function splitFrontmatter(content: string): [string, string] {
if (!content.startsWith('---')) return ['', content]
const end = content.indexOf('\n---', 3)
if (end === -1) return ['', content]
let to = end + 4
if (content[to] === '\n') to++
return [content.slice(0, to), content.slice(to)]
}
/** Extract all outgoing wikilink targets from content.
* Finds [[target]] and [[target|display]] patterns, returning just the target part.
* Returns a sorted, deduplicated array. */
export function extractOutgoingLinks(content: string): string[] {
const links: string[] = []
const re = /\[\[([^\]]+)\]\]/g
let match
while ((match = re.exec(content)) !== null) {
const inner = match[1]
const pipeIdx = inner.indexOf('|')
const target = pipeIdx !== -1 ? inner.slice(0, pipeIdx) : inner
Eif (target) links.push(target)
}
return [...new Set(links)].sort()
}
export function countWords(content: string): number {
const [, body] = splitFrontmatter(content)
const withoutTitle = body.replace(/^\s*# [^\n]+\n?/, '')
const withoutWikilinks = withoutTitle.replace(/\[\[[^\]]*\]\]/g, '')
const text = withoutWikilinks.replace(/[#*_[\]`>~\-|]/g, '').trim()
if (!text) return 0
return text.split(/\s+/).filter(Boolean).length
}
|