思源笔记折腾记录-简单挂件-更美观地展示属性

思源笔记折腾记录-简单挂件-更美观地展示属性

一、前情提要

我们之前做了一个挂件:

它能够显示自定义属性,而且可以显示出中文名

但是它现在看起来真的好丑.....

于此同时,其他笔记软件的属性显示是这样的

所以我们来鼓捣一下,让它显示得更加好看一点点。

二、实现过程:

之前我们搞出来的组件是长这个样子的:

<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/


有人跟我们一样喜欢吃猪皮冻吗?

其实土笋冻也挺好吃的,不过我自己都好久没有吃过了:

编辑于 2022-11-25 20:27・IP 属地日本