All files / app/util editor.ts

75.6% Statements 31/41
75% Branches 33/44
71.42% Functions 5/7
75% Lines 27/36

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 9294x     94x 94x 94x 94x     6x                 6x 6x 1x 1x           1x                                   13x 13x 11x 56x 56x   56x 4x 52x 8x 44x 3x     11x       22x 20x 4x 4x                                                  
import * as he from 'he';
import { difference, uniq } from 'lodash-es';
import { marked } from 'marked';
import { getMailbox } from '../mods/mailbox';
import { wikiUriFormat } from '../mods/org/wiki';
import { QUALIFIED_USER_REGEX, TAG_REGEX } from './format';
import { getPath } from './http';
 
export function getMailboxes(markdown: string, origin = '') {
  return extractPattern(markdown, /[_+]user\/[a-z0-9]+([./][a-z0-9]+)*(@[a-z0-9]+(\.[a-z0-9]+)*)?/g, undefined, QUALIFIED_USER_REGEX)
    .map(u => getMailbox(u, origin));
}
 
export function getTags(markdown: string) {
  return extractPattern(markdown, /#[_+]?[a-z0-9]+([./][a-z0-9]+)*/g, /#([a-z0-9]+([./][a-z0-9]+)*)/, TAG_REGEX);
}
 
export function extractPattern(markdown: string, pattern: RegExp, extractor?: RegExp, validator?: RegExp) {
  const result: string[] = [];
  if (!markdown) return result;
  const matches = markdown.match(pattern);
  for (const s of matches || []) {
    const url = (extractor ? s.match(extractor)?.[1] : s)?.trim();
    if (url && (!validator || validator.test(url))) {
      result.push(url);
    }
  }
  return result;
}
 
export function getIfNew<T>(list: T[], old?: T[]): T[] {
  old ??= [];
  const diff = difference(uniq(list), uniq(old));
  if (!diff.length) return [];
  return list;
}
 
/**
 * Get the URLs embedded in markdown. These are rendered by looking up the Ref.
 * External wiki embeds are rendered as links, and images that are not Refs
 * (web, data and blob URLs, relative paths and tag queries) are rendered
 * directly, so they are skipped. Images linking to a Ref page, either relative
 * or under the given base URL, are rendered by looking up the Ref.
 */
export function getEmbeds(markdown: string, wikiPrefix?: string, wikiExternal = false, base = '') {
  const result: string[] = [];
  if (!markdown) return result;
  marked.walkTokens(marked.lexer(markdown), t => {
    const customType = t as any;
    Iif (customType.type === 'bang-embed' && customType.href) {
      result.push(customType.href);
    } else if (customType.type === 'image' && customType.href && isRefPath(customType.href, base)) {
      result.push(customType.href);
    } else if (customType.type === 'image' && customType.href && /^[a-z][a-z0-9+.-]*:/i.test(customType.href) && !/^(https?|data|blob):/i.test(customType.href)) {
      result.push(customType.href);
    } else if (customType.type === 'wiki-embed' && customType.text && !wikiExternal) {
      result.push(wikiUriFormat(customType.text, wikiPrefix));
    }
  });
  return uniq(result);
}
 
function isRefPath(url: string, base: string) {
  if (url.startsWith('/ref/')) return true;
  if (!base) return false;
  const refPrefix = base + 'ref/';
  return url.startsWith(refPrefix) || url.startsWith(getPath(refPrefix)!);
}
 
export function getLinks(markdown: string, withText?: RegExp) {
  const result: string[] = [];
  marked.walkTokens(marked.lexer(markdown), t => {
    if (t.type === 'link') {
      if (!t.href) return;
      if (withText && !withText.test(t.text)) return;
      if (t.href.startsWith('mailto:')) t.href = he.decode(t.href);
      result.push(t.href);
      // @ts-ignore
    }
    if (withText) return;
    const customType = t as any;
    if (customType.text) {
      if (customType.type === 'wiki' || customType.type === 'wiki-embed') {
        result.push(wikiUriFormat(customType.text));
      } else if (customType.type === 'embed') {
        result.push(customType.text);
      }
    }
  });
  return result;
}