summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authoranatawa12 <anatawa12@icloud.com>2025-04-15 16:14:52 +0900
committerGitHub <noreply@github.com>2025-04-15 16:14:52 +0900
commitf454e820bd5f1698a25fde38277ce37adbd277dc (patch)
treedf3a3e333a15439e11f4be9c25fad129ff16279f /packages/backend/src/core
parentfix: avatarId が null のときにも avatarUrl が non null 担ってるこ... (diff)
downloadsharkey-f454e820bd5f1698a25fde38277ce37adbd277dc.tar.gz
sharkey-f454e820bd5f1698a25fde38277ce37adbd277dc.tar.bz2
sharkey-f454e820bd5f1698a25fde38277ce37adbd277dc.zip
feat: render quote note with `quote-inline` class for ap compatibility (#15818)
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/MfmService.ts10
-rw-r--r--packages/backend/src/core/activitypub/ApMfmService.ts10
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts22
3 files changed, 31 insertions, 11 deletions
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 00208927e2..28d980f718 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -6,7 +6,7 @@
import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5';
-import { Window, XMLSerializer } from 'happy-dom';
+import { type Document, type HTMLParagraphElement, Window, XMLSerializer } from 'happy-dom';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js';
@@ -23,6 +23,8 @@ type ChildNode = DefaultTreeAdapterMap['childNode'];
const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
+export type Appender = (document: Document, body: HTMLParagraphElement) => void;
+
@Injectable()
export class MfmService {
constructor(
@@ -267,7 +269,7 @@ export class MfmService {
}
@bindThis
- public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
+ public toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], additionalAppenders: Appender[] = []) {
if (nodes == null) {
return null;
}
@@ -492,6 +494,10 @@ export class MfmService {
appendChildren(nodes, body);
+ for (const additionalAppender of additionalAppenders) {
+ additionalAppender(doc, body);
+ }
+
// Remove the unnecessary namespace
const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index 4036d2794a..f4c07e472c 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -5,7 +5,7 @@
import { Injectable } from '@nestjs/common';
import * as mfm from 'mfm-js';
-import { MfmService } from '@/core/MfmService.js';
+import { MfmService, Appender } from '@/core/MfmService.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';
import { extractApHashtagObjects } from './models/tag.js';
@@ -25,17 +25,17 @@ export class ApMfmService {
}
@bindThis
- public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, apAppend?: string) {
+ public getNoteHtml(note: Pick<MiNote, 'text' | 'mentionedRemoteUsers'>, additionalAppender: Appender[] = []) {
let noMisskeyContent = false;
- const srcMfm = (note.text ?? '') + (apAppend ?? '');
+ const srcMfm = (note.text ?? '');
const parsed = mfm.parse(srcMfm);
- if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
+ if (!additionalAppender.length && parsed.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
noMisskeyContent = true;
}
- const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers));
+ const content = this.mfmService.toHtml(parsed, JSON.parse(note.mentionedRemoteUsers), additionalAppender);
return {
content,
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index f01874952f..55521d6e3a 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -19,7 +19,7 @@ import type { MiEmoji } from '@/models/Emoji.js';
import type { MiPoll } from '@/models/Poll.js';
import type { MiPollVote } from '@/models/PollVote.js';
import { UserKeypairService } from '@/core/UserKeypairService.js';
-import { MfmService } from '@/core/MfmService.js';
+import { MfmService, type Appender } from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import type { MiUserKeypair } from '@/models/UserKeypair.js';
@@ -430,10 +430,24 @@ export class ApRendererService {
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
}
- let apAppend = '';
+ const apAppend: Appender[] = [];
if (quote) {
- apAppend += `\n\nRE: ${quote}`;
+ // Append quote link as `<br><br><span class="quote-inline">RE: <a href="...">...</a></span>`
+ // the claas name `quote-inline` is used in non-misskey clients for styling quote notes.
+ // For compatibility, the span part should be kept as possible.
+ apAppend.push((doc, body) => {
+ body.appendChild(doc.createElement('br'));
+ body.appendChild(doc.createElement('br'));
+ const span = doc.createElement('span');
+ span.className = 'quote-inline';
+ span.appendChild(doc.createTextNode('RE: '));
+ const link = doc.createElement('a');
+ link.setAttribute('href', quote);
+ link.textContent = quote;
+ span.appendChild(link);
+ body.appendChild(span);
+ });
}
const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
@@ -509,7 +523,7 @@ export class ApRendererService {
const urlPart = match[0];
const urlPartParsed = new URL(urlPart);
const restPart = maybeUrl.slice(match[0].length);
-
+
return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
} catch (e) {
return maybeUrl;