思源笔记折腾记录 - 做一个白板 - 保存数据和显示模式

本贴最后更新于 700 天前,其中的信息可能已经时过境迁

一、前情提要

我们之前实现了一个白板,并且让它能够显示文档下的子文档了。

思源笔记折腾记录 - 做一个白板 - 显示多张卡片 - 链滴 (ld246.com)

但是好像还是不够给力,因为它的数据没有办法保存啊,每次重新打开,它就被打回原型了。

这样好像不行,所以我们来给它加上数据保存。

二、实现过程

1、数据设计

之前我做过一个简单的白板,cc-image-tag-new,在那个里面我使用了 indexDB 来存储数据并且把数据结构搞得害挺复杂,把我自己都整得晕晕的。

所以这回我们就不搞那么复杂了,白板的数据不单独存储,而是依赖于思源的数据结构,然后提供几个显示模式,把数据直接存到思源的块里面去~~~

这里就有一个问题,肯定一个块不止显示在一个白板上啊,所以我们需要一个方式来让块知道,在哪个白板上显示的时候它应该在什么位置,显示成什么样子。

所以我们来搞一个块属性 custom-whiteBoardData-< 白板 id>,里面就弄一个数组,记录它被哪个块的白板视图显示的时候,该显示到哪里去。

[
	{
		mode:<白板的显示模式>
		data:{
			...transform:<卡片的几何信息>
		}
	}
]

这样就可以直接查 attribute 表来找到数据了。

但是这样还有一个问题,如果保存的时候直接存储的话,那就会把其他白板的数据也覆盖掉了,这个肯定不是我们想要的。

所以干脆这样好了,每次保存卡片数据的时候先获取一次数据,然后再用最新的数据保存~~~

为甚数据不用 custom-whiteBoardData 来保存然后把白板 id 写到属性里面去?

因为这样的话,一删掉这个属性,那么这个块在所有白板上的属性显示都没有了.......

2、抽离数据获取过程

之前我们都是把数据获取直接写到组件里面的,之后要是数据越来越多可就没法搞了,我们尝试一下把数据获取过程抽离出来:

先不管那么多,抽到 data/index.js 好了:

//没错ref是可以在组件外面用的嗷
import  {ref} from 'vue'
function 获取地址参数(){
	let 中间变量 = {}
	new URL(window.location.href).searchParams.forEach(
		(value,key)=>{中间变量[key]=value}
	)
	return 中间变量
} 
let 块列表 = ref([])

let 获取所有子块id= ()=>{
    let 块id = 获取地址参数().id
    fetch(
        'api/query/sql',{
            method:'POST',
            body:JSON.stringify(
                {
                    stmt:`select * from blocks where path like '%${块id}%' and type='d'`
                }
            )
        }
    ).then(
        data=>{
            return data.json()
        }
    ).then(
        json=>{
            if(json.data){
                块列表.value= json.data
            }
        }
    )
}
 获取所有子块id()
export {块列表 as 块列表}

这样我们的 app.vue 就只有这么长了:

<template>
    <div class="container" @click="(当前激活卡片序号=-1)">
        <template v-for = "(块数据,i) in 块列表">
          <DragableCard :激活="当前激活卡片序号===i" :思源块id="块数据.id" @click="e=>{e.stopPropagation();当前激活卡片序号=i}"></DragableCard>
        </template>
    </div>
</template>
<script setup>
import DragableCard from './components/DragableCard.vue';
import {ref} from 'vue'
import {块列表} from './data/index.js' 
//这里是在控制卡片状态
let 当前激活卡片序号 = ref(null)
//这里是获取数据,已经挪出去了
</script>
<style scoped>
.container {
    width: 100%;
    height: 100%
}
</style>

然后不是说了要搞出多种视图吗,所以要弄一个 adapter.js,用来适配视图,现阶段我觉得一个适配器大概要告诉白板:

1、怎么获取所有的卡片

2、怎么获取单个卡片的几何数据

3、怎么获取卡片的内容数据

4、怎么保存单个卡片的几何数据

先来做一个最简单的:

import { 获取地址参数, 核心api } from "./index.js";

let { id, mode } = 获取地址参数()
if (!mode) {
    mode = "子文档视图"
}
if (!id) {
    id = '20200812220555-lj3enxa'
}
let 白板id = id


let 适配器列表 = {
    子文档视图: {
	//获取所有在这个文档路径下的文档
        获取卡片列表: async () => {
            return await 核心api.sql(
                {
                    stmt: `select * from blocks where path like '%${id}%' and type='d'`
                }
            )
        },
	//用导出html的预览当成卡片显示内容
        获取卡片内容数据: async (卡片id) => {
            return await 核心api.exportPreview(
                { id: 卡片id }
            )
        },
	//根据窗口的id和模式来获取对应的数据
        获取卡片几何数据: async (卡片id) => {
            let 原始数据 = await 核心api.sql(
                {
                    stmt: `select * from attributes where  name = 'custom-whiteBoardData-${白板id}' and block_id='${卡片id}'`
                }
            )
            if (原始数据 && 原始数据[0]) {
                let json数据 = JSON.parse(原始数据[0].value)
                return json数据.find(
                    item => { return item.mode === mode }
                )
            }
        },
	//将卡片的几何数据保存到块属性里
        保存卡片几何数据: async (卡片id, 显示数据) => {
            let 原始数据 = await 核心api.sql(
                {
                    stmt: `select * from attributes where  name = 'custom-whiteBoardData-${白板id}' and block_id='${卡片id}'`
                }
            )
            if (原始数据 && 原始数据[0]) {
                let json数据 = JSON.parse(原始数据[0].value)

                json数据.find(
                    item => { return  item.mode === mode }
                ).data = 显示数据
                let obj = {}
                obj.id =卡片id
                obj.attrs ={}
                obj.attrs[`custom-whiteBoardData-${白板id}`]=JSON.stringify(json数据)
                await 核心api.setBlockAttrs(obj)
            } else {
                let obj = {}
                obj.id =卡片id
                obj.attrs ={}
                obj.attrs[`custom-whiteBoardData-${白板id}`]=JSON.stringify([{
                    mode: mode,
                    data: 显示数据
                }])

                await 核心api.setBlockAttrs(obj)

            }
        }
    }
}
let 当前数据适配器 = 适配器列表[mode]
export default 当前数据适配器

这个适配器就是获取了文档下的所有子文档来显示卡片视图,现在先把它当成默认适配器。

然后我们引用它获取数据和保存数据就可以了

import  {ref} from 'vue'
import  核心api from 'http://127.0.0.1:6806/snippets/noobApi/util/kernelApi.js'
import 当前适配器 from './adapter'
export  function 获取地址参数(){
	let 中间变量 = {}
	new URL(window.location.href).searchParams.forEach(
		(value,key)=>{中间变量[key]=value}
	)
	return 中间变量
} 
let 块列表 = await 当前适配器.获取卡片列表()
export {块列表 as 块列表}
export {核心api as 核心api}
export {当前适配器  as 当前适配器}

然后在卡片里利用它来显示和保存数据。

这样就鼓捣完啦。

<template>
    <div ref="卡片框架元素" class="card_frame" @click="e => { emits('click', e) }">
        <div class="card_body">
            <div class="card_content" v-bind:innerHTML="卡片内容.html"></div>
        </div>
    </div>
    <Moveable className="moveable" v-if="激活" :target="卡片框架元素" :draggable="true" :scalable="true" :resizable="true"
        :rotatable="true" :keepRatio="false" @drag="onDrag" @scale="onScale" @rotate="onRotate" @resize="onResize">
    </Moveable>
</template>
<script setup>
import Moveable from 'vue3-moveable';
import { defineProps } from 'vue';
import { reactive, ref, onMounted, onActivated } from 'vue'
import { 当前适配器 } from '../data/index.js'
let emits = defineEmits(['click'])
//这里来获取数据
let { 思源块id, 激活 } = defineProps(['思源块id', '激活'])
let 卡片内容 = ref({})
const 卡片框架元素 = ref(null)
onMounted(async () => {
    卡片内容.value = await 当前适配器.获取卡片内容数据(思源块id)
    let 几何数据 = await 当前适配器.获取卡片几何数据(思源块id)
    console.log(几何数据)
    if (几何数据) {
        卡片框架元素.value.style.width = 几何数据.data.width
        卡片框架元素.value.style.height = 几何数据.data.height
        卡片框架元素.value.style.transform = 几何数据.data.transform
    }
})

//这里来保存数据
let 保存数据 = () => {
    if (卡片框架元素.value) {
        当前适配器.保存卡片几何数据(思源块id,
            {
                width: 卡片框架元素.value.style.width,
                height: 卡片框架元素.value.style.height,
                transform: 卡片框架元素.value.style.transform
            }
        )
    }

}


//这里定义了卡片的外观属性
const 卡片尺寸 = reactive({
    边框宽度: 1,
    内边距: 15,
    宽度: 120,
    高度: 160,
})

//这里的都是事件回调
function onDrag(e) {
    卡片框架元素.value.style.transform = e.transform;

    保存数据()
}
function onScale(e) {
    卡片框架元素.value.style.transform = e.drag.transform;

    保存数据()
}
function onRotate(e) {
    卡片框架元素.value.style.transform = e.drag.transform;
    保存数据()
}
function onResize(e) {
    卡片框架元素.value.style.width = `${e.width}px`;
    卡片框架元素.value.style.height = `${e.height}px`;
    卡片框架元素.value.style.transform = e.drag.transform;
    保存数据()
}

</script>
<style scoped>
.card_frame {
    font-size: large;
    position: absolute;
    box-sizing: border-box;
    width: v-bind('(卡片尺寸.高度+"px")');
    height: v-bind('(卡片尺寸.高度+"px")');
    margin: 0%;
    padding: 5px;
}

.card_body {
    border:v-bind('`${卡片尺寸.边框宽度}px solid grey`');
    border-radius: 15px;
    width:v-bind('`calc(100% - ${2*(卡片尺寸.边框宽度+卡片尺寸.内边距)}px)`');
    height:v-bind('`calc(100% - ${2*(卡片尺寸.边框宽度+卡片尺寸.内边距)}px)`');
    padding:v-bind('`${卡片尺寸.内边距}px`');
    background-color: white;
}

.card_content {
    max-height: 100%;
    max-width: 100%;
    overflow-y: scroll;
    overflow-x: hidden;
}
</style>

其实这里也可以直接保存卡片的样式的,反正位置和大小那些都是通过样式来保存的。

不过先不管啦。

现在尝试一下打开 127.0.0.1:6809/?id=20210808180320-fqgskfj 试试看吧。

移动卡片之后数据应该已经可以保存了。

之后只需要更多的适配器,就能够显示更多的卡片视图,而且在不同的白板上,同一个文档作为卡片也可以显示到不同的位置了。


代码片段的仓库在这里

leolee9086/snippets (github.com)

viteWdigets 的仓库在这里

leolee9086/viteWidgets (github.com)

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    22012 引用 • 87776 回帖 • 2 关注

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...