Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-underline-markdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: patch
---

Added the ability to **underline** using `__underscores__`.
2 changes: 1 addition & 1 deletion src/app/components/message/Reply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export const Reply = as<'div', ReplyProps>(
{(pinsAdded?.length > 0 &&
`pinned ${pinsAdded.length} message${pinsAdded.length > 1 ? 's' : ''}`) ||
''}
{(pinsAdded?.length > 0 && pinsRemoved?.length > 0 && `and`) || ''}
{(pinsAdded?.length > 0 && pinsRemoved?.length > 0 && ` and `) || ''}
{(pinsRemoved?.length > 0 &&
`unpinned ${pinsRemoved.length} message${pinsRemoved.length > 1 ? 's' : ''}`) ||
''}
Expand Down
2 changes: 1 addition & 1 deletion src/app/hooks/timeline/useTimelineEventRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ export function useTimelineEventRenderer({
{(pinsAdded?.length > 0 &&
`pinned ${pinsAdded.length} message${pinsAdded.length > 1 ? 's' : ''}`) ||
''}
{(pinsAdded?.length > 0 && pinsRemoved?.length > 0 && ` and`) || ''}
{(pinsAdded?.length > 0 && pinsRemoved?.length > 0 && ` and `) || ''}
{(pinsRemoved?.length > 0 &&
`unpinned ${pinsRemoved.length} message${pinsRemoved.length > 1 ? 's' : ''}`) ||
''}
Expand Down
8 changes: 8 additions & 0 deletions src/app/plugins/markdown/bidirectional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ describe('bidirectional round-trip', () => {
expect(result).toContain('*italic text*');
});

it('round-trips underline', () => {
const markdown = '__underlined__';
const html = markdownToHtml(markdown);
const injected = injectDataMd(html);
const result = htmlToMarkdown(injected);
expect(result).toContain('__underlined__');
});

it('round-trips inline code', () => {
const markdown = '`inline code`';
const html = markdownToHtml(markdown);
Expand Down
38 changes: 38 additions & 0 deletions src/app/plugins/markdown/extensions/matrix-underline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { TokenizerExtension, RendererExtension, Tokens } from 'marked';

// Underline extension: __text__
export const matrixUnderlineExtension = {
name: 'matrixUnderline',
level: 'inline',
start(src: string) {
return src.indexOf('__');
},
tokenizer(
this: {
lexer: { inlineTokens: (t: string, tokens: Tokens.Generic[]) => void };
},
src: string
) {
if (!src.startsWith('__')) return undefined;
const rule = /^__(.+?)__/;
const match = rule.exec(src);
if (match) {
const token = {
type: 'matrixUnderline',
raw: match[0],
text: match[1],
tokens: [] as Tokens.Generic[],
};
this.lexer.inlineTokens(token.text!, token.tokens);
return token;
}
return undefined;
},
renderer(
this: { parser: { parseInline: (tokens: Tokens.Generic[]) => string } },
token: Tokens.Generic
) {
const tokens = (token as { tokens: Tokens.Generic[] }).tokens || [];
return `<u data-md="__">${this.parser.parseInline(tokens)}</u>`;
},
} satisfies TokenizerExtension & RendererExtension;
6 changes: 4 additions & 2 deletions src/app/plugins/markdown/htmlToMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,10 @@ function processNode(node: ChildNode, listDepth: number = 0): string {
case 'i':
return processInlineWrapper(node, '*');

case 'u':
return processInlineWrapper(node, '_');
case 'u': {
const md = node.attribs['data-md'];
return processInlineWrapper(node, md ?? '__');
}

case 's':
case 'del':
Expand Down
2 changes: 1 addition & 1 deletion src/app/plugins/markdown/injectDataMd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('injectDataMd', () => {

it('injects data-md into u tags', () => {
const result = injectDataMd('<u>underline</u>');
expect(result).toContain('data-md="_"');
expect(result).toContain('data-md="__"');
});

it('injects data-md into s tags', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/plugins/markdown/injectDataMd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function injectDataMd(html: string): string {
// Inject inline markdown markers for underline
html = html.replace(/<u([^>]*)>([^<]*)<\/u>/g, (_, attrs, content) => {
if (attrs.includes('data-md')) return `<u${attrs}>${content}</u>`;
return `<u data-md="_"${attrs}>${content}</u>`;
return `<u data-md="__"${attrs}>${content}</u>`;
});

// Inject inline markdown markers for strikethrough
Expand Down
8 changes: 8 additions & 0 deletions src/app/plugins/markdown/markdownToHtml.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ describe('markdownToHtml', () => {
expect(result).toContain('<strong>bold</strong>');
});

it('converts __ to underline, not bold', () => {
const result = markdownToHtml('__underlined__');
expect(result).toContain('<u');
expect(result).toContain('data-md="__"');
expect(result).toContain('>underlined<');
expect(result).not.toContain('<strong>underlined</strong>');
});

it('converts italic text', () => {
const result = markdownToHtml('*italic*');
expect(result).toContain('<em>italic</em>');
Expand Down
2 changes: 2 additions & 0 deletions src/app/plugins/markdown/markdownToHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from './extensions/matrix-math';
import { matrixSubscriptExtension } from './extensions/matrix-subscript';
import { matrixEmoticonExtension, preprocessEmoticon } from './extensions/matrix-emoticon';
import { matrixUnderlineExtension } from './extensions/matrix-underline';
import {
escapeLineStartBlockquoteWithoutFollowingSpace,
unescapeMarkdownInlineSequences,
Expand All @@ -18,6 +19,7 @@ import {
const processor = marked.use({
breaks: true,
extensions: [
matrixUnderlineExtension,
matrixSpoilerExtension,
matrixMathExtension,
matrixMathBlockExtension,
Expand Down
Loading