思源笔记折腾记录-简单挂件-更美观地展示属性
一、前情提要
我们之前做了一个挂件:
它能够显示自定义属性,而且可以显示出中文名
但是它现在看起来真的好丑.....
于此同时,其他笔记软件的属性显示是这样的
所以我们来鼓捣一下,让它显示得更加好看一点点。
二、实现过程:
之前我们搞出来的组件是长这个样子的:
<template>
<table border="1" >
<tr>
<th>属性名</th>
<th>属性值</th>
</tr>
<!--这里的意思是根据获取到的data的值,把他们全都按中文名显示到文档里面-->
<template v-for="(自定义属性, 序号) in data">
<tr v-if="自定义属性名对照表&&自定义属性名对照表[自定义属性.name]">
<td>{{ 自定义属性名对照表[自定义属性.name]["显示名称"]["中文"] }}</td>
<td>{{ 自定义属性.value }}</td>
</tr>
</template>
</table>
</template>
<script setup>
//这里的为什么要这么写可以参考vue3的文档
import { ref, onMounted } from 'vue'
let 上次更新时间 = new Date().getTime()
let 时间间隔
let 块id = window.frameElement.parentElement.parentElement.dataset.nodeId
let data = ref({})
let 获取数据 = async() => {
await 读取配置文件()
fetch("/api/query/sql", {
method: "post",
body: JSON.stringify({
stmt: ` SELECT * FROM attributes where block_id = (select root_id from blocks where id = "${块id}")`
})
}).then(
res => {
return res.json()
}
).then(
json => {
data.value = json.data
}
)
}
async function 更新数据() {
let 当前时间 = new Date().getTime()
if (!时间间隔||时间间隔 < 1000) {
时间间隔= 当前时间 - 上次更新时间
return
}
else {
时间间隔=0
上次更新时间=当前时间
获取数据()
}
}
let 自定义属性名对照表 = ref(null)
async function 读取配置文件(){
let 请求响应 = await fetch("/widgets/属性名对照表.json")
let 配置内容 = await 请求响应.json()
自定义属性名对照表.value = 配置内容
}
let 开始监听 = () => {
let ws = window.top.siyuan.ws.ws
ws.addEventListener("message", (msg) => {
if (msg && msg.data && msg.data) {
if (JSON.parse(msg.data).cmd == "transactions") {
更新数据()
}
}
}
)
}
onMounted(() => {
获取数据()
开始监听()
})
</script>
但是在之前我们已经学会了拆分文件,也就是把一些代码写到其他的模块里面去,然后用import 把它们引用过来。
这回我们也先拆分一下文件,但是因为vue-sfc-loader的特性,只能这样拆:
<html>
<body>
<div id="app"></div>
<script src="./static/vue.global.js"></script>
<script src="./static/vue3-sfc-loader.js"></script>
<script type="module">
import * as data from './util/data.js'
const options = {
moduleCache: {
vue: Vue,
data:data
},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
}
const { loadModule } = window['vue3-sfc-loader'];
const app = Vue.createApp({
components: {
'App': Vue.defineAsyncComponent( () => loadModule('./components/app.vue', options) )
},
template: '<App></App>'
});
app.mount('#app');
</script>
</body>
</html>
额,也就是说先把东西引入进来,放到cache 里面,才能在组件里面引用。
//没错ref是可以单独用的
let {ref} =Vue
let 上次更新时间 = new Date().getTime()
let 时间间隔
let 块id = window.frameElement.parentElement.parentElement.dataset.nodeId
let data = ref({})
let 获取数据 = async() => {
await 读取配置文件()
fetch("/api/query/sql", {
method: "post",
body: JSON.stringify({
stmt: ` SELECT * FROM attributes where block_id = (select root_id from blocks where id = "${块id}")`
})
}).then(
res => {
return res.json()
}
).then(
json => {
data.value = json.data
}
)
}
async function 更新数据() {
let 当前时间 = new Date().getTime()
if (!时间间隔||时间间隔 < 1000) {
时间间隔= 当前时间 - 上次更新时间
return
}
else {
时间间隔=0
上次更新时间=当前时间
获取数据()
}
}
let 自定义属性名对照表 = ref(null)
async function 读取配置文件(){
let 请求响应 = await fetch("/widgets/属性名对照表.json")
let 配置内容 = await 请求响应.json()
自定义属性名对照表.value = 配置内容
}
let 开始监听 = () => {
更新数据()
let ws = window.top.siyuan.ws.ws
ws.addEventListener("message", (msg) => {
if (msg && msg.data && msg.data) {
if (JSON.parse(msg.data).cmd == "transactions") {
更新数据()
}
}
}
)
}
export {data as data}
export {开始监听 as 开始监听}
export {自定义属性名对照表 as 自定义属性名对照表}
好了现在app.vue就只有这么点儿了
<template>
<table border="1" >
<tr>
<th>属性名</th>
<th>属性值</th>
</tr>
<!--这里的意思是根据获取到的data的值,把他们全都按中文名显示到文档里面-->
<template v-for="(自定义属性, 序号) in data">
<tr v-if="自定义属性名对照表&&自定义属性名对照表[自定义属性.name]">
<td>{{ 自定义属性名对照表[自定义属性.name]["显示名称"]["中文"] }}</td>
<td>{{ 自定义属性.value }}</td>
</tr>
</template>
</table>
</template>
<script setup >
//这里的为什么要这么写可以参考vue3的文档
import {onMounted} from "vue"
import { data, 开始监听,自定义属性名对照表 } from 'data'
onMounted(() => {
开始监听()
})
</script>
接下来我们来改造一下这个组件,先找个图标库。
emm 好像其实可以不用找,借用一下思源的图标:
<script src="/appearance/icons/material/icon.js" async="" id="iconDefaultScript"></script>
<script src="/appearance/emojis/twitter-emoji.js" async="" id="emojiScript"></script>
把这两行放到index.html里面去。
额,反正图标都借用了,不如干脆连主题也借用一下算了
<link href="base.1bf40f9fe06bea782a0f.css" rel="stylesheet">
<link id="themeDefaultStyle" rel="stylesheet" type="text/css" href="/appearance/themes/daylight/theme.css?v=2.5.1">
<script src="/appearance/themes/daylight/theme.js?v=1.0.2" async="" id="themeScript"></script>
接下来开始愉快地借鉴(chaoxi)思源的原生ui
<template>
<div class='b3-tab-container'>
<template v-for="(属性配置, 属性名) in 自定义属性名对照表">
<label class="fn__flex b3-label" >
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconAlignLeft"></use>
</svg>
<span>
{{ 属性配置["显示名称"]["中文"] }}
</span>
</div>
<span class="fn__space"></span>
<span class="fn__flex-1">{{
data.find&&data.find(item=>{return item&&item.name==属性名})?data.find(item=>{return item&&item.name==属性名}).value:'空的'
}} </span>
</label>
</template>
</div>
</template>
<script setup >
//这里的为什么要这么写可以参考vue3的文档
import { onMounted } from "vue"
import { data, 开始监听, 自定义属性名对照表 } from 'data'
onMounted(() => {
开始监听()
})
</script>
<style scoped>
.b3-tab-container{
font-size:20px ;
color: var(--b3-theme-on-surface-light);
}
.b3-tab-container svg.b3-list-item__graphic{
margin-right: 4px;
padding: 0 2px;
flex-shrink: 0;
height: 24px;
width: 22px;
line-height: 24px;
font-size: 22px;
}
.attribute-name{
width: 200px;
}
</style>
啊现在它长这个样子
能不能再给力一点啊老湿
嗯,我们来搞一个按钮,点一下就增加一个框框,然后加载到挂件里面
<label class="fn__flex b3-label" v-show="显示属性编辑框">
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconAlignLeft"></use>
</svg>
<span ref="属性名" contenteditable="true">
属性名
</span>
</div>
<span class="fn__space"></span>
<span ref="显示名" class="fn__flex-1" contenteditable="true"> 显示名 </span>
<span ref="属性值" class="fn__flex-1" contenteditable="true"> 属性值 </span>
</label>
<label class="fn__flex b3-label" @click="显示属性编辑框=true" v-if="!显示属性编辑框">
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconAdd"></use>
</svg>
<span>
增加属性
</span>
</div>
</label>
<label class="fn__flex b3-label" v-if="显示属性编辑框" @click="提交">
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconChecked"></use>
</svg>
<button>
确定
</button>
</div>
</label>
上面的模板里面有好几个ref=' ',这个可以参考:
Vue3 setup 在渲染函数中通过ref访问Dom元素_废柴前端的博客-CSDN博客_setup 获取dom
通过这个我们就可以在其他地方使用它们的值了
<script setup >
//这里的为什么要这么写可以参考vue3的文档
import {ref, onMounted } from "vue"
import { data, 开始监听, 自定义属性名对照表,获取数据,保存配置文件} from 'data'
let 显示属性编辑框 =ref(null)
let 属性名 = ref()
let 属性值 = ref()
let 显示名 = ref()
async function 提交(){
let obj = {}
obj[属性名.value.innerText]=属性值.value.innerText
fetch('/api/attr/setBlockAttrs',{
method:"post",
body:JSON.stringify(
{
id:data.value[0].block_id,
attrs:obj
}
)
}).then(
res=>{
return res.json()
}
).then(
resdata=>{
自定义属性名对照表.value[属性名.value.innerText]={显示名称:{中文:显示名.value.innerText}}
显示属性编辑框.value =false
保存配置文件()
获取数据()
}
)
}
onMounted(() => {
开始监听()
})
</script>
就象上面这样,我们就实现了一个保存数据的功能,然后在data.js里面,实现保存配置文件:
async function 保存配置文件() {
let 文本 = JSON.stringify(自定义属性名对照表.value)
let 数据blob = new Blob([文本], {
type: "application/json",
});
let 文件 = new File([数据blob], "属性名对照表.json", { lastModified: Date.now() });
let modTime = new Date().getTime()
let 数据 = new FormData()
数据.append('path','/data/widgets/属性名对照表.json')
数据.append('modTime',modTime)
数据.append('file',文件)
await fetch('/api/file/putFile',{
body: 数据,
method: "POST",
})
}
这样现在我们实现的挂件就长这个样子啦
主要文件内容
更美观的文档自定义属性\index.html
<html>
<body>
<div id="app"></div>
<script src="./static/vue.global.js"></script>
<script src="./static/vue3-sfc-loader.js"></script>
<link href="/stage/build/app/base.1bf40f9fe06bea782a0f.css" rel="stylesheet">
<link id="themeDefaultStyle" rel="stylesheet" type="text/css" href="/appearance/themes/daylight/theme.css?v=2.5.1">
<script src="/appearance/themes/daylight/theme.js?v=1.0.2" async="" id="themeScript"></script>
<script src="/appearance/icons/material/icon.js" async="" id="iconDefaultScript"></script>
<script src="/appearance/emojis/twitter-emoji.js" async="" id="emojiScript"></script>
<script type="module">
import * as data from './util/data.js'
const options = {
moduleCache: {
vue: Vue,
data:data
},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
addStyle(textContent) {
const style = Object.assign(document.createElement('style'), { textContent });
const ref = document.head.getElementsByTagName('style')[0] || null;
document.head.insertBefore(style, ref);
},
}
const { loadModule } = window['vue3-sfc-loader'];
const app = Vue.createApp({
components: {
'App': Vue.defineAsyncComponent( () => loadModule('./components/app.vue', options) )
},
template: '<App></App>'
});
app.mount('#app');
</script>
</body>
</html>
更美观的文档自定义属性\components\app.vue
<template>
<div class='b3-tab-container'>
<template v-for="(属性配置, 属性名) in 自定义属性名对照表">
<label class="fn__flex b3-label" >
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconAlignLeft"></use>
</svg>
<span>
{{ 属性配置["显示名称"]["中文"] }}
</span>
</div>
<span class="fn__space"></span>
<span class="fn__flex-1">{{
data.find&&data.find(item=>{return item&&item.name==属性名})?data.find(item=>{return item&&item.name==属性名}).value:'空的'
}} </span>
</label>
</template>
<label class="fn__flex b3-label" v-show="显示属性编辑框">
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconAlignLeft"></use>
</svg>
<span ref="属性名" contenteditable="true">
属性名
</span>
</div>
<span class="fn__space"></span>
<span ref="显示名" class="fn__flex-1" contenteditable="true"> 显示名 </span>
<span ref="属性值" class="fn__flex-1" contenteditable="true"> 属性值 </span>
</label>
<label class="fn__flex b3-label" @click="显示属性编辑框=true" v-if="!显示属性编辑框">
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconAdd"></use>
</svg>
<span>
增加属性
</span>
</div>
</label>
<label class="fn__flex b3-label" v-if="显示属性编辑框" @click="提交">
<div class="fn__flex attribute-name" >
<svg class="b3-list-item__graphic">
<use xlink:href="#iconChecked"></use>
</svg>
<button>
确定
</button>
</div>
</label>
</div>
</template>
<script setup >
//这里的为什么要这么写可以参考vue3的文档
import {ref, onMounted } from "vue"
import { data, 开始监听, 自定义属性名对照表,获取数据,保存配置文件} from 'data'
let 显示属性编辑框 =ref(null)
let 属性名 = ref()
let 属性值 = ref()
let 显示名 = ref()
async function 提交(){
let obj = {}
if(!属性名.value.innerText.startsWith('custom-')){
属性名.value.innerText = "custom-"+属性名.value.innerText
}
obj[属性名.value.innerText]=属性值.value.innerText
data.value.forEach(属性数据 => {
obj[属性数据.name] = 属性数据.value
});
await fetch('/api/attr/resetBlockAttrs',{
method:"post",
body:JSON.stringify(
{
id:data.value[0].block_id,
attrs:obj
}
)
})
自定义属性名对照表.value[属性名.value.innerText]={显示名称:{中文:显示名.value.innerText}}
显示属性编辑框.value =false
await 保存配置文件()
window.location.reload()
}
onMounted(() => {
开始监听()
})
</script>
<style scoped>
.b3-tab-container{
font-size:20px ;
color: var(--b3-theme-on-surface-light);
}
.b3-tab-container svg.b3-list-item__graphic{
margin-right: 4px;
padding: 0 2px;
flex-shrink: 0;
height: 24px;
width: 22px;
line-height: 24px;
font-size: 22px;
}
.attribute-name{
width: 200px;
}
</style>
更美观的文档自定义属性\util\data.js
let { ref } = Vue
let 上次更新时间 = new Date().getTime()
let 时间间隔
let 块id = window.frameElement.parentElement.parentElement.dataset.nodeId
let data = ref({})
let 获取数据 = async () => {
await 读取配置文件()
fetch("/api/query/sql", {
method: "post",
body: JSON.stringify({
stmt: ` SELECT * FROM attributes where block_id = (select root_id from blocks where id = "${块id}")`
})
}).then(
res => {
return res.json()
}
).then(
json => {
data.value = json.data
}
)
}
async function 更新数据() {
let 当前时间 = new Date().getTime()
if (!时间间隔 || 时间间隔 < 1000) {
时间间隔 = 当前时间 - 上次更新时间
return
}
else {
时间间隔 = 0
上次更新时间 = 当前时间
获取数据()
}
}
let 自定义属性名对照表 = ref(null)
async function 读取配置文件() {
let 请求响应 = await fetch("/widgets/属性名对照表.json")
let 配置内容 = await 请求响应.json()
自定义属性名对照表.value = 配置内容
}
async function 保存配置文件() {
let 文本 = JSON.stringify(自定义属性名对照表.value,null,2)
let 数据blob = new Blob([文本], {
type: "application/json",
});
let 文件 = new File([数据blob], "属性名对照表.json", { lastModified: Date.now() });
let modTime = new Date().getTime()
let 数据 = new FormData()
数据.append('path','/data/widgets/属性名对照表.json')
数据.append('modTime',modTime)
数据.append('file',文件)
return await fetch('/api/file/putFile',{
body: 数据,
method: "POST",
})
}
let 开始监听 = () => {
获取数据()
let ws = window.top.siyuan.ws.ws
ws.addEventListener("message", (msg) => {
if (msg && msg.data && msg.data) {
if (JSON.parse(msg.data).cmd == "transactions") {
更新数据()
}
}
}
)
}
export { data as data }
export { 开始监听 as 开始监听 }
export { 自定义属性名对照表 as 自定义属性名对照表 }
export { 获取数据 as 获取数据 }
export { 保存配置文件 as 保存配置文件 }
本文使用思源笔记编辑生成,更新于:2022-11-25。
协议:CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/
有人跟我们一样喜欢吃猪皮冻吗?
其实土笋冻也挺好吃的,不过我自己都好久没有吃过了: