nodebb-plugin-emoji/lib/build.ts
2021-02-03 21:35:21 -07:00

215 lines
6.1 KiB
TypeScript

import { join, resolve, basename } from 'path';
import {
mkdirp,
copy,
remove,
writeFile,
symlink,
} from 'fs-extra';
import { uniq } from 'lodash';
import * as cssBuilders from './css-builders';
import { clearCache } from './parse';
import { getCustomizations } from './customizations';
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');
const linkDirs = (sourceDir: string, destDir: string) => {
const type = (process.platform === 'win32') ? 'junction' : 'dir';
return symlink(sourceDir, destDir, type);
};
export const tableFile = join(assetsDir, 'table.json');
export const aliasesFile = join(assetsDir, 'aliases.json');
export const asciiFile = join(assetsDir, 'ascii.json');
export const charactersFile = join(assetsDir, 'characters.json');
export const categoriesFile = join(assetsDir, 'categories.json');
export const packsFile = join(assetsDir, 'packs.json');
export default async function build() {
winston.verbose('[emoji] Building emoji assets');
// fetch the emoji definitions
const { packs }: { packs: EmojiDefinition[] } = await plugins.hooks.fire('filter:emoji.packs', { packs: [] });
// filter out invalid ones
const filtered = packs.filter((pack) => {
if (pack && pack.id && pack.name && pack.mode && pack.dictionary && pack.path) {
return true;
}
winston.warn('[emoji] pack invalid: ', pack.path || pack.id);
return false;
});
winston.verbose(`[emoji] Loaded packs: ${filtered.map(pack => pack.id).join(', ')}`);
await remove(assetsDir);
await mkdirp(assetsDir);
const customizations = await getCustomizations();
const table: MetaData.Table = {};
const aliases: MetaData.Aliases = {};
const ascii: MetaData.Ascii = {};
const characters: MetaData.Characters = {};
const categoriesInfo: MetaData.Categories = {};
const packsInfo: MetaData.Packs = [];
packs.forEach((pack) => {
packsInfo.push({
name: pack.name,
id: pack.id,
attribution: pack.attribution,
license: pack.license,
});
Object.keys(pack.dictionary).forEach((key) => {
const name = key.toLowerCase();
const emoji = pack.dictionary[key];
if (!table[name]) {
table[name] = {
name,
character: emoji.character || `:${name}:`,
image: emoji.image || '',
pack: pack.id,
aliases: emoji.aliases || [],
keywords: emoji.keywords || [],
};
}
if (!characters[emoji.character]) {
characters[emoji.character] = name;
}
if (emoji.aliases) {
emoji.aliases.forEach((alias) => {
const a = alias.toLowerCase();
if (!aliases[a]) {
aliases[a] = name;
}
});
}
if (emoji.ascii) {
emoji.ascii.forEach((str) => {
if (!ascii[str]) {
ascii[str] = name;
}
});
}
const categories = emoji.categories || ['other'];
categories.forEach((category) => {
categoriesInfo[category] = categoriesInfo[category] || [];
categoriesInfo[category].push(name);
});
});
});
Object.keys(categoriesInfo).forEach((category) => {
categoriesInfo[category] = uniq(categoriesInfo[category]);
});
customizations.emojis.forEach((emoji) => {
const name = emoji.name.toLowerCase();
table[name] = {
name,
character: `:${name}:`,
pack: 'customizations',
keywords: [],
image: emoji.image,
aliases: emoji.aliases,
};
emoji.aliases.forEach((alias) => {
const a = alias.toLowerCase();
if (!aliases[a]) {
aliases[a] = name;
}
});
emoji.ascii.forEach((str) => {
if (!ascii[str]) {
ascii[str] = name;
}
});
categoriesInfo.custom = categoriesInfo.custom || [];
categoriesInfo.custom.push(name);
});
customizations.adjuncts.forEach((adjunct) => {
const name = adjunct.name;
if (!table[name]) { return; }
table[name] = {
...table[name],
aliases: table[name].aliases.concat(adjunct.aliases),
};
adjunct.aliases.forEach((alias) => { aliases[alias] = name; });
adjunct.ascii.forEach((str) => { ascii[str] = name; });
});
// generate CSS styles
const css = packs.map(pack => cssBuilders[pack.mode](pack)).join('\n');
const cssFile = `${css}\n.emoji-customizations {
display: inline-block;
height: 23px;
margin-top: -1px;
margin-bottom: -1px;
}`.split('\n').map(x => x.trim()).join('');
// handling copying or linking necessary assets
const assetOperations = packs.map(async (pack) => {
const dir = join(assetsDir, pack.id);
if (pack.mode === 'images') {
await linkDirs(resolve(pack.path, pack.images.directory), dir);
} else if (pack.mode === 'sprite') {
const filename = basename(pack.sprite.file);
await mkdirp(dir);
await copy(resolve(pack.path, pack.sprite.file), join(dir, filename));
} else { // pack.mode === 'font'
const fontFiles = [
pack.font.eot,
pack.font.svg,
pack.font.woff,
pack.font.ttf,
pack.font.woff2,
].filter(Boolean);
await mkdirp(dir);
await Promise.all(fontFiles.map(async (file) => {
const filename = basename(file);
copy(resolve(pack.path, file), join(dir, filename));
}));
}
});
await Promise.all([
...assetOperations,
// store CSS styles
writeFile(join(assetsDir, 'styles.css'), cssFile, { encoding: 'utf8' }),
// persist metadata to disk
writeFile(tableFile, JSON.stringify(table)),
writeFile(aliasesFile, JSON.stringify(aliases)),
writeFile(asciiFile, JSON.stringify(ascii)),
writeFile(charactersFile, JSON.stringify(characters)),
writeFile(categoriesFile, JSON.stringify(categoriesInfo)),
writeFile(packsFile, JSON.stringify(packsInfo)),
// link customizations to public/uploads/emoji
linkDirs(
join(nconf.get('upload_path'), 'emoji'),
join(assetsDir, 'customizations')
),
]);
clearCache();
}