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>
75 lines
2.4 KiB
TypeScript
75 lines
2.4 KiB
TypeScript
import path from "node:path";
|
|
import env from "@server/env";
|
|
import type Document from "@server/models/Document";
|
|
import { serializeFilename } from "@server/utils/fs";
|
|
|
|
/**
|
|
* Helpers for writing documents in the TextBundle format, a directory holding
|
|
* a document's text alongside the files it references.
|
|
*
|
|
* @see https://textbundle.org/spec/
|
|
*/
|
|
export default class TextBundleHelper {
|
|
/**
|
|
* Name of a bundle's text entry. Markdown is the format's default type, so
|
|
* the extension is the conventional one rather than a choice.
|
|
*/
|
|
public static readonly textFileName = "text.markdown";
|
|
|
|
/** Name of a bundle's metadata entry. */
|
|
public static readonly infoFileName = "info.json";
|
|
|
|
/** Directory a bundle's referenced files live in. */
|
|
public static readonly assetsDirectory = "assets";
|
|
|
|
/** Extension of a bundle directory, without a leading dot. */
|
|
public static readonly bundleExtension = "textbundle";
|
|
|
|
/** Extension of a zipped bundle, without a leading dot. */
|
|
public static readonly packExtension = "textpack";
|
|
|
|
/**
|
|
* Builds the metadata entry that identifies a directory as a TextBundle.
|
|
*
|
|
* @param document The document the bundle was built from.
|
|
* @returns The contents of the bundle's info.json.
|
|
*/
|
|
public static info(document: Document): string {
|
|
return JSON.stringify(
|
|
{
|
|
version: 2,
|
|
type: "net.daringfireball.markdown",
|
|
transient: false,
|
|
creatorIdentifier: "com.getoutline.outline",
|
|
creatorURL: env.URL,
|
|
sourceURL: `${env.URL}${document.url}`,
|
|
},
|
|
null,
|
|
2
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolves a name for an asset that is safe as a path component and unique
|
|
* within a single bundle, since two attachments on one document may share an
|
|
* original file name.
|
|
*
|
|
* @param name The attachment's original file name.
|
|
* @param used Names already taken within this bundle, added to in place.
|
|
* @returns The path of the asset relative to the bundle's text file.
|
|
*/
|
|
public static assetPath(name: string, used: Set<string>): string {
|
|
const serialized = serializeFilename(path.basename(name));
|
|
const { name: base, ext } = path.parse(serialized);
|
|
|
|
let candidate = base ? serialized : "file";
|
|
let i = 0;
|
|
while (used.has(candidate)) {
|
|
candidate = `${base || "file"} (${++i})${ext}`;
|
|
}
|
|
|
|
used.add(candidate);
|
|
return path.join(this.assetsDirectory, candidate);
|
|
}
|
|
}
|