Skip to content

Commit

Permalink
merge: mastodon api changes (transfem-org#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marie authored Oct 31, 2023
2 parents 32e877f + b57b644 commit 20ec630
Show file tree
Hide file tree
Showing 22 changed files with 764 additions and 513 deletions.
208 changes: 208 additions & 0 deletions packages/backend/src/core/MfmService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,212 @@ export class MfmService {

return `<p>${doc.body.innerHTML}</p>`;
}

@bindThis
public async toMastoHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = [], inline = false, quoteUri: string | null = null) {
if (nodes == null) {
return null;
}

const { window } = new Window();

const doc = window.document;

async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> {
if (children) {
for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
}
}

const handlers: {
[K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
} = {
async bold(node) {
const el = doc.createElement('span');
el.textContent = '**';
await appendChildren(node.children, el);
el.textContent += '**';
return el;
},

async small(node) {
const el = doc.createElement('small');
await appendChildren(node.children, el);
return el;
},

async strike(node) {
const el = doc.createElement('span');
el.textContent = '~~';
await appendChildren(node.children, el);
el.textContent += '~~';
return el;
},

async italic(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},

async fn(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},

blockCode(node) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');

const nodes = node.props.code
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));

for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
inner.appendChild(x === 'br' ? doc.createElement('br') : x);
}

pre.appendChild(inner);
return pre;
},

async center(node) {
const el = doc.createElement('div');
await appendChildren(node.children, el);
return el;
},

emojiCode(node) {
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
},

unicodeEmoji(node) {
return doc.createTextNode(node.props.emoji);
},

hashtag: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
a.textContent = `#${node.props.hashtag}`;
a.setAttribute('rel', 'tag');
a.setAttribute('class', 'hashtag');
return a;
},

inlineCode(node) {
const el = doc.createElement('code');
el.textContent = node.props.code;
return el;
},

mathInline(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},

mathBlock(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},

async link(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
await appendChildren(node.children, a);
return a;
},

async mention(node) {
const { username, host, acct } = node.props;
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);

const el = doc.createElement('span');
if (!resolved) {
el.textContent = acct;
} else {
el.setAttribute('class', 'h-card');
el.setAttribute('translate', 'no');
const a = doc.createElement('a');
a.setAttribute('href', resolved.url ? resolved.url : resolved.uri);
a.className = 'u-url mention';
const span = doc.createElement('span');
span.textContent = resolved.username || username;
a.textContent = '@';
a.appendChild(span);
el.appendChild(a);
}

return el;
},

async quote(node) {
const el = doc.createElement('blockquote');
await appendChildren(node.children, el);
return el;
},

text(node) {
const el = doc.createElement('span');
const nodes = node.props.text
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));

for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
el.appendChild(x === 'br' ? doc.createElement('br') : x);
}

return el;
},

url(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
a.textContent = node.props.url.replace(/^https?:\/\//, '');
return a;
},

search: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `https"google.com/${node.props.query}`);
a.textContent = node.props.content;
return a;
},

async plain(node) {
const el = doc.createElement('span');
await appendChildren(node.children, el);
return el;
},
};

await appendChildren(nodes, doc.body);

if (quoteUri !== null) {
const a = doc.createElement('a');
a.setAttribute('href', quoteUri);
a.textContent = quoteUri.replace(/^https?:\/\//, '');

const quote = doc.createElement('span');
quote.setAttribute('class', 'quote-inline');
quote.appendChild(doc.createElement('br'));
quote.appendChild(doc.createElement('br'));
quote.innerHTML += 'RE: ';
quote.appendChild(a);

doc.body.appendChild(quote);
}

return inline ? doc.body.innerHTML : `<p>${doc.body.innerHTML}</p>`;
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/core/SearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class SearchService {
}
}
const res = await this.meilisearchNoteIndex!.search(q, {
sort: [`createdAt:${opts.order}`],
sort: [`createdAt:${opts.order ? opts.order : 'desc'}`],
matchingStrategy: 'all',
attributesToRetrieve: ['id', 'createdAt'],
filter: compileQuery(filter),
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/misc/prelude/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ export function toArray<T>(x: T | T[] | undefined): T[] {
export function toSingle<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x[0] : x;
}

export function toSingleLast<T>(x: T | T[] | undefined): T | undefined {
return Array.isArray(x) ? x.at(-1) : x;
}
2 changes: 2 additions & 0 deletions packages/backend/src/server/ServerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SigninService } from './api/SigninService.js';
import { SignupApiService } from './api/SignupApiService.js';
import { StreamingApiServerService } from './api/StreamingApiServerService.js';
import { ClientServerService } from './web/ClientServerService.js';
import { MastoConverters } from './api/mastodon/converters.js';
import { FeedService } from './web/FeedService.js';
import { UrlPreviewService } from './web/UrlPreviewService.js';
import { MainChannelService } from './api/stream/channels/main.js';
Expand Down Expand Up @@ -87,6 +88,7 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
OpenApiServerService,
MastodonApiServerService,
OAuth2ProviderService,
MastoConverters,
],
exports: [
ServerService,
Expand Down
Loading

0 comments on commit 20ec630

Please sign in to comment.