refactor Setting
This commit is contained in:
@@ -62,9 +62,9 @@ export default class IOSize {
|
||||
* @param stopUnit 停止单位
|
||||
* @return
|
||||
*/
|
||||
public static format(size?: number, fixed = 2, stopUnit?: Unit): string {
|
||||
if (!size) {
|
||||
return "";
|
||||
public static format(size?: number | null, fixed = 2, stopUnit?: Unit): string {
|
||||
if (size === undefined || size === null) {
|
||||
return "0 B";
|
||||
}
|
||||
const units = Object.keys(Unit);
|
||||
if (0 < size) {
|
||||
@@ -97,9 +97,9 @@ export default class IOSize {
|
||||
* @return 字节量
|
||||
* @throws Error 格式无效
|
||||
*/
|
||||
public static parse(sizeStr: string): number {
|
||||
if (sizeStr.trim() === "") {
|
||||
throw new Error("not found sizeStr");
|
||||
public static parse(sizeStr?: string | null): number {
|
||||
if (sizeStr === undefined || sizeStr === null || sizeStr.trim() === "") {
|
||||
return 0;
|
||||
}
|
||||
// 正则匹配:可选符号 + 数字(含小数点)+ 可选空格 + 单位
|
||||
const pattern = /([-+]?\d+(?:\.\d+)?)\s*([a-zA-Z]+)/;
|
||||
@@ -169,8 +169,8 @@ export default class IOSize {
|
||||
* @param unit 单位
|
||||
* @return 字节量
|
||||
*/
|
||||
private static toBytes(val: number | undefined, unit: Unit): number {
|
||||
if (!val) {
|
||||
private static toBytes(val: number | undefined | null, unit: Unit): number {
|
||||
if (val === undefined || val === null) {
|
||||
return 0;
|
||||
}
|
||||
const units = Object.values(Unit);
|
||||
@@ -178,8 +178,8 @@ export default class IOSize {
|
||||
return Math.round(val * Math.pow(1024, ordinal));
|
||||
}
|
||||
|
||||
public static speed(val?: number) {
|
||||
if (!val) {
|
||||
public static speed(val?: number | null) {
|
||||
if (val === undefined || val === null) {
|
||||
return "";
|
||||
}
|
||||
return Text.unit(IOSize.format(val, 2), " / s");
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { marked } from "marked";
|
||||
import { gfmHeadingId } from "marked-gfm-heading-id";
|
||||
import { markedHighlight } from "marked-highlight";
|
||||
import { mangle } from "marked-mangle";
|
||||
import {marked} from "marked";
|
||||
import {gfmHeadingId} from "marked-gfm-heading-id";
|
||||
import {markedHighlight} from "marked-highlight";
|
||||
import {mangle} from "marked-mangle";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/themes/prism.css";
|
||||
import SettingMapper from "./SettingMapper";
|
||||
|
||||
export default class Markdown {
|
||||
|
||||
@@ -97,10 +96,6 @@ export default class Markdown {
|
||||
target = "_blank";
|
||||
href = href.substring(1);
|
||||
}
|
||||
// 内部资源链接
|
||||
if (href.indexOf("@") !== -1) {
|
||||
href = SettingMapper.toResURL(href);
|
||||
}
|
||||
{
|
||||
// 处理嵌套 markdown,这可能不是最优解
|
||||
const tokens = (marked.lexer(text, { inline: true } as any) as any)[0].tokens;
|
||||
@@ -125,61 +120,6 @@ export default class Markdown {
|
||||
return `<span class="red">${text}</span>`;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ### 组件渲染方式(原为图像渲染方式)
|
||||
*
|
||||
* ```md
|
||||
* [] 内文本以 # 开始时,该组件带边框
|
||||
* ```
|
||||
*
|
||||
* 1. 渲染为网页:``
|
||||
* 2. 渲染为视频:``
|
||||
* 3. 渲染为音频:``
|
||||
* 4. 渲染为图片:``
|
||||
* 6. 带边框图片:``
|
||||
*/
|
||||
this.renderer.image = ({ href, title, text }) => {
|
||||
const clazz = ["media"];
|
||||
|
||||
const hasBorder = text[0] === "#";
|
||||
if (hasBorder) {
|
||||
clazz.push("border");
|
||||
}
|
||||
title = title ?? text;
|
||||
|
||||
const extendTags = ["~", "#", "$"];
|
||||
let extendTag;
|
||||
if (extendTags.indexOf(href.charAt(0)) !== -1) {
|
||||
extendTag = href.charAt(0);
|
||||
}
|
||||
|
||||
// 内部资源链接
|
||||
if (href.indexOf("@") !== -1) {
|
||||
if (extendTag) {
|
||||
href = href.substring(1);
|
||||
}
|
||||
href = SettingMapper.toResURL(href);
|
||||
}
|
||||
|
||||
const clazzStr = clazz.join(" ");
|
||||
let elStr;
|
||||
switch (extendTag) {
|
||||
case "~":
|
||||
elStr = `<audio class="${clazzStr}" controls><source type="audio/mp3" src="${href}" title="${title}" /></audio>`;
|
||||
break;
|
||||
case "#":
|
||||
elStr = `<video class="${clazzStr}" controls><source type="video/mp4" src="${href}" title="${title}" /></video>`;
|
||||
break;
|
||||
case "$":
|
||||
elStr = `<iframe class="${clazzStr}" src="${href}" allowfullscreen title="${title}"></iframe>`;
|
||||
break;
|
||||
default:
|
||||
elStr = `<img class="${clazzStr}" src="${href}" alt="${title}" />`;
|
||||
break;
|
||||
}
|
||||
return elStr + `<span class="media-tips">${title}</span>`;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,97 +1,99 @@
|
||||
import { readonly, Ref, ref } from "vue";
|
||||
import { RunEnv, SettingKey } from "../types";
|
||||
import Toolkit from "./Toolkit";
|
||||
import CommonAPI from "../api/CommonAPI";
|
||||
import {ref, Ref} from "vue";
|
||||
import {Setting} from "../types";
|
||||
import {CommonAPI} from "../api";
|
||||
|
||||
export default class SettingMapper {
|
||||
|
||||
private static instance: SettingMapper;
|
||||
|
||||
private map = new Map<string, Ref<string>>();
|
||||
private moduleMap = new Map<string, Map<string, Ref<Setting>>>();
|
||||
|
||||
public static async loadSetting(...settings: (string | { key: string, args?: { [key: string]: any }})[]): Promise<void> {
|
||||
const map = new Map<string, object | undefined>();
|
||||
{
|
||||
// 默认配置
|
||||
map.set(SettingKey.RUN_ENV, undefined);
|
||||
map.set(SettingKey.PUBLIC_RESOURCES, {
|
||||
as: "json"
|
||||
});
|
||||
map.set(SettingKey.DOMAIN_ROOT, undefined);
|
||||
map.set(SettingKey.DOMAIN_API, undefined);
|
||||
map.set(SettingKey.DOMAIN_GIT, undefined);
|
||||
map.set(SettingKey.DOMAIN_BLOG, undefined);
|
||||
map.set(SettingKey.DOMAIN_SPACE, undefined);
|
||||
map.set(SettingKey.DOMAIN_RESOURCE, undefined);
|
||||
map.set(SettingKey.DOMAIN_DOWNLOAD, undefined);
|
||||
public static async loadSetting(map: Record<string, string[]>): Promise<void> {
|
||||
const result = await CommonAPI.settingMap(map);
|
||||
this.appendSettingMap(result);
|
||||
}
|
||||
|
||||
map.set(SettingKey.ENABLE_COMMENT, undefined);
|
||||
map.set(SettingKey.ENABLE_DEBUG, undefined);
|
||||
map.set(SettingKey.ENABLE_LOGIN, undefined);
|
||||
map.set(SettingKey.ENABLE_REGISTER, undefined);
|
||||
map.set(SettingKey.ENABLE_USER_UPDATE, undefined);
|
||||
}
|
||||
{
|
||||
// 附加配置
|
||||
for (let i = 0; i < settings.length; i++) {
|
||||
const setting = settings[i];
|
||||
if (typeof setting === 'string') {
|
||||
map.set(setting, undefined);
|
||||
} else {
|
||||
map.set(setting.key, setting.args);
|
||||
}
|
||||
public static appendSettingMap(result: Map<string, Map<string, Setting>>): void {
|
||||
for (const [module, settingMap] of result) {
|
||||
for (const [key, setting] of settingMap) {
|
||||
this.setValue(module, key, setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static createModule(module: string): Map<string, Ref<Setting>> {
|
||||
const instance = this.getInstance();
|
||||
const result = await CommonAPI.listSetting(map);
|
||||
for (const [key, value] of result) {
|
||||
instance.map.set(key, ref(value));
|
||||
const moduleVal = instance.moduleMap.get(module);
|
||||
if (moduleVal) {
|
||||
return moduleVal;
|
||||
}
|
||||
const created = new Map<string, Ref<Setting>>();
|
||||
instance.moduleMap.set(module, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
public static is(key: SettingKey | string, args?: { [key: string]: any }): boolean {
|
||||
const value = this.getValueRef(key, args).value;
|
||||
return !!value && value === 'true';
|
||||
public static getModule(module: string): Map<string, Ref<Setting>> | undefined {
|
||||
return this.getInstance().moduleMap.get(module);
|
||||
}
|
||||
|
||||
public static not(key: SettingKey | string, args?: { [key: string]: any }): boolean {
|
||||
return !this.is(key, args);
|
||||
public static hasModule(module: string): boolean {
|
||||
return this.getInstance().moduleMap.has(module);
|
||||
}
|
||||
|
||||
public static getValue(key: SettingKey | string, args?: { [key: string]: any }): string | undefined {
|
||||
return this.getValueRef(key, args).value;
|
||||
public static deleteModule(module: string): boolean {
|
||||
return this.getInstance().moduleMap.delete(module);
|
||||
}
|
||||
|
||||
public static getValueRef(key: SettingKey| string, args?: { [key: string]: any }): Ref<string | undefined> {
|
||||
const instance = this.getInstance();
|
||||
let result = instance.map.get(key);
|
||||
if (result) {
|
||||
return result;
|
||||
public static setModule(module: string, moduleVal: Map<string, Ref<Setting>>): void {
|
||||
this.getInstance().moduleMap.set(module, moduleVal);
|
||||
}
|
||||
|
||||
public static is(module: string, key: string): boolean {
|
||||
const setting = this.getValueRef(module, key).value;
|
||||
return !!setting.value && setting.value === "true";
|
||||
}
|
||||
|
||||
public static not(module: string, key: string): boolean {
|
||||
return !this.is(module, key);
|
||||
}
|
||||
|
||||
public static getValue(module: string, key: string): Setting {
|
||||
return this.getValueRef(module, key).value;
|
||||
}
|
||||
|
||||
public static setValue(module: string, key: string, setting: Setting): void {
|
||||
const moduleVal = this.createModule(module);
|
||||
const settingRef = moduleVal.get(key);
|
||||
if (settingRef) {
|
||||
settingRef.value = setting;
|
||||
return;
|
||||
}
|
||||
instance.map.set(key, result = ref<any>());
|
||||
Toolkit.async(async () => {
|
||||
const value = instance.map.get(key);
|
||||
if (value) {
|
||||
return value.value;
|
||||
}
|
||||
result.value = await CommonAPI.getSetting(key, args);
|
||||
});
|
||||
return readonly(result);
|
||||
moduleVal.set(key, ref(setting));
|
||||
}
|
||||
|
||||
public static getDomainLink(domainKey: SettingKey): string | undefined {
|
||||
const runEnv = <RunEnv>(this.getValue(SettingKey.RUN_ENV));
|
||||
let protocol = "https";
|
||||
switch (runEnv) {
|
||||
case RunEnv.DEV:
|
||||
protocol = "http";
|
||||
break;
|
||||
case RunEnv.DEV_SSL:
|
||||
case RunEnv.PROD:
|
||||
protocol = "https";
|
||||
break;
|
||||
public static setValueRef(module: string, key: string, settingRef: Ref<Setting>): void {
|
||||
const moduleVal = this.createModule(module);
|
||||
moduleVal.set(key, settingRef);
|
||||
}
|
||||
|
||||
public static hasValue(module: string, key: string): boolean {
|
||||
return !!this.getModule(module)?.has(key);
|
||||
}
|
||||
|
||||
public static deleteValue(module: string, key: string): boolean {
|
||||
return !!this.getModule(module)?.delete(key);
|
||||
}
|
||||
|
||||
public static getValueRef(module: string, key: string): Ref<Setting> {
|
||||
const moduleVal = this.getModule(module);
|
||||
if (!moduleVal) {
|
||||
throw new Error(`Setting module not found: ${module}`);
|
||||
}
|
||||
return `${protocol}://${this.getValue(domainKey)}`;
|
||||
const valueRef = moduleVal.get(key);
|
||||
if (!valueRef) {
|
||||
throw new Error(`Setting key not found: ${module}.${key}`);
|
||||
}
|
||||
return valueRef;
|
||||
}
|
||||
|
||||
public static getInstance(): SettingMapper {
|
||||
@@ -100,22 +102,4 @@ export default class SettingMapper {
|
||||
}
|
||||
return SettingMapper.instance = new SettingMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析资源 URL 协议前缀
|
||||
*
|
||||
* @param val 原始值,如 `res@/path/to/file`
|
||||
* @returns 完整 URL
|
||||
*/
|
||||
public static toResURL(val: string): string {
|
||||
const at = val.indexOf("@");
|
||||
const start = val.substring(0, at);
|
||||
const path = val.substring(at + 1);
|
||||
switch (start) {
|
||||
case "res": return SettingMapper.getDomainLink(SettingKey.DOMAIN_RESOURCE) + path;
|
||||
case "dl": return SettingMapper.getDomainLink(SettingKey.DOMAIN_DOWNLOAD) + path;
|
||||
case "attach": return CommonAPI.getAttachmentReadAPI(path);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,20 +46,24 @@ describe("Text", () => {
|
||||
|
||||
describe("unit", () => {
|
||||
it("should format number with fixed and unit", () => {
|
||||
expect(Text.unitSpace(1.236, 2, "rem")).toBe("1.24 rem");
|
||||
expect(Text.unitCompact(1.236, "rem", 2)).toBe("1.24rem");
|
||||
});
|
||||
it("should format number with fixed and unit not space", () => {
|
||||
expect(Text.unit(1.236, "rem", 2)).toBe("1.24rem");
|
||||
expect(Text.unit(1.236, "rem", 2)).toBe("1.24 rem");
|
||||
});
|
||||
|
||||
it("should trim string and append unit", () => {
|
||||
expect(Text.unit(" 12 ", "px")).toBe("12px");
|
||||
expect(Text.unit(" 12 ", "px")).toBe("12 px");
|
||||
});
|
||||
|
||||
it("test zero", () => {
|
||||
expect(Text.unit(0, "毫秒")).toBe("0 毫秒");
|
||||
});
|
||||
|
||||
it("should return empty string for nullish zero and NaN", () => {
|
||||
expect(Text.unit(null, "px")).toBe("");
|
||||
expect(Text.unit(undefined, "px")).toBe("");
|
||||
expect(Text.unit(0, "px")).toBe("");
|
||||
expect(Text.unit(0, "px")).toBe("0 px");
|
||||
expect(Text.unit(Number.NaN, "px")).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -83,6 +87,15 @@ describe("Text", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("displayBool", () => {
|
||||
it("should show boolean text", () => {
|
||||
expect(Text.displayBool(true)).toBe("true");
|
||||
expect(Text.displayBool(false)).toBe("false");
|
||||
expect(Text.displayBool(false, "开启", "关闭")).toBe("关闭");
|
||||
expect(Text.displayBool(undefined)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("readableColor", () => {
|
||||
it("should return #FFF for dark backgrounds", () => {
|
||||
expect(Text.readableColor("#000")).toBe("#FFF");
|
||||
|
||||
@@ -30,12 +30,15 @@ export default class Text {
|
||||
return template.replace(/\$\{(\w+)}/g, (_, key) => variables[key]);
|
||||
}
|
||||
|
||||
public static unitSpace(val: number | string | null | undefined, fixed = 0, unit: string): string {
|
||||
public static unitCompact(val: number | string | null | undefined, unit: string, fixed = 0): string {
|
||||
return this.unit(val, unit, fixed, true);
|
||||
};
|
||||
|
||||
public static unit(val: number | string | null | undefined, unit: string, fixed = 0, space = false): string {
|
||||
if (!val) {
|
||||
public static unit(val: number | string | null | undefined, unit: string, fixed = 0, isCompact = false): string {
|
||||
if (val === null || val === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof val === "string" && !val.trim()) {
|
||||
return "";
|
||||
}
|
||||
const result = [];
|
||||
@@ -47,7 +50,7 @@ export default class Text {
|
||||
} else {
|
||||
result.push(val.trim());
|
||||
}
|
||||
if (space) {
|
||||
if (!isCompact) {
|
||||
result.push(" ");
|
||||
}
|
||||
result.push(unit);
|
||||
@@ -64,6 +67,13 @@ export default class Text {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static displayBool(val: boolean | null | undefined, onTrue = "true", onFalse = "false") {
|
||||
if (val === null || val === undefined) {
|
||||
return "";
|
||||
}
|
||||
return val ? onTrue : onFalse;
|
||||
}
|
||||
|
||||
/** 根据背景色返回更友好的文本色(仅返回 #111 或 #FFF) */
|
||||
public static readableColor(color: string): "#111" | "#FFF" {
|
||||
const match = color.trim().match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
||||
|
||||
@@ -92,8 +92,8 @@ export default class Time {
|
||||
return { l, y, d, h, m, s, ms };
|
||||
}
|
||||
|
||||
public static duration(totalMs?: number, ms?: boolean): string {
|
||||
if (!totalMs) {
|
||||
public static duration(totalMs?: number | null | undefined, ms?: boolean): string {
|
||||
if (totalMs === null || totalMs === undefined) {
|
||||
return "";
|
||||
}
|
||||
let remain = Math.floor(totalMs);
|
||||
|
||||
Reference in New Issue
Block a user