mirror of
https://github.com/outline/outline.git
synced 2026-08-03 13:27:25 +03:00
* feat: Add TextPack (.textpack) import support Adds a single-document importer for TextPack, the zipped variant of TextBundle used by Bear, Ulysses, iA Writer and others. Bare .textbundle directories are not supported, as a directory cannot be delivered through a browser file input. The bundle's text entry may use any extension, per the spec, with info.json's type deciding whether it can be read as markdown. Assets are inlined as data URIs for the existing attachment pipeline to pick up, bounded by the attachment size limit and a memory ceiling, and only for media types markdown-it accepts as a link destination. Also fixes an existing bug where a file embedded in an HTML or email import as a data URI was stored in the document as base64 rather than being uploaded as an attachment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Refactor to individual converters * refactor --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
const escapes: [RegExp, string][] = [
|
|
[/\\/g, "\\\\"],
|
|
[/\*/g, "\\*"],
|
|
[/^-/g, "\\-"],
|
|
[/^\+ /g, "\\+ "],
|
|
[/^(=+)/g, "\\$1"],
|
|
[/^(#{1,6}) /g, "\\$1 "],
|
|
[/`/g, "\\`"],
|
|
[/^~~~/g, "\\~~~"],
|
|
[/\[/g, "\\["],
|
|
[/\]/g, "\\]"],
|
|
[/\(/g, "\\("], // OLN-91
|
|
[/\)/g, "\\)"], // OLN-91
|
|
[/^>/g, "\\>"],
|
|
[/_/g, "\\_"],
|
|
[/^(\d+)\. /g, "$1\\. "],
|
|
[/\$/g, "\\$"],
|
|
];
|
|
|
|
/**
|
|
* Escape markdown characters in a string
|
|
*
|
|
* @param text - The text to escape
|
|
* @returns The escaped text
|
|
*/
|
|
export const escape = function (text: string) {
|
|
return escapes.reduce(function (accumulator, esc) {
|
|
return accumulator.replace(esc[0], esc[1]);
|
|
}, text);
|
|
};
|
|
|
|
/**
|
|
* Unescape markdown characters in a string
|
|
*
|
|
* @param text - The text to unescape
|
|
* @returns The unescaped text
|
|
*/
|
|
export const unescape = function (text: string) {
|
|
return text.replace(/\\([\\*+-\d.])/g, "$1");
|
|
};
|
|
|
|
/**
|
|
* Matches a markdown link or image, capturing the text between its
|
|
* parentheses. A destination containing an unescaped closing parenthesis is
|
|
* not matched.
|
|
*/
|
|
const linkRegex = /(!?\[[^\]]*\]\()([^)]*)(\))/g;
|
|
|
|
/**
|
|
* Replaces the destination of every markdown link and image in a string.
|
|
*
|
|
* Understands the angle-bracket form used when a destination contains spaces,
|
|
* and preserves any link title.
|
|
*
|
|
* @param text The markdown text to rewrite.
|
|
* @param replace Called with each destination; return the replacement, or
|
|
* undefined to leave the link as it was written.
|
|
* @returns The markdown with replaced destinations.
|
|
*/
|
|
export function replaceMarkdownLinks(
|
|
text: string,
|
|
replace: (href: string) => string | undefined
|
|
): string {
|
|
return text.replace(
|
|
linkRegex,
|
|
(match, prefix: string, target: string, suffix: string) => {
|
|
const leading = target.length - target.trimStart().length;
|
|
const trimmed = target.trim();
|
|
|
|
let href: string;
|
|
let title: string;
|
|
|
|
if (trimmed.startsWith("<")) {
|
|
const end = trimmed.indexOf(">");
|
|
if (end === -1) {
|
|
return match;
|
|
}
|
|
href = trimmed.slice(1, end);
|
|
title = target.slice(leading + end + 1);
|
|
} else {
|
|
[href] = trimmed.split(/\s/, 1);
|
|
if (!href) {
|
|
return match;
|
|
}
|
|
title = target.slice(leading + href.length);
|
|
}
|
|
|
|
const replacement = replace(href);
|
|
return replacement === undefined
|
|
? match
|
|
: `${prefix}${replacement}${title}${suffix}`;
|
|
}
|
|
);
|
|
}
|