Introduce plugin system supporting theme registration
This commit is contained in:
parent
83fedcff3b
commit
0fd0b4f466
23 changed files with 141 additions and 48 deletions
|
@ -47,7 +47,14 @@ gulp.task('build:copy:views', () =>
|
|||
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
|
||||
);
|
||||
|
||||
gulp.task('build:copy', gulp.parallel('build:copy:views', () =>
|
||||
gulp.task('build:copy:plugins', () =>
|
||||
gulp.src([
|
||||
'./src/plugins/**/*',
|
||||
'!./src/plugins/**/*.ts'
|
||||
]).pipe(gulp.dest('./built/plugins'))
|
||||
);
|
||||
|
||||
gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:copy:plugins', () =>
|
||||
gulp.src([
|
||||
'./src/const.json',
|
||||
'./src/server/web/views/**/*',
|
||||
|
|
|
@ -21,11 +21,11 @@ export default async function() {
|
|||
process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`;
|
||||
|
||||
if (cluster.isMaster || program.disableClustering) {
|
||||
await masterMain();
|
||||
|
||||
if (cluster.isMaster) {
|
||||
ev.mount();
|
||||
}
|
||||
|
||||
await masterMain();
|
||||
}
|
||||
|
||||
if (cluster.isWorker || program.disableClustering) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as cluster from 'cluster';
|
||||
import chalk from 'chalk';
|
||||
|
@ -12,6 +13,7 @@ import * as pkg from '../../package.json';
|
|||
import { program } from '../argv';
|
||||
import { showMachineInfo } from '../misc/show-machine-info';
|
||||
import { initDb } from '../db/postgre';
|
||||
import Xev from 'xev';
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
||||
|
@ -75,6 +77,10 @@ export async function masterMain() {
|
|||
await spawnWorkers(config.clusterLimit);
|
||||
}
|
||||
|
||||
loadPlugins();
|
||||
|
||||
bootLogger.succ('All plugins loaded');
|
||||
|
||||
if (!program.noDaemons) {
|
||||
require('../daemons/server-stats').default();
|
||||
require('../daemons/notes-stats').default();
|
||||
|
@ -109,6 +115,24 @@ function showEnvironment(): void {
|
|||
logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`);
|
||||
}
|
||||
|
||||
const pluginService = {
|
||||
registerTheme(theme: any) {
|
||||
const ev = new Xev();
|
||||
ev.emit('registerPluginTheme', theme);
|
||||
}
|
||||
};
|
||||
|
||||
function loadPlugins(): void {
|
||||
const plugins = [
|
||||
path.resolve(`${__dirname}/../plugins/featured-themes`)
|
||||
];
|
||||
for (const plugin of plugins) {
|
||||
const pluginMeta = require(`${plugin}/plugin-meta.json`);
|
||||
bootLogger.info(`Plugin loaded: ${pluginMeta.name} v${pluginMeta.version}`);
|
||||
require(`${plugin}/main.js`).onActivate(pluginService);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init app
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import * as cluster from 'cluster';
|
||||
import { initDb } from '../db/postgre';
|
||||
import Xev from 'xev';
|
||||
import { registerTheme } from '../pluginThemes';
|
||||
|
||||
const ev = new Xev();
|
||||
|
||||
/**
|
||||
* Init worker process
|
||||
|
@ -16,5 +20,9 @@ export async function workerMain() {
|
|||
if (cluster.isWorker) {
|
||||
// Send a 'ready' message to parent process
|
||||
process.send!('ready');
|
||||
|
||||
ev.on('registerPluginTheme', theme => {
|
||||
registerTheme(theme);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<summary><fa icon="folder-open"/> {{ $t('manage-themes') }}</summary>
|
||||
<ui-select v-model="selectedThemeId" :placeholder="$t('select-theme')">
|
||||
<optgroup :label="$t('builtin-themes')">
|
||||
<option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||
<option v-for="x in presetThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$t('my-themes')">
|
||||
<option v-for="x in installedThemes.filter(t => t.author == this.$store.state.i.username)" :value="x.id" :key="x.id">{{ x.name }}</option>
|
||||
|
@ -113,7 +113,7 @@
|
|||
<span>{{ $t('theme-code') }}</span>
|
||||
</ui-textarea>
|
||||
<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export"><fa icon="box"/> {{ $t('export') }}</ui-button>
|
||||
<ui-button @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><fa :icon="['far', 'trash-alt']"/> {{ $t('uninstall') }}</ui-button>
|
||||
<ui-button @click="uninstall()" v-if="!presetThemes.some(t => t.id == selectedTheme.id)"><fa :icon="['far', 'trash-alt']"/> {{ $t('uninstall') }}</ui-button>
|
||||
</template>
|
||||
</details>
|
||||
</section>
|
||||
|
@ -123,7 +123,7 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import { lightTheme, darkTheme, builtinThemes, applyTheme, Theme } from '../../../../theme';
|
||||
import { lightTheme, darkTheme, applyTheme, Theme } from '../../../../theme';
|
||||
import { Chrome } from 'vue-color';
|
||||
import * as uuid from 'uuid';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
|
@ -138,7 +138,8 @@ export default Vue.extend({
|
|||
|
||||
data() {
|
||||
return {
|
||||
builtinThemes: builtinThemes,
|
||||
themes: [],
|
||||
presetThemes: [],
|
||||
installThemeCode: null,
|
||||
selectedThemeId: null,
|
||||
myThemeBase: 'light',
|
||||
|
@ -151,11 +152,17 @@ export default Vue.extend({
|
|||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
themes(): Theme[] {
|
||||
return builtinThemes.concat(this.$store.state.device.themes);
|
||||
},
|
||||
created() {
|
||||
this.$root.getThemes().then(themes => {
|
||||
this.themes = themes;
|
||||
});
|
||||
|
||||
this.$root.getPresetThemes().then(presetThemes => {
|
||||
this.presetThemes = presetThemes;
|
||||
});
|
||||
}
|
||||
|
||||
computed: {
|
||||
darkThemes(): Theme[] {
|
||||
return this.themes.filter(t => t.base == 'dark' || t.kind == 'dark');
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import App from './app.vue';
|
|||
import checkForUpdate from './common/scripts/check-for-update';
|
||||
import MiOS from './mios';
|
||||
import { version, codename, lang, locale } from './config';
|
||||
import { builtinThemes, applyTheme, futureTheme } from './theme';
|
||||
import { applyTheme, futureTheme } from './theme';
|
||||
import Dialog from './common/views/components/dialog.vue';
|
||||
|
||||
if (localStorage.getItem('theme') == null) {
|
||||
|
@ -364,28 +364,31 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
|||
os.store.watch(s => {
|
||||
return s.device.darkmode;
|
||||
}, v => {
|
||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
||||
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
|
||||
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
|
||||
applyTheme(v ? dark : light);
|
||||
os.getThemes().then(themes => {
|
||||
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
|
||||
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
|
||||
applyTheme(v ? dark : light);
|
||||
});
|
||||
});
|
||||
os.store.watch(s => {
|
||||
return s.device.lightTheme;
|
||||
}, v => {
|
||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
||||
const theme = themes.find(t => t.id == v);
|
||||
if (!os.store.state.device.darkmode) {
|
||||
applyTheme(theme);
|
||||
}
|
||||
os.getThemes().then(themes => {
|
||||
const theme = themes.find(t => t.id == v);
|
||||
if (!os.store.state.device.darkmode) {
|
||||
applyTheme(theme);
|
||||
}
|
||||
});
|
||||
});
|
||||
os.store.watch(s => {
|
||||
return s.device.darkTheme;
|
||||
}, v => {
|
||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
||||
const theme = themes.find(t => t.id == v);
|
||||
if (os.store.state.device.darkmode) {
|
||||
applyTheme(theme);
|
||||
}
|
||||
os.getThemes().then(themes => {
|
||||
const theme = themes.find(t => t.id == v);
|
||||
if (os.store.state.device.darkmode) {
|
||||
applyTheme(theme);
|
||||
}
|
||||
});
|
||||
});
|
||||
//#endregion
|
||||
|
||||
|
@ -447,6 +450,8 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
|||
api: os.api,
|
||||
getMeta: os.getMeta,
|
||||
getMetaSync: os.getMetaSync,
|
||||
getThemes: os.getThemes,
|
||||
getPresetThemes: os.getPresetThemes,
|
||||
signout: os.signout,
|
||||
new(vm, props) {
|
||||
const x = new vm({
|
||||
|
|
|
@ -9,6 +9,8 @@ import Progress from './common/scripts/loading';
|
|||
|
||||
import Err from './common/views/components/connect-failed.vue';
|
||||
import Stream from './common/scripts/stream';
|
||||
import { Theme, builtinThemes } from './theme';
|
||||
import { concat } from '../../prelude/array';
|
||||
|
||||
//#region api requests
|
||||
let spinner = null;
|
||||
|
@ -478,6 +480,28 @@ export default class MiOS extends EventEmitter {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* プリセットテーマ一覧を取得します
|
||||
*/
|
||||
@autobind
|
||||
public getPresetThemes() {
|
||||
return new Promise<Theme[]>(async (res, rej) => {
|
||||
const pluginThemes = await this.api('plugins/themes') as Theme[];
|
||||
res(concat([builtinThemes, pluginThemes]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* テーマ一覧を取得します
|
||||
*/
|
||||
@autobind
|
||||
public getThemes() {
|
||||
return new Promise<Theme[]>(async (res, rej) => {
|
||||
const installedThemes = this.store.state.device.themes as Theme[];
|
||||
res(concat([await this.getPresetThemes(), installedThemes]));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class WindowSystem extends EventEmitter {
|
||||
|
|
|
@ -12,34 +12,12 @@ export type Theme = {
|
|||
|
||||
export const lightTheme: Theme = require('../themes/light.json5');
|
||||
export const darkTheme: Theme = require('../themes/dark.json5');
|
||||
export const lavenderTheme: Theme = require('../themes/lavender.json5');
|
||||
export const futureTheme: Theme = require('../themes/future.json5');
|
||||
export const halloweenTheme: Theme = require('../themes/halloween.json5');
|
||||
export const cafeTheme: Theme = require('../themes/cafe.json5');
|
||||
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
|
||||
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
|
||||
export const monokaiTheme: Theme = require('../themes/monokai.json5');
|
||||
export const vividTheme: Theme = require('../themes/vivid.json5');
|
||||
export const rainyTheme: Theme = require('../themes/rainy.json5');
|
||||
export const mauveTheme: Theme = require('../themes/mauve.json5');
|
||||
export const grayTheme: Theme = require('../themes/gray.json5');
|
||||
export const tweetDeckTheme: Theme = require('../themes/tweet-deck.json5');
|
||||
|
||||
export const builtinThemes = [
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
lavenderTheme,
|
||||
futureTheme,
|
||||
halloweenTheme,
|
||||
cafeTheme,
|
||||
japaneseSushiSetTheme,
|
||||
gruvboxDarkTheme,
|
||||
monokaiTheme,
|
||||
vividTheme,
|
||||
rainyTheme,
|
||||
mauveTheme,
|
||||
grayTheme,
|
||||
tweetDeckTheme,
|
||||
];
|
||||
|
||||
export function applyTheme(theme: Theme, persisted = true) {
|
||||
|
|
9
src/pluginThemes.ts
Normal file
9
src/pluginThemes.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
const themes: any[] = [];
|
||||
|
||||
export function registerTheme(theme: any) {
|
||||
themes.push(theme);
|
||||
}
|
||||
|
||||
export function getThemes() {
|
||||
return themes;
|
||||
}
|
12
src/plugins/featured-themes/main.ts
Normal file
12
src/plugins/featured-themes/main.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
require('json5/lib/register');
|
||||
import * as fs from 'fs';
|
||||
|
||||
export function onActivate(service: any) {
|
||||
const fileNames = fs.readdirSync(`${__dirname}/themes`)
|
||||
.filter(f => fs.statSync(`${__dirname}/themes/${f}`).isFile());
|
||||
|
||||
for (const fileName of fileNames) {
|
||||
const theme = require(`${__dirname}/themes/${fileName}`);
|
||||
service.registerTheme(theme);
|
||||
}
|
||||
}
|
5
src/plugins/featured-themes/plugin-meta.json
Normal file
5
src/plugins/featured-themes/plugin-meta.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"misskeyVersion": "11",
|
||||
"name": "featured-themes",
|
||||
"version": "1.0.0"
|
||||
}
|
14
src/server/api/endpoints/plugins/themes.ts
Normal file
14
src/server/api/endpoints/plugins/themes.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import define from '../../define';
|
||||
import { getThemes } from '../../../../pluginThemes';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': 'プラグインによって登録されたテーマを取得します。'
|
||||
},
|
||||
|
||||
tags: ['themes']
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
return getThemes();
|
||||
});
|
Loading…
Reference in a new issue