mirror of
https://hk.gh-proxy.com/https://github.com/NodeBB/nodebb-plugin-emoji.git
synced 2025-10-03 01:10:57 +08:00
support for activitypub
This commit is contained in:
parent
3a4d93f958
commit
4ddcbba7e4
15 changed files with 159 additions and 62 deletions
|
@ -50,6 +50,7 @@ module.exports = {
|
|||
minProperties: 5,
|
||||
consistent: true,
|
||||
}],
|
||||
'no-unsafe-optional-chaining': 'off',
|
||||
'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
|
||||
'@typescript-eslint/indent': ['error', 2],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
|
|
|
@ -7,7 +7,7 @@ jQuery(window).on('action:ajaxify.end', () => {
|
|||
if (ajaxify.data.template['admin/plugins/emoji']) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Settings({
|
||||
target: document.getElementById('content'),
|
||||
target: document.getElementById('content') as HTMLElement,
|
||||
props: {
|
||||
settings: ajaxify.data.settings,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const nconf = require.main.require('nconf');
|
||||
const nconf = require.main?.require('nconf');
|
||||
|
||||
export function getBaseUrl(): string {
|
||||
const relative_path = nconf.get('relative_path') || '';
|
||||
|
|
18
lib/build.ts
18
lib/build.ts
|
@ -13,9 +13,9 @@ import { getBaseUrl } from './base-url';
|
|||
import { clearCache } from './parse';
|
||||
import { getAll as getCustomizations } from './customizations';
|
||||
|
||||
const nconf = require.main.require('nconf');
|
||||
const winston = require.main.require('winston');
|
||||
const plugins = require.main.require('./src/plugins');
|
||||
const nconf = require.main?.require('nconf');
|
||||
const winston = require.main?.require('winston');
|
||||
const plugins = require.main?.require('./src/plugins');
|
||||
|
||||
export const assetsDir = join(__dirname, '../emoji');
|
||||
|
||||
|
@ -65,8 +65,8 @@ export default async function build(): Promise<void> {
|
|||
packsInfo.push({
|
||||
name: pack.name,
|
||||
id: pack.id,
|
||||
attribution: pack.attribution,
|
||||
license: pack.license,
|
||||
attribution: pack.attribution || '',
|
||||
license: pack.license || '',
|
||||
});
|
||||
|
||||
Object.keys(pack.dictionary).forEach((key) => {
|
||||
|
@ -76,7 +76,7 @@ export default async function build(): Promise<void> {
|
|||
if (!table[name]) {
|
||||
table[name] = {
|
||||
name,
|
||||
character: emoji.character || `:${name}:`,
|
||||
character: emoji.character,
|
||||
image: emoji.image || '',
|
||||
pack: pack.id,
|
||||
aliases: emoji.aliases || [],
|
||||
|
@ -122,7 +122,7 @@ export default async function build(): Promise<void> {
|
|||
|
||||
table[name] = {
|
||||
name,
|
||||
character: `:${name}:`,
|
||||
character: undefined,
|
||||
pack: 'customizations',
|
||||
keywords: [],
|
||||
image: emoji.image,
|
||||
|
@ -160,7 +160,7 @@ export default async function build(): Promise<void> {
|
|||
|
||||
// generate CSS styles
|
||||
cssBuilders.setBaseUrl(getBaseUrl());
|
||||
const css = packs.map(pack => cssBuilders[pack.mode](pack)).join('\n');
|
||||
const css = packs.map(pack => (cssBuilders as any)[pack.mode](pack)).join('\n');
|
||||
const cssFile = `${css}\n.emoji-customizations {
|
||||
display: inline-block;
|
||||
height: 23px;
|
||||
|
@ -185,7 +185,7 @@ export default async function build(): Promise<void> {
|
|||
pack.font.woff,
|
||||
pack.font.ttf,
|
||||
pack.font.woff2,
|
||||
].filter(Boolean);
|
||||
].filter(Boolean) as [string];
|
||||
|
||||
await mkdirp(dir);
|
||||
await Promise.all(fontFiles.map(async (file) => {
|
||||
|
|
|
@ -7,9 +7,9 @@ import * as settings from './settings';
|
|||
import { build } from './pubsub';
|
||||
import * as customizations from './customizations';
|
||||
|
||||
const nconf = require.main.require('nconf');
|
||||
const { setupApiRoute, setupAdminPageRoute } = require.main.require('./src/routes/helpers');
|
||||
const { formatApiResponse } = require.main.require('./src/controllers/helpers');
|
||||
const nconf = require.main?.require('nconf');
|
||||
const { setupApiRoute, setupAdminPageRoute } = require.main?.require('./src/routes/helpers');
|
||||
const { formatApiResponse } = require.main?.require('./src/controllers/helpers');
|
||||
|
||||
// eslint-disable-next-line import/no-dynamic-require, @typescript-eslint/no-var-requires
|
||||
const version: string = require(join(__dirname, '../../package.json')).version;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { basename } from 'path';
|
||||
|
||||
const buster = require.main.require('./src/meta').config['cache-buster'];
|
||||
const buster = require.main?.require('./src/meta').config['cache-buster'];
|
||||
let baseUrl = '';
|
||||
|
||||
export function setBaseUrl(url:string):void {
|
||||
|
@ -16,7 +16,7 @@ export function images(pack: EmojiDefinition): string {
|
|||
'}';
|
||||
}
|
||||
|
||||
export function sprite(pack: EmojiDefinition): string {
|
||||
export function sprite(pack: EmojiDefinition & { mode: 'sprite' }): string {
|
||||
const classes = Object.keys(pack.dictionary).map(name => `.emoji-${pack.id}.emoji--${name} {` +
|
||||
`background-position: ${pack.dictionary[name].backgroundPosition};` +
|
||||
'}');
|
||||
|
@ -50,7 +50,7 @@ export function sprite(pack: EmojiDefinition): string {
|
|||
${classes.join('')}`.split('\n').map(x => x.trim()).join('');
|
||||
}
|
||||
|
||||
export function font(pack: EmojiDefinition): string {
|
||||
export function font(pack: EmojiDefinition & { mode: 'font' }): string {
|
||||
const route = `${baseUrl}/plugins/nodebb-plugin-emoji/emoji/${pack.id}`;
|
||||
|
||||
return `@font-face {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const db = require.main.require('./src/database');
|
||||
const db = require.main?.require('./src/database');
|
||||
|
||||
const emojisKey = 'emoji:customizations:emojis';
|
||||
const adjunctsKey = 'emoji:customizations:adjuncts';
|
||||
|
|
|
@ -5,9 +5,9 @@ import { build } from './pubsub';
|
|||
import controllers from './controllers';
|
||||
import { getBaseUrl } from './base-url';
|
||||
|
||||
const nconf = require.main.require('nconf');
|
||||
const buster = require.main.require('./src/meta').config['cache-buster'];
|
||||
const file = require.main.require('./src/file');
|
||||
const nconf = require.main?.require('nconf');
|
||||
const buster = require.main?.require('./src/meta').config['cache-buster'];
|
||||
const file = require.main?.require('./src/file');
|
||||
|
||||
export async function init(params: any): Promise<void> {
|
||||
controllers(params);
|
||||
|
|
119
lib/parse.ts
119
lib/parse.ts
|
@ -2,24 +2,26 @@ import { readFile } from 'fs-extra';
|
|||
|
||||
import { tableFile, aliasesFile, asciiFile, charactersFile } from './build';
|
||||
|
||||
const buster = require.main.require('./src/meta').config['cache-buster'];
|
||||
const winston = require.main.require('winston');
|
||||
const buster = require.main?.require('./src/meta').config['cache-buster'];
|
||||
const winston = require.main?.require('winston');
|
||||
|
||||
let metaCache: {
|
||||
interface MetaCache {
|
||||
table: MetaData.Table;
|
||||
aliases: MetaData.Aliases;
|
||||
ascii: MetaData.Ascii;
|
||||
asciiPattern: RegExp;
|
||||
characters: MetaData.Characters;
|
||||
charPattern: RegExp;
|
||||
} = null;
|
||||
}
|
||||
|
||||
let metaCache: MetaCache | null = null;
|
||||
export function clearCache(): void {
|
||||
metaCache = null;
|
||||
}
|
||||
|
||||
const escapeRegExpChars = (text: string) => text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
|
||||
const getTable = async (): Promise<typeof metaCache> => {
|
||||
async function getTable(): Promise<MetaCache> {
|
||||
if (metaCache) {
|
||||
return metaCache;
|
||||
}
|
||||
|
@ -64,7 +66,7 @@ const getTable = async (): Promise<typeof metaCache> => {
|
|||
};
|
||||
|
||||
return metaCache;
|
||||
};
|
||||
}
|
||||
|
||||
const outsideCode = /(^|<\/code>)([^<]*|<(?!code[^>]*>))*(<code[^>]*>|$)/g;
|
||||
const outsideElements = /(<[^>]*>)?([^<>]*)/g;
|
||||
|
@ -91,10 +93,18 @@ export function setOptions(newOptions: ParseOptions): void {
|
|||
Object.assign(options, newOptions);
|
||||
}
|
||||
|
||||
export const buildEmoji = (emoji: StoredEmoji, whole: string, returnCharacter = false): string => {
|
||||
export const buildEmoji = (
|
||||
emoji: StoredEmoji,
|
||||
whole: string,
|
||||
returnCharacter: boolean,
|
||||
onReplace: (e: StoredEmoji, w: string) => void
|
||||
): string => {
|
||||
if (returnCharacter && emoji.character) {
|
||||
return emoji.character;
|
||||
return emoji.character || whole;
|
||||
}
|
||||
|
||||
onReplace(emoji, whole);
|
||||
|
||||
if (emoji.image) {
|
||||
const route = `${options.baseUrl}/plugins/nodebb-plugin-emoji/emoji/${emoji.pack}`;
|
||||
return `<img
|
||||
|
@ -114,12 +124,13 @@ export const buildEmoji = (emoji: StoredEmoji, whole: string, returnCharacter =
|
|||
|
||||
const replaceAscii = (
|
||||
str: string,
|
||||
{ ascii, asciiPattern, table }: (typeof metaCache),
|
||||
returnCharacter = false
|
||||
{ ascii, asciiPattern, table }: MetaCache,
|
||||
returnCharacter: boolean,
|
||||
onReplace: (e: StoredEmoji, w: string) => void
|
||||
) => str.replace(asciiPattern, (full: string, before: string, text: string) => {
|
||||
const emoji = ascii[text] && table[ascii[text]];
|
||||
if (emoji) {
|
||||
return `${before}${buildEmoji(emoji, text, returnCharacter)}`;
|
||||
return `${before}${buildEmoji(emoji, text, returnCharacter, onReplace)}`;
|
||||
}
|
||||
|
||||
return full;
|
||||
|
@ -127,17 +138,23 @@ const replaceAscii = (
|
|||
|
||||
const replaceNative = (
|
||||
str: string,
|
||||
{ characters, charPattern, table }: (typeof metaCache)
|
||||
{ characters, charPattern, table }: MetaCache,
|
||||
onReplace: (e: StoredEmoji, w: string) => void
|
||||
) => str.replace(charPattern, (char: string) => {
|
||||
const name = characters[char];
|
||||
if (table[name]) {
|
||||
return `:${name}:`;
|
||||
const emoji = table[name];
|
||||
if (emoji) {
|
||||
return buildEmoji(emoji, char, false, onReplace);
|
||||
}
|
||||
|
||||
return char;
|
||||
});
|
||||
|
||||
const parse = async (content: string, returnCharacter = false): Promise<string> => {
|
||||
async function parse(
|
||||
content: string,
|
||||
returnCharacter = false,
|
||||
onReplace: (e: StoredEmoji, w: string) => void = () => {}
|
||||
): Promise<string> {
|
||||
if (!content) {
|
||||
return content;
|
||||
}
|
||||
|
@ -155,14 +172,14 @@ const parse = async (content: string, returnCharacter = false): Promise<string>
|
|||
outsideCodeStr => outsideCodeStr.replace(outsideElements, (_, inside, outside) => {
|
||||
let output = outside;
|
||||
|
||||
if (options.native) {
|
||||
if (options.native && !returnCharacter) {
|
||||
// avoid parsing native inside HTML tags
|
||||
// also avoid converting ascii characters
|
||||
output = output.replace(
|
||||
/(<[^>]+>)|([^0-9a-zA-Z`~!@#$%^&*()\-=_+{}|[\]\\:";'<>?,./\s\n]+)/g,
|
||||
(full: string, tag: string, text: string) => {
|
||||
if (text) {
|
||||
return replaceNative(text, store);
|
||||
return replaceNative(text, store, onReplace);
|
||||
}
|
||||
|
||||
return full;
|
||||
|
@ -175,19 +192,19 @@ const parse = async (content: string, returnCharacter = false): Promise<string>
|
|||
const emoji = table[name] || table[aliases[name]];
|
||||
|
||||
if (emoji) {
|
||||
return buildEmoji(emoji, whole, returnCharacter);
|
||||
return buildEmoji(emoji, whole, returnCharacter, onReplace);
|
||||
}
|
||||
|
||||
return whole;
|
||||
});
|
||||
|
||||
if (options.ascii) {
|
||||
// avoid parsing native inside HTML tags
|
||||
// avoid parsing ascii inside HTML tags
|
||||
output = output.replace(
|
||||
/(<[^>]+>)|([^<]+)/g,
|
||||
(full: string, tag: string, text: string) => {
|
||||
if (text) {
|
||||
return replaceAscii(text, store, returnCharacter);
|
||||
return replaceAscii(text, store, returnCharacter, onReplace);
|
||||
}
|
||||
|
||||
return full;
|
||||
|
@ -200,13 +217,17 @@ const parse = async (content: string, returnCharacter = false): Promise<string>
|
|||
);
|
||||
|
||||
return parsed;
|
||||
};
|
||||
}
|
||||
|
||||
export function raw(content: string): Promise<string> {
|
||||
return parse(content);
|
||||
}
|
||||
|
||||
export async function post(data: { postData: { content: string } }): Promise<any> {
|
||||
export async function post(data: { postData: { content: string } }, type: string): Promise<any> {
|
||||
if (type === 'activitypub.note') {
|
||||
return data;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.postData.content = await parse(data.postData.content);
|
||||
return data;
|
||||
|
@ -280,3 +301,57 @@ export async function email(
|
|||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
let mimeModule: typeof import('mime') | null = null;
|
||||
async function importMime(): Promise<typeof import('mime').default> {
|
||||
if (!mimeModule) {
|
||||
mimeModule = await import('mime');
|
||||
}
|
||||
return mimeModule.default;
|
||||
}
|
||||
|
||||
export async function activitypubNote(data: {
|
||||
object: {
|
||||
source: { content: string },
|
||||
'@context'?: { 'Emoji'?: string },
|
||||
tag: {
|
||||
id: string;
|
||||
type: 'Emoji';
|
||||
name: string;
|
||||
icon: {
|
||||
type: 'Image';
|
||||
mediaType: string;
|
||||
url: string;
|
||||
}
|
||||
}[]
|
||||
},
|
||||
post: unknown
|
||||
}): Promise<any> {
|
||||
const mime = await importMime();
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
data.object['@context'] = data.object['@context'] || {};
|
||||
data.object['@context'].Emoji = 'http://joinmastodon.org/ns#Emoji';
|
||||
|
||||
data.object.tag = data.object.tag || [];
|
||||
|
||||
await parse(data.object.source.content, false, (emoji, whole) => {
|
||||
if (!emoji.image) {
|
||||
return;
|
||||
}
|
||||
|
||||
const route = `${options.baseUrl}/plugins/nodebb-plugin-emoji/emoji/${emoji.pack}`;
|
||||
data.object.tag.push({
|
||||
id: emoji.name,
|
||||
type: 'Emoji',
|
||||
name: whole,
|
||||
icon: {
|
||||
type: 'Image',
|
||||
mediaType: mime.getType(emoji.image) || '',
|
||||
url: `${route}/${emoji.image}?${buster}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ import * as os from 'os';
|
|||
|
||||
import buildAssets from './build';
|
||||
|
||||
const nconf = require.main.require('nconf');
|
||||
const winston = require.main.require('winston');
|
||||
const pubsub = require.main.require('./src/pubsub');
|
||||
const nconf = require.main?.require('nconf');
|
||||
const winston = require.main?.require('winston');
|
||||
const pubsub = require.main?.require('./src/pubsub');
|
||||
|
||||
const primary = nconf.get('isPrimary') === 'true' || nconf.get('isPrimary') === true;
|
||||
const hostname = os.hostname();
|
||||
|
|
|
@ -3,7 +3,7 @@ const settings: {
|
|||
set(key: string, value: unknown): Promise<void>;
|
||||
getOne(key: string, field: string): Promise<unknown>;
|
||||
setOne(key: string, field: string, value: unknown): Promise<void>;
|
||||
} = require.main.require('./src/meta').settings;
|
||||
} = require.main?.require('./src/meta').settings;
|
||||
|
||||
const defaults: Settings = {
|
||||
parseNative: true,
|
||||
|
|
46
lib/types.d.ts
vendored
46
lib/types.d.ts
vendored
|
@ -40,7 +40,7 @@ interface Emoji {
|
|||
categories?: string[];
|
||||
}
|
||||
|
||||
interface EmojiDefinition {
|
||||
type EmojiDefinition = {
|
||||
/**
|
||||
* human-friendly name of this emoji pack
|
||||
*/
|
||||
|
@ -68,34 +68,59 @@ interface EmojiDefinition {
|
|||
*/
|
||||
license?: string;
|
||||
|
||||
/**
|
||||
* A map of emoji names to `Emoji`
|
||||
*/
|
||||
dictionary: {
|
||||
[name: string]: Emoji;
|
||||
};
|
||||
} & ({
|
||||
/**
|
||||
* The mode of this emoji pack.
|
||||
* `images` for individual image files.
|
||||
* `sprite` for a single image sprite file.
|
||||
* `font` for an emoji font.
|
||||
*/
|
||||
mode: 'images' | 'sprite' | 'font';
|
||||
mode: 'images';
|
||||
|
||||
/**
|
||||
* **`images` mode** options
|
||||
*/
|
||||
images?: {
|
||||
images: {
|
||||
/** Path to the directory where the image files are located */
|
||||
directory: string;
|
||||
};
|
||||
} | {
|
||||
/**
|
||||
* The mode of this emoji pack.
|
||||
* `images` for individual image files.
|
||||
* `sprite` for a single image sprite file.
|
||||
* `font` for an emoji font.
|
||||
*/
|
||||
mode: 'sprite';
|
||||
|
||||
/**
|
||||
* **`sprite` mode** options
|
||||
*/
|
||||
sprite?: {
|
||||
sprite: {
|
||||
/** Path to the sprite image file */
|
||||
file: string;
|
||||
/** CSS `background-size` */
|
||||
backgroundSize: string;
|
||||
};
|
||||
} | {
|
||||
/**
|
||||
* The mode of this emoji pack.
|
||||
* `images` for individual image files.
|
||||
* `sprite` for a single image sprite file.
|
||||
* `font` for an emoji font.
|
||||
*/
|
||||
mode: 'font';
|
||||
|
||||
/**
|
||||
* **`font` mode** options
|
||||
*/
|
||||
font?: {
|
||||
font: {
|
||||
/** Path to the emoji font `.eot` file (for old IE support) */
|
||||
eot?: string;
|
||||
/** Path to the emoji font `.ttf` file */
|
||||
|
@ -111,20 +136,13 @@ interface EmojiDefinition {
|
|||
/** CSS `font-family` name */
|
||||
family: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A map of emoji names to one of the following
|
||||
*/
|
||||
dictionary: {
|
||||
[name: string]: Emoji;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
declare type NodeBack<T = any> = (err?: Error, ...args: T[]) => void;
|
||||
|
||||
interface StoredEmoji {
|
||||
name: string;
|
||||
character: string;
|
||||
character?: string;
|
||||
image: string;
|
||||
pack: string;
|
||||
aliases: string[];
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"url": "https://github.com/NodeBB/nodebb-plugin-emoji.git"
|
||||
},
|
||||
"nbbpm": {
|
||||
"compatibility": "^3.0.0"
|
||||
"compatibility": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"nodebb",
|
||||
|
@ -24,6 +24,7 @@
|
|||
"@textcomplete/textarea": "^0.1.12",
|
||||
"fs-extra": "^11.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mime": "^4.0.4",
|
||||
"multer": "^1.4.5-lts.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
{ "hook": "filter:composer.formatting", "method": "composerFormatting", "priority": 19 },
|
||||
{ "hook": "filter:parse.raw", "method": "parse.raw", "priority": 9 },
|
||||
{ "hook": "filter:parse.post", "method": "parse.post", "priority": 9 },
|
||||
{ "hook": "filter:activitypub.mocks.note", "method": "parse.activitypubNote" },
|
||||
{ "hook": "filter:topic.get", "method": "parse.topic" },
|
||||
{ "hook": "filter:topics.get", "method": "parse.topics" },
|
||||
{ "hook": "filter:post.getPostSummaryByPids", "method": "parse.postSummaries" },
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"alwaysStrict": true,
|
||||
"strictNullChecks": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"typeAcquisition": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue