Files
note2any/src/utils.ts
2025-10-16 14:03:45 +08:00

248 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 文件utils.ts
* 作用:通用工具函数集合(事件、版本、字符串处理等)。
*/
import { App, sanitizeHTMLToDom, Platform } from "obsidian";
import * as postcss from "./postcss/postcss"; // 内置 PostCSS runtime解析主题 CSS 用于内联样式
let PluginVersion = "0.0.0";
let PlugPlatform = "obsidian";
export function setVersion(version: string) {
PluginVersion = version;
if (Platform.isWin) {
PlugPlatform = "win";
}
else if (Platform.isMacOS) {
PlugPlatform = "mac";
}
else if (Platform.isLinux) {
PlugPlatform = "linux";
}
else if (Platform.isIosApp) {
PlugPlatform = "ios";
}
else if (Platform.isAndroidApp) {
PlugPlatform = "android";
}
}
function getStyleSheet() {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.title == 'note2any-style') {
return sheet;
}
}
}
function applyStyles(element: HTMLElement, styles: CSSStyleDeclaration, computedStyle: CSSStyleDeclaration) {
for (let i = 0; i < styles.length; i++) {
const propertyName = styles[i];
let propertyValue = computedStyle.getPropertyValue(propertyName);
if (propertyName == 'width' && styles.getPropertyValue(propertyName) == 'fit-content') {
propertyValue = 'fit-content';
}
if (propertyName.indexOf('margin') >= 0 && styles.getPropertyValue(propertyName).indexOf('auto') >= 0) {
propertyValue = styles.getPropertyValue(propertyName);
}
element.style.setProperty(propertyName, propertyValue);
}
}
function parseAndApplyStyles(element: HTMLElement, sheet:CSSStyleSheet) {
try {
const computedStyle = getComputedStyle(element);
for (let i = 0; i < sheet.cssRules.length; i++) {
const rule = sheet.cssRules[i];
if (rule instanceof CSSStyleRule && element.matches(rule.selectorText)) {
applyStyles(element, rule.style, computedStyle);
}
}
} catch (e) {
console.warn("Unable to access stylesheet: " + sheet.href, e);
}
}
function traverse(root: HTMLElement, sheet:CSSStyleSheet) {
let element = root.firstElementChild;
while (element) {
if (element.tagName === 'svg') {
// pass
}
else {
traverse(element as HTMLElement, sheet);
}
element = element.nextElementSibling;
}
parseAndApplyStyles(root, sheet);
}
export async function CSSProcess(content: HTMLElement) {
// 获取样式表
const style = getStyleSheet();
if (style) {
traverse(content, style);
}
}
export function parseCSS(css: string) {
return postcss.parse(css);
}
export function ruleToStyle(rule: postcss.Rule) {
let style = '';
rule.walkDecls(decl => {
style += decl.prop + ':' + decl.value + ';';
})
return style;
}
function processPseudoSelector(selector: string) {
if (selector.includes('::before') || selector.includes('::after')) {
selector = selector.replace(/::before/g, '').replace(/::after/g, '');
}
return selector;
}
function getPseudoType(selector: string) {
if (selector.includes('::before')) {
return 'before';
}
else if (selector.includes('::after')) {
return 'after';
}
return undefined;
}
function applyStyle(root: HTMLElement, cssRoot: postcss.Root) {
if (root.tagName.toLowerCase() === 'a' && root.classList.contains('wx_topic_link')) {
return;
}
const cssText = root.style.cssText;
cssRoot.walkRules(rule => {
const selector = processPseudoSelector(rule.selector);
try {
if (root.matches(selector)) {
let item = root;
const pseudoType = getPseudoType(rule.selector);
if (pseudoType) {
let content = '';
rule.walkDecls('content', decl => {
content = decl.value || '';
})
item = createSpan();
item.textContent = content.replace(/(^")|("$)/g, '');
if (pseudoType === 'before') {
root.prepend(item);
}
else if (pseudoType === 'after') {
root.appendChild(item);
}
}
rule.walkDecls(decl => {
// 如果已经设置了,则不覆盖
const setted = cssText.includes(decl.prop);
if (!setted || decl.important) {
item.style.setProperty(decl.prop, decl.value);
}
})
}
}
catch (err) {
if (err.message && err.message.includes('is not a valid selector')) {
return;
}
else {
throw err;
}
}
});
if (root.tagName === 'svg') {
return;
}
let element = root.firstElementChild;
while (element) {
applyStyle(element as HTMLElement, cssRoot);
element = element.nextElementSibling;
}
}
export function applyCSS(html: string, css: string) {
const doc = sanitizeHTMLToDom(html);
const root = doc.firstChild as HTMLElement;
const cssRoot = postcss.parse(css);
applyStyle(root, cssRoot);
return root.outerHTML;
}
export function uevent(name: string) {
console.debug(`[uevent] ${name} @${PlugPlatform} v${PluginVersion}`);
}
/**
* 创建一个防抖函数
* @param func 要执行的函数
* @param wait 等待时间(毫秒)
* @returns 防抖处理后的函数
*/
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function(this: any, ...args: Parameters<T>) {
const context = this;
const later = () => {
timeout = null;
func.apply(context, args);
};
if (timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(later, wait);
};
}
export function cleanUrl(href: string) {
try {
href = encodeURI(href).replace(/%25/g, '%');
} catch (e) {
return null;
}
return href;
}
export async function waitForLayoutReady(app: App): Promise<void> {
if (app.workspace.layoutReady) {
console.log('[waitForLayoutReady] already ready');
return;
}
console.log('[waitForLayoutReady] waiting...');
return new Promise((resolve) => {
let resolved = false;
const timer = setTimeout(() => {
if (!resolved) {
console.warn('[waitForLayoutReady] timeout fallback (5s)');
resolved = true; resolve();
}
}, 5000);
app.workspace.onLayoutReady(() => {
if (!resolved) {
resolved = true;
clearTimeout(timer);
console.log('[waitForLayoutReady] event fired');
resolve();
}
});
});
}