parent
fe1d2720a0
commit
6e79fb489c
@ -0,0 +1,150 @@ |
|||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
.editor { |
||||||
|
display: flex; |
||||||
|
align-items: stretch; |
||||||
|
} |
||||||
|
.setbox, |
||||||
|
.menu, |
||||||
|
.outbox { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
max-height: 100vh; |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
.menu { |
||||||
|
justify-content: center; |
||||||
|
max-width: 90px; |
||||||
|
margin: 0 5px; |
||||||
|
} |
||||||
|
.menu .button { |
||||||
|
width: 90px; |
||||||
|
height: 30px; |
||||||
|
min-height: 30px; |
||||||
|
margin: 5px 0px; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
@keyframes stroker { |
||||||
|
0% { |
||||||
|
stroke-dashoffset: 0; |
||||||
|
} |
||||||
|
100% { |
||||||
|
stroke-dashoffset: -240; |
||||||
|
} |
||||||
|
} |
||||||
|
.button rect { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
fill: transparent; |
||||||
|
stroke: #666; |
||||||
|
stroke-width: 2px; |
||||||
|
} |
||||||
|
.button rect.busy { |
||||||
|
stroke: #fd1850; |
||||||
|
stroke-dasharray: 30 90; |
||||||
|
animation: stroker 1s linear infinite; |
||||||
|
} |
||||||
|
.button text { |
||||||
|
text-anchor: middle; |
||||||
|
dominant-baseline: middle; |
||||||
|
} |
||||||
|
.setbox { |
||||||
|
min-width: 40em; |
||||||
|
} |
||||||
|
.rules { |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
.tabbox { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
flex-flow: column; |
||||||
|
} |
||||||
|
.rules > * { |
||||||
|
display: flex; |
||||||
|
margin: 2px 0; |
||||||
|
} |
||||||
|
.rules textarea { |
||||||
|
flex: 1; |
||||||
|
margin-left: 5px; |
||||||
|
} |
||||||
|
.rules > *, |
||||||
|
.rules > * > div, |
||||||
|
.rules textarea { |
||||||
|
min-height: 1em; |
||||||
|
} |
||||||
|
textarea { |
||||||
|
word-break: break-all; |
||||||
|
} |
||||||
|
.tabtitle { |
||||||
|
display: flex; |
||||||
|
z-index: 1; |
||||||
|
justify-content: flex-end; |
||||||
|
} |
||||||
|
.tabtitle > div { |
||||||
|
cursor: pointer; |
||||||
|
padding: 1px 10px 0 10px; |
||||||
|
border-bottom: 3px solid transparent; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.tabtitle > .this { |
||||||
|
color: #4f9da6; |
||||||
|
border-bottom-color: #4ebbe4; |
||||||
|
} |
||||||
|
.tabbody { |
||||||
|
flex: 1; |
||||||
|
display: flex; |
||||||
|
margin-top: -1px; |
||||||
|
border: 1px solid #a9a9a9; |
||||||
|
height: 0; |
||||||
|
} |
||||||
|
.tabbody > * { |
||||||
|
flex: 1; |
||||||
|
flex-flow: column; |
||||||
|
display: none; |
||||||
|
} |
||||||
|
.tabbody > .this { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.tabbody > * > .titlebar { |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.tabbody > * > .titlebar > * { |
||||||
|
flex: 1; |
||||||
|
margin: 1px 1px 1px 1px; |
||||||
|
} |
||||||
|
.tabbody > * > .context { |
||||||
|
flex: 1; |
||||||
|
flex-flow: column; |
||||||
|
border: 0; |
||||||
|
padding: 5px; |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
.tabbody > * > .inputbox { |
||||||
|
border: 0; |
||||||
|
border-bottom: #a9a9a9 solid 1px; |
||||||
|
height: 15px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
.link > * { |
||||||
|
display: flex; |
||||||
|
margin: 5px; |
||||||
|
border-bottom: 1px solid; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
#RuleList > label > * { |
||||||
|
background: #eee; |
||||||
|
padding-left: 3px; |
||||||
|
margin: 2px 0; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
#RuleList input[type="radio"] { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
#RuleList input[type="radio"]:checked + * { |
||||||
|
background: #15cda8; |
||||||
|
} |
||||||
|
.isError { |
||||||
|
color: #ff0000; |
||||||
|
} |
@ -0,0 +1,488 @@ |
|||||||
|
// 简化js原生选择器
|
||||||
|
function $(selector) { return document.querySelector(selector); } |
||||||
|
function $$(selector) { return document.querySelectorAll(selector); } |
||||||
|
// 读写Hash值(val未赋值时为读取)
|
||||||
|
function hashParam(key, val) { |
||||||
|
let hashstr = decodeURIComponent(window.location.hash); |
||||||
|
let regKey = new RegExp(`${key}=([^&]*)`); |
||||||
|
let getVal = regKey.test(hashstr) ? hashstr.match(regKey)[1] : null; |
||||||
|
if (val == undefined) return getVal; |
||||||
|
if (hashstr == '' || hashstr == '#') { |
||||||
|
window.location.hash = `#${key}=${val}`; |
||||||
|
} |
||||||
|
else { |
||||||
|
if (getVal) window.location.hash = hashstr.replace(getVal, val); |
||||||
|
else { |
||||||
|
window.location.hash = hashstr.indexOf(key) > -1 ? hashstr.replace(regKey, `${key}=${val}`) : `${hashstr}&${key}=${val}`; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// 创建书源规则容器对象
|
||||||
|
function Container() { |
||||||
|
let ruleJson = {}; |
||||||
|
|
||||||
|
// 基本以及其他
|
||||||
|
$$('.rules .base').forEach(item => ruleJson[item.title] = ''); |
||||||
|
ruleJson.customOrder = 0; |
||||||
|
ruleJson.enabled = true; |
||||||
|
|
||||||
|
return ruleJson; |
||||||
|
} |
||||||
|
// 选项卡Tab切换事件处理
|
||||||
|
function showTab(tabName) { |
||||||
|
$$('.tabtitle>*').forEach(node => { node.className = node.className.replace(' this', ''); }); |
||||||
|
$$('.tabbody>*').forEach(node => { node.className = node.className.replace(' this', ''); }); |
||||||
|
$(`.tabbody>.${$(`.tabtitle>*[name=${tabName}]`).className}`).className += ' this'; |
||||||
|
$(`.tabtitle>*[name=${tabName}]`).className += ' this'; |
||||||
|
hashParam('tab', tabName); |
||||||
|
} |
||||||
|
// 书源列表列表标签构造函数
|
||||||
|
function newRule(rule) { |
||||||
|
return `<label for="${rule.bookSourceUrl}"><input type="radio" name="rule" id="${rule.bookSourceUrl}"><div>${rule.bookSourceName}<br>${rule.bookSourceUrl}</div></label>`; |
||||||
|
} |
||||||
|
// 缓存规则列表
|
||||||
|
var RuleSources = []; |
||||||
|
if (localStorage.getItem('RssSources')) { |
||||||
|
RuleSources = JSON.parse(localStorage.getItem('RssSources')); |
||||||
|
RuleSources.forEach(item => $('#RuleList').innerHTML += newRule(item)); |
||||||
|
} |
||||||
|
// 页面加载完成事件
|
||||||
|
window.onload = () => { |
||||||
|
$$('.tabtitle>*').forEach(item => { |
||||||
|
item.addEventListener('click', () => { |
||||||
|
showTab(item.innerHTML); |
||||||
|
}); |
||||||
|
}); |
||||||
|
if (hashParam('tab')) showTab(hashParam('tab')); |
||||||
|
} |
||||||
|
// 获取数据
|
||||||
|
function HttpGet(url) { |
||||||
|
return fetch(hashParam('domain') ? hashParam('domain') + url : url) |
||||||
|
.then(res => res.json()).catch(err => console.error('Error:', err)); |
||||||
|
} |
||||||
|
// 提交数据
|
||||||
|
function HttpPost(url, data) { |
||||||
|
return fetch(hashParam('domain') ? hashParam('domain') + url : url, { |
||||||
|
body: JSON.stringify(data), |
||||||
|
method: 'POST', |
||||||
|
mode: "cors", |
||||||
|
headers: new Headers({ |
||||||
|
'Content-Type': 'application/json;charset=utf-8' |
||||||
|
}) |
||||||
|
}).then(res => res.json()).catch(err => console.error('Error:', err)); |
||||||
|
} |
||||||
|
// 将书源表单转化为书源对象
|
||||||
|
function rule2json() { |
||||||
|
let RuleJSON = Container(); |
||||||
|
// 转换base
|
||||||
|
Object.keys(RuleJSON).forEach(key => { |
||||||
|
if (!key.startsWith("rule")) { |
||||||
|
RuleJSON[key] = $('#' + key).value; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 转换搜索规则
|
||||||
|
let searchJson = {}; |
||||||
|
Object.keys(RuleJSON.ruleSearch).forEach(key => { |
||||||
|
if ($('#' + 'ruleSearch_' + key).value) |
||||||
|
searchJson[key] = $('#' + 'ruleSearch_' + key).value; |
||||||
|
}); |
||||||
|
RuleJSON.ruleSearch = searchJson; |
||||||
|
|
||||||
|
// 转换发现规则
|
||||||
|
let exploreJson = {}; |
||||||
|
Object.keys(RuleJSON.ruleExplore).forEach(key => { |
||||||
|
if ($('#' + 'ruleExplore_' + key).value) |
||||||
|
exploreJson[key] = $('#' + 'ruleExplore_' + key).value; |
||||||
|
}); |
||||||
|
RuleJSON.ruleExplore = exploreJson; |
||||||
|
|
||||||
|
// 转换详情页规则
|
||||||
|
let bookInfoJson = {}; |
||||||
|
Object.keys(RuleJSON.ruleBookInfo).forEach(key => { |
||||||
|
if ($('#' + 'ruleBookInfo_' + key).value) |
||||||
|
bookInfoJson[key] = $('#' + 'ruleBookInfo_' + key).value; |
||||||
|
}); |
||||||
|
RuleJSON.ruleBookInfo = bookInfoJson; |
||||||
|
|
||||||
|
// 转换目录规则
|
||||||
|
let tocJson = {}; |
||||||
|
Object.keys(RuleJSON.ruleToc).forEach(key => { |
||||||
|
if ($('#' + 'ruleToc_' + key).value) |
||||||
|
tocJson[key] = $('#' + 'ruleToc_' + key).value; |
||||||
|
}); |
||||||
|
RuleJSON.ruleToc = tocJson; |
||||||
|
|
||||||
|
// 转换正文规则
|
||||||
|
let contentJson = {}; |
||||||
|
Object.keys(RuleJSON.ruleContent).forEach(key => { |
||||||
|
if ($('#' + 'ruleContent_' + key).value) |
||||||
|
contentJson[key] = $('#' + 'ruleContent_' + key).value; |
||||||
|
}); |
||||||
|
RuleJSON.ruleContent = contentJson; |
||||||
|
|
||||||
|
RuleJSON.lastUpdateTime = new Date().getTime(); |
||||||
|
RuleJSON.customOrder = RuleJSON.customOrder == '' ? 0 : parseInt(RuleJSON.customOrder); |
||||||
|
RuleJSON.weight = RuleJSON.weight == '' ? 0 : parseInt(RuleJSON.weight); |
||||||
|
RuleJSON.bookSourceType == RuleJSON.bookSourceType == '' ? 0 : parseInt(RuleJSON.bookSourceType); |
||||||
|
RuleJSON.enabled = RuleJSON.enabled == '' || String(RuleJSON.enabled).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true'; |
||||||
|
RuleJSON.enabledExplore = RuleJSON.enabledExplore == '' || String(RuleJSON.enabledExplore).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true'; |
||||||
|
return RuleJSON; |
||||||
|
} |
||||||
|
// 将书源对象填充到书源表单
|
||||||
|
function json2rule(RuleEditor) { |
||||||
|
let RuleJSON = Container(); |
||||||
|
// 转换base
|
||||||
|
Object.keys(RuleJSON).forEach(key => { |
||||||
|
if (!key.startsWith("rule")) { |
||||||
|
let val = RuleEditor[key]; |
||||||
|
if (typeof val == "number") { |
||||||
|
$("#" + key).value = val ? String(val) : '0'; |
||||||
|
} |
||||||
|
else if (typeof val == "boolean") { |
||||||
|
$("#" + key).value = val ? String(val) : 'false'; |
||||||
|
} |
||||||
|
else { |
||||||
|
$("#" + key).value = val ? String(val) : ''; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 转换搜索规则
|
||||||
|
if (RuleEditor.ruleSearch) { |
||||||
|
let searchJson = RuleEditor.ruleSearch; |
||||||
|
Object.keys(RuleJSON.ruleSearch).forEach(key => { |
||||||
|
$('#' + 'ruleSearch_' + key).value = searchJson[key] ? searchJson[key] : ''; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// 转换发现规则
|
||||||
|
if (RuleEditor.ruleExplore) { |
||||||
|
let exploreJson = RuleEditor.ruleExplore; |
||||||
|
Object.keys(RuleJSON.ruleExplore).forEach(key => { |
||||||
|
$('#' + 'ruleExplore_' + key).value = exploreJson[key] ? exploreJson[key] : ''; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// 转换详情页规则
|
||||||
|
if (RuleEditor.ruleBookInfo) { |
||||||
|
let bookInfoJson = RuleEditor.ruleBookInfo; |
||||||
|
Object.keys(RuleJSON.ruleBookInfo).forEach(key => { |
||||||
|
$('#' + 'ruleBookInfo_' + key).value = bookInfoJson[key] ? bookInfoJson[key] : ''; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// 转换目录规则
|
||||||
|
if (RuleEditor.ruleToc) { |
||||||
|
let tocJson = RuleEditor.ruleToc; |
||||||
|
Object.keys(RuleJSON.ruleToc).forEach(key => { |
||||||
|
$('#' + 'ruleToc_' + key).value = tocJson[key] ? tocJson[key] : ''; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// 转换正文规则
|
||||||
|
if (RuleEditor.ruleContent) { |
||||||
|
let contentJson = RuleEditor.ruleContent; |
||||||
|
Object.keys(RuleJSON.ruleContent).forEach(key => { |
||||||
|
$('#' + 'ruleContent_' + key).value = contentJson[key] ? contentJson[key] : ''; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
// 记录操作过程
|
||||||
|
var course = { "old": [], "now": {}, "new": [] }; |
||||||
|
if (localStorage.getItem('rssSourceCourse')) { |
||||||
|
course = JSON.parse(localStorage.getItem('rssSourceCourse')); |
||||||
|
json2rule(course.now); |
||||||
|
} |
||||||
|
else { |
||||||
|
course.now = rule2json(); |
||||||
|
window.localStorage.setItem('rssSourceCourse', JSON.stringify(course)); |
||||||
|
} |
||||||
|
function todo() { |
||||||
|
course.old.push(Object.assign({}, course.now)); |
||||||
|
course.now = rule2json(); |
||||||
|
course.new = []; |
||||||
|
if (course.old.length > 50) course.old.shift(); // 限制历史记录堆栈大小
|
||||||
|
localStorage.setItem('rssSourceCourse', JSON.stringify(course)); |
||||||
|
} |
||||||
|
function undo() { |
||||||
|
course = JSON.parse(localStorage.getItem('rssSourceCourse')); |
||||||
|
if (course.old.length > 0) { |
||||||
|
course.new.push(course.now); |
||||||
|
course.now = course.old.pop(); |
||||||
|
localStorage.setItem('rssSourceCourse', JSON.stringify(course)); |
||||||
|
json2rule(course.now); |
||||||
|
} |
||||||
|
} |
||||||
|
function redo() { |
||||||
|
course = JSON.parse(localStorage.getItem('rssSourceCourse')); |
||||||
|
if (course.new.length > 0) { |
||||||
|
course.old.push(course.now); |
||||||
|
course.now = course.new.pop(); |
||||||
|
localStorage.setItem('rssSourceCourse', JSON.stringify(course)); |
||||||
|
json2rule(course.now); |
||||||
|
} |
||||||
|
} |
||||||
|
function setRule(editRule) { |
||||||
|
let checkRule = RuleSources.find(x => x.bookSourceUrl == editRule.bookSourceUrl); |
||||||
|
if ($(`input[id="${editRule.bookSourceUrl}"]`)) { |
||||||
|
Object.keys(checkRule).forEach(key => { checkRule[key] = editRule[key]; }); |
||||||
|
$(`input[id="${editRule.bookSourceUrl}"]+*`).innerHTML = `${editRule.bookSourceName}<br>${editRule.bookSourceUrl}`; |
||||||
|
} else { |
||||||
|
RuleSources.push(editRule); |
||||||
|
$('#RuleList').innerHTML += newRule(editRule); |
||||||
|
} |
||||||
|
} |
||||||
|
$$('input').forEach((item) => { item.addEventListener('change', () => { todo() }) }); |
||||||
|
$$('textarea').forEach((item) => { item.addEventListener('change', () => { todo() }) }); |
||||||
|
// 处理按钮点击事件
|
||||||
|
$('.menu').addEventListener('click', e => { |
||||||
|
let thisNode = e.target; |
||||||
|
thisNode = thisNode.parentNode.nodeName == 'svg' ? thisNode.parentNode.querySelector('rect') : |
||||||
|
thisNode.nodeName == 'svg' ? thisNode.querySelector('rect') : null; |
||||||
|
if (!thisNode) return; |
||||||
|
if (thisNode.getAttribute('class') == 'busy') return; |
||||||
|
thisNode.setAttribute('class', 'busy'); |
||||||
|
switch (thisNode.id) { |
||||||
|
case 'push': |
||||||
|
$$('#RuleList>label>div').forEach(item => { item.className = ''; }); |
||||||
|
(async () => { |
||||||
|
await HttpPost(`/saveSources`, RuleSources).then(json => { |
||||||
|
if (json.isSuccess) { |
||||||
|
let okData = json.data; |
||||||
|
if (Array.isArray(okData)) { |
||||||
|
let failMsg = ``; |
||||||
|
if (RuleSources.length > okData.length) { |
||||||
|
RuleSources.forEach(item => { |
||||||
|
if (okData.find(x => x.bookSourceUrl == item.bookSourceUrl)) { } |
||||||
|
else { $(`#RuleList #${item.bookSourceUrl}+*`).className += 'isError'; } |
||||||
|
}); |
||||||
|
failMsg = '\n推送失败的书源将用红色字体标注!'; |
||||||
|
} |
||||||
|
alert(`批量推送书源到「阅读3.0APP」\n共计: ${RuleSources.length} 条\n成功: ${okData.length} 条\n失败: ${RuleSources.length - okData.length} 条${failMsg}`); |
||||||
|
} |
||||||
|
else { |
||||||
|
alert(`批量推送书源到「阅读3.0APP」成功!\n共计: ${RuleSources.length} 条`); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
alert(`批量推送书源失败!\nErrorMsg: ${json.errorMsg}`); |
||||||
|
} |
||||||
|
}).catch(err => { alert(`批量推送书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); |
||||||
|
thisNode.setAttribute('class', ''); |
||||||
|
})(); |
||||||
|
return; |
||||||
|
case 'pull': |
||||||
|
showTab('书源列表'); |
||||||
|
(async () => { |
||||||
|
await HttpGet(`/getSources`).then(json => { |
||||||
|
if (json.isSuccess) { |
||||||
|
$('#RuleList').innerHTML = '' |
||||||
|
localStorage.setItem('RssSources', JSON.stringify(RuleSources = json.data)); |
||||||
|
RuleSources.forEach(item => { |
||||||
|
$('#RuleList').innerHTML += newRule(item); |
||||||
|
}); |
||||||
|
alert(`成功拉取 ${RuleSources.length} 条书源`); |
||||||
|
} |
||||||
|
else { |
||||||
|
alert(`批量拉取书源失败!\nErrorMsg: ${json.errorMsg}`); |
||||||
|
} |
||||||
|
}).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); |
||||||
|
thisNode.setAttribute('class', ''); |
||||||
|
})(); |
||||||
|
return; |
||||||
|
case 'editor': |
||||||
|
if ($('#RuleJsonString').value == '') break; |
||||||
|
try { |
||||||
|
json2rule(JSON.parse($('#RuleJsonString').value)); |
||||||
|
todo(); |
||||||
|
} catch (error) { |
||||||
|
console.log(error); |
||||||
|
alert(error); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'conver': |
||||||
|
showTab('编辑书源'); |
||||||
|
$('#RuleJsonString').value = JSON.stringify(rule2json(), null, 4); |
||||||
|
break; |
||||||
|
case 'initial': |
||||||
|
$$('.rules textarea').forEach(item => { item.value = '' }); |
||||||
|
todo(); |
||||||
|
break; |
||||||
|
case 'undo': |
||||||
|
undo() |
||||||
|
break; |
||||||
|
case 'redo': |
||||||
|
redo() |
||||||
|
break; |
||||||
|
case 'debug': |
||||||
|
showTab('调试书源'); |
||||||
|
let wsOrigin = (hashParam('domain') || location.origin).replace(/^.*?:/, 'ws:').replace(/\d+$/, (port) => (parseInt(port) + 1)); |
||||||
|
let DebugInfos = $('#DebugConsole'); |
||||||
|
function DebugPrint(msg) { DebugInfos.value += `\n${msg}`; DebugInfos.scrollTop = DebugInfos.scrollHeight; } |
||||||
|
let saveRule = [rule2json()]; |
||||||
|
HttpPost(`/saveSources`, saveRule).then(sResult => { |
||||||
|
if (sResult.isSuccess) { |
||||||
|
let sKey = DebugKey.value ? DebugKey.value : '我的'; |
||||||
|
$('#DebugConsole').value = `书源《${saveRule[0].bookSourceName}》保存成功!使用搜索关键字“${sKey}”开始调试...`; |
||||||
|
let ws = new WebSocket(`${wsOrigin}/sourceDebug`); |
||||||
|
ws.onopen = () => { |
||||||
|
ws.send(`{"tag":"${saveRule[0].bookSourceUrl}", "key":"${sKey}"}`); |
||||||
|
}; |
||||||
|
ws.onmessage = (msg) => { |
||||||
|
console.log('[调试]', msg); |
||||||
|
DebugPrint(msg.data); |
||||||
|
}; |
||||||
|
ws.onerror = (err) => { |
||||||
|
throw `${err.data}`; |
||||||
|
} |
||||||
|
ws.onclose = () => { |
||||||
|
thisNode.setAttribute('class', ''); |
||||||
|
DebugPrint(`\n调试服务已关闭!`); |
||||||
|
} |
||||||
|
} else throw `${sResult.errorMsg}`; |
||||||
|
}).catch(err => { |
||||||
|
DebugPrint(`调试过程意外中止,以下是详细错误信息:\n${err}`); |
||||||
|
thisNode.setAttribute('class', ''); |
||||||
|
}); |
||||||
|
return; |
||||||
|
case 'accept': |
||||||
|
(async () => { |
||||||
|
let saveRule = [rule2json()]; |
||||||
|
await HttpPost(`/saveSources`, saveRule).then(json => { |
||||||
|
alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读3.0APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`); |
||||||
|
setRule(saveRule[0]); |
||||||
|
}).catch(err => { alert(`保存书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); |
||||||
|
thisNode.setAttribute('class', ''); |
||||||
|
})(); |
||||||
|
return; |
||||||
|
default: |
||||||
|
} |
||||||
|
setTimeout(() => { thisNode.setAttribute('class', ''); }, 500); |
||||||
|
}); |
||||||
|
$('#DebugKey').addEventListener('keydown', e => { |
||||||
|
if (e.keyCode == 13) { |
||||||
|
let clickEvent = document.createEvent('MouseEvents'); |
||||||
|
clickEvent.initEvent("click", true, false); |
||||||
|
$('#debug').dispatchEvent(clickEvent); |
||||||
|
} |
||||||
|
}); |
||||||
|
$('#Filter').addEventListener('keydown', e => { |
||||||
|
if (e.keyCode == 13) { |
||||||
|
let cashList = []; |
||||||
|
$('#RuleList').innerHTML = ""; |
||||||
|
let sKey = Filter.value ? Filter.value : ''; |
||||||
|
if (sKey == '') { |
||||||
|
cashList = RuleSources; |
||||||
|
} else { |
||||||
|
let patt = new RegExp(sKey); |
||||||
|
RuleSources.forEach(source => { |
||||||
|
if (patt.test(source.bookSourceUrl) || patt.test(source.bookSourceName) || patt.test(source.bookSourceGroup)) { |
||||||
|
cashList.push(source); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
cashList.forEach(source => { |
||||||
|
$('#RuleList').innerHTML += newRule(source); |
||||||
|
}) |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 列表规则更改事件
|
||||||
|
$('#RuleList').addEventListener('click', e => { |
||||||
|
let editRule = null; |
||||||
|
if (e.target && e.target.getAttribute('name') == 'rule') { |
||||||
|
editRule = rule2json(); |
||||||
|
json2rule(RuleSources.find(x => x.bookSourceUrl == e.target.id)); |
||||||
|
} else return; |
||||||
|
if (editRule.bookSourceUrl == '') return; |
||||||
|
if (editRule.bookSourceName == '') editRule.bookSourceName = editRule.bookSourceUrl.replace(/.*?\/\/|\/.*/g, ''); |
||||||
|
setRule(editRule); |
||||||
|
localStorage.setItem('RssSources', JSON.stringify(RuleSources)); |
||||||
|
}); |
||||||
|
// 处理列表按钮事件
|
||||||
|
$('.tab3>.titlebar').addEventListener('click', e => { |
||||||
|
let thisNode = e.target; |
||||||
|
if (thisNode.nodeName != 'BUTTON') return; |
||||||
|
switch (thisNode.id) { |
||||||
|
case 'Import': |
||||||
|
let fileImport = document.createElement('input'); |
||||||
|
fileImport.type = 'file'; |
||||||
|
fileImport.accept = '.json'; |
||||||
|
fileImport.addEventListener('change', () => { |
||||||
|
let file = fileImport.files[0]; |
||||||
|
let reader = new FileReader(); |
||||||
|
reader.onloadend = function (evt) { |
||||||
|
if (evt.target.readyState == FileReader.DONE) { |
||||||
|
let fileText = evt.target.result; |
||||||
|
try { |
||||||
|
let fileJson = JSON.parse(fileText); |
||||||
|
let newSources = []; |
||||||
|
newSources.push(...fileJson); |
||||||
|
if (window.confirm(`如何处理导入的书源?\n"确定": 覆盖当前列表(不会删除APP源)\n"取消": 插入列表尾部(自动忽略重复源)`)) { |
||||||
|
localStorage.setItem('RssSources', JSON.stringify(RuleSources = newSources)); |
||||||
|
$('#RuleList').innerHTML = '' |
||||||
|
RuleSources.forEach(item => { |
||||||
|
$('#RuleList').innerHTML += newRule(item); |
||||||
|
}); |
||||||
|
} |
||||||
|
else { |
||||||
|
newSources = newSources.filter(item => !JSON.stringify(RuleSources).includes(item.bookSourceUrl)); |
||||||
|
RuleSources.push(...newSources); |
||||||
|
localStorage.setItem('RssSources', JSON.stringify(RuleSources)); |
||||||
|
newSources.forEach(item => { |
||||||
|
$('#RuleList').innerHTML += newRule(item); |
||||||
|
}); |
||||||
|
} |
||||||
|
alert(`成功导入 ${newSources.length} 条书源`); |
||||||
|
} |
||||||
|
catch (err) { |
||||||
|
alert(`导入书源文件失败!\n${err}`); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
reader.readAsText(file); |
||||||
|
}, false); |
||||||
|
fileImport.click(); |
||||||
|
break; |
||||||
|
case 'Export': |
||||||
|
let fileExport = document.createElement('a'); |
||||||
|
fileExport.download = `Rules${Date().replace(/.*?\s(\d+)\s(\d+)\s(\d+:\d+:\d+).*/, '$2$1$3').replace(/:/g, '')}.json`; |
||||||
|
let myBlob = new Blob([JSON.stringify(RuleSources, null, 4)], { type: "application/json" }); |
||||||
|
fileExport.href = window.URL.createObjectURL(myBlob); |
||||||
|
fileExport.click(); |
||||||
|
break; |
||||||
|
case 'Delete': |
||||||
|
let selectRule = $('#RuleList input:checked'); |
||||||
|
if (!selectRule) { |
||||||
|
alert(`没有书源被选中!`); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (confirm(`确定要删除选定书源吗?\n(同时删除APP内书源)`)) { |
||||||
|
let selectRuleUrl = selectRule.id; |
||||||
|
let deleteSources = RuleSources.filter(item => item.bookSourceUrl == selectRuleUrl); // 提取待删除的书源
|
||||||
|
let laveSources = RuleSources.filter(item => !(item.bookSourceUrl == selectRuleUrl)); // 提取待留下的书源
|
||||||
|
HttpPost(`/deleteSources`, deleteSources).then(json => { |
||||||
|
if (json.isSuccess) { |
||||||
|
let selectNode = document.getElementById(selectRuleUrl).parentNode; |
||||||
|
selectNode.parentNode.removeChild(selectNode); |
||||||
|
localStorage.setItem('RssSources', JSON.stringify(RuleSources = laveSources)); |
||||||
|
if ($('#bookSourceUrl').value == selectRuleUrl) { |
||||||
|
$$('.rules textarea').forEach(item => { item.value = '' }); |
||||||
|
todo(); |
||||||
|
} |
||||||
|
console.log(deleteSources); |
||||||
|
console.log(`以上书源已删除!`) |
||||||
|
} |
||||||
|
}).catch(err => { alert(`删除书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'ClrAll': |
||||||
|
if (confirm(`确定要清空当前书源列表吗?\n(不会删除APP内书源)`)) { |
||||||
|
localStorage.setItem('RssSources', JSON.stringify(RuleSources = [])); |
||||||
|
$('#RuleList').innerHTML = '' |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
} |
||||||
|
}); |
@ -0,0 +1,83 @@ |
|||||||
|
package io.legado.app.api.controller |
||||||
|
|
||||||
|
|
||||||
|
import android.text.TextUtils |
||||||
|
import io.legado.app.api.ReturnData |
||||||
|
import io.legado.app.data.appDb |
||||||
|
import io.legado.app.data.entities.RssSource |
||||||
|
import io.legado.app.utils.GSON |
||||||
|
import io.legado.app.utils.fromJsonArray |
||||||
|
import io.legado.app.utils.fromJsonObject |
||||||
|
import io.legado.app.utils.msg |
||||||
|
|
||||||
|
object RssSourceController { |
||||||
|
|
||||||
|
val sources: ReturnData |
||||||
|
get() { |
||||||
|
val source = appDb.rssSourceDao.all |
||||||
|
val returnData = ReturnData() |
||||||
|
return if (source.isEmpty()) { |
||||||
|
returnData.setErrorMsg("订阅源列表为空") |
||||||
|
} else returnData.setData(source) |
||||||
|
} |
||||||
|
|
||||||
|
fun saveSource(postData: String?): ReturnData { |
||||||
|
val returnData = ReturnData() |
||||||
|
postData ?: return returnData.setErrorMsg("数据不能为空") |
||||||
|
kotlin.runCatching { |
||||||
|
val source = GSON.fromJsonObject<RssSource>(postData) |
||||||
|
if (source != null) { |
||||||
|
if (TextUtils.isEmpty(source.sourceName) || TextUtils.isEmpty(source.sourceUrl)) { |
||||||
|
returnData.setErrorMsg("源名称和URL不能为空") |
||||||
|
} else { |
||||||
|
appDb.rssSourceDao.insert(source) |
||||||
|
returnData.setData("") |
||||||
|
} |
||||||
|
} else { |
||||||
|
returnData.setErrorMsg("转换源失败") |
||||||
|
} |
||||||
|
}.onFailure { |
||||||
|
returnData.setErrorMsg(it.msg) |
||||||
|
} |
||||||
|
return returnData |
||||||
|
} |
||||||
|
|
||||||
|
fun saveSources(postData: String?): ReturnData { |
||||||
|
val okSources = arrayListOf<RssSource>() |
||||||
|
kotlin.runCatching { |
||||||
|
val source = GSON.fromJsonArray<RssSource>(postData) |
||||||
|
if (source != null) { |
||||||
|
for (rssSource in source) { |
||||||
|
if (rssSource.sourceName.isBlank() || rssSource.sourceUrl.isBlank()) { |
||||||
|
continue |
||||||
|
} |
||||||
|
appDb.rssSourceDao.insert(rssSource) |
||||||
|
okSources.add(rssSource) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return ReturnData().setData(okSources) |
||||||
|
} |
||||||
|
|
||||||
|
fun getSource(parameters: Map<String, List<String>>): ReturnData { |
||||||
|
val url = parameters["url"]?.firstOrNull() |
||||||
|
val returnData = ReturnData() |
||||||
|
if (url.isNullOrEmpty()) { |
||||||
|
return returnData.setErrorMsg("参数url不能为空,请指定书源地址") |
||||||
|
} |
||||||
|
val source = appDb.rssSourceDao.getByKey(url) |
||||||
|
?: return returnData.setErrorMsg("未找到源,请检查源地址") |
||||||
|
return returnData.setData(source) |
||||||
|
} |
||||||
|
|
||||||
|
fun deleteSources(postData: String?): ReturnData { |
||||||
|
kotlin.runCatching { |
||||||
|
GSON.fromJsonArray<RssSource>(postData)?.let { |
||||||
|
it.forEach { source -> |
||||||
|
appDb.rssSourceDao.delete(source) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return ReturnData().setData("已执行"/*okSources*/) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue