|
|
|
|
/*
|
|
|
|
|
* v.js
|
|
|
|
|
* Copyright (C) 2024 veypi <i@veypi.com>
|
|
|
|
|
*
|
|
|
|
|
* Distributed under terms of the GPL license.
|
|
|
|
|
*/
|
|
|
|
|
import { proxy } from './vyes.js'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
'use strict';
|
|
|
|
|
const config = { childList: true, subtree: true };
|
|
|
|
|
/**
|
|
|
|
|
* Represents a book.
|
|
|
|
|
* @param {Array} mutationsList
|
|
|
|
|
* @param {MutationObserver} observer
|
|
|
|
|
*/
|
|
|
|
|
const callback = function(mutationsList, observer) {
|
|
|
|
|
for (let mutation of mutationsList) {
|
|
|
|
|
if (mutation.type === 'childList') {
|
|
|
|
|
// console.log(mutation)
|
|
|
|
|
// console.log('A child node has been added or removed.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const observer = new MutationObserver(callback);
|
|
|
|
|
|
|
|
|
|
var cacheUrl = {}
|
|
|
|
|
var pendingRequests = {};
|
|
|
|
|
|
|
|
|
|
async function fetchFileWithCache(url) {
|
|
|
|
|
if (cacheUrl[url]) {
|
|
|
|
|
return Promise.resolve(cacheUrl[url]);
|
|
|
|
|
}
|
|
|
|
|
if (pendingRequests[url]) {
|
|
|
|
|
return pendingRequests[url];
|
|
|
|
|
}
|
|
|
|
|
const promise = fetch(url)
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
return response.text();
|
|
|
|
|
})
|
|
|
|
|
.then(txt => {
|
|
|
|
|
cacheUrl[url] = txt;
|
|
|
|
|
return txt;
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
// console.error('Error fetching the file:', error);
|
|
|
|
|
delete pendingRequests[url];
|
|
|
|
|
throw error;
|
|
|
|
|
})
|
|
|
|
|
.finally(() => {
|
|
|
|
|
delete pendingRequests[url];
|
|
|
|
|
});
|
|
|
|
|
pendingRequests[url] = promise;
|
|
|
|
|
return promise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractDoubleBraceContents(str) {
|
|
|
|
|
const regex = /{{|}}|{for{|{end{/g;
|
|
|
|
|
let depth = 0;
|
|
|
|
|
let currentIndex = 0
|
|
|
|
|
let results = [];
|
|
|
|
|
let match;
|
|
|
|
|
let flag = ''
|
|
|
|
|
|
|
|
|
|
while ((match = regex.exec(str)) !== null) {
|
|
|
|
|
if (match[0] === '{{') {
|
|
|
|
|
if (depth === 0) {
|
|
|
|
|
// 开始一个新的匹配
|
|
|
|
|
results.push([flag, str.slice(currentIndex, match.index)]);
|
|
|
|
|
currentIndex = match.index + 2
|
|
|
|
|
flag = 'val'
|
|
|
|
|
}
|
|
|
|
|
depth++;
|
|
|
|
|
} else if (match[0] === '}}') {
|
|
|
|
|
depth--;
|
|
|
|
|
if (depth === 0) {
|
|
|
|
|
results.push([flag, str.slice(currentIndex, match.index)]);
|
|
|
|
|
currentIndex = match.index + 2
|
|
|
|
|
flag = ''
|
|
|
|
|
}
|
|
|
|
|
} else if (match[0] === '{for{') {
|
|
|
|
|
if (depth === 0) {
|
|
|
|
|
// 开始一个新的匹配
|
|
|
|
|
results.push([flag, str.slice(currentIndex, match.index)]);
|
|
|
|
|
currentIndex = match.index + 5
|
|
|
|
|
flag = 'for'
|
|
|
|
|
}
|
|
|
|
|
depth++;
|
|
|
|
|
} else if (match[0] === '{end{') {
|
|
|
|
|
if (depth === 0) {
|
|
|
|
|
// 开始一个新的匹配
|
|
|
|
|
results.push([flag, str.slice(currentIndex, match.index)]);
|
|
|
|
|
currentIndex = match.index + 5
|
|
|
|
|
flag = 'end'
|
|
|
|
|
}
|
|
|
|
|
depth++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
results.push([flag, str.slice(currentIndex)]);
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractBodyAndScript(htmlString) {
|
|
|
|
|
const bodyMatch = htmlString.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
|
|
|
const bodyContent = bodyMatch ? bodyMatch[1] : '';
|
|
|
|
|
const bodyItems = extractDoubleBraceContents(bodyContent);
|
|
|
|
|
|
|
|
|
|
let scriptMatches = htmlString.match(/<script[^>]*>([\s\S]*?)<\/script>/ig) || []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let scriptContents = []
|
|
|
|
|
let srcReg = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/
|
|
|
|
|
for (let s of scriptMatches) {
|
|
|
|
|
let match = srcReg.exec(s)
|
|
|
|
|
if (match && match.length) {
|
|
|
|
|
// handle src script
|
|
|
|
|
// console.log(match[1])
|
|
|
|
|
} else {
|
|
|
|
|
scriptContents.push(s.replace(/<script[^>]*>|<\/script>/ig, ''))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提取 <style> 内容
|
|
|
|
|
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
|
|
|
const styleMatches = [...htmlString.matchAll(styleRegex)];
|
|
|
|
|
const styleContents = styleMatches.map(match => match[1].trim());
|
|
|
|
|
|
|
|
|
|
// 解析 <style> 内容,按类名存储
|
|
|
|
|
//
|
|
|
|
|
const stylesBySelector = {};
|
|
|
|
|
if (styleContents.length > 0) {
|
|
|
|
|
styleContents.forEach(styleContent => {
|
|
|
|
|
const ruleRegex = /(@[\w-]+|[^{]+)\s*\{([\s\S]*?)\}/g;
|
|
|
|
|
let ruleMatch;
|
|
|
|
|
|
|
|
|
|
while ((ruleMatch = ruleRegex.exec(styleContent)) !== null) {
|
|
|
|
|
const selector = ruleMatch[1].trim();
|
|
|
|
|
const declarations = ruleMatch[2].trim();
|
|
|
|
|
|
|
|
|
|
if (selector.startsWith('@')) {
|
|
|
|
|
// 处理 @keyframes 等特殊规则
|
|
|
|
|
const keyframesName = selector.split(' ')[1];
|
|
|
|
|
stylesBySelector[keyframesName] = declarations;
|
|
|
|
|
} else {
|
|
|
|
|
// 处理普通选择器
|
|
|
|
|
stylesBySelector[selector] = declarations;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
bodyContent,
|
|
|
|
|
bodyItems,
|
|
|
|
|
scriptContents
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param{HTMLElement} targetDiv
|
|
|
|
|
* @param{HTMLElement} tmplDom
|
|
|
|
|
* */
|
|
|
|
|
function replaceDom(targetDiv, tmplDom) {
|
|
|
|
|
for (var i = 0; i < targetDiv.attributes.length; i++) {
|
|
|
|
|
var attr = targetDiv.attributes[i];
|
|
|
|
|
if (attr.name === 'class') {
|
|
|
|
|
tmplDom.className = tmplDom.className + ' ' + attr.value
|
|
|
|
|
} else {
|
|
|
|
|
tmplDom.setAttribute(attr.name, attr.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var parent = targetDiv.parentNode;
|
|
|
|
|
if (parent) {
|
|
|
|
|
parent.replaceChild(tmplDom, targetDiv);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param{string} code
|
|
|
|
|
* @param{Object} data
|
|
|
|
|
* **/
|
|
|
|
|
function run(code, data) {
|
|
|
|
|
let args = '{' + Object.keys(data).join(',') + '}'
|
|
|
|
|
if (code.indexOf('\n') === -1) {
|
|
|
|
|
code = 'return ' + code
|
|
|
|
|
}
|
|
|
|
|
return new Function(args, code)(data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param{HTMLElement} node
|
|
|
|
|
* */
|
|
|
|
|
async function setupRef(node, data, itercount = 0) {
|
|
|
|
|
if (itercount > 100) {
|
|
|
|
|
log.warn('infinite loop')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const ref = node.getAttribute('ref') || ''
|
|
|
|
|
node.setAttribute('tag', ref)
|
|
|
|
|
node.removeAttribute('ref')
|
|
|
|
|
if (!ref) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (ref.endsWith('.js')) {
|
|
|
|
|
let mod = await import(ref)
|
|
|
|
|
let div = document.createElement('div')
|
|
|
|
|
replaceDom(node, mod.default)
|
|
|
|
|
node = mod.default
|
|
|
|
|
if (typeof mod.setup == 'function') {
|
|
|
|
|
let resp = mod.setup(node, data)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
} else {
|
|
|
|
|
const txt = await fetchFileWithCache(ref + ".html")
|
|
|
|
|
|
|
|
|
|
proxy.DomListen(node, () => {
|
|
|
|
|
handleHtml(txt, node, data)
|
|
|
|
|
})
|
|
|
|
|
// let resp = new Function("{vnode,data}", ast.scriptContents.join('\n'))({ vnode: node, data: data })
|
|
|
|
|
}
|
|
|
|
|
for (let n of node.querySelectorAll("div[ref]")) {
|
|
|
|
|
// 确保子元素先加载完
|
|
|
|
|
// await setupRef(n, resp?.data || {}, itercount + 1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleHtml(txt, node, data) {
|
|
|
|
|
let ast = extractBodyAndScript(txt)
|
|
|
|
|
let resp = new Function("", ast.scriptContents.join('\n'))()
|
|
|
|
|
if (resp && typeof resp.data == 'function') {
|
|
|
|
|
data = proxy.Watch(resp.data(data))
|
|
|
|
|
}
|
|
|
|
|
let bodyContent = ast.bodyContent
|
|
|
|
|
if (ast.bodyItems.length > 1) {
|
|
|
|
|
bodyContent = ''
|
|
|
|
|
console.log(ast.bodyItems)
|
|
|
|
|
let dataWrap = []
|
|
|
|
|
let tmpData = Object.assign({}, data)
|
|
|
|
|
let assign = true
|
|
|
|
|
let idx
|
|
|
|
|
for (idx = 0; idx < ast.bodyItems.length; idx++) {
|
|
|
|
|
let item = ast.bodyItems[idx]
|
|
|
|
|
if (!assign) {
|
|
|
|
|
assign = true
|
|
|
|
|
tmpData = Object.assign({}, data)
|
|
|
|
|
dataWrap.forEach((item) => {
|
|
|
|
|
if (item[1] >= 0) {
|
|
|
|
|
tmpData = Object.assign(tmpData, item[0][item[2]])
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
console.log(tmpData)
|
|
|
|
|
}
|
|
|
|
|
if (item[0] === 'val') {
|
|
|
|
|
let valr = run(item[1], tmpData)
|
|
|
|
|
bodyContent += valr
|
|
|
|
|
} else if (item[0] === '') {
|
|
|
|
|
bodyContent += item[1]
|
|
|
|
|
} else if (item[0] == 'for') {
|
|
|
|
|
dataWrap.push([run(item[1], tmpData), idx, 0])
|
|
|
|
|
assign = false
|
|
|
|
|
} else if (item[0] == 'end') {
|
|
|
|
|
let tdw = dataWrap[dataWrap.length - 1]
|
|
|
|
|
if (tdw[1] >= 0) {
|
|
|
|
|
if (tdw[2] < tdw[0].length - 1) {
|
|
|
|
|
tdw[2]++
|
|
|
|
|
idx = tdw[1]
|
|
|
|
|
} else {
|
|
|
|
|
dataWrap.pop()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assign = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let div = document.createElement('div')
|
|
|
|
|
div.innerHTML = bodyContent
|
|
|
|
|
handleSlots(node, div)
|
|
|
|
|
// node.innerHTML = div.innerHTML
|
|
|
|
|
// let div = document.createElement('div')
|
|
|
|
|
replaceDom(node, div)
|
|
|
|
|
return resp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSlots(origin, template) {
|
|
|
|
|
// handle slots
|
|
|
|
|
let originSnap = { '': [] }
|
|
|
|
|
let i = 0
|
|
|
|
|
let child
|
|
|
|
|
while (child = origin.children.item(i)) {
|
|
|
|
|
i++
|
|
|
|
|
let slotname = child.getAttribute ? child.getAttribute('slot') || '' : ''
|
|
|
|
|
if (slotname !== '') {
|
|
|
|
|
child.removeAttribute('slot')
|
|
|
|
|
originSnap[slotname] = child
|
|
|
|
|
} else {
|
|
|
|
|
originSnap[''].push(child)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let slots = template.querySelectorAll('slot')
|
|
|
|
|
slots.forEach((slot) => {
|
|
|
|
|
let slotname = slot.getAttribute('name') || ''
|
|
|
|
|
let snap = originSnap[slotname]
|
|
|
|
|
if (snap && snap.length !== 0) {
|
|
|
|
|
if (slotname === '') {
|
|
|
|
|
slot.replaceWith(...snap)
|
|
|
|
|
} else {
|
|
|
|
|
slot.replaceWith(snap)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
slot.replaceWith(...slot.childNodes)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
console.log('DOMContentLoaded');
|
|
|
|
|
const node = document.getElementById('app');
|
|
|
|
|
// const node = document.body
|
|
|
|
|
observer.observe(node, config);
|
|
|
|
|
const sync = () => {
|
|
|
|
|
let test = node.querySelectorAll("div[ref]")
|
|
|
|
|
test.forEach(n => setupRef(n, {}))
|
|
|
|
|
}
|
|
|
|
|
sync()
|
|
|
|
|
// setInterval(sync, 10)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
})();
|