// The pinned dialect, in ONE place.
//
// markdown-strip.md §2.1 pins CommonMark 0.31.2 plus exactly six extensions, and the set is CLOSED:
// syntax outside it is literal text, not markup. Both `strip()` (→ text + facets, for bridges) and
// `render()` (→ HTML, for our own surfaces) must parse identically — if they drift, the same post
// says two different things depending on who is looking, which is the whole failure the pinned
// dialect exists to prevent. So the config and the lexical rules live here and are imported, never
// copied.
import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'
import { gfmStrikethroughFromMarkdown } from 'mdast-util-gfm-strikethrough'
import { gfmTable } from 'micromark-extension-gfm-table'
import { gfmTableFromMarkdown } from 'mdast-util-gfm-table'
import { gfmFootnote } from 'micromark-extension-gfm-footnote'
import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote'
import { directive } from 'micromark-extension-directive'
import { directiveFromMarkdown } from 'mdast-util-directive'
import { parseUri } from './uri.js'
export const MICROMARK = {
// ⛔ singleTilde:false — micromark defaults it TRUE, which would consume `~x~` as strikethrough.
extensions: [gfmStrikethrough({ singleTilde: false }), gfmTable(), gfmFootnote(), directive()],
mdastExtensions: [
gfmStrikethroughFromMarkdown(),
gfmTableFromMarkdown(),
gfmFootnoteFromMarkdown(),
directiveFromMarkdown()
]
}
// §3.3 NORMATIVE — favela-web's shipped URL_RE verbatim. Changing it changes records.
export const URL_RE = /\bhttps?:\/\/[^\s<]+[^\s<.,:;!?)\]}'"]/g
export const WIKILINK_RE = /\[\[([^\][|]+)(?:\|([^\][]*))?\]\]/g
export const HASHTAG_RE = /(^|\s)(#([A-Za-z0-9_À-]{1,64}))/g
// §3.2 — only http/https may carry a link. Everything else renders as text.
export const isHttp = (uri) => /^https?:\/\//i.test(String(uri || ''))
// §3.4 — a mention names a USER: `favela://<identity-root>`; `did:` is the legacy form.
//
// ⛔ NARROWED. This was bare `/^(favela:\/\/|did:)/`, so it matched EVERY favela URI — including
// `favela://blob/<oid>` (worked around below by telling every caller to test for a blob first) and
// `favela://<root>/<collection>/<name>`, which was NOT worked around because the record form was
// never specified. favela-surface emits one for a quote (router.js:291), so a quote was classified
// as a mention of a user whose identity was actually a feed key. See lib/uri.js.
//
// A mention is now the USER form specifically: a favela URI with no path, or a `did:`.
export const isMention = (uri) => {
const s = String(uri || '')
if (/^did:/i.test(s)) return true
const u = parseUri(s)
return !!u && u.kind === 'user'
}
// record-vocabulary.md §3.5 — an embedded file: `favela://blob/<oid>`, where `<oid>` is the
// hyper-overlay content hash. A record never contains media; it names a file the overlay holds.
//
// ⛔ This form ALSO matches `isMention`, which is `^favela://`. Every caller must test for a blob
// FIRST — otherwise an embed renders as a mention span and the picture silently disappears. That
// ordering is not a style choice, it is the difference between an image and nothing.
export const BLOB_RE = /^favela:\/\/blob\/([0-9a-f]{8,128})$/i
export const isBlob = (uri) => BLOB_RE.test(String(uri || ''))
export const blobOid = (uri) => {
const m = BLOB_RE.exec(String(uri || ''))
return m ? m[1].toLowerCase() : null
}