Introduce plugin system supporting theme registration
This commit is contained in:
parent
83fedcff3b
commit
0fd0b4f466
@ -47,7 +47,14 @@ gulp.task('build:copy:views', () =>
|
|||||||
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/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([
|
gulp.src([
|
||||||
'./src/const.json',
|
'./src/const.json',
|
||||||
'./src/server/web/views/**/*',
|
'./src/server/web/views/**/*',
|
||||||
|
@ -21,11 +21,11 @@ export default async function() {
|
|||||||
process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`;
|
process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`;
|
||||||
|
|
||||||
if (cluster.isMaster || program.disableClustering) {
|
if (cluster.isMaster || program.disableClustering) {
|
||||||
await masterMain();
|
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
if (cluster.isMaster) {
|
||||||
ev.mount();
|
ev.mount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await masterMain();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cluster.isWorker || program.disableClustering) {
|
if (cluster.isWorker || program.disableClustering) {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as cluster from 'cluster';
|
import * as cluster from 'cluster';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
@ -12,6 +13,7 @@ import * as pkg from '../../package.json';
|
|||||||
import { program } from '../argv';
|
import { program } from '../argv';
|
||||||
import { showMachineInfo } from '../misc/show-machine-info';
|
import { showMachineInfo } from '../misc/show-machine-info';
|
||||||
import { initDb } from '../db/postgre';
|
import { initDb } from '../db/postgre';
|
||||||
|
import Xev from 'xev';
|
||||||
|
|
||||||
const logger = new Logger('core', 'cyan');
|
const logger = new Logger('core', 'cyan');
|
||||||
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
||||||
@ -75,6 +77,10 @@ export async function masterMain() {
|
|||||||
await spawnWorkers(config.clusterLimit);
|
await spawnWorkers(config.clusterLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadPlugins();
|
||||||
|
|
||||||
|
bootLogger.succ('All plugins loaded');
|
||||||
|
|
||||||
if (!program.noDaemons) {
|
if (!program.noDaemons) {
|
||||||
require('../daemons/server-stats').default();
|
require('../daemons/server-stats').default();
|
||||||
require('../daemons/notes-stats').default();
|
require('../daemons/notes-stats').default();
|
||||||
@ -109,6 +115,24 @@ function showEnvironment(): void {
|
|||||||
logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`);
|
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
|
* Init app
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import * as cluster from 'cluster';
|
import * as cluster from 'cluster';
|
||||||
import { initDb } from '../db/postgre';
|
import { initDb } from '../db/postgre';
|
||||||
|
import Xev from 'xev';
|
||||||
|
import { registerTheme } from '../pluginThemes';
|
||||||
|
|
||||||
|
const ev = new Xev();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init worker process
|
* Init worker process
|
||||||
@ -16,5 +20,9 @@ export async function workerMain() {
|
|||||||
if (cluster.isWorker) {
|
if (cluster.isWorker) {
|
||||||
// Send a 'ready' message to parent process
|
// Send a 'ready' message to parent process
|
||||||
process.send!('ready');
|
process.send!('ready');
|
||||||
|
|
||||||
|
ev.on('registerPluginTheme', theme => {
|
||||||
|
registerTheme(theme);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
<summary><fa icon="folder-open"/> {{ $t('manage-themes') }}</summary>
|
<summary><fa icon="folder-open"/> {{ $t('manage-themes') }}</summary>
|
||||||
<ui-select v-model="selectedThemeId" :placeholder="$t('select-theme')">
|
<ui-select v-model="selectedThemeId" :placeholder="$t('select-theme')">
|
||||||
<optgroup :label="$t('builtin-themes')">
|
<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>
|
||||||
<optgroup :label="$t('my-themes')">
|
<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>
|
<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>
|
<span>{{ $t('theme-code') }}</span>
|
||||||
</ui-textarea>
|
</ui-textarea>
|
||||||
<ui-button @click="export_()" link :download="`${selectedTheme.name}.misskeytheme`" ref="export"><fa icon="box"/> {{ $t('export') }}</ui-button>
|
<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>
|
</template>
|
||||||
</details>
|
</details>
|
||||||
</section>
|
</section>
|
||||||
@ -123,7 +123,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../../i18n';
|
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 { Chrome } from 'vue-color';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import * as tinycolor from 'tinycolor2';
|
import * as tinycolor from 'tinycolor2';
|
||||||
@ -138,7 +138,8 @@ export default Vue.extend({
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
builtinThemes: builtinThemes,
|
themes: [],
|
||||||
|
presetThemes: [],
|
||||||
installThemeCode: null,
|
installThemeCode: null,
|
||||||
selectedThemeId: null,
|
selectedThemeId: null,
|
||||||
myThemeBase: 'light',
|
myThemeBase: 'light',
|
||||||
@ -151,11 +152,17 @@ export default Vue.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
created() {
|
||||||
themes(): Theme[] {
|
this.$root.getThemes().then(themes => {
|
||||||
return builtinThemes.concat(this.$store.state.device.themes);
|
this.themes = themes;
|
||||||
},
|
});
|
||||||
|
|
||||||
|
this.$root.getPresetThemes().then(presetThemes => {
|
||||||
|
this.presetThemes = presetThemes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
computed: {
|
||||||
darkThemes(): Theme[] {
|
darkThemes(): Theme[] {
|
||||||
return this.themes.filter(t => t.base == 'dark' || t.kind == 'dark');
|
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 checkForUpdate from './common/scripts/check-for-update';
|
||||||
import MiOS from './mios';
|
import MiOS from './mios';
|
||||||
import { version, codename, lang, locale } from './config';
|
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';
|
import Dialog from './common/views/components/dialog.vue';
|
||||||
|
|
||||||
if (localStorage.getItem('theme') == null) {
|
if (localStorage.getItem('theme') == null) {
|
||||||
@ -364,29 +364,32 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
|||||||
os.store.watch(s => {
|
os.store.watch(s => {
|
||||||
return s.device.darkmode;
|
return s.device.darkmode;
|
||||||
}, v => {
|
}, v => {
|
||||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
os.getThemes().then(themes => {
|
||||||
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
|
const dark = themes.find(t => t.id == os.store.state.device.darkTheme);
|
||||||
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
|
const light = themes.find(t => t.id == os.store.state.device.lightTheme);
|
||||||
applyTheme(v ? dark : light);
|
applyTheme(v ? dark : light);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
os.store.watch(s => {
|
os.store.watch(s => {
|
||||||
return s.device.lightTheme;
|
return s.device.lightTheme;
|
||||||
}, v => {
|
}, v => {
|
||||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
os.getThemes().then(themes => {
|
||||||
const theme = themes.find(t => t.id == v);
|
const theme = themes.find(t => t.id == v);
|
||||||
if (!os.store.state.device.darkmode) {
|
if (!os.store.state.device.darkmode) {
|
||||||
applyTheme(theme);
|
applyTheme(theme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
os.store.watch(s => {
|
os.store.watch(s => {
|
||||||
return s.device.darkTheme;
|
return s.device.darkTheme;
|
||||||
}, v => {
|
}, v => {
|
||||||
const themes = os.store.state.device.themes.concat(builtinThemes);
|
os.getThemes().then(themes => {
|
||||||
const theme = themes.find(t => t.id == v);
|
const theme = themes.find(t => t.id == v);
|
||||||
if (os.store.state.device.darkmode) {
|
if (os.store.state.device.darkmode) {
|
||||||
applyTheme(theme);
|
applyTheme(theme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
/*// Reapply current theme
|
/*// Reapply current theme
|
||||||
@ -447,6 +450,8 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
|||||||
api: os.api,
|
api: os.api,
|
||||||
getMeta: os.getMeta,
|
getMeta: os.getMeta,
|
||||||
getMetaSync: os.getMetaSync,
|
getMetaSync: os.getMetaSync,
|
||||||
|
getThemes: os.getThemes,
|
||||||
|
getPresetThemes: os.getPresetThemes,
|
||||||
signout: os.signout,
|
signout: os.signout,
|
||||||
new(vm, props) {
|
new(vm, props) {
|
||||||
const x = new vm({
|
const x = new vm({
|
||||||
|
@ -9,6 +9,8 @@ import Progress from './common/scripts/loading';
|
|||||||
|
|
||||||
import Err from './common/views/components/connect-failed.vue';
|
import Err from './common/views/components/connect-failed.vue';
|
||||||
import Stream from './common/scripts/stream';
|
import Stream from './common/scripts/stream';
|
||||||
|
import { Theme, builtinThemes } from './theme';
|
||||||
|
import { concat } from '../../prelude/array';
|
||||||
|
|
||||||
//#region api requests
|
//#region api requests
|
||||||
let spinner = null;
|
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 {
|
class WindowSystem extends EventEmitter {
|
||||||
|
@ -12,34 +12,12 @@ export type Theme = {
|
|||||||
|
|
||||||
export const lightTheme: Theme = require('../themes/light.json5');
|
export const lightTheme: Theme = require('../themes/light.json5');
|
||||||
export const darkTheme: Theme = require('../themes/dark.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 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 = [
|
export const builtinThemes = [
|
||||||
lightTheme,
|
lightTheme,
|
||||||
darkTheme,
|
darkTheme,
|
||||||
lavenderTheme,
|
|
||||||
futureTheme,
|
futureTheme,
|
||||||
halloweenTheme,
|
|
||||||
cafeTheme,
|
|
||||||
japaneseSushiSetTheme,
|
|
||||||
gruvboxDarkTheme,
|
|
||||||
monokaiTheme,
|
|
||||||
vividTheme,
|
|
||||||
rainyTheme,
|
|
||||||
mauveTheme,
|
|
||||||
grayTheme,
|
|
||||||
tweetDeckTheme,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function applyTheme(theme: Theme, persisted = true) {
|
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 New Issue
Block a user