UNPKG

wikirefs

Version:

wikiref utilities (including [[wikilinks]]).

725 lines (704 loc) 28.3 kB
// 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 `![${label}](${uri})`; }); } 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 += `![](${uri})`; 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