From 01a5300be88665ae414c15e8e0667c93799d8f0e Mon Sep 17 00:00:00 2001
From: dakkar
Date: Sat, 18 Jan 2025 12:51:38 +0000
Subject: [PATCH] handle more complex ruby from/to html - fixes #605
this is not exactly great, but it should be "good enough"
note that the new `group` function should not escape in the wild, as
we don't document it and only use it internally
I tried using `$[scale foo bar]` instead of `$[group foo bar]`, but
that would be rendered as `foo bar` when sent over the network
to non-misskey instances, and we don't want that
---
packages/backend/src/core/MfmService.ts | 67 +++++++++++++++++++
packages/backend/test/unit/MfmService.ts | 13 ++++
.../frontend/src/components/global/MkMfm.ts | 4 ++
3 files changed, 84 insertions(+)
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 42676d6f98..48995672c5 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -230,6 +230,67 @@ export class MfmService {
break;
}
+ case 'rp': break
+ case 'rt': {
+ appendChildren(node.childNodes);
+ break;
+ }
+ case 'ruby': {
+ if (node.childNodes) {
+ /*
+ we get:
+ ```
+
+ some text
+ more text
+ ```
+
+ and we want to produce:
+ ```
+ $[ruby $[group some text] annotation]
+ $[ruby $[group more text] more annotation]
+ ```
+
+ that `group` is a hack, because when the `ruby` render
+ sees just text inside the `$[ruby]`, it splits on
+ whitespace, considers the first "word" to be the main
+ content, and the rest the annotation
+
+ with that `group`, we force it to consider the whole
+ group as the main content
+
+ (note that the `rp` are to be ignored, they only exist
+ for browsers who don't understand ruby)
+ */
+ let nonRtNodes=[];
+ // scan children, ignore `rp`, split on `rt`
+ for (const child of node.childNodes) {
+ if (treeAdapter.isTextNode(child)) {
+ nonRtNodes.push(child);
+ continue;
+ }
+ if (!treeAdapter.isElementNode(child)) {
+ continue;
+ }
+ if (child.nodeName == 'rp') {
+ continue;
+ }
+ if (child.nodeName == 'rt') {
+ text += '$[ruby $[group ';
+ appendChildren(nonRtNodes);
+ text += '] ';
+ analyze(child);
+ text += '] ';
+ nonRtNodes=[];
+ continue;
+ }
+ nonRtNodes.push(child);
+ }
+ }
+ break;
+ }
+
default: // includes inline elements
{
appendChildren(node.childNodes);
@@ -348,6 +409,12 @@ export class MfmService {
}
}
+ case 'group': { // this is mostly a hack for `ruby`
+ const el = doc.createElement('span');
+ appendChildren(node.children, el);
+ return el;
+ }
+
default: {
return fnDefault(node);
}
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index 8d5683329f..93ce0672dc 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -45,6 +45,12 @@ describe('MfmService', () => {
const output = '<p>Hello, world!</p>
';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
+
+ test('ruby', () => {
+ const input = '$[ruby $[group *some* text] ignore me]';
+ const output = 'some textignore me
';
+ assert.equal(mfmService.toHtml(mfm.parse(input)), output);
+ });
});
describe('fromHtml', () => {
@@ -115,5 +121,12 @@ describe('MfmService', () => {
test('hashtag', () => {
assert.deepStrictEqual(mfmService.fromHtml('a #a d
', ['#a']), 'a #a d');
});
+
+ test('ruby', () => {
+ assert.deepStrictEqual(
+ mfmService.fromHtml(' some text ignore me and more'),
+ '$[ruby $[group some text ] ignore me] $[ruby $[group and ] more]'
+ );
+ });
});
});
diff --git a/packages/frontend/src/components/global/MkMfm.ts b/packages/frontend/src/components/global/MkMfm.ts
index 1039572a06..aceed17189 100644
--- a/packages/frontend/src/components/global/MkMfm.ts
+++ b/packages/frontend/src/components/global/MkMfm.ts
@@ -358,6 +358,10 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext