core&ui: remove Hydro.service.bus, Hydro.ui.template

This commit is contained in:
undefined committed 2025-03-09 16:05:02 +08:00
1 parent a459b9a1fd
commit 1a7b08e56e
8 files changed
+151 -136

No files matched your search

-34
View File
@@ -14,17 +14,6 @@ import { unwrapExports } from '../utils';
const logger = new Logger('common'); const logger = new Logger('common');
async function getFiles(folder: string, base = ''): Promise<string[]> {
const files = [];
const f = await fs.readdir(folder);
for (const i of f) {
if ((await fs.stat(path.join(folder, i))).isDirectory()) {
files.push(...await getFiles(path.join(folder, i), path.join(base, i)));
} else files.push(path.join(base, i));
}
return files.map((item) => item.replace(/\\/gmi, '/'));
}
function locateFile(basePath: string, filenames: string[]) { function locateFile(basePath: string, filenames: string[]) {
for (const i of filenames) { for (const i of filenames) {
const p = path.resolve(basePath, i); const p = path.resolve(basePath, i);
@@ -136,26 +125,3 @@ export async function setting(pending: string[], fail: string[], modelSetting: t
} }
} }
} }
export async function template(pending: string[], fail: string[]) {
for (const i of pending) {
const p = locateFile(i, ['template', 'templates']);
if (p && (await fs.stat(p)).isDirectory() && !fail.includes(i)) {
try {
const files = await getFiles(p);
for (const file of files) {
const l = path.resolve(p, file);
if (file.endsWith('.tsx')) global.Hydro.ui.template[file] = require(l);
global.Hydro.ui.template[file] = await fs.readFile(l, 'utf-8');
if (process.env.DEV) global.Hydro.ui.template[`${file}.source`] = l;
}
logger.info('Template init: %s', i);
} catch (e) {
fail.push(i);
app.injectUI('Notification', 'Template load fail: {0}', { args: [i], type: 'warn' }, PRIV.PRIV_VIEW_SYSTEM_NOTIFICATION);
logger.error('Template Load Fail: %s', i);
logger.error(e);
}
}
}
}
+1 -4
View File
@@ -30,10 +30,7 @@ export async function apply(ctx: Context) {
} }
const pending = global.addons; const pending = global.addons;
const fail = []; const fail = [];
await Promise.all([ await locale(pending, fail);
locale(pending, fail),
template(pending, fail),
]);
await ctx.plugin(MongoService, load() || {}); await ctx.plugin(MongoService, load() || {});
await ctx.plugin(ConfigService); await ctx.plugin(ConfigService);
const modelSystem = require('../model/system'); const modelSystem = require('../model/system');
+1 -5
View File
@@ -9,8 +9,6 @@ if (!global.Hydro) {
hydrooj: require('hydrooj/package.json').version, hydrooj: require('hydrooj/package.json').version,
}, },
// @ts-ignore // @ts-ignore
service: {},
// @ts-ignore
model: {}, model: {},
script: {}, script: {},
// @ts-ignore // @ts-ignore
@@ -22,9 +20,7 @@ if (!global.Hydro) {
}, },
}), }),
// @ts-ignore // @ts-ignore
ui: { ui: {},
template: {},
},
// @ts-ignore // @ts-ignore
error: {}, error: {},
locales: {}, locales: {},
-7
View File
@@ -596,11 +596,6 @@ export interface Model {
rp: typeof import('./script/rating').RpTypes, rp: typeof import('./script/rating').RpTypes,
} }
export interface HydroService {
/** @deprecated */
bus: Context,
}
export interface GeoIP { export interface GeoIP {
provider: string, provider: string,
lookup: (ip: string, locale?: string) => any, lookup: (ip: string, locale?: string) => any,
@@ -629,7 +624,6 @@ export interface Lib extends Record<string, any> {
export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel'; export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel';
export interface UI { export interface UI {
template: Record<string, string>,
nodes: Record<UIInjectableFields, any[]>, nodes: Record<UIInjectableFields, any[]>,
getNodes: typeof import('./lib/ui').getNodes, getNodes: typeof import('./lib/ui').getNodes,
inject: typeof import('./lib/ui').inject, inject: typeof import('./lib/ui').inject,
@@ -650,7 +644,6 @@ export interface HydroGlobal {
version: Record<string, string>; version: Record<string, string>;
model: Model; model: Model;
script: Record<string, Script>; script: Record<string, Script>;
service: HydroService;
lib: Lib; lib: Lib;
module: { [K in keyof ModuleInterfaces]: Record<string, ModuleInterfaces[K]> }; module: { [K in keyof ModuleInterfaces]: Record<string, ModuleInterfaces[K]> };
ui: UI; ui: UI;
-1
View File
@@ -115,5 +115,4 @@ export function apply(ctx: Context) {
export default app; export default app;
global.Hydro.service.bus = app as any;
global.bus = app; global.bus = app;
-6
View File
@@ -238,12 +238,6 @@ export async function apply(ctx: Context) {
h.translate = h.translate.bind(h); h.translate = h.translate.bind(h);
h.url = h.url.bind(h); h.url = h.url.bind(h);
h.ctx = h.ctx.extend({ domain: h.domain }); h.ctx = h.ctx.extend({ domain: h.domain });
h.renderHTML = ((orig) => function (name: string, args: Record<string, any>) {
const s = name.split('.');
let templateName = `${s[0]}.${args.domainId}.${s[1]}`;
if (!global.Hydro.ui.template[templateName]) templateName = name;
return orig(templateName, args);
})(h.renderHTML).bind(h);
}); });
on('handler/create/http', async (h) => { on('handler/create/http', async (h) => {
if (!argv.options.benchmark && !h.notUsage) await h.limitRate('global', 5, 100); if (!argv.options.benchmark && !h.notUsage) await h.limitRate('global', 5, 100);
+147 -78
View File
@@ -2,7 +2,7 @@ import * as status from '@hydrooj/common/status';
import { findFileSync } from '@hydrooj/utils/lib/utils'; import { findFileSync } from '@hydrooj/utils/lib/utils';
import { convert } from 'html-to-text'; import { convert } from 'html-to-text';
import { import {
avatar, Context, fs, PERM, PRIV, STATUS, yaml, avatar, Context, fs, PERM, PRIV, Service, STATUS, yaml,
} from 'hydrooj'; } from 'hydrooj';
import jsesc from 'jsesc'; import jsesc from 'jsesc';
import nunjucks from 'nunjucks'; import nunjucks from 'nunjucks';
@@ -16,48 +16,47 @@ let { template } = argv.options;
if (!template || typeof template !== 'string') template = findFileSync('@hydrooj/ui-default/templates'); if (!template || typeof template !== 'string') template = findFileSync('@hydrooj/ui-default/templates');
else template = findFileSync(template); else template = findFileSync(template);
class Loader extends nunjucks.Loader {
getSource(name) {
const src = global.Hydro.ui.template[name];
const ref = global.Hydro.ui.template[`${name}.source`];
if (!process.env.DEV) {
if (!src) throw new Error(`Cannot get template ${name}`);
return {
src,
path: name,
noCache: false,
};
}
let fullpath = null;
const p = path.resolve(template, name);
if (fs.existsSync(p)) fullpath = p;
if (!fullpath && ref && fs.existsSync(ref)) fullpath = ref;
if (!fullpath) {
if (src) {
return {
src,
path: name,
noCache: true,
};
}
throw new Error(`Cannot get template ${name}`);
}
return {
src: fs.readFileSync(fullpath, 'utf-8'),
path: fullpath,
noCache: true,
};
}
}
const replacer = (k, v) => { const replacer = (k, v) => {
if (k.startsWith('_') && k !== '_id') return undefined; if (k.startsWith('_') && k !== '_id') return undefined;
if (typeof v === 'bigint') return `BigInt::${v.toString()}`; if (typeof v === 'bigint') return `BigInt::${v.toString()}`;
return v; return v;
}; };
async function getFiles(folder: string, base = ''): Promise<string[]> {
const files: string[] = [];
const f = await fs.readdir(folder);
for (const i of f) {
if ((await fs.stat(path.join(folder, i))).isDirectory()) {
files.push(...await getFiles(path.join(folder, i), path.join(base, i)));
} else files.push(path.join(base, i));
}
return files.map((item) => item.replace(/\\/gmi, '/'));
}
function locateFile(basePath: string, filenames: string[]) {
for (const i of filenames) {
const p = path.resolve(basePath, i);
if (fs.existsSync(p)) return p;
}
return null;
}
// @ts-ignore
nunjucks.runtime.memberLookup = function memberLookup(obj, val) {
if ((obj || {})._original) obj = obj._original;
if (obj === undefined || obj === null) return undefined;
if (typeof obj[val] === 'function') {
const fn = function (...args) {
return obj[val].call(obj, ...args);
};
fn._original = obj[val];
return fn;
}
return obj[val];
};
class Nunjucks extends nunjucks.Environment { class Nunjucks extends nunjucks.Environment {
constructor() { constructor(Loader: any) {
super(new Loader(), { autoescape: true, trimBlocks: true }); super(new Loader(), { autoescape: true, trimBlocks: true });
this.addFilter('await', async (promise, callback) => { this.addFilter('await', async (promise, callback) => {
try { try {
@@ -117,62 +116,107 @@ class Nunjucks extends nunjucks.Environment {
console.log(self); console.log(self);
return self; return self;
}); });
}
}
// @ts-ignore
nunjucks.runtime.memberLookup = function memberLookup(obj, val) {
if ((obj || {})._original) obj = obj._original;
if (obj === undefined || obj === null) return undefined;
if (typeof obj[val] === 'function') {
const fn = function (...args) {
return obj[val].call(obj, ...args);
};
fn._original = obj[val];
return fn;
}
return obj[val];
};
const env = new Nunjucks();
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
env.addGlobal('eval', eval); this.addGlobal('eval', eval);
env.addGlobal('Date', Date); this.addGlobal('Date', Date);
env.addGlobal('Object', Object); this.addGlobal('Object', Object);
env.addGlobal('String', String); this.addGlobal('String', String);
env.addGlobal('Array', Array); this.addGlobal('Array', Array);
env.addGlobal('Math', Math); this.addGlobal('Math', Math);
env.addGlobal('process', process); this.addGlobal('process', process);
env.addGlobal('global', global); this.addGlobal('global', global);
env.addGlobal('typeof', (o) => typeof o); this.addGlobal('typeof', (o) => typeof o);
env.addGlobal('instanceof', (a, b) => a instanceof b); this.addGlobal('instanceof', (a, b) => a instanceof b);
env.addGlobal('paginate', misc.paginate); this.addGlobal('paginate', misc.paginate);
env.addGlobal('size', misc.size); this.addGlobal('size', misc.size);
env.addGlobal('utils', { status }); this.addGlobal('utils', { status });
env.addGlobal('avatarUrl', avatar); this.addGlobal('avatarUrl', avatar);
env.addGlobal('formatSeconds', misc.formatSeconds); this.addGlobal('formatSeconds', misc.formatSeconds);
env.addGlobal('lib', global.Hydro.lib); this.addGlobal('lib', global.Hydro.lib);
env.addGlobal('model', global.Hydro.model); this.addGlobal('model', global.Hydro.model);
env.addGlobal('ui', global.Hydro.ui); this.addGlobal('ui', global.Hydro.ui);
env.addGlobal('isIE', (str) => { this.addGlobal('isIE', (str) => {
if (!str) return false; if (!str) return false;
if (['MSIE', 'rv:11.0'].some((i) => str.includes(i))) return true; if (['MSIE', 'rv:11.0'].some((i) => str.includes(i))) return true;
if (str.includes('Chrome/') && +str.split('Chrome/')[1].split('.')[0] < 60) return true; if (str.includes('Chrome/') && +str.split('Chrome/')[1].split('.')[0] < 60) return true;
return false; return false;
}); });
env.addGlobal('set', (obj, key, val) => { this.addGlobal('set', (obj, key, val) => {
if (val !== undefined) obj[key] = val; if (val !== undefined) obj[key] = val;
else Object.assign(obj, key); else Object.assign(obj, key);
return ''; return '';
}); });
env.addGlobal('findSubModule', (prefix) => Object.keys(global.Hydro.ui.template).filter((n) => n.startsWith(prefix)));
env.addGlobal('templateExists', (name) => !!global.Hydro.ui.template[name]);
const platformIconMap = { 'mac os': 'mac' }; const platformIconMap = { 'mac os': 'mac' };
const supportedPlatformIcons = ['android', 'chromeos', 'ios', 'linux', 'mac', 'mobile', 'windows']; const supportedPlatformIcons = ['android', 'chromeos', 'ios', 'linux', 'mac', 'mobile', 'windows'];
env.addGlobal('platformIcon', (platform) => { this.addGlobal('platformIcon', (platform) => {
const key = platformIconMap[platform?.toLowerCase()] || platform?.toLowerCase(); const key = platformIconMap[platform?.toLowerCase()] || platform?.toLowerCase();
if (supportedPlatformIcons.includes(key)) return key; if (supportedPlatformIcons.includes(key)) return key;
return 'unknown'; return 'unknown';
}); });
}
}
export class TemplateService extends Service {
static inject = ['server'];
registry: Record<string, string> = {};
constructor(public ctx: Context) {
super(ctx, 'template');
const that = this;
ctx.on('handler/create', async (h) => {
h.user = h.context.HydroContext.user as any;
h.domain = h.context.HydroContext.domain as any;
h.translate = h.translate.bind(h);
h.url = h.url.bind(h);
h.ctx = h.ctx.extend({ domain: h.domain });
h.renderHTML = ((orig) => function (name: string, args: Record<string, any>) {
const s = name.split('.');
let templateName = `${s[0]}.${args.domainId}.${s[1]}`;
if (!that.registry[templateName]) templateName = name;
return orig(templateName, args);
})(h.renderHTML).bind(h);
});
class Loader extends nunjucks.Loader {
getSource(name) {
const src = that.registry[name];
const ref = that.registry[`${name}.source`];
if (!process.env.DEV) {
if (!src) throw new Error(`Cannot get template ${name}`);
return {
src,
path: name,
noCache: false,
};
}
let fullpath: string | null = null;
const p = path.resolve(template, name);
if (fs.existsSync(p)) fullpath = p;
if (!fullpath && ref && fs.existsSync(ref)) fullpath = ref;
if (!fullpath) {
if (src) {
return {
src,
path: name,
noCache: true,
};
}
throw new Error(`Cannot get template ${name}`);
}
return {
src: fs.readFileSync(fullpath, 'utf-8'),
path: fullpath,
noCache: true,
};
}
}
const env = new Nunjucks(Loader);
env.addGlobal('findSubModule', (prefix) => Object.keys(that.registry).filter((n) => n.startsWith(prefix)));
env.addGlobal('templateExists', (name) => !!that.registry[name]);
const render = (name: string, state: any) => new Promise<string>((resolve, reject) => { const render = (name: string, state: any) => new Promise<string>((resolve, reject) => {
const start = Date.now(); const start = Date.now();
@@ -193,12 +237,10 @@ const render = (name: string, state: any) => new Promise<string>((resolve, rejec
const end = Date.now(); const end = Date.now();
if (end - start > 5000) console.error(`Render of ${name} took ${end - start}ms`); if (end - start > 5000) console.error(`Render of ${name} took ${end - start}ms`);
if (err) reject(err); if (err) reject(err);
else resolve(res); else resolve(res || '');
}); });
}); });
export const inject = ['server'];
export async function apply(ctx: Context) {
ctx.server.registerRenderer('ui-default', { ctx.server.registerRenderer('ui-default', {
name: 'ui-default', name: 'ui-default',
output: 'html', output: 'html',
@@ -208,3 +250,30 @@ export async function apply(ctx: Context) {
render: (name, args, context) => render(name, { ...context, ...args }), render: (name, args, context) => render(name, { ...context, ...args }),
}); });
} }
async [Service.setup]() {
const pending = global.addons;
const logger = this.ctx.logger('template');
for (const i of pending) {
const p = locateFile(i, ['template', 'templates']);
if (p && (await fs.stat(p)).isDirectory()) {
try {
const files = await getFiles(p);
for (const file of files) {
const l = path.resolve(p, file);
this.registry[file] = await fs.readFile(l, 'utf-8');
if (process.env.DEV) this.registry[`${file}.source`] = l;
}
logger.info('Template init: %s', i);
} catch (e) {
this.ctx.injectUI('Notification', 'Template load fail: {0}', { args: [i], type: 'warn' }, PRIV.PRIV_VIEW_SYSTEM_NOTIFICATION);
logger.error('Template Load Fail: %s', i);
logger.error(e);
}
}
}
}
}
export async function apply(ctx: Context) {
}
+2 -1
View File
@@ -5,6 +5,7 @@ import {
} from 'hydrooj'; } from 'hydrooj';
import convert from 'schemastery-jsonschema'; import convert from 'schemastery-jsonschema';
import markdown from './backendlib/markdown'; import markdown from './backendlib/markdown';
import { TemplateService } from './backendlib/template';
class WikiHelpHandler extends Handler { class WikiHelpHandler extends Handler {
noCheckPermView = true; noCheckPermView = true;
@@ -164,6 +165,6 @@ export function apply(ctx: Context) {
domains: SystemModel.get('ui-default.domains') || [], domains: SystemModel.get('ui-default.domains') || [],
}; };
}); });
ctx.plugin(require('./backendlib/template')); ctx.plugin(TemplateService);
ctx.plugin(require('./backendlib/builder')); ctx.plugin(require('./backendlib/builder'));
} }