wikirefs
Version:
wikiref utilities (including [[wikilinks]]).
725 lines (704 loc) • 28.3 kB
JavaScript
// wikirefs v0.0.7 - https://github.com/wikibonsai/wikirefs.git
import { esc, getEscIndices, isStrEscaped } from 'escape-mkdn';
import path from 'path';
/* 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).
let CONST;
(function (_CONST) {
_CONST.DIR = {
FORE: 'fore',
BACK: 'back'
};
_CONST.REL = {
// tree
FAM: {
FAM: 'fam',
ANCESTOR: 'ancestor',
// all up
PARENT: 'parent',
// one up
SIBLING: 'sibling',
// all sides
CHILD: 'child',
// one down
DESCENDANT: 'descendant',
// all down
LINEAGE: 'lineage' // all up + all down - self (excludes self)
},
// web
REF: {
REF: 'ref',
ATTR: 'attr',
LINK: 'link',
EMBED: 'embed',
// direction
DIR: {
FORE: 'fore',
BACK: 'back'
}
}
};
_CONST.WIKI = {
REF: 'wikiref',
ATTR: 'wikiattr',
LINK: 'wikilink',
EMBED: 'wikiembed'
};
_CONST.TYPE = {
REF: 'reftype',
ATTR: 'attrtype',
LINK: 'linktype'
};
_CONST.MEDIA = {
// individual
MD: 'markdown',
PDF: 'pdf',
// categorical
AUD: 'audio',
IMG: 'image',
VID: 'video'
};
_CONST.URI = {
FNAME: 'filename',
ABSPATH: 'absolute',
RELPATH: 'relative'
};
_CONST.MARKER = {
EMBED: '!',
PREFIX: ':',
TYPE: '::',
OPEN: '[[',
HEADER: '#',
// BLOCK : '#^',
LABEL: '|',
CLOSE: ']]'
};
_CONST.EXTS = {
MD: '.md',
PDF: '.pdf',
AUD: new Set(['.mp3', '.webm', '.wav', '.m4a', '.ogg', '.3gp', '.flac']),
// DOC : new Set([
// '.doc',
// '.docx',
// '.rtf',
// '.txt',
// '.odt',
// '.xls',
// '.xlsx',
// '.ppt',
// '.pptm',
// '.pptx',
// '.pages',
// '.tldr',
// '.excalidraw',
// ]),
IMG: new Set(['.png', '.jpg', '.jpeg', '.gif', '.psd', '.svg',
// from: https://github.com/blacksmithgu/obsidian-dataview/blob/0f1ecc771bba6561fbb1767bc96f221dc2978bd8/src/util/media.ts#L3
'.tif', '.tiff', '.apng', '.avif', '.jfif', '.pjepg', '.pjp', '.webp', '.bmp', '.ico', '.cur']),
VID: new Set(['.mp4', '.mov', '.wmv', '.flv', '.avi', '.mkv', '.ogv'])
};
_CONST.GLOB_MEDIA = '[' + [].concat([CONST.EXTS.PDF]).concat(Array.from(CONST.EXTS.AUD)).concat(Array.from(CONST.EXTS.IMG)).concat(Array.from(CONST.EXTS.VID)).join('|') + ']';
})(CONST || (CONST = {}));
/* eslint-disable indent*/
let RGX;
(function (_RGX) {
const MARKER = _RGX.MARKER = {
// markdown (originally from kramdown)
// atx header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rbL29
ATX_HEADER: /^#{1,6}[\t ]*([^ \t].*)\n/im,
// setext header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rbL17
SETEXT_HEADER: /^ {0,3}([^ \t].*)\n[-=][-=]*[ \t\r\f\v]*\n/im,
// list item: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/list.rbL49
BULLET: /[^\n\r\S]{0,4}([+*-]) /i,
// markdown-style block-reference
// BLOCK : /".* \^" + BLOCK_ID$/i,
// wikilink (by order of syntactic appearance)
EMBED: new RegExp(esc(CONST.MARKER.EMBED)),
NOT_EMBED: new RegExp('(?<!' + esc(CONST.MARKER.EMBED) + ')', 'i'),
PREFIX: new RegExp('(?:' + CONST.MARKER.PREFIX + ' ?)'),
TYPE: new RegExp('(?: *' + CONST.MARKER.TYPE + ' ?)'),
OPEN: new RegExp(esc(CONST.MARKER.OPEN)),
HEADER: new RegExp(CONST.MARKER.HEADER),
// BLOCK : new RegExp(esc(CONST.MARKER.BLOCK)),
LABEL: new RegExp(esc(CONST.MARKER.LABEL)),
CLOSE: new RegExp(esc(CONST.MARKER.CLOSE))
};
const VALID_CHARS = _RGX.VALID_CHARS = {
TYPE: /[^\n\r!:^|[\]]+/i,
FILENAME: /[^\n\r!:^|[\]]+/i,
HEADER: /[^\n\r!^|[\]]+/i,
// BLOCK_ID : /[^\n\r!:^|[\]\/]+/i,
LABEL: /.+?(?=\]{2})/i
};
const CAP_GRP = _RGX.CAP_GRP = {
TYPE: new RegExp('(' + VALID_CHARS.TYPE.source + ')', 'i'),
FILENAME: new RegExp('(' + VALID_CHARS.FILENAME.source + ')', 'i'),
HEADER: new RegExp('(' + VALID_CHARS.HEADER.source + ')', 'i'),
// BLOCK_ID : new RegExp('(' + VALID_CHARS.BLOCK_ID.source + ')', 'i'),
LABEL: new RegExp('(' + VALID_CHARS.LABEL.source + ')', 'i')
};
// <------------------------------------------------------------------------>
// used to build wikiattr regexes; will not return complete wikilinks
// <------------------------------------------------------------------------>
// this is a local-only variable -- see WIKILINK.BASE for exported version
const _CAP_GRP_BASE = new RegExp(MARKER.OPEN.source + CAP_GRP.FILENAME.source + MARKER.CLOSE.source, 'i');
const _BASE = new RegExp(MARKER.OPEN.source + VALID_CHARS.FILENAME.source + MARKER.CLOSE.source, 'i');
const ATTR_UTIL = _RGX.ATTR_UTIL = {
// list-comma is responsible for catching the single case too
LIST_COMMA: new RegExp(_CAP_GRP_BASE.source + ' ?' + '(?:' + ', *' + _CAP_GRP_BASE.source + ' *' + ')*', 'i'),
PREFIX: new RegExp('^' + MARKER.PREFIX.source + '?' + CAP_GRP.TYPE.source + MARKER.TYPE.source, 'im')
};
const ATTR_LINE = _RGX.ATTR_LINE = {
// todo: TYPE -> SINGLE_OR_TYPE
TYPE: new RegExp(ATTR_UTIL.PREFIX.source + '(?:' + ATTR_UTIL.LIST_COMMA.source + ')?' + '$', 'im'),
LIST_ITEM: new RegExp('^' + '(?: *)' + MARKER.BULLET.source + '(' + _CAP_GRP_BASE.source + ')', 'im')
};
_RGX.WIKI = {
// base [[wikilink]] for wikiattrs, since they only support file level links
BASE: new RegExp(MARKER.OPEN.source + CAP_GRP.FILENAME.source + MARKER.CLOSE.source, 'i'),
// javascript/typescript regex does not support + or * in lookarounds (from: https://stackoverflow.com/questions/9030305/regular-expression-lookbehind-doesnt-work-with-quantifiers-or)
// link / inline / text
LINK: new RegExp(
// capture indices (0 is full match)
'(?:' + '(?:' + MARKER.PREFIX.source + CAP_GRP.TYPE.source // 1
+ MARKER.TYPE.source + ')?'
// skip embed
+ MARKER.NOT_EMBED.source + MARKER.OPEN.source + CAP_GRP.FILENAME.source // 2
// + '(?:'
// + '(?:' + CAP_GRP.HEADER.source + CAP_GRP.HEADER.source + ')?' //
// + '|'
// + '(?:' + CAP_GRP.BLOCK.source + CAP_GRP.BLOCK_ID.source + ')?'//
// + '|'
// + ')?'
+ '(?:' + MARKER.LABEL.source + CAP_GRP.LABEL.source + ')?' // 3
+ MARKER.CLOSE.source + ')', 'i'),
EMBED: new RegExp(
// capture indices (0 is full match)
'(?:'
// skip embed
+ MARKER.EMBED.source + MARKER.OPEN.source + CAP_GRP.FILENAME.source // 1
+ MARKER.CLOSE.source + ')', 'i'),
// since javascript/typescript regex does not support the \G anchor (see: https://ruby-doc.org/core-2.5.1/Regexp.html#class-Regexp-label-Anchors),
// filenames should be extracted from list items in the full match string
// ('WIKI.BASE' is useful for this)
// attr / block / flow
ATTR: new RegExp(ATTR_UTIL.PREFIX.source // 1
+ '(?:'
// comma-separated
+ '(?:' + '(?<!\n)' + ATTR_UTIL.LIST_COMMA.source // only captures first and last item
+ '(?:\n|$)' + ')' + '|'
// mkdwn-list-separated
+ '\n' + '(?:' + ATTR_LINE.LIST_ITEM.source // only captures last item
+ '(?:\n|$)' + ')+' + ')', 'im')
};
_RGX.GET = {
// capture indices (0 is full match)
// link / inline / text
LINKTYPE: new RegExp(MARKER.PREFIX.source + CAP_GRP.TYPE.source // 1
+ MARKER.TYPE.source + MARKER.OPEN.source + VALID_CHARS.FILENAME.source
// + '(?:'
// + '(?:' + CAP_GRP.HEADER.source + CAP_GRP.HEADER.source + ')?' //
// + '|'
// + '(?:' + CAP_GRP.BLOCK.source + CAP_GRP.BLOCK_ID.source + ')?'//
// + '|'
// + ')?'
+ '(?:' + MARKER.LABEL.source + VALID_CHARS.LABEL.source + ')?' + MARKER.CLOSE.source + '(?!\n)', 'i'),
// attr / block / flow
ATTRTYPE: new RegExp('^' + '(?:' + MARKER.PREFIX.source + ')?' + CAP_GRP.TYPE.source // 1
+ MARKER.TYPE.source + '(?:'
// single / comma
+ '(?:' + _BASE.source + '(' + ', *' + _BASE.source + ' *' + ')*' + '\n' + ')' + '|'
// mkdn-list-separated
+ '(?:' + '\n' + '(?:' + '^ *' + MARKER.BULLET.source + _BASE.source + '\n' + ')+' + ')' + ')', 'im'),
REFTYPE: new RegExp(
// attr
'(?:' + '^' + '(?:' + MARKER.PREFIX.source + ')?' + CAP_GRP.TYPE.source + MARKER.TYPE.source // 1 (attr)
+ '(?:'
// single / comma
+ '(?:' + _BASE.source + '$' + '|' + ',' + ')' + '|'
// mkdn list
+ '$' + ')' + ')' + '|'
// link
+ '(?:' + MARKER.PREFIX.source + CAP_GRP.TYPE.source + MARKER.TYPE.source // 2 (link)
+ MARKER.OPEN.source + ')', 'im'),
FILENAME: new RegExp(MARKER.OPEN.source + CAP_GRP.FILENAME.source // 1
+ '(?:' + MARKER.HEADER.source // includes BLOCK
+ '|' + MARKER.LABEL.source + '|' + MARKER.CLOSE.source + ')', 'i')
// HEADER : new RegExp(MARKER.LINK_LEFT.source
// + '(?:' + VALID_CHARS.FILENAME.source + ')'
// + MARKER.HEADER.source
// + CAP_GRP.HEADER.source
// + '(?:'
// + MARKER.LINK_LABEL.source
// + '|' + MARKER.LINK_RIGHT.source
// + ')'
// , 'i'),
// BLOCK_ID : new RegExp(MARKER.LINK_LEFT.source
// + '(?:' + VALID_CHARS.FILENAME.source + ')'
// + MARKER.BLOCK.source
// + CAP_GRP.BLOCK_ID.source
// + '(?:'
// + MARKER.LINK_LABEL.source
// + '|' + MARKER.LINK_RIGHT.source
// + ')'
// , 'i'),
};
})(RGX || (RGX = {}));
function getMediaKind(filename) {
const mediaExt = path.extname(filename).toLowerCase();
let media;
// individual
if (mediaExt === CONST.EXTS.PDF) {
media = CONST.MEDIA.PDF;
// categorical
} else if (CONST.EXTS.AUD.has(mediaExt)) {
media = CONST.MEDIA.AUD;
} else if (CONST.EXTS.IMG.has(mediaExt)) {
media = CONST.MEDIA.IMG;
} else if (CONST.EXTS.VID.has(mediaExt)) {
media = CONST.MEDIA.VID;
} else {
// presume markdown -- might actually include unsupported media extension though...
media = CONST.MEDIA.MD;
}
return media;
}
function isMedia(filename) {
const mediaExt = path.extname(filename).toLowerCase();
return CONST.EXTS.PDF === mediaExt || CONST.EXTS.AUD.has(mediaExt) || CONST.EXTS.IMG.has(mediaExt) || CONST.EXTS.VID.has(mediaExt);
}
function scan(content, opts) {
const res = [];
// opts
const kind = opts ? opts.kind : undefined;
const filename = opts ? opts.filename : undefined;
const skipEsc = opts ? opts.skipEsc : true;
const escdIndices = getEscIndices(content);
// go
// attr //
const fullTxtAttrs = [];
let attrMatch, fnameMatch;
const attrsGottaCatchEmAll = new RegExp(RGX.WIKI.ATTR, 'gim');
const singlesGottaCatchEmAll = new RegExp(RGX.WIKI.BASE, 'gim');
// 🦨 do-while: https://stackoverflow.com/a/6323598
do {
attrMatch = attrsGottaCatchEmAll.exec(content);
if (attrMatch) {
fullTxtAttrs.push(attrMatch[0]);
const matchText = attrMatch[0];
const attrtypeText = attrMatch[1];
// files
const filenames = [];
do {
fnameMatch = singlesGottaCatchEmAll.exec(matchText);
if (fnameMatch) {
const twoLeftBrackets = 2;
const filenameText = fnameMatch[1];
const filenameOffset = attrMatch.index + twoLeftBrackets + fnameMatch.index;
if (!filename || filename === filenameText) {
/* eslint-disable indent */
// override indented code blocks for wikiattr-lists-mkdn-pretty
const noEscLists = /:: *\n *[-+*] /.test(matchText);
const escaped = isStrEscaped(filenameText, content, filenameOffset, escdIndices) && !noEscLists;
/* eslint-enable indent */
if (!kind || kind === CONST.WIKI.REF || kind === CONST.WIKI.ATTR && (skipEsc || !escaped)) {
filenames.push([filenameText, filenameOffset]);
}
}
}
} while (fnameMatch);
const attrtypeOffset = attrMatch.index + matchText.indexOf(attrtypeText);
const trimmedAttrTypeText = attrtypeText.trim();
let listFormat = 'none';
// single case
if (filenames.length === 1) {
listFormat = 'none';
}
if (/\n *- /.test(matchText)) {
listFormat = 'mkdn';
}
if (/ *, */.test(matchText)) {
listFormat = 'comma';
}
/* eslint-disable indent */
const escaped = isStrEscaped(trimmedAttrTypeText, content, attrtypeOffset, escdIndices);
/* eslint-enable indent */
if ((!kind || kind === CONST.WIKI.REF || kind === CONST.WIKI.ATTR) && filenames.length !== 0 && (skipEsc || !escaped)) {
res.push({
kind: CONST.WIKI.ATTR,
text: matchText,
start: attrMatch.index,
type: [trimmedAttrTypeText, attrtypeOffset],
filenames: filenames,
listFormat: listFormat
});
}
}
} while (attrMatch);
// note: consume processed wikiattrs so they are
// not mistaken for inlines in next section
for (const txt of fullTxtAttrs) {
content = content.replace(txt, match => {
// pad with whitespace so positions don't get screwed up
return ' '.repeat(match.length);
});
}
// embed //
if (!kind || kind === CONST.WIKI.REF || kind === CONST.WIKI.EMBED) {
const embedsGottaCatchEmAll = new RegExp(RGX.WIKI.EMBED, 'g');
// 🦨 do-while: https://stackoverflow.com/a/6323598
let embedMatch;
do {
embedMatch = embedsGottaCatchEmAll.exec(content);
if (embedMatch) {
const matchText = embedMatch[0];
const fileNameText = embedMatch[1];
const wikilinkOffset = embedMatch.index;
const filenameOffset = matchText.indexOf(fileNameText);
if (!filename || filename === fileNameText) {
/* eslint-disable indent */
const escaped = isStrEscaped(matchText, content, wikilinkOffset, escdIndices);
/* eslint-enable indent */
if (skipEsc || !escaped) {
res.push({
kind: CONST.WIKI.EMBED,
text: matchText,
start: embedMatch.index,
filename: [fileNameText, wikilinkOffset + filenameOffset],
media: getMediaKind(fileNameText)
});
}
}
}
} while (embedMatch);
}
// link //
if (!kind || kind === CONST.WIKI.REF || kind === CONST.WIKI.LINK) {
const linksGottaCatchEmAll = new RegExp(RGX.WIKI.LINK, 'g');
// 🦨 do-while: https://stackoverflow.com/a/6323598
let linkMatch;
do {
linkMatch = linksGottaCatchEmAll.exec(content);
if (linkMatch) {
const matchText = linkMatch[0];
const linkTypeText = linkMatch[1];
const fileNameText = linkMatch[2];
// const headerText : string = linkMatch[4];
const labelText = linkMatch[3];
const wikilinkOffset = linkMatch.index;
const linkTypeOffset = matchText.indexOf(linkTypeText);
const filenameOffset = matchText.indexOf(fileNameText);
const labelOffset = matchText.indexOf(labelText);
if (!filename || filename === fileNameText) {
/* eslint-disable indent */
const escaped = isStrEscaped(matchText, content, wikilinkOffset, escdIndices);
/* eslint-enable indent */
if (skipEsc || !escaped) {
const type = linkTypeText ? [linkTypeText.trim(), linkMatch.index + linkTypeOffset] : [];
const label = labelText ? [labelText, linkMatch.index + labelOffset] : [];
res.push({
kind: CONST.WIKI.LINK,
text: matchText,
start: linkMatch.index,
type: type,
filename: [fileNameText, wikilinkOffset + filenameOffset],
label: label
});
}
}
}
} while (linkMatch);
}
// sort matches by start position
return res.sort((a, b) => a.start - b.start);
}
// note: (atm, attrs are implicitly included in 'link' option)
function isValidWikiKind(kind) {
const isValid = kind === CONST.WIKI.REF
// || (kind === CONST.WIKI.ATTR) todo
|| kind === CONST.WIKI.LINK || kind === CONST.WIKI.EMBED;
if (!isValid) {
console.warn('invalid kind: ' + '"' + kind + '"' + '; ' + 'using default instead: "wikiref"');
}
return isValid;
}
function isValidUriFormat(format) {
const isValid = format === CONST.URI.FNAME || format === CONST.URI.RELPATH || format === CONST.URI.ABSPATH;
if (!isValid) {
console.warn('invalid uri format: ' + '"' + format + '"' + '; ' + 'using default instead: "filename"');
}
return isValid;
}
// export interface InitUrl {
// configUri: string;
// doctypeUri: string;
// fnameToUri: Record<string, string>;
// // baseUrl: string;
// }
function buildURI(fileUri) {
let uriFormat = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'filename';
let uriExt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
let uri;
if (!isValidUriFormat(uriFormat)) {
console.warn('invalid uri format: ' + uriFormat);
return;
}
if (uriFormat === CONST.URI.FNAME) {
uri = '/' + path.basename(fileUri, CONST.EXTS.MD);
}
if (uriFormat === CONST.URI.RELPATH) {
uri = fileUri.replace(process.cwd(), '');
}
if (uriFormat === CONST.URI.ABSPATH) {
uri = fileUri;
}
// w/ file extension
if (uriExt) {
const slugifyUri = fileUri.trim().toLowerCase().replace(/ /g, '-');
return slugifyUri;
// w/o file extension
} else {
// this nonsense is to strip the 'extname' (e.g. '.md')
// @ts-expect-error: if-checks should prevent error
const dir = path.dirname(uri);
const fname = path.basename(fileUri, path.extname(fileUri));
const fullUri = path.join(dir, fname);
const slugifyUri = fullUri.trim().toLowerCase().replace(/ /g, '-');
return slugifyUri;
}
}
// 'extractFileName' is the opposite of 'buildURL' -- it takes in a
// uri and infers the filename of the file that corresponds to the uri
// 'uriFormat' should show what the uri is describing.
function extractFileName(uri) {
let uriFormat = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'filename';
if (!isValidUriFormat(uriFormat)) {
console.warn('invalid uri format: ' + uriFormat);
return;
}
return path.basename(uri, CONST.EXTS.MD);
}
// todo:
// - handle 'wikiattr' individually
// - ignore escaped links?
const RGX_MKDN_LINK = /(?<!!)\[(^$|.*?)\]\((.*?)\)/gi;
const RGX_MKDN_IMAGE = /!\[(.*?)]\((.*?)\)/gi;
function mkdnToWiki(content, opts) {
var _opts$uriToFnameHash;
// opts
const kind = opts !== null && opts !== void 0 && opts.kind && isValidWikiKind(opts.kind) ? opts.kind : CONST.WIKI.REF;
const format = opts !== null && opts !== void 0 && opts.format && isValidUriFormat(opts.format) ? opts.format : CONST.URI.FNAME;
const uriToFnameHash = (_opts$uriToFnameHash = opts === null || opts === void 0 ? void 0 : opts.uriToFnameHash) !== null && _opts$uriToFnameHash !== void 0 ? _opts$uriToFnameHash : {};
// links (includes attrs)
if (kind === CONST.WIKI.REF || kind === CONST.WIKI.LINK) {
content = content.replace(RGX_MKDN_LINK, (match, label, uri) => {
/* eslint-disable indent */
const filename = Object.keys(uriToFnameHash).includes(uri) ? uriToFnameHash[uri] : extractFileName(uri, format);
/* eslint-disable indent */
if (filename !== undefined) {
// unlabelled
if (label === filename) {
return CONST.MARKER.OPEN + filename + CONST.MARKER.CLOSE;
// labelled
} else {
return CONST.MARKER.OPEN + filename + CONST.MARKER.LABEL + label + CONST.MARKER.CLOSE;
}
}
// simply put back if unable to determine wiki-equivalent
return `[${label}](${uri})`;
});
}
// embeds
if (kind === CONST.WIKI.REF || kind === CONST.WIKI.EMBED) {
content = content.replace(RGX_MKDN_IMAGE, (match, label, uri) => {
const filename = extractFileName(uri, format);
if (filename) {
return CONST.MARKER.EMBED + CONST.MARKER.OPEN + filename + CONST.MARKER.CLOSE;
}
// simply put back if unable to determine wiki-equivalent
return ``;
});
}
return content;
}
function wikiToMkdn(content, opts) {
var _opts$fnameToUriHash;
// opts
const kind = opts !== null && opts !== void 0 && opts.kind && isValidWikiKind(opts.kind) ? opts.kind : CONST.WIKI.REF;
const format = opts !== null && opts !== void 0 && opts.format && isValidUriFormat(opts.format) ? opts.format : CONST.URI.FNAME;
const ext = opts !== null && opts !== void 0 && opts.ext ? opts.ext : false;
const fnameToUriHash = (_opts$fnameToUriHash = opts === null || opts === void 0 ? void 0 : opts.fnameToUriHash) !== null && _opts$fnameToUriHash !== void 0 ? _opts$fnameToUriHash : {};
// vars
let mkdnContent = '';
let curPos = 0;
const matches = scan(content);
for (const m of matches) {
let linkedFileUri;
// process non-wikiref text
mkdnContent += content.substring(curPos, m.start);
curPos = m.start;
////
// attr
if (m.kind === CONST.WIKI.ATTR && (kind === CONST.WIKI.REF || kind === CONST.WIKI.ATTR || kind === CONST.WIKI.LINK) // todo: remove
) {
for (const filename of m.filenames) {
/* eslint-disable indent */
linkedFileUri = Object.keys(fnameToUriHash).includes(filename[0]) ? fnameToUriHash[filename[0]] : '/' + filename[0] + CONST.EXTS.MD;
/* eslint-enable indent */
const uri = buildURI(linkedFileUri, format, ext);
if (uri === undefined) {
console.warn('invalid uri from file uri: ', linkedFileUri);
continue;
}
const isFirstItem = filename === m.filenames[0];
const isNotLastItem = filename !== m.filenames[m.filenames.length - 1];
let suffix = '';
if (isNotLastItem) {
if (m.listFormat === 'comma') {
// todo: extract precise number of whitespaces
suffix = ', ';
}
if (m.listFormat === 'mkdn') {
suffix = '\n- ';
}
} else {
suffix = '\n';
}
if (isFirstItem) {
mkdnContent += content.substring(curPos, filename[1] - CONST.MARKER.OPEN.length);
}
mkdnContent += `[${filename[0]}](${uri})` + suffix;
}
curPos = m.start + m.text.length;
////
// link
} else if (m.kind === CONST.WIKI.LINK && (kind === CONST.WIKI.REF || kind === CONST.WIKI.LINK)) {
/* eslint-disable indent */
linkedFileUri = Object.keys(fnameToUriHash).includes(m.filename[0]) ? fnameToUriHash[m.filename[0]] : '/' + m.filename[0] + CONST.EXTS.MD;
/* eslint-enable indent */
const uri = buildURI(linkedFileUri, format, ext);
if (uri === undefined) {
console.warn('invalid uri from file uri: ', linkedFileUri);
continue;
}
// unlabelled
if (m.label.length === 0) {
mkdnContent += `[${m.filename[0]}](${uri})`;
curPos = m.start + m.text.length;
// labelled
} else {
mkdnContent += `[${m.label[0]}](${uri})`;
curPos = m.start + m.text.length;
}
////
// embed
// convert to markdown img link -- img
} else if (m.kind === CONST.WIKI.EMBED && (kind === CONST.WIKI.REF || kind === CONST.WIKI.EMBED)) {
// image embed
if (m.media === CONST.MEDIA.IMG) {
/* eslint-disable indent */
linkedFileUri = Object.keys(fnameToUriHash).includes(m.filename[0]) ? fnameToUriHash[m.filename[0]] : '/' + m.filename[0];
/* eslint-enable indent */
const INCLUDE_IMG_EXT = true;
const uri = buildURI(linkedFileUri, format, INCLUDE_IMG_EXT);
if (uri === undefined) {
console.warn('invalid uri from file uri: ', linkedFileUri);
continue;
}
mkdnContent += ``;
curPos = m.start + m.text.length;
// convert to markdown link -- markdown, audio, video
} else if (m.media === CONST.MEDIA.MD || m.media === CONST.MEDIA.AUD || m.media === CONST.MEDIA.VID) {
/* eslint-disable indent */
linkedFileUri = Object.keys(fnameToUriHash).includes(m.filename[0]) ? fnameToUriHash[m.filename[0]] : '/' + m.filename[0];
/* eslint-enable indent */
const uri = buildURI(linkedFileUri, format, ext);
if (uri === undefined) {
console.warn('invalid uri from file uri: ', linkedFileUri);
continue;
}
mkdnContent += `[${m.filename[0]}](${uri})`;
curPos = m.start + m.text.length;
}
// just add the match back since we are not processing it
} else {
mkdnContent += m.text;
curPos = m.start + m.text.length;
}
}
// add remaining content
mkdnContent += content.substring(curPos);
// if no links were found, return original content
return mkdnContent.length === 0 ? content : mkdnContent;
}
function replace(regex, oldStr, newStr, content, opts) {
/* eslint-disable indent */
const defaults = {
escape: true,
pad: false
};
/* eslint-enable indent */
opts = {
...defaults,
...opts
};
// 🦨 do-while: https://stackoverflow.com/a/6323598
let match;
let lastOffset = 0;
let updatedContent = '';
const escdIndices = opts.escape ? getEscIndices(content) : [];
do {
match = regex.exec(content);
if (match) {
const matchFull = match[0];
const matchOldStr = opts.pad ? match[1].trim() : match[1];
if (matchOldStr === oldStr) {
const fnameOffset = matchFull.indexOf(oldStr);
// filename range
const start = match.index + fnameOffset;
const end = match.index + fnameOffset + oldStr.length;
// check for escapes
/* eslint-disable indent */
const escaped = isStrEscaped(oldStr, content, fnameOffset, escdIndices);
/* eslint-enable indent */
if (!escaped) {
updatedContent += content.substring(lastOffset, start) + newStr;
lastOffset = end;
}
}
}
} while (match);
updatedContent += content.substring(lastOffset);
return updatedContent;
}
// 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)
function renameFileName(oldFileName, newFileName, content) {
const wikiTextFilename = new RegExp(RGX.GET.FILENAME, 'g');
return replace(wikiTextFilename, oldFileName, newFileName, content);
}
// todo: rename #headers
function retypeRefType(oldRefType, newRefType, content) {
content = retypeAttrType(oldRefType, newRefType, content);
content = retypeLinkType(oldRefType, newRefType, content);
return content;
}
function retypeAttrType(oldAttrType, newAttrType, content) {
const wikiattr = new RegExp(RGX.WIKI.ATTR, 'gm');
return replace(wikiattr, oldAttrType, newAttrType, content, {
pad: true
});
}
function retypeLinkType(oldLinkType, newLinkType, content) {
const wikilink = new RegExp(RGX.WIKI.LINK, 'g');
return replace(wikilink, oldLinkType, newLinkType, content);
}
export { CONST, RGX, RGX_MKDN_IMAGE, RGX_MKDN_LINK, getMediaKind, isMedia, mkdnToWiki, renameFileName, retypeAttrType, retypeLinkType, retypeRefType, scan, wikiToMkdn };
//# sourceMappingURL=index.esm.js.map