wikirefs
Version:
wikiref utilities (including [[wikilinks]]).
284 lines (283 loc) • 9.38 kB
TypeScript
/* eslint indent: 0 */
/* eslint @typescript-eslint/no-namespace: 0 */
////
// note:
//
// at the time of writing, constants are mainly a means of defining a
// controlled vocabulary that is referenced across the entire WikiBonsai
// project (https://github.com/wikibonsai/wikibonsai/blob/main/docs/TERMS.md).
declare namespace CONST {
// const names
// "direction"
const DIR: {
readonly FORE: "fore";
readonly BACK: "back";
};
// kind of relationships
// (used for short-hand)
const REL: {
readonly FAM: {
readonly FAM: "fam";
readonly ANCESTOR: "ancestor";
readonly PARENT: "parent";
readonly SIBLING: "sibling";
readonly CHILD: "child";
readonly DESCENDANT: "descendant";
readonly LINEAGE: "lineage";
};
readonly REF: {
readonly REF: "ref";
readonly ATTR: "attr";
readonly LINK: "link";
readonly EMBED: "embed";
readonly DIR: {
readonly FORE: "fore";
readonly BACK: "back";
};
};
};
// wiki construct kinds
const WIKI: {
readonly REF: "wikiref";
readonly ATTR: "wikiattr";
readonly LINK: "wikilink";
readonly EMBED: "wikiembed";
};
// type kinds
// (embeds don't have types)
const TYPE: {
readonly REF: "reftype";
readonly ATTR: "attrtype";
readonly LINK: "linktype";
};
const MEDIA: {
readonly MD: "markdown";
readonly PDF: "pdf";
readonly AUD: "audio";
readonly IMG: "image";
readonly VID: "video";
};
// uri formats
// (used for markdown link conversion)
const URI: {
readonly FNAME: "filename";
readonly ABSPATH: "absolute";
readonly RELPATH: "relative";
};
// const marker symbols
const MARKER: {
readonly EMBED: "!";
readonly PREFIX: ":";
readonly TYPE: "::";
readonly OPEN: "[[";
readonly HEADER: "#";
readonly LABEL: "|";
readonly CLOSE: "]]";
};
// const file extensions
const EXTS: {
readonly MD: ".md";
readonly PDF: ".pdf";
readonly AUD: Set<string>;
readonly IMG: Set<string>;
readonly VID: Set<string>;
};
// glob
const GLOB_MEDIA: string;
}
interface MatchAttr {
text: string;
start: number;
type: [
string,
number
];
filenames: [
string,
number
][];
listFormat: "comma" | "mkdn" | "none";
}
declare namespace RGX {
// <------------------------------------------------------------------------>
// markdown/wikilink special/reserved characters
// <------------------------------------------------------------------------>
const _MKDN: {
readonly ATX_HEADER: RegExp;
readonly SETEXT_HEADER: RegExp;
readonly BULLET: RegExp;
readonly LINK: RegExp;
readonly IMAGE: RegExp;
};
const MARKER: {
readonly EMBED: RegExp;
readonly NOT_EMBED: RegExp;
readonly PREFIX: RegExp;
readonly TYPE: RegExp;
readonly OPEN: RegExp;
readonly HEADER: RegExp;
readonly LABEL: RegExp;
readonly CLOSE: RegExp;
};
// <------------------------------------------------------------------------>
// valid characters for wikilink text (wikitext)
// <------------------------------------------------------------------------>
const VALID_CHARS: {
readonly TYPE: RegExp;
readonly FILENAME: RegExp;
readonly HEADER: RegExp;
readonly LABEL: RegExp;
};
// <------------------------------------------------------------------------>
// capture groups for wikilink text (wikitext)
// <------------------------------------------------------------------------>
const CAP_GRP: {
readonly TYPE: RegExp;
readonly FILENAME: RegExp;
readonly HEADER: RegExp;
readonly LABEL: RegExp;
};
const ATTR_UTIL: {
readonly LIST_COMMA: RegExp;
readonly PREFIX: RegExp;
};
// <------------------------------------------------------------------------>
// for full parsing (w/ line-by-line utilities)
// <------------------------------------------------------------------------>
// for line-by-line attr processing
const ATTR_LINE: {
readonly TYPE: RegExp;
readonly LIST_ITEM: RegExp;
};
// full regex -- can be used with 'g' (global) option
const WIKI: {
BASE: RegExp;
LINK: RegExp;
EMBED: RegExp;
_ATTR: RegExp;
ATTR(content: string): MatchAttr[];
};
// <------------------------------------------------------------------------>
// utilities to extract out target wikitext
// (useful for entity interactions, renaming things, etc.)
// <------------------------------------------------------------------------>
const GET: {
readonly LINKTYPE: RegExp;
readonly ATTRTYPE: RegExp;
readonly REFTYPE: RegExp;
readonly FILENAME: RegExp;
readonly HEADER: RegExp;
};
}
// todo:
// - handle 'wikiattr' individually
interface ConvertOpts {
kind?: "wikiref" | "wikilink" | "wikiembed"; // CONST.WIKI...
format?: "filename" | "relative" | "absolute"; // CONST.URL...
ext?: boolean;
fnameToUriHash?: Record<string, string>;
uriToFnameHash?: Record<string, string>;
}
/**
* Remove pointless wikilink labels: empty `|`, or `|target` when label duplicates the target
* (including `[[file#header|file#header]]`). Applies to `[[...]]` and `![[...]]`.
*/
declare function stripEmptyWikiLinkLabels(content: string): string;
declare function mkdnToWiki(content: string, opts?: ConvertOpts): string | undefined;
declare function wikiToMkdn(content: string, opts?: ConvertOpts): string | undefined;
// todo:
// - should filename validity be checked here, or at a higher level? (duplicate filenames + valid filename chars)
// - add ability to update only certain kinds of wikirefs (see 'wikiToMkdn' for logic)
declare function renameFileName(oldFileName: string, newFileName: string, content: string, opts?: {
escape?: boolean;
}): string;
// alias
declare function rename(oldFileName: string, newFileName: string, content: string, opts?: {
escape?: boolean;
}): string;
declare function renameHeader(oldHeader: string, newHeader: string, content: string, opts?: {
filename?: string;
escape?: boolean;
}): string;
// alias
declare function rehead(oldHeader: string, newHeader: string, content: string, opts?: {
filename?: string;
escape?: boolean;
}): string;
declare function retypeRefType(oldRefType: string, newRefType: string, content: string, opts?: {
escape?: boolean;
}): string;
declare function retypeAttrType(oldAttrType: string, newAttrType: string, content: string, opts?: {
escape?: boolean;
}): string;
declare function retypeLinkType(oldLinkType: string, newLinkType: string, content: string, opts?: {
escape?: boolean;
}): string;
// types
interface ScanOpts {
filename?: string;
kind?: string;
skipEsc?: boolean;
}
interface ScanTxt {
text: string;
start: number;
}
// wikirefs (grouped constructs)
interface ScanAttr {
kind: "wikiattr";
match: string;
start: number;
type: ScanTxt;
filenames: ScanTxt[];
listFormat: "comma" | "mkdn" | "none";
}
interface ScanLink {
kind: "wikilink";
match: string;
start: number;
type?: ScanTxt;
filename: ScanTxt;
header?: ScanTxt;
label?: ScanTxt;
}
interface ScanEmbed {
kind: "wikiembed";
match: string;
start: number;
filename: ScanTxt;
header?: ScanTxt;
media: string;
}
type ScanRef = ScanAttr | ScanLink | ScanEmbed;
// filenames (flat)
interface ScannedFileName {
filename: ScanTxt;
kind: "wikiattr" | "wikilink" | "wikiembed";
type?: ScanTxt;
header?: ScanTxt;
label?: ScanTxt;
media?: string;
listFormat?: "comma" | "mkdn" | "none";
}
// scan result
interface ScanResult {
wikirefs: ScanRef[];
filenames: ScannedFileName[];
}
// scan
declare function scan(content: string, opts?: ScanOpts): ScanResult;
declare function getMediaKind(filename: string): string;
declare function isMedia(filename: string): boolean;
declare function slugify(text: string): string;
/**
* Extract the markdown section for a given header, for embed rendering.
* The section runs from the end of the matching header line until the next
* header of the same or higher level, or end of content.
*
* @param content - Full markdown document
* @param headerRef - Header identifier: id/slug (e.g. "header-text") or raw text (e.g. "Header Text")
* @returns The section markdown (after the header line), or undefined if no matching header
*/
declare function getHeaderSection(content: string, headerRef: string): string | undefined;
export { CONST, MatchAttr, RGX, ConvertOpts, stripEmptyWikiLinkLabels, mkdnToWiki, wikiToMkdn, renameFileName, rename, renameHeader, rehead, retypeRefType, retypeAttrType, retypeLinkType, ScanOpts, ScanTxt, ScanAttr, ScanLink, ScanEmbed, ScanRef, ScannedFileName, ScanResult, scan, getMediaKind, isMedia, slugify, getHeaderSection };