first commit
This commit is contained in:
commit
6bc4ef1c41
84 files changed
+13968
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 工具函数库 - 搜哩浏览器
|
||||
* 提供HTML解析、URL处理、数据存储等核心功能
|
||||
*/
|
||||
|
||||
// ==================== 常量定义 ====================
|
||||
|
||||
const HTML_ENTITY_MAP = {
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', ''': "'",
|
||||
' ': ' ', '/': '/', '`': '`', '=': '=',
|
||||
'©': '©', '®': '®', '™': '™', '"': '"'
|
||||
};
|
||||
|
||||
const TAGS_TO_REMOVE = [
|
||||
'script', 'style', 'noscript', 'iframe', 'canvas', 'svg',
|
||||
'video', 'audio', 'embed', 'object', 'applet', 'meta', 'link'
|
||||
];
|
||||
|
||||
// ==================== HTML处理 ====================
|
||||
|
||||
export function decodeHtmlEntities(text) {
|
||||
if (!text || typeof text !== 'string') return '';
|
||||
return text.replace(/&[a-zA-Z0-9#]+;/g, entity => HTML_ENTITY_MAP[entity] || entity);
|
||||
}
|
||||
|
||||
export function cleanHtmlText(text) {
|
||||
if (!text || typeof text !== 'string') return '';
|
||||
return text
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/&[a-zA-Z0-9#]+;/g, entity => HTML_ENTITY_MAP[entity] || entity)
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function preprocessHtml(html) {
|
||||
if (!html) return '';
|
||||
let content = html;
|
||||
|
||||
// 移除危险标签
|
||||
TAGS_TO_REMOVE.forEach(tag => {
|
||||
const regex = new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>|<${tag}[^>]*\\/?>`, 'gi');
|
||||
content = content.replace(regex, ' ');
|
||||
});
|
||||
|
||||
// 移除注释
|
||||
content = content.replace(/<!--[\s\S]*?-->/g, ' ');
|
||||
|
||||
// 提取body内容
|
||||
const bodyMatch = content.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
||||
return bodyMatch?.[1] || content;
|
||||
}
|
||||
|
||||
export function extractTitle(html) {
|
||||
if (!html) return '';
|
||||
const match = html.match(/<title[^>]*>([^<]*)<\/title>/i);
|
||||
return match?.[1] ? decodeHtmlEntities(match[1].trim()) : '';
|
||||
}
|
||||
|
||||
// ==================== 内联元素解析 ====================
|
||||
|
||||
export function parseInlineElements(text, baseUrl) {
|
||||
if (!text) return [];
|
||||
|
||||
const parts = [];
|
||||
let remaining = text;
|
||||
|
||||
// 解析链接
|
||||
remaining = remaining.replace(/<a[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi,
|
||||
(match, url, linkText) => {
|
||||
const t = cleanHtmlText(linkText);
|
||||
if (t) parts.push({ type: 'a', text: t, url: resolveUrl(url, baseUrl) });
|
||||
return '';
|
||||
});
|
||||
|
||||
// 解析加粗
|
||||
remaining = remaining.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi,
|
||||
(match, tag, txt) => {
|
||||
const t = cleanHtmlText(txt);
|
||||
if (t) parts.push({ type: 'strong', text: t });
|
||||
return '';
|
||||
});
|
||||
|
||||
// 解析斜体
|
||||
remaining = remaining.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi,
|
||||
(match, tag, txt) => {
|
||||
const t = cleanHtmlText(txt);
|
||||
if (t) parts.push({ type: 'em', text: t });
|
||||
return '';
|
||||
});
|
||||
|
||||
// 剩余文本放前面
|
||||
const plainText = cleanHtmlText(remaining);
|
||||
if (plainText) parts.unshift({ type: 'text', text: plainText });
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
// ==================== URL处理 ====================
|
||||
|
||||
export function getBaseUrl(url) {
|
||||
if (!url) return '';
|
||||
try {
|
||||
const match = url.match(/^(https?:\/\/[^\/]+)/i);
|
||||
return match ? match[1] : url;
|
||||
} catch (e) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveUrl(url, baseUrl) {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
||||
if (url.startsWith('//')) return 'https:' + url;
|
||||
if (url.startsWith('/')) return baseUrl + url;
|
||||
return baseUrl + '/' + url;
|
||||
}
|
||||
|
||||
export function normalizeUrl(url) {
|
||||
if (!url) return '';
|
||||
url = url.trim().toLowerCase();
|
||||
return (url.startsWith('http://') || url.startsWith('https://'))
|
||||
? url
|
||||
: 'https://' + url;
|
||||
}
|
||||
|
||||
export function formatDisplayUrl(url) {
|
||||
if (!url) return '';
|
||||
return url.replace(/^https?:\/\//, '').replace(/\/$/, '').split('/')[0];
|
||||
}
|
||||
|
||||
export function isValidUrl(url) {
|
||||
return url && /^(https?:\/\/)?([\w-]+\.)+[\w-]+/.test(url);
|
||||
}
|
||||
|
||||
// ==================== 数据存储 ====================
|
||||
|
||||
export function getStorageData(storage, key) {
|
||||
return new Promise((resolve) => {
|
||||
storage.get({
|
||||
key,
|
||||
success: (data) => {
|
||||
try { resolve(JSON.parse(data || '[]')); }
|
||||
catch (e) { resolve([]); }
|
||||
},
|
||||
fail: () => resolve([])
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function setStorageData(storage, key, value) {
|
||||
return new Promise((resolve) => {
|
||||
storage.set({
|
||||
key,
|
||||
value: JSON.stringify(value),
|
||||
success: () => resolve(true),
|
||||
fail: () => resolve(false)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 工具函数 ====================
|
||||
|
||||
export function debounce(fn, delay = 300) {
|
||||
let timer = null;
|
||||
return function(...args) {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => fn.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
export function throttle(fn, interval = 300) {
|
||||
let lastTime = 0;
|
||||
return function(...args) {
|
||||
const now = Date.now();
|
||||
if (now - lastTime >= interval) {
|
||||
lastTime = now;
|
||||
fn.apply(this, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function truncateText(text, maxLength = 50) {
|
||||
if (!text || text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
export function generateId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
||||
}
|
||||
|
||||
export function showToast(page, msg, duration = 2000) {
|
||||
page.toastMsg = msg;
|
||||
page.showToast = true;
|
||||
setTimeout(() => page.showToast = false, duration);
|
||||
}
|
||||
|
||||
// ==================== 导出 ====================
|
||||
|
||||
export default {
|
||||
decodeHtmlEntities,
|
||||
cleanHtmlText,
|
||||
preprocessHtml,
|
||||
extractTitle,
|
||||
parseInlineElements,
|
||||
getBaseUrl,
|
||||
resolveUrl,
|
||||
normalizeUrl,
|
||||
formatDisplayUrl,
|
||||
isValidUrl,
|
||||
getStorageData,
|
||||
setStorageData,
|
||||
debounce,
|
||||
throttle,
|
||||
truncateText,
|
||||
generateId,
|
||||
showToast
|
||||
};
|
||||
Reference in new issue
Block a user