diff --git a/README.md b/README.md index 7314df9..0b29cca 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ###### ~DIV&搬运工:只是站在巨人的肩膀上~MINI-VLOOK: Vlook的简化版本^2022.08.27^`#对外公开|1.10.0#(green)` **路人二***COPYRIGHT © 2022 路人二. All Right Reserved* -< `#作者|路人二#(theme1)` | `#对外公开|1.6.0#(green!)` | `#日期|2022-07-03#(blue)` ] +< `#作者|路人二#(theme1)` | `#对外公开|2.0.0#(green!)` | `#日期|2022-09-22#(blue)` ] --- @@ -10,7 +10,7 @@ --- -* V1.0.0 +* V2.0.0 * 新的配色方案 * 文字竖排块属性**f=sp** @@ -45,10 +45,21 @@ * 块属性**f=chk**可关闭 * 在行内代码块中输入(可以把x换成一个空格) - * +[x]+ + * +[ ]+ * +[x]|标签+ + * +[ ]|标签+(red) * +[x]|标签+(red) * +[x]|标签+(red!) + * 微章复选框 + + * 块属性**f=chk-wz**可关闭 + * 在行内代码块中输入(可以把x换成一个空格) + + * #[ ]#+ + * #[x]|标签# + * #[ ]|标签#(red) + * #[x]|标签#(red) + * #[x]|标签#(red!) * 命令 * `@@kanban` 和 `@@map` 快速生成`#看板#`和`#导图#` @@ -124,7 +135,7 @@ `代码块解析功能` 开关方式: Ctrl + Alt + 0 或者思源笔记左上角的按钮![image.png](assets/image-20220429170848-hmkfeh0.png)关闭或开启渲染。 -如果是通过左上角按钮开启渲染,在文档内按下任意键,立即渲染整篇文档。 +在文档内按下 Alt + 0 ,立即渲染整篇文档。(改变!!2.0.0) 已渲染的需要在对应行输入字符,才会取消渲染效果。 @@ -405,9 +416,9 @@ 你好世界 -#### 批注 +#### 评论 -Dark+ 主题的批注功能。感谢大佬! +Dark+ 主题的评论功能。感谢大佬! ## 参考/感谢 diff --git a/app/comment/comment.css b/app/comment/comment.css index 4bca6a0..40ef06d 100644 --- a/app/comment/comment.css +++ b/app/comment/comment.css @@ -1,5 +1,5 @@ /* Common */ -strong[style="blank"]{ +strong[style="blank"] { background-color: var(--b3-theme-on-background); color: var(--b3-theme-on-background); font-weight: normal; @@ -8,12 +8,15 @@ strong[style="blank"]{ transition: all 300ms ease-in-out; /* cursor: pointer; */ } -strong[style="blank"]:hover{ + +strong[style="blank"]:hover { color: var(--b3-theme-on-background); background-color: var(--b3-theme-background); transition: all 300ms ease-in-out; } -.lz-overlay, .lz-overlay-black{ + +.lz-overlay, +.lz-overlay-black { z-index: 999; position: fixed; left: 0; @@ -25,17 +28,19 @@ strong[style="blank"]:hover{ display: none; opacity: 1; } -.lz-overlay-black{ + +.lz-overlay-black { background-color: rgba(0, 0, 0, 0.2); } /* flex card layout */ -.protyle-wysiwyg.card{ +.protyle-wysiwyg.card { display: flex; flex-wrap: wrap; /* background-color: var(--b3-theme-surface); */ } -.protyle-wysiwyg.card div[data-node-id]{ + +.protyle-wysiwyg.card div[data-node-id] { /* box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); */ margin: 5px; padding: 10px; @@ -46,18 +51,21 @@ strong[style="blank"]:hover{ /* Inline comment */ -strong[style*="quote"]{ +strong[style*="quote"], +span[data-type~=strong][style*="quote"] { border-bottom: 2px solid rgb(255, 212, 0); - background-color: rgba(255,212,0,0.16); + background-color: rgba(255, 212, 0, 0.16); padding-bottom: 1px; font-weight: normal; } -strong[style*="quote"]:hover{ + +strong[style*="quote"]:hover, +span[data-type~=strong][style*="quote"]:hover { cursor: pointer; /* background-color: rgb(255, 212, 0); */ } -#lz-comment-box{ +#lz-comment-box { font-size: 15px; display: none; position: fixed; @@ -71,82 +79,98 @@ strong[style*="quote"]:hover{ border-radius: 3px; padding: 10px 0; } -#lz-comment-box a{ + +#lz-comment-box a { border-bottom: 1px solid var(--b3-theme-primary); } -#lz-comment-box a:hover{ + +#lz-comment-box a:hover { text-decoration: none; } -#lz-comment-box .list{ + +#lz-comment-box .list { width: 100%; max-height: 370px; overflow-y: scroll; } -#lz-comment-box .list::-webkit-scrollbar{ + +#lz-comment-box .list::-webkit-scrollbar { width: 3px; border-radius: 5px; } -#lz-comment-box .list .quote{ + +#lz-comment-box .list .quote { border-left: 3px solid #F9DE6D; padding: 2px 0 2px 8px; margin: 10px 15px; color: var(--b3-theme-on-surface); } -#lz-comment-box .quote .delete-quote{ + +#lz-comment-box .quote .delete-quote { color: #f56c6c; font-size: 0.8em; margin-left: 5px; opacity: 0; } -#lz-comment-box .quote:hover .delete-quote{ + +#lz-comment-box .quote:hover .delete-quote { opacity: 1; cursor: pointer; } -#lz-comment-box .list-item{ + +#lz-comment-box .list-item { /* width: calc(100% - 30px); */ padding: 0px 15px 5px 15px; border-bottom: 1px solid var(--b3-border-color); } -#lz-comment-box .list-item:last-child{ + +#lz-comment-box .list-item:last-child { border-bottom: none; } -#lz-comment-box .list-item .header{ + +#lz-comment-box .list-item .header { margin-top: 12px; display: flex; justify-content: space-between; align-items: center; } -#lz-comment-box .list-item .date{ + +#lz-comment-box .list-item .date { color: #999; font-size: 0.9em; } -#lz-comment-box .list-item .actions{ +#lz-comment-box .list-item .actions { display: flex; } -#lz-comment-box .list-item .delete-comment{ + +#lz-comment-box .list-item .delete-comment { color: #f56c6c; font-size: 0.8em; opacity: 0; margin-left: 8px; } -#lz-comment-box .list-item .actions a{ + +#lz-comment-box .list-item .actions a { border: none; } -#lz-comment-box .list-item:hover .delete-comment{ + +#lz-comment-box .list-item:hover .delete-comment { opacity: 1; cursor: pointer; } -#lz-comment-box .list-item .comment{ +#lz-comment-box .list-item .comment { margin: 5px 0; color: var(--b3-theme-on-background); } -#lz-comment-box .add{ + +#lz-comment-box .add { display: flex; - padding: 0 15px ; + padding: 0 15px; } -#lz-comment-box .add .input{ + +#lz-comment-box .add .input { min-height: 30px; background-color: var(--b3-theme-surface); padding: 4px 5px; @@ -156,13 +180,15 @@ strong[style*="quote"]:hover{ outline: none; flex-grow: 1; } -#lz-comment-box .input:empty:before{ + +#lz-comment-box .input:empty:before { content: attr(placeholder); /* content: 'hello'; */ - color:var(--b3-theme-on-surface); + color: var(--b3-theme-on-surface); opacity: 0.4; } -#lz-comment-box .add .btn{ + +#lz-comment-box .add .btn { flex-shrink: 0; height: 30px; line-height: 30px; @@ -177,7 +203,7 @@ strong[style*="quote"]:hover{ /* Toolbar */ -#lz-toolbar{ +#lz-toolbar { font-size: 15px; position: fixed; right: 60px; @@ -191,22 +217,27 @@ strong[style*="quote"]:hover{ padding: 5px 10px; z-index: 99; } -#lz-toolbar.close i:not(.closeBtn){ + +#lz-toolbar.close i:not(.closeBtn) { display: none; } -#lz-toolbar .toolbar-btn{ - display:inline-block; - font-size:20px; - padding:5px; + +#lz-toolbar .toolbar-btn { + display: inline-block; + font-size: 20px; + padding: 5px; margin: 0 5px; - cursor:pointer; + cursor: pointer; } -#lz-toolbar .toolbar-btn:hover,#lz-toolbar .toolbar-btn.show{ + +#lz-toolbar .toolbar-btn:hover, +#lz-toolbar .toolbar-btn.show { color: var(--b3-theme-primary); /* background-color: #efefef; */ } -#lz-toolbar .menu, #lz-toolbar .submenu{ +#lz-toolbar .menu, +#lz-toolbar .submenu { position: fixed; padding: 10px 0px; border-radius: 4px; @@ -219,19 +250,23 @@ strong[style*="quote"]:hover{ max-width: 150px; display: none; } -#lz-toolbar .menu{ + +#lz-toolbar .menu { bottom: 70px; } -#lz-toolbar .submenu{ - right:-999px; + +#lz-toolbar .submenu { + right: -999px; /* position: absolute; */ } -#lz-toolbar .menu.show,#lz-toolbar .submenu.show{ + +#lz-toolbar .menu.show, +#lz-toolbar .submenu.show { display: block; } -.menu-mask{ +.menu-mask { z-index: 98; position: fixed; left: 0; @@ -241,14 +276,16 @@ strong[style*="quote"]:hover{ background-color: transparent; display: none; } -.menu-item{ + +.menu-item { cursor: pointer; padding: 5px 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.menu-item:hover{ + +.menu-item:hover { background-color: var(--b3-list-hover); color: var(--b3-theme-primary); } @@ -268,16 +305,20 @@ strong[style*="quote"]:hover{ top: 30px; font-size: 14px; } -#snackbar.info{ + +#snackbar.info { background-color: #444; } -#snackbar.success{ + +#snackbar.success { background-color: #67c23a; } -#snackbar.danger{ + +#snackbar.danger { background-color: #f56c6c; } -#snackbar.warning{ + +#snackbar.warning { background-color: #e6a23c; } @@ -288,27 +329,55 @@ strong[style*="quote"]:hover{ } @-webkit-keyframes fadein { - from {top: 0; opacity: 0;} - to {top: 30px; opacity: 1;} + from { + top: 0; + opacity: 0; + } + + to { + top: 30px; + opacity: 1; + } } @keyframes fadein { - from {top: 0; opacity: 0;} - to {top: 30px; opacity: 1;} + from { + top: 0; + opacity: 0; + } + + to { + top: 30px; + opacity: 1; + } } @-webkit-keyframes fadeout { - from {top: 30px; opacity: 1;} - to {top: 0; opacity: 0;} + from { + top: 30px; + opacity: 1; + } + + to { + top: 0; + opacity: 0; + } } @keyframes fadeout { - from {top: 30px; opacity: 1;} - to {top: 0; opacity: 0;} + from { + top: 30px; + opacity: 1; + } + + to { + top: 0; + opacity: 0; + } } -#popup{ +#popup { min-width: 120px; max-height: 150px; overflow-y: scroll; @@ -316,27 +385,30 @@ strong[style*="quote"]:hover{ padding: 5px 0; box-shadow: var(--b3-dialog-shadow); background-color: var(--b3-menu-background); - color:var(--b3-theme-on-surface); + color: var(--b3-theme-on-surface); border-radius: 3px; display: none; z-index: 99; } -#popup::-webkit-scrollbar{ +#popup::-webkit-scrollbar { width: 0px; border-radius: 5px; } -.popup-item{ + +.popup-item { font-size: 15px; padding: 5px 10px; } -.popup-item:hover, .popup-item.on{ - background-color: var(--b3-border-color) ; + +.popup-item:hover, +.popup-item.on { + background-color: var(--b3-border-color); cursor: pointer; } -.popup-mask{ +.popup-mask { position: fixed; left: 0; right: 0; @@ -346,17 +418,17 @@ strong[style*="quote"]:hover{ display: none; } -.popup-success{ +.popup-success { background-color: #67c23a23; transition: all ease-in-out 500ms; } -.popup-error{ +.popup-error { background-color: #f56c6c23; transition: all ease-in-out 500ms; } -/* 批注显示批注时间 */ +/* 评论显示评论时间 */ .protyle-wysiwyg div[data-node-id][custom-quote-time]::before { content: attr(custom-quote-time); font-size: 12px; diff --git a/app/comment/comment.js b/app/comment/comment.js index 27d0adc..df1d09d 100644 --- a/app/comment/comment.js +++ b/app/comment/comment.js @@ -6,6 +6,7 @@ import { saveViaTransaction, formatSYDate, dateFormat, + compareVersion, } from './utils.js' import { querySQL, @@ -15,14 +16,18 @@ import { setBlockAttrs, } from './network.js' +const VERSION_LE_2_1_14 = compareVersion( + window.siyuan.config.system.kernelVersion, + '2.1.14', +) <= 0; // 当前版本号 <= v2.1.14 class Comment { constructor() { this.icons = config.icons - this.isShow = false //是否弹出批注框 - setTimeout(() => this.appendToolbarBtn(), 1000) //添加 toolbar 批注按钮 - // setTimeout(()=>this.resolveCommentNodes(),1000) //等待文章内容加载完整后解析批注span todo + this.isShow = false //是否弹出评论框 + setTimeout(() => this.appendToolbarBtn(), 1000) //添加 toolbar 评论按钮 + // setTimeout(()=>this.resolveCommentNodes(),1000) //等待文章内容加载完整后解析评论span todo } async handleKeyDown(e) { @@ -33,7 +38,7 @@ class Comment { // this.showBox(e) // } - // 回车键提交批注 + // 回车键提交评论 if (this.isShow && e.key == 'Enter') { e.preventDefault() e.stopPropagation() @@ -47,7 +52,7 @@ class Comment { } /** - * 渲染弹出框内的批注列表 + * 渲染弹出框内的评论列表 * @param {*} node * @param {*} from 点击来源位置 */ @@ -81,8 +86,8 @@ class Comment {
${formatSYDate(comments[key]['created'])}
-
移除批注
- +
移除评论
+
${comments[key]['content']}
@@ -90,7 +95,7 @@ class Comment { ` } } else { - html += `
暂无批注
` + html += `
暂无评论
` } this.input.setAttribute('data-quote-id', quoteId) @@ -105,22 +110,23 @@ class Comment { } /** - * 提交批注 + * 提交评论 * @returns */ async submitComment() { // 输入框内容为空 if (!this.input.innerText) { this.hiddenBox() - console.log('未填写批注内容'); + console.log('未填写评论内容'); return } // 如果已有 quoteid,则是追加,否则是新增 let quoteId = this.input.dataset.quoteId if (quoteId) { - //追加批注 + //追加评论 let blockId = document.querySelector(`.protyle-wysiwyg [custom-${quoteId}]`).dataset.nodeId //comment 所在 block - let quoteText = document.querySelector(`strong[style*="quote-${quoteId}"]`).innerText + let quoteText = document.querySelector(`strong[style*="quote-${quoteId}"]`)?.innerText + ?? document.querySelector(`span[data-type~=strong][style*="quote-${quoteId}"]`)?.innerText this.appendBlocks(quoteText, blockId, quoteId) let selection = getSelection() selection.removeAllRanges() @@ -128,7 +134,7 @@ class Comment { this.hiddenBox() } else { - //全新批注 + //全新评论 let selection = getSelection() let range = this.range let start = range.startContainer @@ -138,10 +144,16 @@ class Comment { if (start == null) { return } - let block = start //由于没有一炮三响了,所以列表项上无法在属性弹框中看到存储的批注内容 + let block = start //由于没有一炮三响了,所以列表项上无法在属性弹框中看到存储的评论内容 let txt = range.toString() //引用的内容 range.deleteContents() - let strongNode = document.createElement('strong') + + /* 兼容 <= 2.1.14 版本 */ + let strongNode = VERSION_LE_2_1_14 + ? document.createElement('strong') + : document.createElement('span') + if (!VERSION_LE_2_1_14) strongNode.setAttribute('data-type', 'strong') + strongNode.innerText = txt quoteId = createBlockId() this.appendBlocks(txt, block.dataset.nodeId, quoteId) @@ -169,7 +181,7 @@ class Comment { } /** - * 将批注内容以内容块的形式插入到文章尾部 + * 将评论内容以内容块的形式插入到文章尾部 * @param {*} quoteText 引文内容 * @param {*} blockId 引文所在 blockid * @param {*} quoteId 引文 id @@ -179,14 +191,14 @@ class Comment { let background = activeEditor.querySelector('div.protyle:not(.fn__none) .protyle-background') || activeEditor.querySelector('.protyle-background') // 获得桌面端当前编辑的文章 let docId = background.dataset.nodeId //获得当前编辑的文章 id - // 批注 h1 标题 - // let headerHtml = `
批注
` + // 评论 h1 标题 + // let headerHtml = `
评论
` let headerMd = ` -# 批注 +# 评论 {: custom-quote-type="${config.attrs.type.heading}"} ` - // 批注内容块 + // 评论内容块 // let commentHtml = `
${this.input.innerHTML}
` let commentMd = ` ${this.input.innerHTML} @@ -202,7 +214,7 @@ ${this.input.innerHTML} // 分割线 // let hrHtml = `
` - // 先判断是否存在「批注」header,没有则添加,然后依次插入 block(虽然可以一次性批量添加,但不建议,因为可能导致不会及时更新到页面) + // 先判断是否存在「评论」header,没有则添加,然后依次插入 block(虽然可以一次性批量添加,但不建议,因为可能导致不会及时更新到页面) // let header = activeEditor.querySelector('.fn__flex-1.protyle:not(.fn__none) div[style*="comment-header"]') let res = await querySQL(` select @@ -216,7 +228,7 @@ ${this.input.innerHTML} `) // console.log(res) if (res && res.code == 0 && res.data.length == 0) { - // 没有批注标题块,则添加 + // 没有评论标题块,则添加 await this.appendBlockMd(headerMd, docId) } @@ -238,7 +250,7 @@ ${this.input.innerHTML} // console.log(res) if (res && res.code == 0) { if (res.data.length == 0) { - // 没有关联当前批注的超级块(容器块),则添加 + // 没有关联当前评论的超级块(容器块),则添加 let containerMd = ` {{{row ${quoteMd} @@ -255,7 +267,7 @@ ${commentMd} } } - // 如果已经存在之前的引文批注,则直接在其下方插入新批注 + // 如果已经存在之前的引文评论,则直接在其下方插入新评论 // let existQuote = activeEditor.querySelector(`.fn__flex-1.protyle:not(.fn__none) .bq[custom-quote-id*="${quoteId}"]`) // if(existQuote){ // await this.insertBlockDom(commentHtml, existQuote.dataset.nodeId) @@ -266,13 +278,13 @@ ${commentMd} } - /* 批注列表事件,主要是移除批注和引文 */ + /* 评论列表事件,主要是移除评论和引文 */ async handleListEvents(e) { e.stopPropagation() let target = e.target - // 删除批注 + // 删除评论 if (target.className == 'delete-comment') { - // 移除批注按钮 + // 移除评论按钮 let quoteId = target.dataset.quoteId let commentId = target.dataset.commentId let block = document.querySelector(`.protyle-wysiwyg [custom-${quoteId}]`) @@ -282,11 +294,13 @@ ${commentMd} } if (target.className == 'delete-quote') { - // 移除引文按钮, 移除批注块与原文块中的批注 ID 属性 + // 移除引文按钮, 移除评论块与原文块中的评论 ID 属性 let quoteId = target.dataset.quoteId, - quoteNode = document.querySelector(`strong[style*="quote-${quoteId}"]`), + quoteNode = VERSION_LE_2_1_14 + ? document.querySelector(`strong[style*="quote-${quoteId}"]`) + : document.querySelector(`span[data-type~=strong][style*="quote-${quoteId}"]`), block = document.querySelector(`.protyle-wysiwyg [data-node-id][custom-${quoteId}]`) - let blockId = block.dataset.nodeId + if (block) { // 移除 block 中的属性 let attr_key = `custom-${quoteId}` @@ -315,7 +329,7 @@ ${commentMd} saveViaTransaction() } - // 移除文章末尾批注内容 + // 移除文章末尾评论内容 // let nodes = document.querySelectorAll(`div[custom-quote-id="${quoteId}"]`) // if(nodes){ // for(var node of nodes) { @@ -406,7 +420,7 @@ ${commentMd} } /** - * TODO: 批注输入框支持粘贴内容块链接 + * TODO: 评论输入框支持粘贴内容块链接 * @param {*} e */ handlePaste(e) { @@ -463,7 +477,9 @@ ${commentMd} * 解析文章中的 comment 元素 */ resolveCommentNodes() { - let elements = document.querySelectorAll('strong[style*="quote"]') + let elements = VERSION_LE_2_1_14 + ? document.querySelectorAll('strong[style*="quote"]') + : document.querySelector('span[data-type~=strong][style*="quote"]') if (elements) { elements.forEach((item, index, node) => { // 在内容块右侧添加图标 @@ -515,8 +531,10 @@ ${commentMd} if (range) { // 需要进一步判断选取是否是在 strong 标签里面 let start = range.startContainer, end = range.endContainer - if (start.parentElement.tagName == 'STRONG' || end.parentElement.tagName == 'STRONG') { - snackbar('请在非批注区操作', 'warning') + if ((start.parentElement.localName == 'strong' || end.parentElement.localName == 'strong') + || (start.parentElement.localName == 'span' || end.parentElement.localName == 'span') + ) { + snackbar('请不要在行内元素中评论', 'warning') } else if (!range.toString()) { snackbar('没有选中内容', 'danger') } else { @@ -553,7 +571,7 @@ ${commentMd} this.input.focus() } - this.renderCommentsHtml(target, from) //获取批注列表 + this.renderCommentsHtml(target, from) //获取评论列表 // 如果是从 toolbar 触发,box 的坐标不参照事件坐标,而是参照文本选区坐标 if (from == 'toolbar') { @@ -567,7 +585,7 @@ ${commentMd} } /** - * 创建批注框 + * 创建评论框 */ createBox() { let fragment = document.createDocumentFragment() @@ -589,12 +607,12 @@ ${commentMd} this.btn = document.createElement('div') this.btn.className = 'btn' - this.btn.innerText = '批注' + this.btn.innerText = '评论' this.btn.addEventListener('click', async () => this.submitComment()) this.add.appendChild(this.input) this.add.appendChild(this.btn) - //遮罩层,用于实现点击空白处关闭批注框 + //遮罩层,用于实现点击空白处关闭评论框 this.overlay = document.createElement('div') this.overlay.className = 'lz-overlay' this.overlay.addEventListener('click', () => this.hiddenBox()) @@ -608,7 +626,7 @@ ${commentMd} } /** - * 关闭批注框 + * 关闭评论框 */ hiddenBox() { if (this.box) { @@ -658,7 +676,7 @@ ${commentMd} let btn = document.createElement('button') btn.className = 'protyle-toolbar__item b3-tooltips b3-tooltips__n' btn.setAttribute('data-type', 'comment') - btn.setAttribute('aria-label', '批注') + btn.setAttribute('aria-label', '评论') btn.innerHTML = this.icons.comment btn.addEventListener('click', (e) => { btn.parentElement.classList.add('fn__none') //关闭 toolbar diff --git a/app/comment/config.js b/app/comment/config.js index 0b65903..65dd8bf 100644 --- a/app/comment/config.js +++ b/app/comment/config.js @@ -10,7 +10,7 @@ const config = { // 块属性类型值 heading: "heading", // 标题 quote: "quote", // 原文引用 - comment: "comment", // 批注 + comment: "comment", // 评论 container: "container", // 容器 }, }, diff --git a/app/comment/index.js b/app/comment/index.js index 98b5c2c..109f632 100644 --- a/app/comment/index.js +++ b/app/comment/index.js @@ -1,10 +1,10 @@ /** - * 行内批注功能 + * 行内评论功能 * REF: [siyuan-note/siyuan-comment at main · langzhou/siyuan-note · GitHub](https://github.com/langzhou/siyuan-note/tree/main/siyuan-comment) */ import Comment from './comment.js' -import { config } from '../../script/module/b320config.js'; +import { config } from './../../script/module/b320config.js'; class SiyuanUtil { constructor() { @@ -18,6 +18,7 @@ class SiyuanUtil { this.appendStyleSheet() this.comment = new Comment() this.domWatcher() + this.popoverDomWatcher() this.handleEvents() } @@ -65,7 +66,7 @@ class SiyuanUtil { domWatcher() { var targetNode = document.querySelector('.layout__center.fn__flex.fn__flex-1'); if (!targetNode) { - setTimeout(() => { this.domWatcher() }, 300); + setTimeout(() => { this.domWatcher() }, 500); } else { const config = { attributes: false, childList: true, subtree: true }; const callback = (mutationsList, observer) => { @@ -87,7 +88,7 @@ class SiyuanUtil { /* 处理观察对象节点变动事件 */ childListChangedHook(mutation) { // 监听 node added 事件 - if (mutation.addedNodes) { + if (mutation.addedNodes.length > 0) { let node = mutation.addedNodes.item(0) // 新增 protyle 节点,即判断为打开了新文档 if (node && node.className == 'fn__flex-1 protyle') { @@ -102,6 +103,35 @@ class SiyuanUtil { } } + + /* 检测子窗口 dom 变动,用于动态插入元素 */ + popoverDomWatcher() { + const config = { childList: true }; + const callback = (mutationsList, observer) => { + for (let mutation of mutationsList) { + // console.log(mutation) + this.popoverChildListChangedHook(mutation) + } + }; + const observer = new MutationObserver(callback); + observer.observe(document.body, config); + // observer.disconnect(); + } + + + /* 处理观察对象节点变动事件 */ + popoverChildListChangedHook(mutation) { + // 监听 node added 事件 + if (mutation.addedNodes.length > 0) { + let node = mutation.addedNodes.item(0) + // 新增 protyle 节点,即判断为打开了新文档 + if (node && node.classList.contains('block__popover')) { + // 因为 dom 树可能没有完全加载,需要延迟处理 + this.comment.appendToolbarBtn(node.querySelector('.protyle')) + } + } + } + /* 插入样式表 */ appendStyleSheet() { let node = document.querySelector('#protyleHljsStyle') diff --git a/app/comment/utils.js b/app/comment/utils.js index bd77416..9423619 100644 --- a/app/comment/utils.js +++ b/app/comment/utils.js @@ -170,3 +170,47 @@ export function formatSYDate(value, from = "date") { return year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + second } + +/** + * 比较版本号 + * @params {string} version1: 版本号1 + * @params {string} version2: 版本号2 + * @return {number}: 1: v1 > v2; -1: v1 < v2; 0: v1 = v2 + */ +export function compareVersion(version1, version2) { + let v1_arr = version1.split('.'); + let v2_arr = version2.split('.'); + for (let i = 0; i < v1_arr.length; i++) { + let v1, v2; + if (!isNaN(v1_arr[i]) && !isNaN(v2_arr[i])) { // 两者都为数字 + v1 = parseInt(v1_arr[i]); + v2 = parseInt(v2_arr[i]); + } + else if (!isNaN(v1_arr[i]) || !isNaN(v2_arr[i])) { // 其中一者为数字 + v1 = v1_arr[i]; + v2 = v2_arr[i]; + if (v1 == undefined || v2 == undefined) // 其中一者没有更细分的版本号 + return 0; + else if (!isNaN(v1) && isNaN(v2)) // v1 是发行版 | v2 是内测(x-alphaX)/公测版(x-devX) + return 1; + else if (isNaN(v1) && !isNaN(v2)) // v2 是发行版 | v1 是内测(x-alphaX)/公测版(x-devX) + return -1; + else // 意外的情况 + return 0; + } + else { // 都不为数字, 比较字符串, 内测版(alpha)比公测版(dev)版本号小 + v1 = v1_arr[i]; + v2 = v2_arr[i]; + } + switch (true) { + case v1 > v2: + return 1; + case v1 < v2: + return -1; + case v1 === v2: + default: + break; + } + } + return 0; +} diff --git a/app/comment2/comment.css b/app/comment2/comment.css new file mode 100644 index 0000000..4bca6a0 --- /dev/null +++ b/app/comment2/comment.css @@ -0,0 +1,363 @@ +/* Common */ +strong[style="blank"]{ + background-color: var(--b3-theme-on-background); + color: var(--b3-theme-on-background); + font-weight: normal; + /* border: 1px solid var(--b3-theme-on-background); */ + border-radius: 1px; + transition: all 300ms ease-in-out; + /* cursor: pointer; */ +} +strong[style="blank"]:hover{ + color: var(--b3-theme-on-background); + background-color: var(--b3-theme-background); + transition: all 300ms ease-in-out; +} +.lz-overlay, .lz-overlay-black{ + z-index: 999; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: transparent; + transition: opacity 150ms linear; + display: none; + opacity: 1; +} +.lz-overlay-black{ + background-color: rgba(0, 0, 0, 0.2); +} + +/* flex card layout */ +.protyle-wysiwyg.card{ + display: flex; + flex-wrap: wrap; + /* background-color: var(--b3-theme-surface); */ +} +.protyle-wysiwyg.card div[data-node-id]{ + /* box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); */ + margin: 5px; + padding: 10px; + border-radius: 3px; + flex-basis: 200px; + background-color: var(--b3-theme-surface); +} + + +/* Inline comment */ +strong[style*="quote"]{ + border-bottom: 2px solid rgb(255, 212, 0); + background-color: rgba(255,212,0,0.16); + padding-bottom: 1px; + font-weight: normal; +} +strong[style*="quote"]:hover{ + cursor: pointer; + /* background-color: rgb(255, 212, 0); */ +} + +#lz-comment-box{ + font-size: 15px; + display: none; + position: fixed; + z-index: 1000; + left: 800px; + top: 200px; + width: 480px; + border: 1px solid var(--b3-border-color); + box-shadow: var(--b3-dialog-shadow); + background-color: var(--b3-theme-background); + border-radius: 3px; + padding: 10px 0; +} +#lz-comment-box a{ + border-bottom: 1px solid var(--b3-theme-primary); +} +#lz-comment-box a:hover{ + text-decoration: none; +} +#lz-comment-box .list{ + width: 100%; + max-height: 370px; + overflow-y: scroll; +} +#lz-comment-box .list::-webkit-scrollbar{ + width: 3px; + border-radius: 5px; +} +#lz-comment-box .list .quote{ + border-left: 3px solid #F9DE6D; + padding: 2px 0 2px 8px; + margin: 10px 15px; + color: var(--b3-theme-on-surface); +} +#lz-comment-box .quote .delete-quote{ + color: #f56c6c; + font-size: 0.8em; + margin-left: 5px; + opacity: 0; +} +#lz-comment-box .quote:hover .delete-quote{ + opacity: 1; + cursor: pointer; +} +#lz-comment-box .list-item{ + /* width: calc(100% - 30px); */ + padding: 0px 15px 5px 15px; + border-bottom: 1px solid var(--b3-border-color); +} +#lz-comment-box .list-item:last-child{ + border-bottom: none; +} +#lz-comment-box .list-item .header{ + margin-top: 12px; + display: flex; + justify-content: space-between; + align-items: center; +} +#lz-comment-box .list-item .date{ + color: #999; + font-size: 0.9em; +} + +#lz-comment-box .list-item .actions{ + display: flex; +} +#lz-comment-box .list-item .delete-comment{ + color: #f56c6c; + font-size: 0.8em; + opacity: 0; + margin-left: 8px; +} +#lz-comment-box .list-item .actions a{ + border: none; +} +#lz-comment-box .list-item:hover .delete-comment{ + opacity: 1; + cursor: pointer; +} + +#lz-comment-box .list-item .comment{ + margin: 5px 0; + color: var(--b3-theme-on-background); +} +#lz-comment-box .add{ + display: flex; + padding: 0 15px ; +} +#lz-comment-box .add .input{ + min-height: 30px; + background-color: var(--b3-theme-surface); + padding: 4px 5px; + line-height: 30px; + color: var(--b3-theme-on-background); + border-radius: 3px; + outline: none; + flex-grow: 1; +} +#lz-comment-box .input:empty:before{ + content: attr(placeholder); + /* content: 'hello'; */ + color:var(--b3-theme-on-surface); + opacity: 0.4; +} +#lz-comment-box .add .btn{ + flex-shrink: 0; + height: 30px; + line-height: 30px; + text-align: center; + padding: 4px 10px; + background-color: var(--b3-theme-primary); + color: var(--b3-theme-on-primary); + border-radius: 3px; + margin-left: 8px; + cursor: pointer; +} + + +/* Toolbar */ +#lz-toolbar{ + font-size: 15px; + position: fixed; + right: 60px; + bottom: 25px; + background-color: #fefefe; + background-color: var(--b3-theme-background); + box-shadow: var(--b3-point-shadow); + /* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.14); */ + /* border: 1px solid #efefef; */ + border-radius: 20px; + padding: 5px 10px; + z-index: 99; +} +#lz-toolbar.close i:not(.closeBtn){ + display: none; +} +#lz-toolbar .toolbar-btn{ + display:inline-block; + font-size:20px; + padding:5px; + margin: 0 5px; + cursor:pointer; +} +#lz-toolbar .toolbar-btn:hover,#lz-toolbar .toolbar-btn.show{ + color: var(--b3-theme-primary); + /* background-color: #efefef; */ +} + +#lz-toolbar .menu, #lz-toolbar .submenu{ + position: fixed; + padding: 10px 0px; + border-radius: 4px; + background-color: white; + background-color: var(--b3-theme-background); + /* box-shadow: 0 0px 2px rgba(0, 0, 0, 0.14); */ + box-shadow: var(--b3-point-shadow); + z-index: 99; + min-width: 100px; + max-width: 150px; + display: none; +} +#lz-toolbar .menu{ + bottom: 70px; +} +#lz-toolbar .submenu{ + right:-999px; + /* position: absolute; */ + +} +#lz-toolbar .menu.show,#lz-toolbar .submenu.show{ + display: block; +} + +.menu-mask{ + z-index: 98; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: transparent; + display: none; +} +.menu-item{ + cursor: pointer; + padding: 5px 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.menu-item:hover{ + background-color: var(--b3-list-hover); + color: var(--b3-theme-primary); +} + +#snackbar { + visibility: hidden; + min-width: 120px; + margin-left: -125px; + background-color: #666; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 10px; + position: fixed; + z-index: 10000; + left: 50%; + top: 30px; + font-size: 14px; +} +#snackbar.info{ + background-color: #444; +} +#snackbar.success{ + background-color: #67c23a; +} +#snackbar.danger{ + background-color: #f56c6c; +} +#snackbar.warning{ + background-color: #e6a23c; +} + +#snackbar.show { + visibility: visible; + -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; + animation: fadein 0.5s, fadeout 0.5s 2.5s; +} + +@-webkit-keyframes fadein { + from {top: 0; opacity: 0;} + to {top: 30px; opacity: 1;} +} + +@keyframes fadein { + from {top: 0; opacity: 0;} + to {top: 30px; opacity: 1;} +} + +@-webkit-keyframes fadeout { + from {top: 30px; opacity: 1;} + to {top: 0; opacity: 0;} +} + +@keyframes fadeout { + from {top: 30px; opacity: 1;} + to {top: 0; opacity: 0;} +} + + +#popup{ + min-width: 120px; + max-height: 150px; + overflow-y: scroll; + position: absolute; + padding: 5px 0; + box-shadow: var(--b3-dialog-shadow); + background-color: var(--b3-menu-background); + color:var(--b3-theme-on-surface); + border-radius: 3px; + display: none; + z-index: 99; +} + +#popup::-webkit-scrollbar{ + width: 0px; + border-radius: 5px; +} +.popup-item{ + font-size: 15px; + padding: 5px 10px; +} +.popup-item:hover, .popup-item.on{ + background-color: var(--b3-border-color) ; + cursor: pointer; +} + + +.popup-mask{ + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 98; + display: none; +} + +.popup-success{ + background-color: #67c23a23; + transition: all ease-in-out 500ms; +} + +.popup-error{ + background-color: #f56c6c23; + transition: all ease-in-out 500ms; +} + +/* 批注显示批注时间 */ +.protyle-wysiwyg div[data-node-id][custom-quote-time]::before { + content: attr(custom-quote-time); + font-size: 12px; +} diff --git a/app/comment2/comment.js b/app/comment2/comment.js new file mode 100644 index 0000000..27d0adc --- /dev/null +++ b/app/comment2/comment.js @@ -0,0 +1,674 @@ +import config from './config.js' +import { + snackbar, + computeBoxPosition, + createBlockId, + saveViaTransaction, + formatSYDate, + dateFormat, +} from './utils.js' +import { + querySQL, + insertBlock, + appendBlock, + deleteBlock, + setBlockAttrs, +} from './network.js' + + +class Comment { + + constructor() { + this.icons = config.icons + this.isShow = false //是否弹出批注框 + setTimeout(() => this.appendToolbarBtn(), 1000) //添加 toolbar 批注按钮 + // setTimeout(()=>this.resolveCommentNodes(),1000) //等待文章内容加载完整后解析批注span todo + } + + async handleKeyDown(e) { + // 监听组合快捷键(暂时没用) + // if(e.shiftKey && e.altKey && e.code =='KeyC'){ + // e.preventDefault() + // e.stopPropagation() + // this.showBox(e) + // } + + // 回车键提交批注 + if (this.isShow && e.key == 'Enter') { + e.preventDefault() + e.stopPropagation() + await this.submitComment() + } + + // esc 关闭 box + if (this.isShow && e.key == 'Escape') { + this.hiddenBox() + } + } + + /** + * 渲染弹出框内的批注列表 + * @param {*} node + * @param {*} from 点击来源位置 + */ + async renderCommentsHtml(node, from) { + + let html = '' + + switch (from) { + case 'toolbar': + html = `
${this.range.toString()}
` + this.list.innerHTML = html + break + case 'attr': + let item = node + this.list.innerHTML = '//Todo: fetch all comments in the block' + break + case 'block': + let quoteId = node.getAttribute('style'); + if (quoteId && quoteId.indexOf('quote') > -1) { + quoteId = quoteId.replace("quote-", "") //移除 style 属性中用于表示的“quote”,获得原始 id + let sql = `select * from blocks as b left join attributes as a on b.id = a.block_id where a.name = 'custom-quote-id' and a.value = '${quoteId}' and b.type = 'p' order by b.created`, + res = await querySQL(sql), + quote = node.innerText, + comments = res.data + html += `
${quote}移除引文
` + + if (res.data.length > 0) { + for (let key in comments) { + html += ` +
+
+
${formatSYDate(comments[key]['created'])}
+
+
移除批注
+ +
+
+
${comments[key]['content']}
+
+ ` + } + } else { + html += `
暂无批注
` + } + + this.input.setAttribute('data-quote-id', quoteId) + this.list.innerHTML = html + } + + break + + default: + break + } + } + + /** + * 提交批注 + * @returns + */ + async submitComment() { + // 输入框内容为空 + if (!this.input.innerText) { + this.hiddenBox() + console.log('未填写批注内容'); + return + } + // 如果已有 quoteid,则是追加,否则是新增 + let quoteId = this.input.dataset.quoteId + if (quoteId) { + //追加批注 + let blockId = document.querySelector(`.protyle-wysiwyg [custom-${quoteId}]`).dataset.nodeId //comment 所在 block + let quoteText = document.querySelector(`strong[style*="quote-${quoteId}"]`).innerText + this.appendBlocks(quoteText, blockId, quoteId) + let selection = getSelection() + selection.removeAllRanges() + selection.addRange(this.range) // 使得 protyle 获得光标 + this.hiddenBox() + + } else { + //全新批注 + let selection = getSelection() + let range = this.range + let start = range.startContainer + while (start != null && (start.dataset == null || start.dataset.nodeId == null)) { + start = start.parentElement + } + if (start == null) { + return + } + let block = start //由于没有一炮三响了,所以列表项上无法在属性弹框中看到存储的批注内容 + let txt = range.toString() //引用的内容 + range.deleteContents() + let strongNode = document.createElement('strong') + strongNode.innerText = txt + quoteId = createBlockId() + this.appendBlocks(txt, block.dataset.nodeId, quoteId) + strongNode.setAttribute('style', 'quote-' + quoteId) + + let attr = { key: `custom-${quoteId}`, value: "true" } + block.setAttribute(attr.key, attr.value) + // 使用 API 设置块属性 + await setBlockAttrs({ + id: block.dataset.nodeId, + attrs: { + [attr.key]: attr.value, + }, + }) + range.insertNode(strongNode) + range.setStartAfter(strongNode) + range.collapse(true) //取消文本选择状态 + selection.removeAllRanges() + selection.addRange(range) + this.hiddenBox() + } + + saveViaTransaction() + + } + + /** + * 将批注内容以内容块的形式插入到文章尾部 + * @param {*} quoteText 引文内容 + * @param {*} blockId 引文所在 blockid + * @param {*} quoteId 引文 id + */ + async appendBlocks(quoteText, blockId, quoteId) { + let activeEditor = document.querySelector('.layout__center [data-type="wnd"].layout__wnd--active') || document.querySelector('.layout__center [data-type="wnd"]') || document.getElementById('editor') //获得当前光标所在页面 + let background = activeEditor.querySelector('div.protyle:not(.fn__none) .protyle-background') || activeEditor.querySelector('.protyle-background') // 获得桌面端当前编辑的文章 + let docId = background.dataset.nodeId //获得当前编辑的文章 id + + // 批注 h1 标题 + // let headerHtml = `
批注
` + let headerMd = ` +# 批注 +{: custom-quote-type="${config.attrs.type.heading}"} +` + + // 批注内容块 + // let commentHtml = `
${this.input.innerHTML}
` + let commentMd = ` +${this.input.innerHTML} +{: custom-quote-id="${quoteId}" custom-quote-type="${config.attrs.type.comment}" custom-quote-time="${dateFormat("YYYY-mm-dd HH:MM:SS", new Date())}"} +` + + // 引文内容块 + // let quoteHtml = `
${quoteText}
` + let quoteMd = ` +> [${quoteText}](siyuan://blocks/${blockId}) +{: custom-quote-id="${quoteId}" custom-quote-type="${config.attrs.type.quote}"} +` + + // 分割线 + // let hrHtml = `
` + // 先判断是否存在「批注」header,没有则添加,然后依次插入 block(虽然可以一次性批量添加,但不建议,因为可能导致不会及时更新到页面) + // let header = activeEditor.querySelector('.fn__flex-1.protyle:not(.fn__none) div[style*="comment-header"]') + let res = await querySQL(` + select + * + from + attributes as a + where + a.root_id = '${docId}' + and a.name = 'custom-quote-type' + and a.value = '${config.attrs.type.heading}' + `) + // console.log(res) + if (res && res.code == 0 && res.data.length == 0) { + // 没有批注标题块,则添加 + await this.appendBlockMd(headerMd, docId) + } + + res = await querySQL(` + select + b.id + from + blocks as b + inner join + attributes as a + on + a.block_id = b.id + where + a.root_id = '${docId}' + and a.name = 'custom-quote-id' + and a.value = '${quoteId}' + and b.type = 's' + `) + // console.log(res) + if (res && res.code == 0) { + if (res.data.length == 0) { + // 没有关联当前批注的超级块(容器块),则添加 + let containerMd = ` +{{{row +${quoteMd} + +${commentMd} +}}} +{: custom-quote-id="${quoteId}" custom-quote-type="${config.attrs.type.container}"} +` + await this.appendBlockMd(containerMd, docId) + } + else if (res.data.length == 1) { + let containerId = res.data[0].id + await this.appendBlockMd(commentMd, containerId) + } + } + + // 如果已经存在之前的引文批注,则直接在其下方插入新批注 + // let existQuote = activeEditor.querySelector(`.fn__flex-1.protyle:not(.fn__none) .bq[custom-quote-id*="${quoteId}"]`) + // if(existQuote){ + // await this.insertBlockDom(commentHtml, existQuote.dataset.nodeId) + // }else{ + // await this.appendBlockDom(quoteHtml, docId) + // await this.appendBlockDom(commentHtml, docId) + // } + + } + + /* 批注列表事件,主要是移除批注和引文 */ + async handleListEvents(e) { + e.stopPropagation() + let target = e.target + // 删除批注 + if (target.className == 'delete-comment') { + // 移除批注按钮 + let quoteId = target.dataset.quoteId + let commentId = target.dataset.commentId + let block = document.querySelector(`.protyle-wysiwyg [custom-${quoteId}]`) + deleteBlock(commentId) + target.parentNode.parentNode.parentNode.remove() + return + } + + if (target.className == 'delete-quote') { + // 移除引文按钮, 移除批注块与原文块中的批注 ID 属性 + let quoteId = target.dataset.quoteId, + quoteNode = document.querySelector(`strong[style*="quote-${quoteId}"]`), + block = document.querySelector(`.protyle-wysiwyg [data-node-id][custom-${quoteId}]`) + let blockId = block.dataset.nodeId + if (block) { + // 移除 block 中的属性 + let attr_key = `custom-${quoteId}` + block.removeAttribute(attr_key) + // 使用 API 移除块属性 + await setBlockAttrs({ + id: block.dataset.nodeId, + attrs: { + [attr_key]: '', + }, + }) + } + if (quoteNode) { + // 移除 strong 标签 + let selection = getSelection(), + range = document.createRange(), + text = document.createTextNode(quoteNode.innerText) + range.setStart(quoteNode.firstChild, 0) + range.setEnd(quoteNode.firstChild, quoteNode.firstChild.length) + selection.removeAllRanges() + selection.addRange(range) + quoteNode.remove() + range.deleteContents() + range.insertNode(text) + range.setStartAfter(text) + saveViaTransaction() + } + + // 移除文章末尾批注内容 + // let nodes = document.querySelectorAll(`div[custom-quote-id="${quoteId}"]`) + // if(nodes){ + // for(var node of nodes) { + // let blockId = node.dataset.nodeId + // if(blockId){ + // deleteBlock(blockId) + // } + // } + // } + let res = await querySQL(` + select + b.id + from + blocks as b + inner join + attributes as a + on + a.block_id = b.id + where + a.name = 'custom-quote-id' + and a.value = '${quoteId}' + and b.type = 's' + `) + // console.log(res) + if (res && res.code == 0) { + if (res.data.length == 1) { + await deleteBlock(res.data[0].id) + } + } + } + this.hiddenBox() + } + + /** + * 插入新块 + * @param {*} html + * @param {*} previousId 前一个块的位置 + * @returns + */ + insertBlockDom(html, previousId) { + return insertBlock({ + "data": html, + "dataType": "dom", + "previousID": previousId + }) + } + + /** + * 以 markdown 的形式插入新块 + * @param {*} html + * @param {*} previousId 前一个块的位置 + * @returns + */ + insertBlockMd(md, previousId) { + return insertBlock({ + "data": md, + "dataType": "markdown", + "previousID": previousId + }) + } + + /** + * 以 dom 的形式插入后置子块 + * @param {string} html + * @param {string} parentId + * @returns + */ + appendBlockDom(html, parentId) { + return appendBlock({ + "data": html, + "dataType": "dom", + "parentID": parentId + }) + } + + /** + * 以 markdown 的形式插入后置子块 + * @param {string} md + * @param {string} parentId + * @returns + */ + appendBlockMd(md, parentId) { + return appendBlock({ + "data": md, + "dataType": "markdown", + "parentID": parentId + }) + } + + /** + * TODO: 批注输入框支持粘贴内容块链接 + * @param {*} e + */ + handlePaste(e) { + e.stopPropagation() + const clipdata = e.clipboardData || window.clipboardData; + const data = clipdata.getData("text/plain") + let selection = getSelection() + if (data && selection.toString()) { + let reg1 = /.*\(\((\d{14}-.*)\)\).*/ //匹配格式:((20210815214330-btqo1b2)) + let reg2 = /.*siyuan:\/\/blocks\/(\d{14}-\S{7})/ //匹配格式:siyuan://blocks/20210815214330-btqo1b2 + let result = data.match(reg1) || data.match(reg2) + if (result) { + e.preventDefault() + let link = document.createElement('a') + link.setAttribute('href', 'siyuan://blocks/' + result[1]) + link.innerText = selection.toString() + let range = selection.getRangeAt(0) + range.deleteContents() + range.insertNode(link) + range.setStartAfter(link) + } + } + } + + /** + * 响应文本选择事件 + * @param {event} e + */ + handleSelectionEvent(e) { + let node = e.target, inProtyle = false + // 判断事件是否发生在 protyle 中 + while (node != document) { + if (node.classList.contains('protyle-wysiwyg')) { + inProtyle = true + break + } + node = node.parentNode + } + + if (inProtyle) { + let selection = getSelection() + // 获得文本选择事件的坐标,用于确定弹出 comment box 的位置 + if (selection.rangeCount > 0 && selection.getRangeAt(0).toString()) { + this.selectionX = e.clientX + this.selectionY = e.clientY + } else { + this.selectionX = null + this.selectionY = null + } + } + } + + /** + * 解析文章中的 comment 元素 + */ + resolveCommentNodes() { + let elements = document.querySelectorAll('strong[style*="quote"]') + if (elements) { + elements.forEach((item, index, node) => { + // 在内容块右侧添加图标 + this.createBlockIcon(item.parentElement) + }) + } + } + + /** + * 在内容块右侧添加图标 + * @param {*} contentBlock + */ + createBlockIcon(contentBlock) { + let sibling = contentBlock.nextSibling + if (sibling && !sibling.querySelector('.protyle-attr--comment')) { + let div = document.createElement('div') + div.className = 'protyle-attr--comment' + div.innerHTML = this.icons.comment + div.addEventListener('click', (e) => this.showBox(e)) + contentBlock.nextSibling.appendChild(div) + } + } + + /** + * 弹出 box + * @param {*} e + */ + showBox(e) { + let show = false, //用来决定是否弹出 box + from = '', //判断弹出 box 点击来源 + x = e.clientX, //事件坐标,用于计算弹框位置 + y = e.clientY, + target = e.target, + parent = target.parentNode || target, + grandParent = parent.parentNode || target, //可能会点击到按钮中的svg、path 元素,所以需要获取父级元素 + style = target.getAttribute('style') //获取 strong 的 style 属性 + + // 如果之前不存在box,则创建 + if (!this.box) { this.createBox() } + + // 首先根据点击事件来源决定哪些情况下要弹出 box + if (target != null && target.dataset != null && target.dataset.type == 'comment' + || parent != null && parent.dataset != null && parent.dataset.type == 'comment' + || grandParent != null && grandParent.dataset != null && grandParent.dataset.type == 'comment') { + // 1)点击 toolbar 图标触发 + e.stopPropagation() + let selection = getSelection(), + range = selection.getRangeAt(0) + if (range) { + // 需要进一步判断选取是否是在 strong 标签里面 + let start = range.startContainer, end = range.endContainer + if (start.parentElement.tagName == 'STRONG' || end.parentElement.tagName == 'STRONG') { + snackbar('请在非批注区操作', 'warning') + } else if (!range.toString()) { + snackbar('没有选中内容', 'danger') + } else { + this.range = range // 因为弹出 box 后,选区会消失,所以提前存储 range + show = true + from = 'toolbar' + } + } + + } else + if (style && style.indexOf('quote') > -1 && getSelection().toString() == '') { + // 2)点击 block 引文触发 + e.stopPropagation() + show = true + from = 'block' + this.range = getSelection().getRangeAt(0) + } else if (target.classList && target.classList.contains('protyle-attr--comment') + || parent.classList && parent.classList.contains('protyle-attr--comment') + || grandParent.classList && grandParent.classList.contains('protyle-attr--comment')) { + // 3)点击内容块右侧图标触发 + e.stopPropagation() + show = true + from = 'attr' + } + + if (show) { + this.isShow = true + this.box.style.display = 'block' + this.overlay.style.display = 'block' + if (from == 'attr') { + this.add.style.display = 'none' //点击attr区图标时不展示输入框 + } else { + this.add.style.display = 'flex' + this.input.focus() + } + + this.renderCommentsHtml(target, from) //获取批注列表 + + // 如果是从 toolbar 触发,box 的坐标不参照事件坐标,而是参照文本选区坐标 + if (from == 'toolbar') { + x = this.selectionX || x + y = this.selectionY || y + } + let p = computeBoxPosition(this.box, x, y) + this.box.style.left = p.x + 'px' + this.box.style.top = p.y + 'px' + } + } + + /** + * 创建批注框 + */ + createBox() { + let fragment = document.createDocumentFragment() + this.box = document.createElement('div') + this.box.id = 'lz-comment-box' + this.list = document.createElement('div') + this.list.className = 'list' + this.list.addEventListener('click', async e => this.handleListEvents(e)) + + this.add = document.createElement('div') + this.add.className = 'add' + this.input = document.createElement('div') + this.input.setAttribute('contenteditable', true) + this.input.className = 'input' + this.input.setAttribute('placeholder', '说点啥呗 ..') + this.input.setAttribute('spellcheck', false) + this.input.setAttribute('data-quote-id', '') + this.input.addEventListener('paste', e => this.handlePaste(e)) + + this.btn = document.createElement('div') + this.btn.className = 'btn' + this.btn.innerText = '批注' + this.btn.addEventListener('click', async () => this.submitComment()) + this.add.appendChild(this.input) + this.add.appendChild(this.btn) + + //遮罩层,用于实现点击空白处关闭批注框 + this.overlay = document.createElement('div') + this.overlay.className = 'lz-overlay' + this.overlay.addEventListener('click', () => this.hiddenBox()) + + this.box.appendChild(this.list) + this.box.appendChild(this.add) + + fragment.appendChild(this.box) + fragment.appendChild(this.overlay) + document.body.appendChild(fragment) + } + + /** + * 关闭批注框 + */ + hiddenBox() { + if (this.box) { + this.box.style.display = 'none' + this.overlay.style.display = 'none' + this.input.innerText = '' + this.input.setAttribute('data-quote-id', '') + this.isShow = false + } + } + + /** + * 往 toolbar 中添加按钮 + * @param {node} protyle 需要添加功能按钮的 protyle editor + */ + appendToolbarBtn(protyle) { + if (protyle) { + // 处理新增的 protyle + let icon = protyle.querySelector('[data-type="comment"]') + if (!icon) { + let toolbar = protyle.querySelector('.protyle-toolbar') + let fragment = this.createToolbarBtn() + toolbar.appendChild(fragment) + } + } else { + // 初始化时找到所有 protyle-toolbar + let toolbars = document.querySelectorAll('.protyle-toolbar') + if (toolbars) { + toolbars.forEach((item, index, node) => { + if (!item.querySelector('[data-type="comment"]')) { + let fragment = this.createToolbarBtn() + item.appendChild(fragment) + } + }) + } + } + } + + /** + * 创建 toolbar 功能按钮 + * @returns + */ + createToolbarBtn() { + let fragment = document.createDocumentFragment() + let divider = document.createElement('div') + divider.className = 'protyle-toolbar__divider' + let btn = document.createElement('button') + btn.className = 'protyle-toolbar__item b3-tooltips b3-tooltips__n' + btn.setAttribute('data-type', 'comment') + btn.setAttribute('aria-label', '批注') + btn.innerHTML = this.icons.comment + btn.addEventListener('click', (e) => { + btn.parentElement.classList.add('fn__none') //关闭 toolbar + this.showBox(e) + }) + fragment.appendChild(divider) + fragment.appendChild(btn) + return fragment + } + +} + +export default Comment diff --git a/app/comment2/config.js b/app/comment2/config.js new file mode 100644 index 0000000..0b65903 --- /dev/null +++ b/app/comment2/config.js @@ -0,0 +1,19 @@ +const config = { + api_token: "", //在 设置 - 关于 里查看 API token + icons: { + comment: '', + brush: '', + }, + attrs: { + // 块属性 + type: { + // 块属性类型值 + heading: "heading", // 标题 + quote: "quote", // 原文引用 + comment: "comment", // 批注 + container: "container", // 容器 + }, + }, +} + +export default config diff --git a/app/comment2/index.js b/app/comment2/index.js new file mode 100644 index 0000000..98b5c2c --- /dev/null +++ b/app/comment2/index.js @@ -0,0 +1,132 @@ +/** + * 行内批注功能 + * REF: [siyuan-note/siyuan-comment at main · langzhou/siyuan-note · GitHub](https://github.com/langzhou/siyuan-note/tree/main/siyuan-comment) + */ + +import Comment from './comment.js' +import { config } from '../../script/module/b320config.js'; + +class SiyuanUtil { + constructor() { + // this.themeName = this.getThemeName() + // if(this.themeName){ + // this.appendStyleSheet() + // this.comment = new Comment() + // this.domWatcher() + // this.handleEvents() + // } + this.appendStyleSheet() + this.comment = new Comment() + this.domWatcher() + this.handleEvents() + } + + /* 事件委托 */ + handleEvents() { + // 按键按下事件 + window.addEventListener('keydown', e => { + // this.shortcutKey(e) + if (this.comment) this.comment.handleKeyDown(e) + }) + + // 输入防抖 + // window.addEventListener('keyup',lodash.debounce(e =>{ + // if(this.searchBox) this.searchBox.handleInput(e) + // },800)) + + // 按键弹起事件 + // window.addEventListener('keyup',e =>{ + // if(this.searchBox) this.searchBox.actionTrigger(e) + // }) + + // 鼠标单击事件 + window.addEventListener('click', e => { + if (this.comment) this.comment.showBox(e) + }) + + // 鼠标松开事件 + window.addEventListener('mouseup', e => { + if (this.comment) this.comment.handleSelectionEvent(e) + }) + } + + // 获取当前主题名称 + // getThemeName(){ + // let themeStyle = document.querySelector('#themeStyle') + // if(themeStyle){ + // let url = themeStyle.getAttribute('href').split('/') + // return url[url.length - 2] + // }else{ + // setTimeout(()=>this.getThemeName(),500) + // } + // } + + /* 检测 dom 变动,用于动态插入元素 */ + domWatcher() { + var targetNode = document.querySelector('.layout__center.fn__flex.fn__flex-1'); + if (!targetNode) { + setTimeout(() => { this.domWatcher() }, 300); + } else { + const config = { attributes: false, childList: true, subtree: true }; + const callback = (mutationsList, observer) => { + for (let mutation of mutationsList) { + if (mutation.type === 'childList') { + this.childListChangedHook(mutation) + } + else if (mutation.type === 'attributes') { + console.log('The ' + mutation.attributeName + ' attribute was modified.'); + } + } + }; + const observer = new MutationObserver(callback); + observer.observe(targetNode, config); + // observer.disconnect(); + } + } + + /* 处理观察对象节点变动事件 */ + childListChangedHook(mutation) { + // 监听 node added 事件 + if (mutation.addedNodes) { + let node = mutation.addedNodes.item(0) + // 新增 protyle 节点,即判断为打开了新文档 + if (node && node.className == 'fn__flex-1 protyle') { + // 因为 dom 树可能没有完全加载,需要延迟处理 + setTimeout(() => { + if (this.comment) { + this.comment.appendToolbarBtn() + // this.comment.resolveCommentNodes() //todo + } + }, 1000) + } + } + } + + /* 插入样式表 */ + appendStyleSheet() { + let node = document.querySelector('#protyleHljsStyle') + if (!node) { + setTimeout(() => { + this.appendStyleSheet() + }, 500); + } else { + let fragment = document.createDocumentFragment() + let css = document.createElement('link') + css.setAttribute('type', 'text/css') + css.setAttribute('rel', 'stylesheet') + css.setAttribute('href', '/appearance/themes/Dark+/app/comment/comment.css') + fragment.appendChild(css) + document.head.insertBefore(fragment, node) + } + } +} + +(() => { + try { + if (config.theme.comment.enable) { + new SiyuanUtil() + } + } catch (err) { + console.error(err); + } +})(); diff --git a/app/comment2/network.js b/app/comment2/network.js new file mode 100644 index 0000000..11956b1 --- /dev/null +++ b/app/comment2/network.js @@ -0,0 +1,101 @@ +/** + * 设置属性 + * @param {object} data + * @returns + */ +export function setBlockAttrs(data) { + return request("/api/attr/setBlockAttrs", data) +} + +export function insertBlock(data) { + return request("/api/block/insertBlock", data) +} + +export function prependBlock(data) { + return request("/api/block/prependBlock", data) +} + +export function appendBlock(data) { + return request("/api/block/appendBlock", data) +} + +export function updateBlock(data) { + return request("/api/block/updateBlock", data) +} + +export function deleteBlock(id) { + return request("/api/block/deleteBlock", { "id": id }) +} + +export function querySQL(sql) { + return request("/api/query/sql", { "stmt": sql }) +} + +/** + * 网络请求 + * @param {*} url 请求地址 + * @param {object} data + * @param {*} method 请求方法 get post + * @returns + */ +export function request(url, data, method = 'POST') { + return new Promise((resolve, reject) => { + if (method.toUpperCase() == 'POST') { + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + // body:data + }).then(handleResponse) + .then(data => resolve(data)) + .then(error => reject(error)) + + } else { + fetch(url) + .then(handleResponse) + .then(data => resolve(data)) + .then(error => reject(error)) + } + }) + + function handleResponse(response) { + let contentType = response.headers.get('content-type') + if (contentType.includes('application/json')) { + return handleJSONResponse(response) + } else if (contentType.includes('text/html')) { + return handleTextResponse(response) + } else { + throw new Error(`Sorry, content-type ${contentType} not supported`) + } + } + + function handleJSONResponse(response) { + return response.json() + .then(json => { + if (response.ok) { + return json + } else { + return Promise.reject(Object.assign({}, json, { + status: response.status, + statusText: response.statusText + })) + } + }) + } + function handleTextResponse(response) { + return response.text() + .then(text => { + if (response.ok) { + return text + } else { + return Promise.reject({ + status: response.status, + statusText: response.statusText, + err: text + }) + } + }) + } +} diff --git a/app/comment2/utils.js b/app/comment2/utils.js new file mode 100644 index 0000000..bd77416 --- /dev/null +++ b/app/comment2/utils.js @@ -0,0 +1,172 @@ +/* +** randomWord 产生任意长度随机字母数字组合 +** randomFlag-是否任意长度 min-任意长度最小位[固定位数] max-任意长度最大位 +** xuanfeng 2014-08-28 +*/ + +function randomWord(randomFlag, min, max) { + var str = "", + range = min, + arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; + + // 随机产生 + if (randomFlag) { + range = Math.round(Math.random() * (max - min)) + min; + } + for (var i = 0; i < range; i++) { + let pos = Math.round(Math.random() * (arr.length - 1)); + str += arr[pos]; + } + return str; +} + +export function dateFormat(fmt, date) { + let ret; + const opt = { + "Y+": date.getFullYear().toString(), // 年 + "m+": (date.getMonth() + 1).toString(), // 月 + "d+": date.getDate().toString(), // 日 + "H+": date.getHours().toString(), // 时 + "M+": date.getMinutes().toString(), // 分 + "S+": date.getSeconds().toString(), + "s+": date.getMilliseconds().toString() // 毫秒 + // 有其他格式化字符需求可以继续添加,必须转化成字符串 + }; + for (let k in opt) { + ret = new RegExp("(" + k + ")").exec(fmt); + if (ret) { + fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) + }; + }; + return fmt; +} + +/** + * 构建符合规范的 blockid + * @param {bool} suffix 是否添加 7 为字母数字组合的后缀,默认为不添加,仅通过日期构建 id + * @returns blockid + */ +export function createBlockId(suffix = true) { + let id = dateFormat("YYYYmmddHHMMSS", new Date()) + if (suffix) { + id += "-" + randomWord(true, 7, 7) + } + return id +} + +/** + * 创建弹框遮罩 + * @param {*} obj 对象句柄 + * @param {*} type 弹框样式: black 半透明遮罩;default 透明遮罩 + * @returns + */ +export function createOverlay(obj = this, type) { + let className = '' + if (type == 'black') { + className = 'lz-overlay-black' + } else { + className = 'lz-overlay' + } + let overlay = document.querySelector(`.${className}`) + if (overlay) { + obj.overlay = overlay + } else { + obj.overlay = document.createElement('div') + obj.overlay.className = className + } + return obj.overlay +} + + +/** + * 通过触发 protyle input 事件来保存 block 内容,需要确保 protyle 获得焦点 + */ +export function saveViaTransaction() { + let protyle = document.querySelector('.fn__flex-1.protyle:not(.fn__none) .protyle-wysiwyg.protyle-wysiwyg--attr') //需要获取到当前正在编辑的 protyle + let e = document.createEvent('HTMLEvents') + e.initEvent('input', true, false) + protyle.dispatchEvent(e) +} + +/** + * 消息提示 toast + * @param text 提示文案 + * @param type 样式,取值:info / success / danger / warning + **/ +export function snackbar(text, type = 'info') { + let snackbar = document.querySelector('#snackbar') + if (!snackbar) { + snackbar = document.createElement('div') + snackbar.id = 'snackbar' + document.body.appendChild(snackbar) + } + snackbar.classList.add('show', type) + snackbar.innerText = text + setTimeout(function () { snackbar.classList.remove("show", type); }, 3000); +} + +/** + * 计算弹出框的坐标位置,使得 box 不会超出页面范围 + * @param box 元素 node + * @param x 事件 Event x 坐标 + * @param y 事件 Event y 坐标 + * @param offsetX x 偏移量 + * @param offsetY y 偏移量 + * @param offsetPostion 设置 box 相对于点击坐标的位置 + */ +export function computeBoxPosition(box, x, y, offsetX = 10, offsetY = 20, offsetPostion = 'center') { + let boxWidth = box.clientWidth, + boxHeight = box.clientHeight, + docWidth = document.body.clientWidth, + docHeight = document.body.clientHeight, + top = y + offsetY, + left = 0 + + switch (offsetPostion) { + case 'left': + left = x - boxWidth - offsetX + break + case 'right': + left = x + offsetX + break + default: + left = x - boxWidth / 2 - offsetX + break + } + + // box右侧超出页面 + if (left + boxWidth > docWidth) left = docWidth - boxWidth - offsetX + // box下侧抽出页面 + if (top + boxHeight > docHeight) top = docHeight - boxHeight - offsetY + // box遮挡了点击位置 + if (y > top && y < top + boxHeight) top = y - boxHeight - offsetY + top = top < 0 ? offsetY : top + left = left < 0 ? offsetX : left + return { x: left, y: top } +} + +/** + * 格式化思源笔记字符串日期 + * @param {*} value + * @param {*} from 可选值:date | blockid + * @returns + */ +export function formatSYDate(value, from = "date") { + + let str = '' + if (from == "blockid") { + let arr = value.split('-') + str = arr[0] + } else { + str = value + } + + let year = str.substring(0, 4), + month = str.substring(4, 6), + day = str.substring(6, 8), + hour = str.substring(8, 10), + min = str.substring(10, 12), + second = str.substring(12, 14) + + return year + '-' + month + '-' + day + ' ' + hour + ':' + min + ':' + second +} diff --git a/script/commons/domex.js b/script/commons/domex.js index b2e6c7d..252bbe0 100644 --- a/script/commons/domex.js +++ b/script/commons/domex.js @@ -276,6 +276,9 @@ mv.GetDomBySelectors = (selector1,selector2,dom)=>{ console.warn(" function mv.GetDomBySelectors(): param selector1 cannot be empty." ) return null; } + + if (dom === null|| dom === undefined) return null; + let elementAll=dom.querySelectorAll(selector1) if (mv.Empty(selector2)) return elementAll; diff --git a/script/module/b320config copy.js b/script/module/b320config copy.js deleted file mode 100644 index 191315d..0000000 --- a/script/module/b320config copy.js +++ /dev/null @@ -1,1465 +0,0 @@ -import { CodeLabelParse } from "../utils/codelabel-parse.js" -import { TASK_HANDLER } from "../utils/ui.js"; -import { mv } from "../commons/domex.js"; -import { InputData, Messagebox, MessageboxInputs, MessageboxYesNo } from "../commons/widget.js"; - - -export function render(nodoDom) { - for (let value of config.theme.codelabel.ptype) { - new CodeLabelParse(value, nodoDom).render(); - } -} - -export const createUL = (e) => { - let ul = document.createElement('ul'); - e.parentNode.insertBefore(ul, e.nextElementSibling); - e.appendChild(ul); - - // 创建 - ul.createli = (text, dataValue, indexValue) => { - let li = document.createElement('li'); - li.setAttribute('custom-li-data', dataValue); - li.setAttribute('custom-li-index', indexValue); - li.innerHTML = text; - ul.appendChild(li); - - return li; - }; - - return ul; -}; - -export var config = { - token: '', // API token, 无需填写 - theme: { - regs: { - - // 正则表达式 - url: /^siyuan:\/\/blocks\/(\d{14}\-[0-9a-z]{7})\/*(?:(?:\?)(\w+=\w+)(?:(?:\&)(\w+=\w+))+)?$/, // 思源 URL Scheme 正则表达式 - time: /^(\d+)(:[0-5]?[0-9]){0,2}(\.\d*)?$/, // 时间戳正则表达式 - id: /^\d{14}\-[0-9a-z]{7}$/, // 块 ID 正则表达式 - - colorvalue: '^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$', // 匹配#开头的颜色 - }, - - common: { - // 通用的配置 - colors: { - names: [ - // 支持的颜色名称 - 'red', 'orange', 'yellow', 'lime', 'green', - 'aqua', 'cyan', 'blue', 'sea', 'steel', 'purple', - 'magenta', 'pink', 'gold', 'brown', 'gray', 'black', - 'theme1', 'theme2' - ], - values: { - 'red': { - 'value': '#CC3140', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'orange': { - 'value': '#F87000', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'yellow': { - 'value': '#FDC000', - 'titlecolor': '#2b1c29', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'lime': { - 'value': '#B2D115', - 'titlecolor': '#2b1c29', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'green': { - 'value': '#30A830', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'aqua': { - 'value': '#2DE0C8', - 'titlecolor': '#2b1c29', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'cyan': { - 'value': '#17B1C2', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'blue': { - 'value': '#2290F0', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - - 'sea': { - 'value': '#2D51E0', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'steel': { - 'value': '#7073D6', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'purple': { - 'value': '#954ECC', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'magenta': { - 'value': '#E64ED6', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'pink': { - 'value': '#FAB9D1', - 'titlecolor': '#2b1c29', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'gold': { - 'value': '#E0BF9D', - 'titlecolor': '#2b1c29', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'brown': { - 'value': '#855F3A', - 'titlecolor': '#2b1c29', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'gray': { - 'value': '#9498A0', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'black': { - 'value': '#16192a', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.9)', - 'msgcolor': '2b1c29', - }, - 'theme1': { - 'value': '#8064A9', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - }, - 'theme2': { - 'value': '#2AA899', - 'titlecolor': '#f6eef3', - 'msgbgcolor': 'rgba(255,255,255,0.6)', - 'msgcolor': '2b1c29', - } - }, - }, - }, - - codelabel: { // 标签增强解析 - enable: true, // 是否启用自定义样式渲染 - render: { // 渲染信息 - enable: true, // 是否启用自定义样式渲染 - toolbar: { // 菜单栏 - enable: true, - id: 'toolbar-theme-style-codelabel', - hotkey: () => config.theme.hotkeys.codelabel.render, - label: { - zh_CN: '标签解析增强', - zh_CNT: null, - fr_FR: null, - en_US: null, - other: 'Inline Code Parse', - }, - icon: '#iconSuper', - index: -3, - }, - }, - ptype: [ // 解析类型 - - // ptypeItem 对应的值 - // { // 解析配置项 - // typeid: "唯一ID", - // reg: '正则表达式', // 针对 innerHTML - // tagName: "标签名称,如 code、strong", - // customf: '禁止渲染的块属性值,如 wz', // 自定义属性 f=wz 即可。 - // className: 'css类属性名称', - // // select1: `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的,默认不用设置 - // // select2: `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 要选择的,默认不用设置 - // maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - // /** - // * 以下字段名称被占用,不要用于下面列表的值中. - // * value, // code 标签的 InnerHTML - // * color1,bgcolor1, // 主颜色计算结果和适配背景色 - // * color2,bgcolor2, // 次颜色计算结果和适配背景色 - // * $0~$9 也不要用. - // */ - // '$0': 'value', // 占用,code 原始的 innerHTML 内容 - // '$1': '', - // '$2': 'title', - // '$3': 'msg', - // '$4': '', - // '$5': 'color', - // '$6': 'endsuffix', - // '$7': '', - // '$8': '', - // '$9': '', - // }, - // emptys: ['title','msg'], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 - // emptysValues:{ // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 - // 'title':'ke', - // }, - // onlyValue:{ // 不为null时,必须在这个范围内取值,可以不设置, - // 'title':['1','2'], - // }, - // style:{ // 样式映射信息 - // rerender:true, // 是否计算颜色 - // color: { - // value:'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 - // suffix:'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 - // }, - // default:'theme2', // 主颜色缺省时,默认的颜色值 - // defaultSuffix:false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) - // colors:{ - // suffixs:{ // 颜色后缀内容,表示的值 - // '!':true, - // }, - // names: ()=>config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, - // values: ()=>config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 - // } - // }, - // customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // 'custom-codelabel-wz-title': "${title}", - // 'custom-codelabel-wz-msg': "${msg}", - // }, - // inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // "--theme-wz-bgcolor":"${bgcolor1}", - // "--theme-wz-title-color":"${color1}", - // "--theme-wz-msg-color":"${color2}", - // "--theme-wz-msg-bgcolor":"${bgcolor2}", - // }, - // innerHTML: '${value}', // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 - // renderEnd: (parse, element,oldHTML) => { //在每个元素渲染解析完成后的回调函数 - // // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) - // // element 当前元素(解析后的) - // // oldHTML (解析前的 innerHTML 内容) - // }, - // }, - - - { // done微章 - typeid: "wz", - // reg: '(#(.*?)[|](.*?)#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - reg: '(#(.*?)([|](.*?))?#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - tagName: "code", - customf: 'wz', // 忽略解析的属性值 - className: 'custom-codelabel-wz', // 自定义的属性名称 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': '', - '$2': 'title', - '$3': 'msgG', // 是否有消息 - '$4': 'msg', - '$5': '', - '$6': 'color', - '$7': 'endsuffix', - '$8': '', - '$9': '', - }, - - emptys: ['title'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 - 'msg': '' - }, - style: { // 样式映射信息 - rerender: true, // 是否计算配色 - color: { - value: 'color', // 主颜色字段 - suffix: 'endsuffix', // 颜色后缀对应的字段 - }, - default: 'theme2', // 缺省颜色值 - defaultSuffix: false, // 缺省时,颜色后缀,对应的值. - colors: { - suffixs: { - '!': true, - }, - names: () => config.theme.common.colors.names, // 颜色名称-列表 - values: () => config.theme.common.colors.values, // 适配配色-列表 - } - }, - customAttr: { // 自定义属性 - // 'custom-codelabel-value':'${value}', - 'custom-codelabel-wz-title': "${title}", - 'custom-codelabel-wz-msg': "${msg}", - }, - inlineStyle: { - "--theme-wz-bgcolor": "${bgcolor1}", - "--theme-wz-title-color": "${color1}", - "--theme-wz-msg-color": "${color2}", - "--theme-wz-msg-bgcolor": "${bgcolor2}", - }, - innerHTML: '${value}', - renderEnd: (parse, element, oldHTML) => { // 渲染完单个元素的回调. - }, - }, - { // done复选框 - typeid: "chk", - // reg: '(#(.*?)[|](.*?)#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - // reg: '(\\\+(\\\[()\\\])([|](.*?))?){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - reg: '(\\\+(\\\[(\\s|x)\\\])([|](.*?))?\\\+){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - - tagName: "code", - customf: 'chk', // 忽略解析的属性值 - className: 'custom-codelabel-chk', // 自定义的属性名称 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': '', - '$2': 'chkG', - '$3': 'chk', // 是否有消息 - '$4': 'msgG', - '$5': 'msg', - '$6': 'colorG', - '$7': 'color', - '$8': 'endsuffix', - '$9': '', - }, - emptys: ['chkG'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 - 'chk': ' ', - 'msg': '', - }, - style: { // 样式映射信息 - rerender: true, // 是否计算配色 - color: { - value: 'color', // 主颜色字段 - suffix: 'endsuffix', // 颜色后缀对应的字段 - }, - default: 'theme2', // 缺省颜色值 - defaultSuffix: false, // 缺省时,颜色后缀,对应的值. - colors: { - suffixs: { - '!': true, - }, - names: () => config.theme.common.colors.names, // 颜色名称-列表 - values: () => config.theme.common.colors.values, // 适配配色-列表 - } - }, - customAttr: { // 自定义属性 - // 'custom-codelabel-value':'${value}', - 'custom-codelabel-chk-chk': "${chk}", - 'custom-codelabel-chk-msg': "${msg}", - 'custom-codelabel-chk-colorG': "${colorG}", - 'custom-codelabel-chk-endsuffix': "${endsuffix}", - }, - inlineStyle: { - "--theme-wz-bgcolor": "${bgcolor1}", - "--theme-wz-title-color": "${color1}", - "--theme-wz-msg-color": "${color2}", - "--theme-wz-msg-bgcolor": "${bgcolor2}", - }, - innerHTML: '${value}', - renderEnd: (parse, element, oldHTML) => { // 渲染完单个元素的回调. - - // let parentNode = mv.GetSiyuanBlock(element); - let id = mv.GetSiyuanBlockId(element) - - let div= mv.GetDomByAtrrs(element,'class','cw-chk-wrap','span')[0]; - if (div===null || div===undefined){ - div = mv.CreateSpan(null,'cw-chk-wrap',null); - let span1 = mv.CreateSpan(null,'cw-chk-gaph-one',null) - let span2 = mv.CreateSpan(null,'cw-chk-gaph-two',null) - div.appendChild(span1); - div.appendChild(span2); - element.appendChild(div); - } - - div.classList.remove('cw-chk-tick') - if (parse.parseInfo.chk === 'x'){ - div.classList.add('cw-chk-tick') - } - - div['onclick'] = async function(){ - let msg = mv.GetAttrs(element,"custom-codelabel-chk-msg") - if (msg === undefined) msg='' - - msg = mv.Empty(msg)?"":`|${msg}` - let colorG = mv.GetAttrs(element,"custom-codelabel-chk-colorG") - if (colorG === "undefined" || colorG===undefined){ - colorG="" - } - - if (div.classList.contains('cw-chk-tick')){ - div.classList.remove('cw-chk-tick') - element.innerHTML = `+[ ]${msg}+${colorG}`; - mv.SetAttrs(element,'custom-codelabel-chk-chk',' ') - mv.SetAttrs(element,'custom-codelabel-value',`+[ ]${msg}+${colorG}`) - } - else{ - div.classList.add('cw-chk-tick') - element.innerHTML =`+[x]${msg}+${colorG}`; - mv.SetAttrs(element,'custom-codelabel-chk-chk','x') - mv.SetAttrs(element,'custom-codelabel-value',`+[ ]${msg}+${colorG}`) - } - - let md = mv.GetLute().BlockDOM2Md(element.parentNode.parentNode.innerHTML) - console.log(md) - let kid = await mv.UpdateBlockByMd_API(id,md) - let dom = document.querySelectorAll(`div[data-node-id="${kid}"]`)[0]; - render(dom) - } - - }, - }, - { // done刮刮乐 - typeid: "rb", - reg: '^\\\*\\\{(.*)\\\}\\\((.*?)(\\s*\\\"(#?[\\d\\w]+)\\\")?\\\)$', // 正则表达式 - tagName: "code", - customf: 'rb', // 忽略解析的属性值 - className: 'v-rb-coat', // 自定义的属性名称 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'coat_text', - '$2': 'coat_data', - '$3': '', - '$4': 'color', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - - emptys: ['coat_data'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 - 'coat_text': '****' - }, - style: { // 样式映射信息 - rerender: true, // 是否计算配色 - color: { - value: 'color', // 主颜色字段 - suffix: '', // 颜色后缀对应的字段 - }, - default: 'gray', // 缺省颜色值 - defaultSuffix: false, // 缺省时,颜色后缀,对应的值. - colors: { - suffixs: { - '!': true, - }, - names: () => config.theme.common.colors.names, // 颜色名称-列表 - values: () => config.theme.common.colors.values, // 适配配色-列表 - } - }, - customAttr: { // 自定义属性 - // 'custom-codelabel-value':'${value}', - 'custom-codelabel-rb-coat-text': "${coat_text}", - 'custom-codelabel-rb-coat-data': "${coat_data}", - 'custom-codelabel-rb-coat-showe': 'false', - }, - inlineStyle: { - '--theme-rb-bgcolor': "${bgcolor1}", - '--theme-rb-title-color': "${color1}", - '--theme-rb-msg-color': "${color2}", - '--theme-rb-msg-bgcolor': "${bgcolor2}", - }, - innerHTML: '${value}', - renderEnd: (parse, element, oldHTML) => { // 渲染完单个元素的回调. - function bingOnClick(button) { - let value = button.getAttribute('custom-codelabel-rb-coat-showe') === false || - button.getAttribute('custom-codelabel-rb-coat-showe') === 'false' ? - 'true' : 'false'; - button.setAttribute('custom-codelabel-rb-coat-showe', value) - } - element.onclick = bingOnClick.bind(element, element); - }, - }, - { // done注音 - typeid: "pg", - reg: '^\\\{(.*)\\\}\\s*\\\((.*)\\\)$', // 正则表达式 - tagName: "code", - customf: 'pg', // 忽略解析的属性值 - className: 'vk-pg', // 自定义的属性名称 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'text', - '$2': 'pgdata', - '$3': '', - '$4': '', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - - emptys: ['text', 'pgdata'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 - }, - style: { // 样式映射信息 - rerender: false, // 是否计算配色 - // color: { - // value:'color', // 主颜色字段 - // suffix:'', // 颜色后缀对应的字段 - // }, - // default:'gray', // 缺省颜色值 - // defaultSuffix:false, // 缺省时,颜色后缀,对应的值. - // colors:{ - // suffixs:{ - // '!':true, - // }, - // names: ()=>config.theme.common.colors.names, // 颜色名称-列表 - // values: ()=>config.theme.common.colors.values, // 适配配色-列表 - // } - }, - customAttr: { // 自定义属性 - // 'custom-codelabel-value':'${value}', - 'custom-codelabel-pg-text': "${text}", - 'custom-codelabel-pg-data': "${pgdata}", - }, - inlineStyle: { - }, - innerHTML: '{${text}}(${pgdata})', - renderEnd: (parse, element, oldHTML) => { // 渲染完单个元素的回调. - }, - }, - { // done计数任务 - typeid: "todo", - reg: '^\\\+\\\[(\\d+)\\\]\\s*\\\((.*?)(\\s*\\\"([\\d\\w]+)\\\")?\\\)$', // 正则表达式 - tagName: "code", - customf: 'todo', // 忽略解析的属性值 - className: 'vk-todo', // 自定义的属性名称 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'count', - '$2': 'data', - '$3': 'colorTag', - '$4': 'color', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - - emptys: ['count', 'data'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 - 'colorTag': '' - }, - style: { // 样式映射信息 - rerender: true, // 是否计算配色 - color: { - value: 'color', // 主颜色字段 - suffix: '', // 颜色后缀对应的字段 - }, - default: 'black', // 缺省颜色值 - defaultSuffix: false, // 缺省时,颜色后缀,对应的值. - colors: { - suffixs: { - '!': true, - }, - names: () => config.theme.common.colors.names, // 颜色名称-列表 - values: () => config.theme.common.colors.values, // 适配配色-列表 - } - }, - customAttr: { // 自定义属性 - 'custom-codelabel-value': '${value}', - 'custom-codelabel-todo-count': "${count}", - 'custom-codelabel-todo-data': "${data}", - 'custom-codelabel-todo-colorTag': "${colorTag}", - }, - inlineStyle: { - 'color': "${color}", - }, - innerHTML: '[${count}](${data}${colorTag})', - renderEnd: async (parse, element, oldHTML) => { - - async function bingOnClick(parse, e, oldHTML) { - - // 获取父节点 - let parentNode = mv.GetSiyuanBlock(e); - let id = mv.GetSiyuanBlockId(e); - - parse.reinitFormat(parse.ptypeItem) - - let parseInfo = parse.clacParseInfo(oldHTML); - let c = 1 + +parseInfo.count; - parseInfo.count = "" + c; - parse.parseInfo = parseInfo; - - // 渲染 - parse.renderSingle(e, parseInfo); - // 这里已经更新了,所有旧的 oldHTML 和 parse.Value 就没用了。重新组合 - let newInnerHtml = `+[${parse.parseInfo.count}](${parse.parseInfo.data}${parse.parseInfo.colorTag})` - - e.firstChild.onclick = bingOnClick.bind(e.firstChild, parse, e, newInnerHtml) - - // 设置新的 自定义的value - e.setAttribute("custom-codelabel-value", newInnerHtml); - - e.firstChild.setAttribute( - "custom-codelabel-todo-count", - parse.parseInfo.count - ); - - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); - let did = await mv.UpdateBlockByMd_API(id, tmd); - let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; - render(dom); - - } - - element.firstChild.onclick = bingOnClick.bind(element.firstChild, parse, element, oldHTML) - element.firstChild.setAttribute( - "custom-codelabel-todo-count", - element.getAttribute("custom-codelabel-todo-count"), - ); - - }, - }, - { // done多级标签 - typeid: "mtag", - reg: '^(.*)/(.*)$', // 针对 innerHTML - tagName: 'span[data-type="tag"]', - customf: 'mtag', // 自定义属性 f=mtag 即可。 - className: 'mult-tag', - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'lv1', - '$2': 'lv2', - '$3': '', - '$4': '', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - emptys: ['lv1', 'lv2'], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 - emptysValues: { // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 - // 'title':'ke', - }, - style: { // 样式映射信息 - rerender: false, // 是否计算颜色 - // color: { - // value:'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 - // suffix:'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 - // }, - // default:'theme2', // 主颜色缺省时,默认的颜色值 - // defaultSuffix:false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) - // colors:{ - // suffixs:{ // 颜色后缀内容,表示的值 - // '!':true, - // }, - // names: ()=>config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, - // values: ()=>config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 - // } - }, - customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // 'mult-tag-lv1': "${lv1}", - // 'mult-tag-lv2': "${lv2}", - }, - inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // "--theme-wz-bgcolor":"${bgcolor1}", - // "--theme-wz-title-color":"${color1}", - // "--theme-wz-msg-color":"${color2}", - // "--theme-wz-msg-bgcolor":"${bgcolor2}", - }, - // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 - innerHTML: "${lv1}" - + "/" - + "${lv2}", - renderEnd: (parse, element, oldHTML) => { //在每个元素渲染解析完成后的回调函数 - // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) - // element 当前元素(解析后的) - // oldHTML (解析前的 innerHTML 内容) - // let innerHTML = ''; - // if (parse['lv1'] !==undefined && parse['lv1'] !==null && parse['lv1'] !==''){ - // innerHTML += `${parse['lv1']}` - // } - // if (parse['lv2'] !==undefined && parse['lv2'] !==null && parse['lv2'] !==''){ - // innerHTML += `${parse['lv2']}` - // } - - // element.innerHTML = innerHTML - }, - }, - { // done下拉框 - typeid: "cx", - reg: '\\\^\\\[(\\d+)\\\](>\\\(.+\\\))', // 针对 innerHTML - tagName: "code", - customf: 'cx', // 自定义属性 f=wz 即可。 - className: 'cw-cbox', - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'index', - '$2': 'itms', - '$3': '', - '$4': '', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - // reg2mapts:{ - // // key: 上一轮匹配的地方 ; value: 对应的解析规则 - // '$0':{ - // reg:'\\\$\\\{(.*?)\\\}', - // isMatchAll:true, // 是否是匹配所有 - // type:'array', // 类型 - // }, - // }, - emptys: ['index', 'itms'], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 - emptysValues: { // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 - }, - style: { // 样式映射信息 - rerender: false, // 是否计算颜色 - // color: { - // value:'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 - // suffix:'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 - // }, - // default:'theme2', // 主颜色缺省时,默认的颜色值 - // defaultSuffix:false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) - // colors:{ - // suffixs:{ // 颜色后缀内容,表示的值 - // '!':true, - // }, - // names: ()=>config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, - // values: ()=>config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 - // } - }, - customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - 'custom-codelabel-cx-index': "${index}", - 'custom-codelabel-cx-itmes': "${itms}", - }, - inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // "--theme-wz-bgcolor":"${bgcolor1}", - // "--theme-wz-title-color":"${color1}", - // "--theme-wz-msg-color":"${color2}", - // "--theme-wz-msg-bgcolor":"${bgcolor2}", - }, - innerHTML: '${value}', // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 - renderEnd: async (parse, element, oldHTML) => { //在每个元素渲染解析完成后的回调函数 - // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) - // element 当前元素(解析后的) - // oldHTML (解析前的 innerHTML 内容) - - let tIndex = parse.parseInfo['index']; - let tItms = parse.parseInfo['itms']; - - // 获取父节点 - let parentNode = mv.GetSiyuanBlock(element); - let id = mv.GetSiyuanBlockId(element); - - let setHtml = async (index, tItms) => { - - let itmes = [...tItms.matchAll('\\\((.*?)\\\)')] - - element.innerHTML = `^[${index}]${tItms}` - - let slt = itmes[index][1]; - element.setAttribute('custom-select-data', slt) - - element.setAttribute('custom-codelabel-cx-index', index) - let pstionX = element.getBoundingClientRect().left - element.parentNode.getBoundingClientRect().left + 20; - let pstionY = element.getBoundingClientRect().bottom - element.parentNode.getBoundingClientRect().bottom; - let mUl = createUL(element); - mUl.setAttribute('style', "margin-left:" + pstionX + "px;margin-top:" + pstionY + "px"); - - let i = 0; - for (let item of itmes) { - let itext = item[1]; - let eli = mUl.createli("", itext, i++); - eli.onclick = async (e) => { - // let idx=e.target.getAttribute('custom-li-data'); - let idx = e.target.getAttribute('custom-li-index'); - let tms = element.getAttribute('custom-codelabel-cx-itmes') - setHtml(idx, tms); - - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); - let did = await mv.UpdateBlockByMd_API(id, tmd); - let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; - render(dom); - - }; - } - - }; - - setHtml(tIndex, tItms); - }, - }, - { // done@@命令 - typeid: "cmd", - reg: '^@@((kanban)|(map)|(bqcolor)|(bqtab))(\\\((.*)\\\))?$', // 针对 innerHTML - tagName: "code", - customf: 'cmd', // 自定义属性 f=wz 即可。 - className: 'custom-codelabel-cmd', - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'func', - '$2': '', - '$3': '', - '$4': '', - '$5': '', - '$6': 'args', - '$7': '', - '$8': '', - '$9': '', - }, - emptys: ['func'], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 - onlyValue: { // 不为null时,必须在这个范围内取值,可以不设置, - 'func': ['kanban', 'map', 'bqcolor','bqtab'], - }, - emptysValues: { // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 - // 'func':'', - }, - style: { // 样式映射信息 - rerender: false, // 是否计算颜色 - // color: { - // value:'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 - // suffix:'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 - // }, - // default:'theme2', // 主颜色缺省时,默认的颜色值 - // defaultSuffix:false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) - // colors:{ - // suffixs:{ // 颜色后缀内容,表示的值 - // '!':true, - // }, - // names: ()=>config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, - // values: ()=>config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 - // } - }, - customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // 'custom-codelabel-cmd-func': "${func}", - // 'custom-codelabel-cmd-args': "${args}", - }, - inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // "--theme-wz-bgcolor":"${bgcolor1}", - // "--theme-wz-title-color":"${color1}", - // "--theme-wz-msg-color":"${color2}", - // "--theme-wz-msg-bgcolor":"${bgcolor2}", - }, - innerHTML: '${value}', // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 - renderEnd: async (parse, element, oldHTML) => { //在每个元素渲染解析完成后的回调函数 - // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) - // element 当前元素(解析后的) - // oldHTML (解析前的 innerHTML 内容) - - var id = mv.GetSiyuanBlockId(element); - - if (parse.parseInfo['func'] === 'kanban') { - - let aid = await mv.InsertBlockByMd_API(id,'---'); - let bid = await mv.InsertBlockByMd_API(aid,'---'); - let cid = await mv.InsertBlockByMd_API(bid,'* 未开始 \n * 任务1 \n* 进行中 \n * 任务2 \n* 已完成 \n * 任务3'); - await mv.DeleteBlockById_API(id); - return; - } - - if (parse.parseInfo['func'] === 'map') { - - let ida = await mv.InsertBlockByMd_API(id,'---'); - let idb = await mv.InsertBlockByMd_API(ida,'---'); - let idc = await mv.InsertBlockByMd_API(idb,'---'); - let idd = await mv.InsertBlockByMd_API(idc,'* 中心主题 \n * 分支1 \n * 分支2 \n * 分支3 '); - let ide = await mv.DeleteBlockById_API(id); - return; - } - - if (parse.parseInfo['func'] === 'bqcolor') { - let szcolor = 'red' - if (mv.Empty(parse.parseInfo['args']) === false) { - szcolor = parse.parseInfo['args'] - } - let aid = await mv.InsertBlockByMd_API(id,'> '); - let bid = await mv.AppendBlockByMd_API(aid,'`>(' + szcolor + ')` .'); - let cid = await mv.UpdateBlockByMd_API(id, ' ') - return; - } - - if (parse.parseInfo['func'] === 'bqtab'){ - - // 插入父容器 - let wid = await mv.InsertBlockByMd_API(id, '> `::tab`'); - await mv.SetAttrs_API(wid,'custom-type','bq-wrap'); - - // 插入选项卡 - let tabtid = await mv.AppendBlockByMd_API(wid,'* 选项1 \n * 选项2 \n * 选项3 '); - await mv.SetAttrs_API(tabtid,'custom-type','bq-tab_t'); - - // 插入选项卡容器 - let tabcid = await mv.AppendBlockByMd_API(wid,'> > 内容1'); - await mv.SetAttrs_API(tabcid,'custom-type','bq-tab_c'); - let bid = await mv.AppendBlockByMd_API(tabcid,'> 内容2'); - let cid = await mv.AppendBlockByMd_API(tabcid,'> 内容3'); - let did = await mv.UpdateBlockByMd_API(id,' '); - - window.location.reload(); - } - }, - }, - { // done彩虹引用 - typeid: "bq", - reg: '>[\(]((#?[\\d\\w]+)(!)?)[\)]', // 针对 innerHTML - tagName: "code", - customf: 'bqcolor', // 自定义属性 f=wz 即可。 - className: 'bqcolor', - //// select1: `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的,默认不用设置 - //// select2: `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 要选择的,默认不用设置 - select1: '.protyle-wysiwyg .bq[data-node-id] .p code:first-of-type', - select2: '.protyle-wysiwyg .bq[data-node-id][custom-f~=bqcolor] .p code:first-of-type', - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'colorTag', - '$2': 'color', - '$3': 'endsuffix', - '$4': '', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - emptys: [ - 'color' - // 'title','msg' - ], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 - emptysValues: { // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 - // 'title':'ke', - }, - onlyValue: { - 'color': () => config.theme.common.colors.names, - }, - style: { // 样式映射信息 - rerender: true, // 是否计算颜色 - color: { - value: 'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 - suffix: 'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 - }, - default: 'theme2', // 主颜色缺省时,默认的颜色值 - defaultSuffix: false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) - colors: { - suffixs: { // 颜色后缀内容,表示的值 - '!': true, - }, - names: () => config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, - values: () => config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 - } - }, - customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // 'custom-codelabel-bq-title': "${title}", - // 'custom-codelabel-bq-msg': "${msg}", - }, - inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - "--theme-bq-bgcolor": "${bgcolor1}", - "--theme-bq-title-color": "${color1}", - "--theme-bq-msg-color": "${color2}", - "--theme-bq-msg-bgcolor": "${bgcolor2}", - }, - innerHTML: '${value}', // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 - renderEnd: (parse, element, oldHTML) => { //在每个元素渲染解析完成后的回调函数 - // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) - // element 当前元素(解析后的) - // oldHTML (解析前的 innerHTML 内容) - - // 获取父节点 - let parentNode = mv.GetSiyuanBlock(element); - let id = mv.GetSiyuanBlockId(element); - - var itms = [] - // var items = config.theme.common.colors.names; - for (let ims of config.theme.common.colors.names) { - itms.push(ims) - itms.push(`${ims}!`) - } - - let setHtml = async (slt, slt2) => { - - let endsuffix = ""; - if (slt2 === '2') { - endsuffix = "!" - } - - element.innerHTML = `>(${slt})` - - var bqNode = element.parentNode.parentNode.parentNode; - mv.SetStyleValue(bqNode.style, '--theme-bq-bgcolor', parse.parseInfo.bgcolor1) - mv.SetStyleValue(bqNode.style, '--theme-bq-color1', parse.parseInfo.color1) - mv.SetStyleValue(bqNode.style, '--theme-bq-bgcolor2', parse.parseInfo.bgcolor2) - mv.SetStyleValue(bqNode.style, '--theme-bq-color2', parse.parseInfo.color2) - - if (mv.Empty(endsuffix) === true) { - bqNode.setAttribute("bqtype", "color1") - } else { - bqNode.setAttribute("bqtype", "color") - } - - element.setAttribute('custom-select-data', slt) - let pstionX = element.getBoundingClientRect().left - element.parentNode.getBoundingClientRect().left + 20; - let pstionY = element.getBoundingClientRect().bottom - element.parentNode.getBoundingClientRect().bottom; - let mUl = createUL(element); - mUl.setAttribute('style', "margin-left:" + pstionX + "px;margin-top:" + pstionY + "px"); - - let i = 0; - for (let item of itms) { - let itext = item; - let eli = mUl.createli("", itext, i++); - eli.onclick = async (e) => { - - let idx = e.target.getAttribute('custom-li-index'); - let slt1 = itms[idx]; - if (slt1.endsWith('!')) { - slt2 = "2" - } else { - slt2 = "1" - } - setHtml(slt1, slt2); - - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); - let did = await mv.UpdateBlockByMd_API(id, tmd); - let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; - render(dom) - - }; - } - - }; - - setHtml(`${parse.parseInfo.color}${parse.parseInfo.endsuffix}`, mv.Empty(parse.parseInfo.endsuffix) ? "1" : "2"); - - }, - }, - { // doneTab引用 - typeid: "bqtab", - reg: '::(tab)(\\d*)', // 针对 innerHTML - tagName: "code", - customf: 'bqtab', // 自定义属性 f=wz 即可。 - className: 'bqtab', - //// select1: `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的,默认不用设置 - //// select2: `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 要选择的,默认不用设置 - select1: '.protyle-wysiwyg .bq[data-node-id] .p:first-of-type code:first-of-type', // 要选择的,默认不用设置 - select2: '.protyle-wysiwyg .bq[data-node-id][custom-f~=bqtab] .p:first-of-type code:first-of-type', // 要选择的,默认不用设置 - - // select1: `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的,默认不用设置 - // select2: `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 要选择的,默认不用设置 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': 'mode', - '$2': 'index', - '$3': '', - '$4': '', - '$5': '', - '$6': '', - '$7': '', - '$8': '', - '$9': '', - }, - emptys: ['mode'], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 - emptysValues: { // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 - 'index': '0', - }, - onlyValue: { // 不为null时,必须在这个范围内取值,可以不设置, - 'mode': ['tab'], - }, - style: { // 样式映射信息 - rerender: false, // 是否计算颜色 - // color: { - // value:'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 - // suffix:'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 - // }, - // default:'theme2', // 主颜色缺省时,默认的颜色值 - // defaultSuffix:false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) - // colors:{ - // suffixs:{ // 颜色后缀内容,表示的值 - // '!':true, - // }, - // names: ()=>config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, - // values: ()=>config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 - // } - }, - customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // 'custom-codelabel-wz-title': "${title}", - // 'custom-codelabel-wz-msg': "${msg}", - }, - inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 - // "--theme-wz-bgcolor":"${bgcolor1}", - // "--theme-wz-title-color":"${color1}", - // "--theme-wz-msg-color":"${color2}", - // "--theme-wz-msg-bgcolor":"${bgcolor2}", - }, - innerHTML: '${value}', // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 - renderEnd: async (parse, element, oldHTML) => { //在每个元素渲染解析完成后的回调函数 - - // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) - // element 当前元素(解析后的) - // oldHTML (解析前的 innerHTML 内容) - // 获取父节点 - - let crtLine = mv.GetSiyuanBlock(element); - let parentNode = crtLine.parentNode; - - let tab = async (tab_t, tab_t_tag, tab_c, tag_c_tag, evt,rix) => { - - // 设置 button - element.classList.remove('bq-tab-button-mode') - element.classList.add('bq-tab-button-mode') - - // 设置插入 - let button = mv.GetDomByAtrrs(parentNode,"bq-button-value","新建","button")[0] - if (button === null || button === undefined){ - button = document.createElement('button') - button.setAttribute('bq-button-value',"新建") - element.parentNode.insertBefore(button,element.nextSibling); - } - - button[evt] = async function () { - - let inner = async ()=>{ - - var tab_t = mv.GetDomByAtrrs(parentNode, "custom-type", "bq-tab_t", "div")[0]; - var tab_t_li = mv.GetDomByAtrrs(tab_t, "class", "li", "div"); - - var tab_c = mv.GetDomByAtrrs(parentNode, "custom-type", "bq-tab_c", "div")[0]; - var tab_c_li = mv.GetDomByAtrrs(tab_c, "class", "bq","div" ); - - let t_ix=mv.GetSiyuanBlockId(tab_t_li[tab_t_li.length-1]) - let c_ix=mv.GetSiyuanBlockId(tab_c_li[tab_c_li.length-1]) - - let aid = await mv.InsertBlockByMd_API(c_ix,"> 内容new"); - let bid = await mv.InsertBlockByMd_API(t_ix, '* 选项卡new'); - let c_li=mv.GetDomByAtrrs(tab_c,"data-node-id",bid,'div')[0]; - tab("bq-tab_t", "li", "bq-tab_c", "bq", "onclick",-1) - - } - - let msgbox=new MessageboxYesNo("确定新建一个标签页吗?",5); - let mgdom = msgbox.Create("bqtmsgbox","bqtmsgbox","width: 480px;top: 45%; left: 45%;"); - document.body.appendChild(mgdom); - msgbox.Start(inner,()=>{}); - - } - - // 设置 bq-none - let button2 = mv.GetDomByAtrrs(parentNode,"bq-button-value","展开/收缩","button")[0] - if (button2 === null || button2 === undefined){ - button2 = document.createElement('button') - button2.setAttribute('bq-button-value',"展开/收缩") - element.parentNode.insertBefore(button2,element.nextSibling); - } - - button2[evt] = async function(){ - - let str2=parentNode.getAttribute('custom-type'); - if (str2.indexOf("bq-none")>=0){ - str2=str2.replace('bq-none','') - - if (str2.indexOf("bq-wrap")<0){ - str2 = str2 + " bq-wrap " - } - - console.log(str2) - parentNode.setAttribute("custom-type",str2) - let kid = await mv.SetAttrs_API(mv.GetSiyuanBlockId(parentNode),"custom-type",str2) - } - else - { - if (str2.indexOf("bq-wrap") <0){ - str2 = str2 + " bq-wrap " - } - - str2 = str2 + " bq-none " - console.log(str2) - parentNode.setAttribute("custom-type",str2) - let kid = await mv.SetAttrs_API(mv.GetSiyuanBlockId(parentNode),"custom-type",str2) - } - } - - var tab_t = mv.GetDomByAtrrs(parentNode, "custom-type", tab_t, "div")[0]; - var tab_t_li = mv.GetDomByAtrrs(tab_t, "class", tab_t_tag, "div"); - - var tab_c = mv.GetDomByAtrrs(parentNode, "custom-type", tab_c, "div")[0]; - var tab_c_li = mv.GetDomByAtrrs(tab_c, "class", tag_c_tag,"div" ); - - var len = tab_t_li.length; - var i = 0; - for (i = 0; i < len; i++) { - tab_t_li[i].index = i; - tab_t_li[i][evt] = async function () { - - for (i = 0; i < len; i++) { - tab_t_li[i].setAttribute('custom-type', 'null'); - tab_c_li[i].setAttribute('custom-type', 'bq-hide'); - - let kid1 = await mv.SetAttrs_API(mv.GetSiyuanBlockId(tab_t_li[i]),"custom-type",'null') - let kid2 = await mv.SetAttrs_API(mv.GetSiyuanBlockId(tab_c_li[i]),"custom-type",'bq-hide') - } - - tab_t_li[this.index].setAttribute('custom-type', 'bq-act'); - tab_c_li[this.index].setAttribute('custom-type', 'null'); - - let id = mv.GetSiyuanBlockId(tab_t_li[this.index]); - let kid1 = await mv.SetAttrs_API(id,"custom-type",'bq-act'); - let kid2 = await mv.SetAttrs_API(mv.GetSiyuanBlockId(tab_c_li[this.index]),"custom-type",'null'); - } - } - - console.log("1") - if (rix!==null && rix!==undefined){ - if (rix>=0){ - tab_t_li[0][evt](); - }else{ - tab_t_li[tab_t_li.length-1][evt](); - } - } - } - - tab("bq-tab_t", "li", "bq-tab_c", "bq", "onclick") - crtLine.onclick = async (e)=>{ - tab("bq-tab_t", "li", "bq-tab_c", "bq", "onclick"); - } - }, - }, - { // 整行样式 - typeid: "wzline", - // reg: '(#(.*?)[|](.*?)#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - reg: '(\\:\\:(.*?)([|](.*?))?\\:\\:){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 - tagName: "code", - customf: 'wzline', // 忽略解析的属性值 - className: 'custom-codelabel-wzline', // 自定义的属性名称 - maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 - /** - * 以下字段名称被占用,不要用于下面列表的值中. - * value, // code 标签的 InnerHTML - * color1,bgcolor1, // 主颜色计算结果和适配背景色 - * color2,bgcolor2, // 次颜色计算结果和适配背景色 - * $0~$9 也不要用. - */ - '$0': 'value', // 占用,code 原始的 innerHTML 内容 - '$1': '', - '$2': 'title', - '$3': 'msgG', // 是否有消息 - '$4': 'msg', - '$5': '', - '$6': 'color', - '$7': 'endsuffix', - '$8': '', - '$9': '', - }, - - emptys: ['title'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 - 'msg': '' - }, - style: { // 样式映射信息 - rerender: true, // 是否计算配色 - color: { - value: 'color', // 主颜色字段 - suffix: 'endsuffix', // 颜色后缀对应的字段 - }, - default: 'theme2', // 缺省颜色值 - defaultSuffix: false, // 缺省时,颜色后缀,对应的值. - colors: { - suffixs: { - '!': true, - }, - names: () => config.theme.common.colors.names, // 颜色名称-列表 - values: () => config.theme.common.colors.values, // 适配配色-列表 - } - }, - customAttr: { // 自定义属性 - // 'custom-codelabel-value':'${value}', - 'custom-codelabel-wzline-title': "${title}", - 'custom-codelabel-wzline-msg': "${msg}", - }, - inlineStyle: { - "--theme-wzline-bgcolor": "${bgcolor1}", - "--theme-wzline-title-color": "${color1}", - "--theme-wzline-msg-color": "${color2}", - "--theme-wzline-msg-bgcolor": "${bgcolor2}", - }, - innerHTML: '${value}', - renderEnd: (parse, element, oldHTML) => { // 渲染完单个元素的回调. - }, - }, - ], - }, - - menu: { - enable: true, // 行内代码编辑增强 - codelabel: { - enable: true, // 行内代码编辑增强 - toolbar: { // 菜单栏 - enable: true, // - id: 'toolbar-theme-menu-codelabel', - hotkey: () => config.theme.hotkeys.menu.codelabel, - label: { - zh_CN: '行内代码编辑增强', - zh_CNT: null, - fr_FR: null, - en_US: null, - other: 'codelabel Menu Enhancements', - }, - icon: '#iconMenu', - index: -4, - }, - items: [ - { - enable: true, // 是否启用菜单项 - prefixSeparator: true, // 是否添加前缀分隔线 - suffixSeparator: false, // 是否添加后缀分隔线 - type: null, // 哪些类型的块启用, 值 null 则全部启用 - id: 'theme-menu-codelabel-common-editor', // 菜单项 ID - mode: "button", // 菜单项类型 - icon: "#iconEdit", // 菜单项图标 - label: { // 菜单项标签 - zh_CN: "编辑行内代码块", - other: "Edit Inline Code", - }, - accelerator: "", // 菜单项快捷键 - click: { // 菜单项点击事件 - enable: true, - callback: null, - tasks: [ - { - type: 'codelabel-editor', - params: { - 'style': null, - 'custom-font-family': null, - }, - }, - ], - }, - itemsLoad: false, // 是否加载子菜单 - // itemsIcon: "#iconRight", - items: null, - }, - ], - }, - }, - - comment: { - // 批注功能开关 - enable: true, - }, - - - wordcount: { - // 字数统计 - enable: true, - }, - - hotkeys: { - // 快捷键 - codelabel: { - render: { - // 渲染(Ctrl + Alt + 0) - ctrlKey: true, - metaKey: true, - shiftKey: false, - altKey: true, - key: '0', - }, - }, - - menu: { - codelabel: { - // 块菜单开关(Shift + Alt + M) - ctrlKey: false, - metaKey: false, - shiftKey: true, - altKey: true, - key: 'M', - }, - }, - - }, - - }, -}; - diff --git a/script/module/b320config.js b/script/module/b320config.js index 9f27fee..0edb1db 100644 --- a/script/module/b320config.js +++ b/script/module/b320config.js @@ -235,6 +235,9 @@ export var config = { // onlyValue:{ // 不为null时,必须在这个范围内取值,可以不设置, // 'title':['1','2'], // }, + // ignoreValue:{ // 不为null时,必须不在这个范围内的取,可以不设置, + // 'title':['1','2'], + // }, // style:{ // 样式映射信息 // rerender:true, // 是否计算颜色 // color: { @@ -269,6 +272,7 @@ export var config = { // }, // }, + { // 微章 typeid: "wz", // reg: '(#(.*?)[|](.*?)#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 @@ -298,8 +302,11 @@ export var config = { }, emptys: ['title'], // 不能为空的字段 - emptysValues: { // 当值为空值的值 + emptysValues: { // 当值为空值的值 'msg': '' + }, + ignoreValue:{ // 当取值是对应的值时,不处理 + 'title':['[ ]','[x]'], }, style: { // 样式映射信息 rerender: true, // 是否计算配色 @@ -585,7 +592,7 @@ export var config = { } setHtml(slt1, slt2); - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); + var tmd = mv.GetLute().BlockDOM2StdMd(parentNode.innerHTML); let did = await mv.UpdateBlockByMd_API(id, tmd); let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; render(dom) @@ -775,7 +782,7 @@ export var config = { let tms = element.getAttribute('custom-codelabel-cx-itmes') setHtml(idx, tms); - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); + var tmd = mv.GetLute().BlockDOM2StdMd(parentNode.innerHTML); let did = await mv.UpdateBlockByMd_API(id, tmd); let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; render(dom); @@ -894,7 +901,134 @@ export var config = { mv.SetAttrs(element,'custom-codelabel-value',`+[ ]${msg}+${colorG}`) } - let md = mv.GetLute().BlockDOM2Md(element.parentNode.parentNode.innerHTML) + if (colorG!==null &&colorG!==undefined && colorG.includes("!")){ + div.classList.add('cw-chk-endsuffix') + }else{ + div.classList.remove('cw-chk-endsuffix') + } + + let md = mv.GetLute().BlockDOM2StdMd(element.parentNode.parentNode.innerHTML) + console.log(md) + let kid = await mv.UpdateBlockByMd_API(id,md) + let dom = document.querySelectorAll(`div[data-node-id="${kid}"]`)[0]; + render(dom) + } + + }, + }, + { // 微章+复选框 + typeid: "chk-wz", + // reg: '(#(.*?)[|](.*?)#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 + // reg: '(\\\+(\\\[()\\\])([|](.*?))?){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 + reg: '(\\\#(\\\[(\\s|x)\\\])([|](.*?))?\\\#){1,1}?([\(](#?[\\d\\w]+)(!)?[\)])?', // 正则表达式 + tagName: "span", + tagDataType:"code", + customf: 'chk-wz', // 忽略解析的属性值 + className: 'custom-codelabel-chk', // 自定义的属性名称 + maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 + /** + * 以下字段名称被占用,不要用于下面列表的值中. + * value, // code 标签的 InnerHTML + * color1,bgcolor1, // 主颜色计算结果和适配背景色 + * color2,bgcolor2, // 次颜色计算结果和适配背景色 + * $0~$9 也不要用. + */ + '$0': 'value', // 占用,code 原始的 innerHTML 内容 + '$1': '', + '$2': 'chkG', + '$3': 'chk', // 是否有消息 + '$4': 'msgG', + '$5': 'msg', + '$6': 'colorG', + '$7': 'color', + '$8': 'endsuffix', + '$9': '', + }, + emptys: ['chkG'], // 不能为空的字段 + emptysValues: { // 当值为空值的值 + 'chk': ' ', + 'msg': '', + }, + style: { // 样式映射信息 + rerender: true, // 是否计算配色 + color: { + value: 'color', // 主颜色字段 + suffix: 'endsuffix', // 颜色后缀对应的字段 + }, + default: 'theme2', // 缺省颜色值 + defaultSuffix: false, // 缺省时,颜色后缀,对应的值. + colors: { + suffixs: { + '!': true, + }, + names: () => config.theme.common.colors.names, // 颜色名称-列表 + values: () => config.theme.common.colors.values, // 适配配色-列表 + } + }, + customAttr: { // 自定义属性 + // 'custom-codelabel-value':'${value}', + 'custom-codelabel-chk-chk': "${chk}", + 'custom-codelabel-chk-msg': "${msg}", + 'custom-codelabel-chk-colorG': "${colorG}", + 'custom-codelabel-chk-endsuffix': "${endsuffix}", + }, + inlineStyle: { + "--theme-wz-bgcolor": "${bgcolor1}", + "--theme-wz-title-color": "${color1}", + "--theme-wz-msg-color": "${color2}", + "--theme-wz-msg-bgcolor": "${bgcolor2}", + }, + innerHTML: '${value}', + renderEnd: (parse, element, oldHTML) => { // 渲染完单个元素的回调. + + // let parentNode = mv.GetSiyuanBlock(element); + let id = mv.GetSiyuanBlockId(element) + + let div= mv.GetDomByAtrrs(element,'class','cw-chk-wrap','span')[0]; + if (div===null || div===undefined){ + div = mv.CreateSpan(null,'cw-chk-wrap',null); + let span1 = mv.CreateSpan(null,'cw-chk-gaph-one',null) + let span2 = mv.CreateSpan(null,'cw-chk-gaph-two',null) + div.appendChild(span1); + div.appendChild(span2); + element.appendChild(div); + } + + div.classList.remove('cw-chk-tick') + if (parse.parseInfo.chk === 'x'){ + div.classList.add('cw-chk-tick') + } + + div['onclick'] = async function(){ + let msg = mv.GetAttrs(element,"custom-codelabel-chk-msg") + if (msg === undefined) msg='' + + msg = mv.Empty(msg)?"":`|${msg}` + let colorG = mv.GetAttrs(element,"custom-codelabel-chk-colorG") + if (colorG === "undefined" || colorG===undefined){ + colorG="" + } + + if (div.classList.contains('cw-chk-tick')){ + div.classList.remove('cw-chk-tick') + element.innerHTML = `#[ ]${msg}#${colorG}`; + mv.SetAttrs(element,'custom-codelabel-chk-chk',' ') + mv.SetAttrs(element,'custom-codelabel-value',`#[ ]${msg}#${colorG}`) + } + else{ + div.classList.add('cw-chk-tick') + element.innerHTML =`#[x]${msg}#${colorG}`; + mv.SetAttrs(element,'custom-codelabel-chk-chk','x') + mv.SetAttrs(element,'custom-codelabel-value',`#[ ]${msg}#${colorG}`) + } + + if (colorG!==null &&colorG!==undefined && colorG.includes("!")){ + div.classList.add('cw-chk-endsuffix') + }else{ + div.classList.remove('cw-chk-endsuffix') + } + + let md = mv.GetLute().BlockDOM2StdMd(element.parentNode.parentNode.innerHTML) console.log(md) let kid = await mv.UpdateBlockByMd_API(id,md) let dom = document.querySelectorAll(`div[data-node-id="${kid}"]`)[0]; @@ -984,7 +1118,7 @@ export var config = { parse.parseInfo.count ); - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); + var tmd = mv.GetLute().BlockDOM2StdMd(parentNode.innerHTML); let did = await mv.UpdateBlockByMd_API(id, tmd); let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; render(dom); @@ -1321,6 +1455,27 @@ export var config = { ], }, + codelabelrender:{ // 渲染自定义样式 + enable: true, // 是否启用自定义样式渲染 + render:{ + enable: true, // 是否启用自定义样式渲染 + toolbar:{ // 菜单栏 + enable: true, // 是否启用自定义样式渲染 + id: 'toolbar-theme-style-codelabelrender', + hotkey: () => config.theme.hotkeys.codelabel.render2, + label: { + zh_CN: '渲染整个页面', + zh_CNT: null, + fr_FR: null, + en_US: null, + other: 'Render Inline Code Parse', + }, + icon: '#iconPreview', + index: -5, + }, + }, + }, + menu: { enable: true, // 行内代码编辑增强 codelabel: { @@ -1396,6 +1551,15 @@ export var config = { altKey: true, key: '0', }, + + render2: { + // 渲染( Alt + 0) + ctrlKey: false, + metaKey: true, + shiftKey: false, + altKey: true, + key: '0', + }, }, menu: { diff --git a/script/module/codelabel-custom copy.js b/script/module/codelabel-custom copy.js deleted file mode 100644 index ac7dfb3..0000000 --- a/script/module/codelabel-custom copy.js +++ /dev/null @@ -1,96 +0,0 @@ - -import { config } from './b320config.js'; -import { isKey } from '../utils/hotkey.js'; -import {CodeLabelParse} from '../utils/codelabel-parse.js' -import { - toolbarItemInit, - toolbarItemChangeStatu, -} from './../utils/ui.js'; - -function render(){ - for(let value of config.theme.codelabel.ptype){ - new CodeLabelParse(value,document).render(); - } -} - -function CodelabelEnable(){ - - config.theme.codelabel.render.enable=!config.theme.codelabel.render.enable - - // console.log("enable:"+config.theme.codelabel.render.enable); - - if (config.theme.codelabel.render.toolbar){ - // 更改菜单栏按钮状态 - toolbarItemChangeStatu( - config.theme.codelabel.render.toolbar.id, - config.theme.codelabel.render.enable, - 'SVG', - undefined, - 1, - ); - } - } - -// fn__flex layout-tab-bar - -// 观察器的配置(需要观察什么变动) -const tab_config = { attributes: true, childList: true, subtree: true }; -// 当观察到变动时执行的回调函数 -const tab_callback = function(mutationsList, observer) { - // Use traditional 'for loops' for IE 11 - for(let mutation of mutationsList) { - if (mutation.type === 'childList') { - // console.log('A child node has been added or removed.'); - setTimeout(render, 500); - } - else if (mutation.type === 'attributes') { - // console.log('The ' + mutation.attributeName + ' attribute was modified.'); - setTimeout(render, 500); - - } - } -}; - -(() => { - try { - - let body = document.body; - - window.onload = setTimeout(render, 0); - - if (config.theme.codelabel.render.toolbar.enable) { - let Fn_guidesEnable = toolbarItemInit( - config.theme.codelabel.render.toolbar, - CodelabelEnable, - ); - } - - body.addEventListener('keyup', (e) => { - - // 通过 Ctrl+Alt+0切换开关 - if (isKey(e, config.theme.hotkeys.codelabel.render)) { - config.theme.codelabel.render.enable = !config.theme.codelabel.render.enable; - // console.warn("bug320_3:", config.theme.codelabel.render.enable) - } - - // console.log("ll:"+config.theme.codelabel.enable); - if (config.theme.codelabel.render.enable !== false) { - setTimeout(render, 0); - } - - }) - - // 选择需要观察变动的节点 - // const targetNode = document.getElementById('some-id'); - const targetNode = document.getElementsByClassName('layout-tab-bar')[4]; - // 创建一个观察器实例并传入回调函数 - const observer = new MutationObserver(tab_callback); - // 以上述配置开始观察目标节点 - observer.observe(targetNode, tab_config); - - - } catch (err) { - console.error(err); - } -})(); - diff --git a/script/module/codelabel-custom.js b/script/module/codelabel-custom.js index ac7dfb3..d1c3810 100644 --- a/script/module/codelabel-custom.js +++ b/script/module/codelabel-custom.js @@ -6,8 +6,15 @@ import { toolbarItemInit, toolbarItemChangeStatu, } from './../utils/ui.js'; +import { mv } from '../commons/domex.js'; -function render(){ +function render(el){ + for(let value of config.theme.codelabel.ptype){ + new CodeLabelParse(value,el).render(); + } +} + +function renderAll(){ for(let value of config.theme.codelabel.ptype){ new CodeLabelParse(value,document).render(); } @@ -19,6 +26,7 @@ function CodelabelEnable(){ // console.log("enable:"+config.theme.codelabel.render.enable); + if (config.theme.codelabel.render.toolbar){ // 更改菜单栏按钮状态 toolbarItemChangeStatu( @@ -29,6 +37,25 @@ function CodelabelEnable(){ 1, ); } + + + } + + function CodelabelEnable2(){ + + renderAll(); + + if (config.theme.codelabelrender.render.toolbar){ + // 更改菜单栏按钮状态 + toolbarItemChangeStatu( + config.theme.codelabelrender.render.toolbar.id, + config.theme.codelabelrender.render.enable, + 'SVG', + undefined, + 1, + ); + } + } // fn__flex layout-tab-bar @@ -41,11 +68,11 @@ const tab_callback = function(mutationsList, observer) { for(let mutation of mutationsList) { if (mutation.type === 'childList') { // console.log('A child node has been added or removed.'); - setTimeout(render, 500); + setTimeout(renderAll, 500); } else if (mutation.type === 'attributes') { // console.log('The ' + mutation.attributeName + ' attribute was modified.'); - setTimeout(render, 500); + setTimeout(renderAll, 500); } } @@ -56,7 +83,7 @@ const tab_callback = function(mutationsList, observer) { let body = document.body; - window.onload = setTimeout(render, 0); + window.onload = setTimeout(renderAll, 0); if (config.theme.codelabel.render.toolbar.enable) { let Fn_guidesEnable = toolbarItemInit( @@ -65,8 +92,17 @@ const tab_callback = function(mutationsList, observer) { ); } + if (config.theme.codelabelrender.render.toolbar.enable) { + let Fn_guidesEnable2 = toolbarItemInit( + config.theme.codelabelrender.render.toolbar, + CodelabelEnable2, + ); + } + body.addEventListener('keyup', (e) => { + var el=mv.GetSiyuanBlock(document.getSelection().focusNode.parentElement); + // 通过 Ctrl+Alt+0切换开关 if (isKey(e, config.theme.hotkeys.codelabel.render)) { config.theme.codelabel.render.enable = !config.theme.codelabel.render.enable; @@ -75,7 +111,12 @@ const tab_callback = function(mutationsList, observer) { // console.log("ll:"+config.theme.codelabel.enable); if (config.theme.codelabel.render.enable !== false) { - setTimeout(render, 0); + // setTimeout(render, 0); + render(el) + } + + if (isKey(e, config.theme.hotkeys.codelabel.render2)){ + setTimeout(renderAll, 0); } }) diff --git a/script/module/rightmenu.js b/script/module/rightmenu.js index 11c6438..1028f93 100644 --- a/script/module/rightmenu.js +++ b/script/module/rightmenu.js @@ -138,7 +138,7 @@ setTimeout(() => { function getCodeLabelMark(target){ let node = target; - if (node.localName === 'code') { + if (node.localName === 'span' && node.getAttribute('data-type') === 'code') { return { element: target, type:'code', diff --git a/script/static/test7.html b/script/static/test7.html deleted file mode 100644 index 00c14a9..0000000 --- a/script/static/test7.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - Document - - -
-
- - -
-
- - -
- -
- - - 无验证码 换一张 -
-
- -
-
- -
-
- - - \ No newline at end of file diff --git a/script/static/test8.html b/script/static/test8.html deleted file mode 100644 index 817d566..0000000 --- a/script/static/test8.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - Document - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
无验证码 换一张
- - \ No newline at end of file diff --git a/script/utils/codelabel-parse copy.js b/script/utils/codelabel-parse copy.js deleted file mode 100644 index 226d860..0000000 --- a/script/utils/codelabel-parse copy.js +++ /dev/null @@ -1,553 +0,0 @@ -import { mv } from "../commons/domex.js"; -import { config } from "../module/b320config.js"; -import { rerenderColor } from "./codetag.js"; - -export { - CodeLabelParse, -} - -// ptypeItem 对应的值 -// { // 解析配置项 -// typeid: "唯一ID", -// reg: '正则表达式', // 针对 innerHTML -// tagName: "标签名称,如 code、strong", -// customf: '禁止渲染的块属性值,如 wz', // 自定义属性 f=wz 即可。 -// className: 'css类属性名称', - -// // select1: `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的,默认不用设置 -// // select2: `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 要选择的,默认不用设置 - -// maps: { // 解析后-分组的别名,也是 parseInfo 中的字段 -// /** -// * 以下字段名称被占用,不要用于下面列表的值中. -// * value, // code 标签的 InnerHTML -// * color1,bgcolor1, // 主颜色计算结果和适配背景色 -// * color2,bgcolor2, // 次颜色计算结果和适配背景色 -// * $0~$9 也不要用. -// */ -// '$0': 'value', // 占用,code 原始的 innerHTML 内容 -// '$1': '', -// '$2': 'title', -// '$3': 'msg', -// '$4': '', -// '$5': 'color', -// '$6': 'endsuffix', -// '$7': '', -// '$8': '', -// '$9': '', -// }, -// emptys: ['title','msg'], // 不能为null,undefined或者空值的字段,用 '$0'-'$9' 对应的别名 -// onlyValue:{ -// 'title':['1','2'], -// }, -// emptysValues:{ // 当值为null,undefined或者空值时,要设置的值,用 'key 用:$0'-'$9' 对应的别名,value 是对应的值。 -// 'title':'ke', -// }, -// style:{ // 样式映射信息 -// rerender:true, // 是否计算颜色 -// color: { -// value:'color', // 主颜色对应的字段,用 $0'-'$9' 对应的别名 -// suffix:'endsuffix', // 颜色后缀对应的字段,用 $0'-'$9' 对应的别名 -// }, -// default:'theme2', // 主颜色缺省时,默认的颜色值 -// defaultSuffix:false, // 颜色后缀缺省时,默认的后缀内容,表示的值(suffixs中value) -// colors:{ -// suffixs:{ // 颜色后缀内容,表示的值 -// '!':true, -// }, -// names: ()=>config.theme.common.colors.names, // 主颜色,支持的颜色名称-列表, -// values: ()=>config.theme.common.colors.values, // 主颜色,对应的适配配色-列表 -// } -// }, -// customAttr: { // 自定义属性,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 -// 'custom-codelabel-wz-title': "${title}", -// 'custom-codelabel-wz-msg': "${msg}", -// }, -// inlineStyle: { // 自定义内联样式,key表示属性名,value是属性值,支持类似js的模板语法,${别名}, 会被实际的值替换 -// "--theme-wz-bgcolor":"${bgcolor1}", -// "--theme-wz-title-color":"${color1}", -// "--theme-wz-msg-color":"${color2}", -// "--theme-wz-msg-bgcolor":"${bgcolor2}", -// }, -// innerHTML: '${value}', // 解析后 code 标签的 innerHTML 内容,支持类似js的模板语法,${别名}, 会被实际的值替换 -// renderEnd: (parse, element,oldHTML) => { //在每个元素渲染解析完成后的回调函数 -// // parse是解析信息,$0~$9 的别名,如果开启 style.rerender为true,还有 color1,bgcolor1,color2,bgcolor2 (主颜色和适配颜色) -// // element 当前元素(解析后的) -// // oldHTML (解析前的 innerHTML 内容) -// }, -// }, - - -/** - * Tag解析帮助类 - */ -class CodeLabelParse { - - constructor(ptypeItem,domNode) { - - this.ptypeItem = mv.deepCopy(ptypeItem); - this.domNode = domNode; - - this.tagName = ptypeItem.tagName; - this.styleConfig = mv.deepCopy(ptypeItem.style); - - this.className = ptypeItem.className; // 属性名称 - this.customf = ptypeItem.customf; // 忽略解析的属性值 - this.reg = ptypeItem.reg; // 正则表达式 - this.parseInfo = {}; // 解析后内容 - this.maps = ptypeItem.maps; // 映射关系 - this.emptys = ptypeItem.emptys; // 不能为空的字段 - this.onlyValue = ptypeItem.onlyValue; - this.emptysValues = ptypeItem.emptysValues; // 为空字段的默认值 - - - this.select1 = `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的 - this.select2 = `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 排除的 - - if (mv.Empty(this.ptypeItem.select1) === false){ - this.select1 = this.ptypeItem.select1 - } - if (mv.Empty(this.ptypeItem.select2) === false){ - this.select2 = this.ptypeItem.select2 - } - - this.innerHTML = ptypeItem.innerHTML; // 内置html - this.customAttr = ptypeItem.customAttr; // 自定义属性 - this.inlineStyle = ptypeItem.inlineStyle; // 内敛样式 - - this.renderEnd = ptypeItem.renderEnd; - - /** - * 解决无法在 class 内调用的问题 - */ - - this.renderEnd = this.renderEnd.bind(this); - this.getElementWithoutCustomf = this.getElementWithoutCustomf.bind(this) - this.isEmptyParse = this.isEmptyParse.bind(this) - this.formatInlineHtml = this.formatInlineHtml.bind(this) - this.formatInlineHtml = this.formatInlineHtml.bind(this) - this.formatCustomAttr = this.formatCustomAttr.bind(this) - this.formatInlineStyle = this.formatInlineStyle.bind(this) - this.render = this.render.bind(this) - this.renderSingle = this.renderSingle.bind(this) - this.clacParseInfo = this.clacParseInfo.bind(this) - this.reinitFormat = this.reinitFormat.bind(this) - } - - /** - * 重新初始化变量值 - * @param {object} ptypeItem - */ - reinitFormat(ptypeItem) { - - this.ptypeItem = mv.deepCopy(ptypeItem); - - this.tagName=ptypeItem.tagName; - - this.styleConfig = mv.deepCopy(ptypeItem.style); - - this.className = ptypeItem.className; // 属性名称 - this.customf = ptypeItem.customf; // 忽略解析的属性值 - this.reg = ptypeItem.reg; // 正则表达式 - this.parseInfo = {}; // 解析后内容 - this.maps = ptypeItem.maps; // 映射关系 - this.emptys = ptypeItem.emptys; // 不能为空的字段 - this.onlyValue = ptypeItem.onlyValue; - this.emptysValues = ptypeItem.emptysValues; // 为空字段的默认值 - - this.innerHTML = ptypeItem.innerHTML; // 内置html - this.customAttr = ptypeItem.customAttr; // 自定义属性 - this.inlineStyle = ptypeItem.inlineStyle; // 内敛样式 - - this.select1 = `.protyle-wysiwyg *[data-node-id] ${this.tagName}` // 要选择的 - this.select2 = `.protyle-wysiwyg *[data-node-id][custom-f~=${this.customf}] ${this.tagName}` // 排除的 - - if (mv.Empty(this.ptypeItem.select1) === false){ - this.select1 = this.ptypeItem.select1 - } - if (mv.Empty(this.ptypeItem.select2) === false){ - this.select2 = this.ptypeItem.select2 - } - - this.innerHTML = mv.deepCopy(ptypeItem.innerHTML); // 内置html - this.customAttr = mv.deepCopy(ptypeItem.customAttr); // 自定义属性 - this.inlineStyle = mv.deepCopy(ptypeItem.inlineStyle); // 内敛样式 - } - - - /** - * 获取不在 customf 属性影响范围内的所有 code 标签 - * @param {*} select1 - * @param {*} select2 - * @param {*} domNode - * @returns - */ - getElementWithoutCustomf(select1,select2,domNode) { - - let rst = mv.GetDomBySelectors( - select1,select2, - domNode - ); - return rst; - } - - /** - * 判断是否符合条件 - * @param {Object} parseInfo 解析后的值 - * @param {Array} emptys 不能为空的字段 - * @param {Object} onlyValue 不为空时,必须包含在内的值 - * @returns - */ - isEmptyParse(parseInfo,emptys,onlyValue) { - - let isEmpty = false; - let count = emptys.length; - for (let i = 0; i < count; i++) { - let key = emptys[i]; - let value = parseInfo[key]; - if (mv.Empty(value)) { - isEmpty = true; - break; - } - - if (onlyValue!==null&&onlyValue!==undefined){ - let values = [] - if (typeof onlyValue[key] === "function"){ - values = onlyValue[key](); - }else{ - values = onlyValue[key]; - } - - if (values.length > 0 && values.includes(value) === false){ - isEmpty = true; - break; - } - } - - } - - - return isEmpty; - } - - /** - * 格式化 inlinehtml - * @param {string} innerHTML 自定义书属性信息 - * @param {object} parseInfo 解析信息 - * @param {Array} maps 映射信息 - * @returns 返回格式化的信息 - */ - formatInlineHtml(innerHTML, parseInfo, maps) { - - // 替换 $1~$9 的别名 - for (let k in parseInfo) { - let tv = parseInfo[k]; - if (tv === undefined || tv === null || mv.Empty(tv)) { - tv = this.emptysValues[k]; - } - innerHTML = innerHTML.replace(`\$\{${k}\}`, tv); - } - - // 替换 $1~$9 - for (let k in maps) { - let tv = parseInfo[maps[k]]; - if (tv === undefined || tv === null || mv.Empty(tv)) { - tv = this.maps[k]; - } - innerHTML = innerHTML.replace(`\$\{${k}\}`, tv); - } - - return innerHTML; - } - - /** - * 格式化 自定义属性 - * @param {object} customAttr 自定义属性信息 - * @param {object} parseInfo 解析信息 - * @param {Array} maps 映射信息 - * @returns 返回格式化的信息 - */ - formatCustomAttr(customAttr, parseInfo, maps) { - - for (let ca in customAttr) { - - // 替换 $1~$9 的别名 - for (let k in parseInfo) { - - let tv = parseInfo[k]; - if (tv === undefined || tv === null || mv.Empty(tv)) { - tv = this.emptysValues[k]; - } - - customAttr[ca] = customAttr[ca].replace(`\$\{${k}\}`, tv); - } - - // 替换 $1~$9 - for (let k in maps) { - - let tv = parseInfo[maps[k]]; - if (tv === undefined || tv === null || mv.Empty(tv)) { - tv = this.maps[k]; - } - - customAttr[ca] = customAttr[ca].replace(`\$\{${k}\}`, tv); - } - - customAttr["custom-codelabel-value"] = parseInfo.value; - } - - - return mv.deepCopy(customAttr); - } - - - - /** - * 格式化 内敛样式 - * @param {object} inlineStyle 自定义样式信息 - * @param {object} parseInfo 解析信息 - * @param {object} maps 映射信息 - * @returns 返回格式化的信息 - */ - formatInlineStyle(inlineStyle, parseInfo, maps) { - - for (let ca in this.inlineStyle) { - - // 替换 $1~$9 的别名 - for (let k in parseInfo) { - - let tv = parseInfo[k]; - if (tv === undefined || tv === null || mv.Empty(tv)) { - tv = this.emptysValues[k]; - } - - inlineStyle[ca] = inlineStyle[ca].replace(`\$\{${k}\}`, tv); - } - - // 替换 $1~$9 - for (let k in maps) { - - let tv = parseInfo[maps[k]]; - if (tv === undefined || tv === null || mv.Empty(tv)) { - tv = this.maps[k]; - } - - inlineStyle[ca] = inlineStyle[ca].replace(`\$\{${k}\}`, tv); - } - - } - - return mv.deepCopy(inlineStyle); - - } - - - /** - * 渲染单个 - * @param {元素} e - * @param {*} oldHTML - * @param {*} isReParseInfo - */ - renderSingle(e, parseInfo) { - - // 添加lclassName - e.classList.add(this.className) - - // 重新计算 html - this.innerHTML = this.formatInlineHtml(this.innerHTML, parseInfo, this.maps); - e.innerHTML = this.innerHTML; - - - // 设置属性 - this.customAttr = this.formatCustomAttr(this.customAttr, parseInfo, this.maps); - for (let k in this.customAttr) { - e.setAttribute(k, this.customAttr[k]); - } - - - // 设置 inline style - this.inlineStyle = this.formatInlineStyle(this.inlineStyle, parseInfo, this.maps); - for (let k in this.inlineStyle) { - mv.SetStyleValue(e.style, k, this.inlineStyle[k]); - } - - // 添加事件 - if (e.tagName.toLowerCase() === "code"){ - - e.addEventListener('dblclick',(event)=>{ - - config.theme.codelabel.render.enable = false; - - e.innerHTML = parseInfo["value"]; - - while(e.attributes.length > 0) - e.removeAttribute(e.attributes[0].name); - - e.focus(); - - }); - - } - } - - /** - * 计算 ParseInfo 的字段和对应值 - * @param {string} oldHTML - * @returns - */ - clacParseInfo(oldHTML) { - - let parseInfo = {}; - let simplePatt = new RegExp(this.reg); - - if (simplePatt.test(oldHTML)) { - // 缓存 Regex.$1~Regex.$9 的值 - let vmap = { - '$1': RegExp.$1, - '$2': RegExp.$2, - '$3': RegExp.$3, - '$4': RegExp.$4, - '$5': RegExp.$5, - '$6': RegExp.$6, - '$7': RegExp.$7, - '$8': RegExp.$8, - '$9': RegExp.$9, - } - - // 设置 parseInfo 对象 - for (let k in this.maps) { - let v = this.maps[k]; - if (mv.Empty(v) === false) { - parseInfo[v] = vmap[k]; - } - } - - parseInfo['value'] = oldHTML; - - // 如果需要计算配色 - if (this.styleConfig.rerender === true) { - let styleinfo = new StyleColorInfo(this.styleConfig); - styleinfo.rerender( - parseInfo[this.styleConfig.color.value], // 颜色值 - parseInfo[this.styleConfig.color.suffix] // 颜色后缀 - ); - - // 添加计算结果到 parseInfo - for (let k in styleinfo) { - parseInfo[k] = styleinfo[k]; - } - } - } - - return mv.deepCopy(parseInfo); - - } - - /** - * 渲染 - * @param {html} params - */ - render() { - // 获取符合条件的 code 标签 - let elements = this.getElementWithoutCustomf(this.select1,this.select2,this.domNode); - - if (elements == null ||elements == undefined ) return; - - // 正则表达式 - let simplePatt = new RegExp(this.reg); - - for (let e of elements) { - - // 重新获取模板 - this.reinitFormat(this.ptypeItem); - - // 获取 code 标签 中的原始内容。 - let oldHTML = e.innerHTML; - - if (simplePatt.test(oldHTML)) { - - // 计算 ParseInfo - this.parseInfo = this.clacParseInfo(oldHTML) - - // 判断关键解析结构是否为空,不为空的时候,设置 - let isEmpty = this.isEmptyParse(this.parseInfo,this.emptys,this.onlyValue); - if (isEmpty === false && e.className !== this.className) { - - // 渲染当个节点 - this.renderSingle(e, this.parseInfo); - - // 渲染结束事件 - this.renderEnd(this, e, oldHTML); - - // e.addEventListener('focus',(event)=>{ - // event.target.style.background = 'pink'; - // console.warn("1"); - // }); - - // e.addEventListener('blur', (event) => { - // event.target.style.background = ''; - // console.warn("2"); - // }); - - } - - - } - - } - - } - - -} - - -/** - * 样式-颜色信息转换类 - */ -class StyleColorInfo { - constructor(styleConfig, colorEndsuffix) { - - this.styleConfig = mv.deepCopy(styleConfig) - this.colorEndsuffix = colorEndsuffix - let gcolor = mv.deepCopy(this.styleConfig.colors.values()[this.styleConfig.default]); - this.bgcolor1 = gcolor.value - this.color1 = gcolor.titlecolor - this.bgcolor2 = gcolor.msgbgcolor - this.color2 = gcolor.msgcolor - } - - - /** - * - * @param {string} color 主颜色 - * @param {string} suffix 主颜色后缀 - */ - async rerender(color, suffix) { - - let vsuffix = false; - - if (suffix !== undefined && suffix !== null && this.styleConfig.colors.suffixs.hasOwnProperty(suffix)) { - vsuffix = this.styleConfig.colors.suffixs[suffix]; - } - else { - vsuffix = this.styleConfig.defaultSuffix - } - - let vlook = rerenderColor(color, vsuffix, - this.styleConfig.colors.values(), - this.styleConfig.colors.names(), - this.styleConfig.default) - - this.bgcolor1 = vlook.value - this.color1 = vlook.titlecolor - this.bgcolor2 = vlook.msgbgcolor - this.color2 = vlook.msgcolor - - // this.bgcolor1 = vlook.value - // this.color1 = vlook.titlecolor - // this.bgcolor2 = vlook.msgcolor - // this.color2 = vlook.msgbgcolor - } - -} \ No newline at end of file diff --git a/script/utils/codelabel-parse.js b/script/utils/codelabel-parse.js index c63c6c7..a0ad5a5 100644 --- a/script/utils/codelabel-parse.js +++ b/script/utils/codelabel-parse.js @@ -99,6 +99,7 @@ class CodeLabelParse { this.maps = ptypeItem.maps; // 映射关系 this.emptys = ptypeItem.emptys; // 不能为空的字段 this.onlyValue = ptypeItem.onlyValue; + this.ignoreValue = ptypeItem.ignoreValue; this.emptysValues = ptypeItem.emptysValues; // 为空字段的默认值 @@ -125,6 +126,7 @@ class CodeLabelParse { this.renderEnd = this.renderEnd.bind(this); this.getElementWithoutCustomf = this.getElementWithoutCustomf.bind(this) this.isEmptyParse = this.isEmptyParse.bind(this) + this.isIgnoreParse = this.isIgnoreParse.bind(this) this.formatInlineHtml = this.formatInlineHtml.bind(this) this.formatInlineHtml = this.formatInlineHtml.bind(this) this.formatCustomAttr = this.formatCustomAttr.bind(this) @@ -155,6 +157,8 @@ class CodeLabelParse { this.maps = ptypeItem.maps; // 映射关系 this.emptys = ptypeItem.emptys; // 不能为空的字段 this.onlyValue = ptypeItem.onlyValue; + this.ignoreValue = ptypeItem.ignoreValue; + this.emptysValues = ptypeItem.emptysValues; // 为空字段的默认值 this.innerHTML = ptypeItem.innerHTML; // 内置html @@ -185,7 +189,7 @@ class CodeLabelParse { * @returns */ getElementWithoutCustomf(select1,select2,domNode) { - + let rst = mv.GetDomBySelectors( select1,select2, domNode @@ -232,6 +236,47 @@ class CodeLabelParse { return isEmpty; } + /** + * 判断是否是忽略处理的值 + * @param {Object} parseInfo 解析后的值 + * @param {Array} emptys 不能为空的字段 + * @param {Object} onlyValue 不为空时,必须包含在内的值 + * @returns + */ + isIgnoreParse(parseInfo,emptys,ignoreValue){ + + let isEmpty = false; + let count = emptys.length; + + for (let i = 0; i < count; i++) { + + let key = emptys[i]; + let value = parseInfo[key]; + + if (mv.Empty(value)) { + isEmpty = true; + break; + } + + if (ignoreValue!==null&&ignoreValue!==undefined){ + let values = [] + if (typeof ignoreValue[key] === "function"){ + values = ignoreValue[key](); + }else{ + values = ignoreValue[key]; + } + + if (values.length > 0 && values.includes(value) === true){ + isEmpty = true; + break; + } + } + + } + + return isEmpty; + } + /** * 格式化 inlinehtml * @param {string} innerHTML 自定义书属性信息 @@ -476,7 +521,8 @@ class CodeLabelParse { // 判断关键解析结构是否为空,不为空的时候,设置 let isEmpty = this.isEmptyParse(this.parseInfo,this.emptys,this.onlyValue); - if (isEmpty === false && e.className !== this.className) { + let isIngore = this.isIgnoreParse(this.parseInfo,this.emptys,this.ignoreValue); + if (isEmpty === false && isIngore==false && e.className !== this.className) { // 渲染当个节点 this.renderSingle(e, this.parseInfo); diff --git a/script/utils/ui-ex.js b/script/utils/ui-ex.js index 3d34ace..1da99f3 100644 --- a/script/utils/ui-ex.js +++ b/script/utils/ui-ex.js @@ -47,22 +47,26 @@ async function showUtil(element,callback){ let cts = new MessageboxInputs(its); let idom=cts.Create( async ()=>{ - console.log("Ok") - console.log(cts.Doms.msg.value) + // console.log("Ok") + // console.log(cts.Doms.msg.value) let value =cts.Doms.msg.value; - // 清空所有属性 - element.className = ""; + // element.className = ""; + console.log("showUtil") + console.log(element) + while (element.attributes.length > 0) { element.removeAttributeNode(element.attributes[0]); } + mv.SetAttrs(element,'data-type','code'); + // 获取父节点 let parentNode=mv.GetSiyuanBlock(element); let id = mv.GetSiyuanBlockId(element); element.innerHTML = value; - var tmd = mv.GetLute().BlockDOM2Md(parentNode.innerHTML); + var tmd = mv.GetLute().BlockDOM2StdMd(parentNode.innerHTML); let did = await mv.UpdateBlockByMd_API(id, tmd); let dom = document.querySelectorAll(`div[data-node-id="${did}"]`)[0]; render(dom); @@ -70,7 +74,7 @@ async function showUtil(element,callback){ }, async ()=>{ console.log("NO") - console.log(cts.Doms.msg.value) + // console.log(cts.Doms.msg.value) },"cbeditor","width: 480px;top: 45%; left: 45%;","" ); diff --git a/style/blocks/v-block-inline-style.css b/style/blocks/v-block-inline-style.css index e36472c..1a87b42 100644 --- a/style/blocks/v-block-inline-style.css +++ b/style/blocks/v-block-inline-style.css @@ -1,13 +1,13 @@ /* --------------- 删除线 --------------- */ -.protyle-wysiwyg s{ +.protyle-wysiwyg span[data-type='s']{ color: var(--v-block-del-color) !important; text-decoration: line-through solid !important; } /* --------------- 删除线 end --------------- */ /* --------------- 键盘按键块 --------------- */ -.protyle-wysiwyg kbd { +.protyle-wysiwyg span[data-type='kbd'] { font: var(--v-block-kbd-font); border-radius: var(--v-block-kbd-border-radius); margin: 0; @@ -76,8 +76,8 @@ * 悬浮提示 */ -kbd:hover, -code:hover +span[data-type='kbd']:hover, +span[data-type='code']:hover { filter: brightness(1.1) } diff --git a/style/customs/code-rb-coad copy.css b/style/customs/code-rb-coad copy.css deleted file mode 100644 index 8e8a451..0000000 --- a/style/customs/code-rb-coad copy.css +++ /dev/null @@ -1,42 +0,0 @@ -.protyle-wysiwyg *[data-node-id] :not([custom-f~=rb]) .v-rb-coat[custom-codelabel-rb-coat-showe="false"] { - background: linear-gradient(45deg, var(--theme-rb-bgcolor) 0%, var(--theme-rb-bgcolor) 25%,var(--d-f-c) 25%, var(--d-f-c) 50%,var(--theme-rb-bgcolor) 50%, var(--theme-rb-bgcolor) 75%,var(--d-f-c) 75%, var(--d-f-c) 100%) ; - border-color: var(--theme-rb-bgcolor) ; - /* color: var(--d-bc); */ - color: var(--theme-rb-title-color); - padding: 5px; - border-radius: 3px; -} - -.protyle-wysiwyg *[data-node-id] :not([custom-f~=rb]) .v-rb-coat[custom-codelabel-rb-coat-showe="true"] { - background: var(--theme-rb-msg-color); - color: var(--theme-rb-bgcolor); - box-shadow: 0 0 0 1px var(--d-f-c), 0 2px 0 0 var(--d-f-c) inset; - text-shadow: none; - padding: 5px; - border-radius: 3px; -} - -.protyle-wysiwyg *[data-node-id] .v-rb-coat span{ - display: none; -} - -.protyle-wysiwyg *[data-node-id] .v-rb-coat[custom-codelabel-rb-coat-showe="false"]::before{ - /* color: var(--theme-wz-title-color); */ - content: attr(custom-codelabel-rb-coat-text); - padding-right: 3px; -} - -.protyle-wysiwyg *[data-node-id] .v-rb-coat[custom-codelabel-rb-coat-showe="true"]::after{ - /* color: #1f2e3b; */ - /* color: var(--theme-wz-msg-color); */ - /* background-color: var(--theme-wz-msg-bgcolor); */ - content: attr(custom-codelabel-rb-coat-data); - padding: 3px; - border-radius: 3px; -} - -.protyle-wysiwyg .v-rb-coat:hover { - box-shadow: 0 0 0 1px #2aa899, 0 2px 0 0 #2aa899 inset !important; - display: inline-block; - transform: scale(1.2) -} \ No newline at end of file diff --git a/style/customs/codelabel-block-bq copy.css b/style/customs/codelabel-block-bq copy.css deleted file mode 100644 index c72860a..0000000 --- a/style/customs/codelabel-block-bq copy.css +++ /dev/null @@ -1,122 +0,0 @@ -/* --------------- 引用块 --------------- */ - -/*在列表中*/ -/* .protyle-wysiwyg .li > .bq[bqtype^="color"]{ - border-left: 5px solid rgba(148, 152, 160, .2); - background: 0 0; - border-radius: 0; - padding: 0 0.5em; -} */ - -/*独立的*/ -.protyle-wysiwyg .bq[bqtype="color"] { - - /* color: var(--v-block-bq-color); - background: var(--v-block-bq-bg-color); - */ - color: var(--theme-bq-color1); - background: var(--theme-bq-bgcolor); - border-radius: var(--v-block-bq-border-radius); - padding: 0.75em 1em; - margin-top: 0; - margin-bottom: 20px; -} - -.protyle-wysiwyg .bq[bqtype="color"] code.bqcolor{ - background: var(--theme-bq-bgcolor); -} - - -.protyle-wysiwyg .bq[bqtype="color1"] { - - /* color: var(--v-block-bq-color); - background: var(--v-block-bq-bg-color); - */ - color: var(--b3-theme-on-background); - background: var(--b3-theme-background); - border-width: 5px ; - border-style: solid ; - border-color: var(--theme-bq-bgcolor); - border-radius: var(--v-block-bq-border-radius); - padding: 0.75em 1em; - margin-top: 0; - margin-bottom: 20px; -} - - -.protyle-wysiwyg .bq[bqtype="color1"] code.bqcolor{ - background: var(--b3-theme-background); -} - -/* --------------- 引用块 end --------------- */ - - -.protyle-wysiwyg .bq[bqtype^="color"] code.bqcolor span{ - display: none; -} - -/* custom-select-endsuffix */ - -.protyle-wysiwyg .bq[bqtype^="color"] code.bqcolor::before{ - - content: attr(custom-select-data); - - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 1px; - padding: 0 4px; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--v-theme2); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); - transform: translate(-3px,-2px) -} - - -/* .protyle-wysiwyg .bq[bqtype^="color"] code::after{ - - content: attr(custom-select-endsuffix); - - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 1px; - padding: 0 4px; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--v-theme1); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); - transform: translate(-3px,-2px) -} */ - -.protyle-wysiwyg .bq[bqtype^="color"] code.bqcolor li::after{ - - content: attr(custom-li-data); - - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 1px; - padding: 0 4px; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--key-bg); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); - transform: translate(-3px,-2px) -} - -.protyle-wysiwyg .bq[bqtype^="color"] code.bqcolor ul{ - display: none; - list-style: none; -} - -.protyle-wysiwyg .bq[bqtype^="color"] code.bqcolor:hover ul{ - display: block; - list-style: none; - /* margin-left: attr(custom-left); */ -} - diff --git a/style/customs/codelabel-block-bq.css b/style/customs/codelabel-block-bq.css index d04fa95..58e562b 100644 --- a/style/customs/codelabel-block-bq.css +++ b/style/customs/codelabel-block-bq.css @@ -22,7 +22,7 @@ margin-bottom: 20px; } -.protyle-wysiwyg .bq[bqtype="color"] code.bqcolor{ +.protyle-wysiwyg .bq[bqtype="color"] span[data-type='code'].bqcolor{ background: var(--theme-bq-bgcolor); } diff --git a/style/customs/codelabel-tick copy.css b/style/customs/codelabel-tick copy.css deleted file mode 100644 index 666886c..0000000 --- a/style/customs/codelabel-tick copy.css +++ /dev/null @@ -1,85 +0,0 @@ -span.hide{ - display: none; -} -.cw-chk-wrap { - display: inline-block; - width: 22px; - height: 22px; - -ms-transform: rotate(45deg); - /* IE 9 */ - -webkit-transform: rotate(45deg); - /* Chrome, Safari, Opera */ - transform: rotate(45deg) translate(4px,3px); - /* background-color: var(--theme-wz-title-color) !important; */ -} - -code.custom-codelabel-chk[custom-codelabel-chk-msg]:not(:empty)::after{ - content: attr(custom-codelabel-chk-msg); - height: 22px; - margin: 0px !important; - padding: 3px 4px !important; - background-color: var(--theme-wz-bgcolor) !important; - color: var(--theme-wz-title-color) !important; - /* transform: translate(-10px,0px) !important; */ -} - -.cw-chk-gaph-one { - position: absolute; - width: 3px; - height: 9px; - background-color: #ccc !important; - left: 11px; - top: 6px; -} - -.cw-chk-gaph-two { - position: absolute; - width: 3px; - height: 3px; - background-color: #ccc !important; - left: 8px; - top: 12px; -} - -.cw-chk-tick .cw-chk-gaph-one, -.cw-chk-tick .cw-chk-gaph-two { - background-color: #4caf50 !important; -} - -code.custom-codelabel-chk{ - padding-left: 0px !important; - padding-right: 0px !important; - background-color: var(--theme-wz-title-bgcolor) !important; - /* border: 1px solid var(--theme-wz-bgcolor) !important; */ -} - -code.custom-codelabel-chk[custom-codelabel-chk-chk=" "]{ - border: 1px solid #ccc !important; -} - -code.custom-codelabel-chk[custom-codelabel-chk-chk="x"]{ - border: 1px solid var(--theme-wz-bgcolor) !important; -} - - -code.custom-codelabel-chk[custom-codelabel-chk-msg]:not(:empty)::after{ - content: attr(custom-codelabel-chk-msg); - height: 22px; - margin: 0px !important; - padding: 3px 4px !important; - color: var(--theme-wz-title-color) !important; -} - -code.custom-codelabel-chk[custom-codelabel-chk-chk="x"][custom-codelabel-chk-msg]:not(:empty)::after{ - background-color: var(--theme-wz-bgcolor) !important; - /* color: var(--theme-wz-title-color) !important; */ -} - -code.custom-codelabel-chk[custom-codelabel-chk-chk=" "][custom-codelabel-chk-msg]:not(:empty)::after{ - background-color: #ccc !important; -} - -[custom-codelabel-chk-endsuffix='!'] .cw-chk-tick .cw-chk-gaph-one, -[custom-codelabel-chk-endsuffix='!'] .cw-chk-tick .cw-chk-gaph-two { - background-color: var(--theme-wz-bgcolor) !important; -} \ No newline at end of file diff --git a/style/customs/codelabel-tick.css b/style/customs/codelabel-tick.css index ad8b012..f4ccb9f 100644 --- a/style/customs/codelabel-tick.css +++ b/style/customs/codelabel-tick.css @@ -3,21 +3,24 @@ span.hide{ } .cw-chk-wrap { display: inline-block; - width: 22px; - height: 22px; + width: 21px; + /* width: 22px; */ + height: 15px; + /* height: 22px; */ -ms-transform: rotate(45deg); /* IE 9 */ -webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */ - transform: rotate(45deg) translate(4px,3px); + transform: rotate(45deg) translate(2px,-2px); /* background-color: var(--theme-wz-title-color) !important; */ } span.custom-codelabel-chk[custom-codelabel-chk-msg]:not(:empty)::after{ content: attr(custom-codelabel-chk-msg); - height: 22px; + height: 15px; + /* height: 22px; */ margin: 0px !important; - padding: 3px 4px !important; + /* padding: 3px 4px !important; */ background-color: var(--theme-wz-bgcolor) !important; color: var(--theme-wz-title-color) !important; /* transform: translate(-10px,0px) !important; */ @@ -44,6 +47,13 @@ span.custom-codelabel-chk[custom-codelabel-chk-msg]:not(:empty)::after{ .cw-chk-tick .cw-chk-gaph-one, .cw-chk-tick .cw-chk-gaph-two { background-color: #4caf50 !important; + /* background-color: var(--theme-wz-bgcolor) !important; */ +} + +span.cw-chk-endsuffix .cw-chk-tick .cw-chk-gaph-one, +span.cw-chk-endsuffix .cw-chk-tick .cw-chk-gaph-two { + /* background-color: #4caf50 !important; */ + background-color: var(--theme-wz-bgcolor) !important; } span.custom-codelabel-chk{ @@ -66,7 +76,7 @@ span.custom-codelabel-chk[custom-codelabel-chk-msg]:not(:empty)::after{ content: attr(custom-codelabel-chk-msg); height: 22px; margin: 0px !important; - padding: 3px 4px !important; + padding: 3px 5px !important; color: var(--theme-wz-title-color) !important; } diff --git a/style/customs/codelabel-todo copy.css b/style/customs/codelabel-todo copy.css deleted file mode 100644 index 4997565..0000000 --- a/style/customs/codelabel-todo copy.css +++ /dev/null @@ -1,45 +0,0 @@ - -.protyle-wysiwyg *[data-node-id] .vk-todo span{ - display: none; -} - -.protyle-wysiwyg *[data-node-id] .vk-todo{ - - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 0px; - padding: 6px 4px; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--key-bg); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); - -} - -.protyle-wysiwyg *[data-node-id] .vk-todo button -{ - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 1px; - padding: 0 4px; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--key-bg); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); - transform: translate(-3px,-2px) -} - -.protyle-wysiwyg *[data-node-id] .vk-todo button:hover{ - - box-shadow: 0 0 0 1px var(--v-theme2); - transform: translate(-4px,-3px) scale(1.1) -} - -.protyle-wysiwyg *[data-node-id] .vk-todo button::after{ - content: attr(custom-codelabel-todo-count); -} - diff --git a/style/customs/codelabel-wz copy.css b/style/customs/codelabel-wz copy.css deleted file mode 100644 index 98a8068..0000000 --- a/style/customs/codelabel-wz copy.css +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 1. 单独安装指南: - a. 在 css 文件中添加 codelabel-wz.css 的路径 - @import url(path/to/codelabel-wz.css); - b. 把 script 文件夹复制到主题的根目录中 - c.如果没有 themes.js ,就把压缩包的 themes.js 直接复制过去;如果有,就添加: - - loadScript("/appearance/themes/minilook/script/module/codelabel.js"); - - 注意把其中的 minilook 改成对应的主题文件夹名称。 - - * 2. 新建属性 f=wz 可以屏蔽该效果的渲染。 - * - * 制作人: 路人二 - * 版本: V0.0.1-dev - * 时间: 2022-04-02 - * 更新日志: - * 1. 渲染 `#微章标题|微章内容#(颜色)` - */ - -/* ****************** custom-codelabel-wz *******************/ -.protyle-wysiwyg *[data-node-id] :not([custom-f~=wz]) code.custom-codelabel-wz { - /* background-color: #2aa899; */ - background-color: var(--theme-wz-bgcolor); - padding: 5px; - border-radius: 3px; -} - -.custom-codelabel-wz:hover { - filter: brightness(1.1) -} - -.protyle-wysiwyg *[data-node-id] .custom-codelabel-wz span{ - display: none; -} - -.protyle-wysiwyg *[data-node-id] .custom-codelabel-wz::before{ - color: var(--theme-wz-title-color); - content: attr(custom-codelabel-wz-title); -} - -.protyle-wysiwyg *[data-node-id] .custom-codelabel-wz:not([custom-codelabel-wz-msg='']):after{ - /* color: #1f2e3b; */ - color: var(--theme-wz-msg-color); - background-color: var(--theme-wz-msg-bgcolor); - content: attr(custom-codelabel-wz-msg); - padding: 3px; - margin-left: 3px; - border-radius: 3px; -} diff --git a/style/customs/codelabel-wz-line.css b/style/customs/codelabel-wz-line.css index 03b4426..e7dde58 100644 --- a/style/customs/codelabel-wz-line.css +++ b/style/customs/codelabel-wz-line.css @@ -1,5 +1,5 @@ /* ****************** custom-codelabel-wzline *******************/ -.protyle-wysiwyg *[data-node-id] :not([custom-f~=wzline]) code.custom-codelabel-wzline { +.protyle-wysiwyg *[data-node-id] :not([custom-f~=wzline]) span[data-type='code'].custom-codelabel-wzline { /* background-color: #2aa899; */ background-color: var(--theme-wzline-bgcolor); padding: 5px; diff --git a/style/customs/codelabel-wz.css b/style/customs/codelabel-wz.css index 5f82b1d..6c7349b 100644 --- a/style/customs/codelabel-wz.css +++ b/style/customs/codelabel-wz.css @@ -19,7 +19,7 @@ */ /* ****************** custom-codelabel-wz *******************/ -.protyle-wysiwyg *[data-node-id] :not([custom-f~=wz]) span[data-type="code"].custom-codelabel-wz, +.protyle-wysiwyg *[data-node-id] :not([custom-f~=wz]) span[data-type~="code"].custom-codelabel-wz, .protyle-wysiwyg *[data-node-id] :not([custom-f~=wz]) span[data-type=" code"].custom-codelabel-wz { /* background-color: #2aa899; */ background-color: var(--theme-wz-bgcolor); diff --git a/style/customs/conver-style.css b/style/customs/conver-style.css index 6f851e8..0a4df45 100644 --- a/style/customs/conver-style.css +++ b/style/customs/conver-style.css @@ -40,34 +40,34 @@ /*------------------------------ 封面 ------------------------------*/ -.protyle-wysiwyg[custom-theme] > .h6:first-child sub{ +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='sub']{ display: block; color: var(--conver-sub-color); font: var(--v-f-w-bd) 75% var(--v-f-fm-subtitle) } -.protyle-wysiwyg[custom-theme] > .h6:first-child sup{ +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='sup']{ display: block; color: var(--conver-sup-color); font: var(--v-f-w-bd) 75% var(--v-f-fm-subtitle) } -.protyle-wysiwyg[custom-theme] > .h6:first-child code{ +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='code']{ display: inline-block; font-size: .5em !important; color: var(--conver-code-color); background: var(--conver-code-bg-color); } -.protyle-wysiwyg[custom-theme] > .h6:first-child strong, -.protyle-wysiwyg[custom-theme] > .h6:first-child strong::before +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='strong'], +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='strong']::before { color: var(--conver-strong-color); font: .625em var(--v-f-fm-subtitle) } -.protyle-wysiwyg[custom-theme] > .h6:first-child strong::before +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='strong']::before { content: "By "; opacity: .6; @@ -76,7 +76,7 @@ } -.protyle-wysiwyg[custom-theme] > .h6:first-child em{ +.protyle-wysiwyg[custom-theme] > .h6:first-child span[data-type='em']{ display:block; color: var(--conver-em-color); font: var(--v-f-w-bd) .4em var(--v-f-fm-subtitle) @@ -116,7 +116,7 @@ } .b3-list-item[data-treetype="outline"][data-subtype="h6"]:first-child~.b3-list-item[data-treetype="outline"][data-subtype="h1"]:last-child code, -.b3-list-item[data-treetype="outline"][data-subtype="h6"]:first-child .b3-list-item__text code{ +.b3-list-item[data-treetype="outline"][data-subtype="h6"]:first-child .b3-list-item__text span[data-type='code']{ color: inherit; background: 0 0; font-family: var(--v-f-fm-tag); diff --git a/style/customs/list-to-kanban-by-line.css b/style/customs/list-to-kanban-by-line.css index e051f55..aa2a1b2 100644 --- a/style/customs/list-to-kanban-by-line.css +++ b/style/customs/list-to-kanban-by-line.css @@ -65,9 +65,9 @@ margin-bottom: var(--kbline-boarditem-margin-bottom); } -.protyle-wysiwyg *[custom-f~=kb] .li .p code, -.protyle-wysiwyg .hr:first-child+.hr+*:not(.bq) .li .p code, -.protyle-wysiwyg :not(.hr)+.hr+.hr+*:not(.bq) .li .p code{ +.protyle-wysiwyg *[custom-f~=kb] .li .p span[data-type='code'], +.protyle-wysiwyg .hr:first-child+.hr+*:not(.bq) .li .p span[data-type='code'], +.protyle-wysiwyg :not(.hr)+.hr+.hr+*:not(.bq) .li .p span[data-type='code']{ color: var(--kbline-board-item-code-color); } diff --git a/style/customs/tab-bqe copy 2.css b/style/customs/tab-bqe copy 2.css deleted file mode 100644 index 295f0b8..0000000 --- a/style/customs/tab-bqe copy 2.css +++ /dev/null @@ -1,173 +0,0 @@ - -[custom-type~="bq-wrap"], -[custom-type~="bq-wrap"] .list, -[custom-type~="bq-wrap"] .li, -[custom-type~="bq-wrap"] .bq, -[custom-type~="bq-wrap"] button, -[custom-type~="bq-wrap"] code.bq-tab-button-mode{ - margin: 0 !important; - padding: 0 !important; -} - -[custom-type~="bq-wrap"] code.bq-tab-button-mode{ - /* display: none; */ - /* background-color: ; - color: #f6eef3; - padding: 5px !important; - font-size: 1em !important; - - margin-right: 2px; */ - - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 3px !important; - padding-left: 2px !important; - padding-right: 2px !important; - padding-top: 2px !important; - padding-bottom: 6px !important; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--key-bg); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); - -} - - -[custom-type~="bq-wrap"] code.bq-tab-button-mode:hover { - filter: brightness(1.1); - font-size: 1em !important; - margin: 3px !important; -} - -[custom-type~="bq-wrap"] code.bq-tab-button-mode::after{ - content: "刷新"; - color: #2b1c29; - background-color: rgba(255,255,255,0.9); - border-radius: 3px; - /* margin-left: 5px; - margin-right: 2px; */ - font-size: 1em !important; - - padding-left: 2px !important; - padding-right: 2px !important; - padding-top: 2px !important; - padding-bottom: 6px !important; -} - -[custom-type~="bq-wrap"] button{ - display: inline; - margin-left: 3px !important; - margin-right: 2px !important; - - padding-left: 2px !important; - padding-right: 2px !important; - padding-bottom: 1px !important; - - font: var(--v-block-kbd-font); - border-radius: var(--v-block-kbd-border-radius); - margin: 0; - color: var(--v-block-kbd-color); - border: 2px solid var(--v-block-kbd-border-shadow); - border-left-color: var(--v-block-kbd-border-color); - border-top-color: var(--v-block-kbd-border-color); - background: var(--key-bg); - box-shadow: 0 0 0 1px var(--v-block-kbd-border-shadow); -} - -[custom-type~="bq-wrap"] button::after{ - font-family: var(--v-black-code-font-family); - font-size: 1em !important; - content: attr(bq-button-value); -} - -[custom-type~="bq-wrap"] { - color: var(--b3-theme-on-background) !important; - background: var(--b3-theme-background)!important; - border-width: 5px !important; - border-style: solid !important; - border-color: var(--theme-bq-bgcolor)!important; - border-radius: var(--v-block-bq-border-radius)!important; - padding: 5px 5px !important; - margin-top: 0!important; - margin-bottom: 20px!important; -} - -[custom-type~="bq-wrap"]:not([custom-type~="bq-none"]) [custom-type~="bq-hide"] { - display: none; -} - -[custom-type~="bq-wrap"] [custom-type~="bq-tab_t"] { - height: 40px; - border-bottom: 1px solid #ccc; -} - -[custom-type~="bq-wrap"] [custom-type~="bq-tab_t"] .li { - float: left; - - cursor: pointer; - padding-right: 20px !important; - color: var(--v-theme2) !important; - background: var(--b3-theme-background)!important; - border-width: 5px !important; - border-style: solid !important; - border-color: var(--theme-bq-bgcolor)!important; - border-radius: 0 !important; -} - -[custom-type~="bq-wrap"] [custom-type~="bq-tab_t"] [custom-type~="bq-act"] { - position: relative; - - cursor: pointer; - padding-right: 20px !important; - color: var(--v-theme1) !important; - background: var(--b3-theme-background)!important; - border-width: 5px !important; - border-style: solid !important; - border-color: var(--theme-bq-bgcolor)!important; - border-radius: 0 !important; -} - -[custom-type~="bq-wrap"] [custom-type~="bq-tab_c"] { - color: var(--v-theme2) !important; - background: var(--b3-theme-background)!important; - border-width: 5px !important; - border-style: solid !important; - border-color: var(--theme-bq-bgcolor)!important; - border-radius: 0 !important; - padding: 0.75em 1em!important; - margin-top: 0!important; - margin-bottom: 20px!important; -} - -[custom-type~="bq-wrap"]:not([custom-type~="bq-none"]) [custom-type~="bq-tab_c"] .bq -{ - border-left: 5px solid rgba(148, 152, 160, .2); - background: 0 0; - border-radius: 0; - padding: 0 01.5em; - margin-bottom: 15px !important; -} - - - - -[custom-type~="bq-wrap"][custom-type~="bq-none"] [custom-type~="bq-tab_c"] .bq -{ - border: 5px solid rgba(var(--v-theme1-rgb-value), .2); - background: 0 0; - border-radius: 0; - padding: 0 01.5em; - margin-bottom: 15px !important; -} - - -[custom-type~="bq-wrap"][custom-type~="bq-none"] .bq[custom-type~="bq-hide"] { - - border: 5px dashed rgba(148, 152, 160, .2); - background: 0 0; - border-radius: 0; - padding: 0 01.5em; - margin-bottom: 15px !important; -} \ No newline at end of file diff --git a/theme.css b/theme.css index 9079a2d..2b75435 100644 --- a/theme.css +++ b/theme.css @@ -5,7 +5,7 @@ @import url(/appearance/themes/mini-vlook/style/menus/four-menu-splite.css); -/* 修改文档树样式 */ +/* 修改文档树样式 */ @import url(/appearance/themes/mini-vlook/style/customs/file-tree.css); /* ——————————————————————————— 基础块设置 ——————————————————————————— */ @@ -15,7 +15,7 @@ /*标题-配色*/ @import url(/appearance/themes/mini-vlook/style/blocks/v-block-heading.css); -/*分隔线-配色*/ +/*分隔线-配色 */ @import url(/appearance/themes/mini-vlook/style/blocks/v-block-hr.css); /*行内样式配色-配色*/ diff --git a/theme.json b/theme.json index b410a6a..c8cbf18 100644 --- a/theme.json +++ b/theme.json @@ -2,7 +2,7 @@ "name": "mini-vlook", "author": "路人二", "url": "https://github.com/idea-zone/mini-vlook", - "version": "1.10.0", + "version": "2.0.0", "modes": [ "dark", "light"