Files

288 lines
7.1 KiB
JavaScript

// Formatting rules for different contexts (channels and special contexts)
// marks: inline formatting (strong, em, code, link, strike)
// nodes: block structures (bulletList, orderedList, codeBlock, blockquote)
export const FORMATTING = {
// Channel formatting
'Channel::Email': {
marks: ['strong', 'em', 'code', 'link'],
nodes: ['bulletList', 'orderedList', 'codeBlock', 'blockquote', 'image'],
menu: [
'copilot',
'strong',
'em',
'code',
'link',
'bulletList',
'orderedList',
'imageUpload',
'undo',
'redo',
],
},
'Channel::WebWidget': {
marks: ['strong', 'em', 'code', 'link', 'strike'],
nodes: ['bulletList', 'orderedList', 'codeBlock', 'blockquote', 'image'],
menu: [
'copilot',
'strong',
'em',
'code',
'link',
'strike',
'bulletList',
'orderedList',
'imageUpload',
'undo',
'redo',
],
},
'Channel::Api': {
marks: ['strong', 'em'],
nodes: [],
menu: ['copilot', 'strong', 'em', 'undo', 'redo'],
},
'Channel::FacebookPage': {
marks: ['strong', 'em', 'code', 'strike'],
nodes: ['bulletList', 'orderedList', 'codeBlock'],
menu: [
'copilot',
'strong',
'em',
'code',
'strike',
'bulletList',
'orderedList',
'undo',
'redo',
],
},
'Channel::TwitterProfile': {
marks: [],
nodes: [],
menu: [],
},
'Channel::TwilioSms': {
marks: [],
nodes: [],
menu: [],
},
'Channel::Sms': {
marks: [],
nodes: [],
menu: [],
},
'Channel::Whatsapp': {
marks: ['strong', 'em', 'code', 'strike'],
nodes: ['bulletList', 'orderedList', 'codeBlock'],
menu: [
'copilot',
'strong',
'em',
'code',
'strike',
'bulletList',
'orderedList',
'undo',
'redo',
],
},
'Channel::Line': {
marks: ['strong', 'em', 'code', 'strike'],
nodes: ['codeBlock'],
menu: ['copilot', 'strong', 'em', 'code', 'strike', 'undo', 'redo'],
},
'Channel::Telegram': {
marks: ['strong', 'em', 'link', 'code'],
nodes: [],
menu: ['copilot', 'strong', 'em', 'link', 'code', 'undo', 'redo'],
},
'Channel::Instagram': {
marks: ['strong', 'em', 'code', 'strike'],
nodes: ['bulletList', 'orderedList'],
menu: [
'copilot',
'strong',
'em',
'code',
'bulletList',
'orderedList',
'strike',
'undo',
'redo',
],
},
'Channel::Tiktok': {
marks: [],
nodes: [],
menu: [],
},
// Special contexts (not actual channels)
'Context::PrivateNote': {
marks: ['strong', 'em', 'code', 'link', 'strike'],
nodes: ['bulletList', 'orderedList', 'codeBlock', 'blockquote'],
menu: [
'copilot',
'strong',
'em',
'code',
'link',
'strike',
'bulletList',
'orderedList',
'undo',
'redo',
],
},
'Context::Default': {
marks: ['strong', 'em', 'code', 'link', 'strike'],
nodes: ['bulletList', 'orderedList', 'codeBlock', 'blockquote'],
menu: [
'strong',
'em',
'code',
'link',
'strike',
'bulletList',
'orderedList',
'undo',
'redo',
],
},
'Context::MessageSignature': {
marks: ['strong', 'em', 'link'],
nodes: ['image'],
menu: ['strong', 'em', 'link', 'undo', 'redo', 'imageUpload'],
},
'Context::InboxSettings': {
marks: ['strong', 'em', 'link'],
nodes: [],
menu: ['strong', 'em', 'link', 'undo', 'redo'],
},
'Context::Plain': {
marks: [],
nodes: [],
menu: [],
},
'Context::NoToolbar': {
marks: ['strong', 'em', 'link'],
nodes: ['bulletList', 'orderedList'],
menu: [],
},
};
// Editor menu options for Full Editor
export const ARTICLE_EDITOR_MENU_OPTIONS = [
'strong',
'em',
'strike',
'link',
'undo',
'redo',
'bulletList',
'orderedList',
'h1',
'h2',
'h3',
'imageUpload',
'code',
'insertTable',
];
// [text](url) -> "text: url" (drop label if it equals the URL). Keep serializer
// escapes; the re-parse renders them literally, unescaping would crash it.
const flattenLink = (_match, text, url) => {
const cleanUrl = url
.trim()
.replace(/\s+["'(].*$/, '')
.replace(/^<|>$/g, '');
return text === cleanUrl ? cleanUrl : `${text}: ${cleanUrl}`;
};
/**
* Markdown formatting patterns for stripping unsupported formatting.
*
* Maps camelCase type names to ProseMirror snake_case schema names.
* Order matters: codeBlock before code to avoid partial matches.
*/
export const MARKDOWN_PATTERNS = [
// --- BLOCK NODES ---
{
type: 'codeBlock', // PM: code_block, eg: ```js\ncode\n```
patterns: [
{ pattern: /`{3}(?:\w+)?\n?([\s\S]*?)`{3}/g, replacement: '$1' },
],
},
{
type: 'blockquote', // PM: blockquote, eg: > quote
patterns: [{ pattern: /^> ?/gm, replacement: '' }],
},
{
type: 'bulletList', // PM: bullet_list, eg: - item
patterns: [{ pattern: /^[\t ]*[-*+]\s+/gm, replacement: '' }],
},
{
type: 'orderedList', // PM: ordered_list, eg: 1. item
patterns: [{ pattern: /^[\t ]*\d+\.\s+/gm, replacement: '' }],
},
{
type: 'heading', // PM: heading, eg: ## Heading
patterns: [{ pattern: /^#{1,6}\s+/gm, replacement: '' }],
},
{
type: 'horizontalRule', // PM: horizontal_rule, eg: ---
patterns: [{ pattern: /^(?:---|___|\*\*\*)\s*$/gm, replacement: '' }],
},
{
type: 'image', // PM: image, eg: ![alt](url)
patterns: [{ pattern: /!\[([^\]]*)\]\([^)]+\)/g, replacement: '$1' }],
},
{
type: 'hardBreak', // PM: hard_break, eg: line\\\n or line \n
patterns: [
{ pattern: /\\\n/g, replacement: '\n' },
{ pattern: / {2,}\n/g, replacement: '\n' },
],
},
// --- INLINE MARKS ---
{
type: 'strong', // PM: strong, eg: **bold** or __bold__
patterns: [
{ pattern: /\*\*(.+?)\*\*/g, replacement: '$1' },
{ pattern: /__(.+?)__/g, replacement: '$1' },
],
},
{
type: 'em', // PM: em, eg: *italic* or _italic_
patterns: [
{ pattern: /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, replacement: '$1' },
// Match _text_ only at word boundaries (whitespace/string start/end)
// Preserves underscores in URLs (e.g., https://example.com/path_name) and variable names
{
pattern: /(?<=^|[\s])_([^_\s][^_]*[^_\s]|[^_\s])_(?=$|[\s])/g,
replacement: '$1',
},
],
},
{
type: 'strike', // PM: strike, eg: ~~strikethrough~~
patterns: [{ pattern: /~~(.+?)~~/g, replacement: '$1' }],
},
{
type: 'code', // PM: code, eg: `inline code`
patterns: [{ pattern: /`([^`]+)`/g, replacement: '$1' }],
},
{
type: 'link', // PM: link
patterns: [
// Escape-aware label + URL captures so a \] or \) can't cut the match
// short and leave link markup that crashes the re-parse.
{
pattern: /\[((?:\\.|[^\]\\])*)\]\(((?:\\.|[^)\\])*)\)/g,
replacement: flattenLink,
},
{ pattern: /<([a-zA-Z][a-zA-Z0-9+.-]*:[^\s>]+)>/g, replacement: '$1' }, // <https://...>, <mailto:...>, <tel:...>, <ftp://...>, etc
{ pattern: /<([^\s@]+@[^\s@>]+)>/g, replacement: '$1' }, // <user@example.com> -> user@example.com
],
},
];