笔记本管理功能已经实现了,接下来我们研究如何保存笔记内容。先来看一下最终效果:
点击新建笔记按钮,中间的笔记列表中就会出现该笔记的预览面板,输入标题和内容,预览面板上的内容会同步更新,笔记内容也会自动保存。
获取笔记内容
文本编辑器的内容实现就是HTML代码,可以借助js获取这段HTML代码,然后通过webview的js绑定传递给Python。
保存笔记
笔记的标题保存在数据库里面,笔记内容是HTML形式的富文本,可以直接保存在文件里面。将富文本去掉HTML标签得到纯文本,用于预览,所以预览文本也可以保存在文件里面。这样一条笔记就会包含两个文件,将它们放在一个文件夹里面,文件夹名称即为笔记本的uuid值。
跨组件通信
在 NavPanel 中点击新建笔记按钮时,就会创建一条Note记录,这条记录如何传递给笔记列表 ListPanel 组件呢?对于这类跨组件通信问题,wxPython 中推荐的做法是使用发布订阅模式。
NavPanel 在创建笔记之后发出一个"note.created" 消息,消息中包含Note对象;在 ListPanel 订阅了 "note.created" 消息,当收到消息时,就能提取出 Note 对象。
我们在这里使用PyPubSub来简化跨组件通信。
参数设置
上面三个问题回答之后,就可以安心开发了。在之前的文章中,数据库文件路径默认为 "data / note.db",现在又涉及到了笔记保存位置,最好能将这些参数集中管理。Python提供了ConfigParser来管理配置文件,在根目录下新建一个 config.py 文件,内容如下:
import
然后将 models / base_model.py 里面的数据库文件路径相应修改:
database
运行主程序,可以发现根目录下生成了一个 config.ini 文件,内容如下:
[app]
data_dir = data
note_dir = notes
[database]
db_file = data/note.db
Note模型调整
根据上面的思路,每条笔记创建的时候就应该完成三件事:
- 生成uuid
- 生成一个空白文件用于保存富文本
- 生成一个空白文件用于保存纯文本(用于预览)
models / note.py 内容如下:
from
这里的 set_content 方法用于保存笔记内容。
新建笔记
原来的新建笔记按钮没有图标,接下来在按钮前面添加一个图标,首先下载图片:
然后运行 encode_bitmap_util.py,修改 views / nav_panel.py 文件:
self
图标已经添加好了,接下来添加事件处理方法:
self
笔记需要一个 notebook_id 属性,可以通过 NoteTree 组件获取,修改 views / note_tree.py :
@property
调用 Note.create 方法就可以创建笔记了,笔记创建好了之后需要传递给笔记列表 ListPanel,由上面的分析可知,还需要 pypubsub 这个包,安装一下:
pip install PyPubSub
安装好了之后,通过 from pubsub import pub 导入pub模块:
note = Note.create(notebook_id=self.note_tree.notebook_id)
pub.sendMessage('note.created', note=note)
views / nav_panel.py 代码如下:
import
在笔记列表中显示
运行主程序,点击新建笔记按钮之后,发现notes文件夹下新建了一些文件:
说明笔记相关的文件创建成功了,不过笔记列表中并没有显示这条笔记。
修改 views / list_panel.py 文件,移除之前的测试代码:
可以看到,我们使用 pub.subscribe 方法来订阅 note.created 主题,并在 _on_note_created 方法中提取到了 NavPanel 新建笔记时传过来的Note对象。
再次运行主程序,点击新建按钮,发现笔记列表出现了一个预览窗体,但显示的是测试文字,接下来修改 views / note_preview_panel.py,移除测试代码:
再次运行程序,点击新建按钮,可以正常显示了:
保存笔记
如果在标题输入框或者文本编辑器中输入文字时,这些文字并没有保存到数据库或者文件里面。 为了能够保存内容,首先编辑器要能够加载笔记。
修改 views / text_editor.py,订阅 note.created 消息:
pub
load_note 方法用于加载笔记标题和笔记富文本,标题很容易更新,调用 wx.TextCtrl 的 ChangeValue (不会触发 wx.EVT_TXT 事件)即可,富文本如何更新呢?
根据前面的讨论,富文本实际上就是编辑器节点的HTML代码,因此可以通过webview调用相应的js方法来更新HTML,编辑 assets / text_editor / core.js,获取编辑器的DOM节点,然后定义一个更新编辑器内容的函数:
let
text_editor.py 的 load_content 方法如下:
def
其中self.note 在初始化的时候默认为 None。
点击新建笔记按钮时,文本编辑器就可以获取到对应的Note对象,并将其保存在了 note 成员变量中。
为了能够保存标题,我们只需要监听标题内容更新事件,并更新note成员变量即可:
self
注意在这里,我们发送了 note.updated 消息,后面的预览窗体同步功能需要用到。
每当标题变化,数据库里面的title字段就会相应更新。同理,当富文本编辑器的内容变化时,笔记的文件内容也会更新。
如何监听编辑器内容变化呢?和之前的监听编辑器格式变化一样,可以借助quill来实现,编辑 assets / text_editor / core.js,添加如下回调方法:
quill
注意:当编辑器内容发生变化时,就会更新笔记文件。当调用 quill.load_content 方法加载笔记时,也会触发 text-change 事件,但我们不希望第一次加载笔记时就立即更新笔记,所以添加了if判断,quill.load_content 的 source 为 api。
在 views / text_editor.py 中添加js绑定:
# 修改 __init__
现在我们来测试一下:
然后查看文件 content.html 内容如下:
<p><span style="font-size: 18px;">hello </span><strong style="font-size: 18px;">world</strong></p>
snippet.txt 内容如下:
hello world
和预期一致!
笔记预览同步更新
我们观察到在编辑器中输入标题和内容时,笔记列表里面的预览区域并没有同步更新,如何解决这个问题?
我们只需要获取到更新之后的笔记对象即可,订阅 note.updated 主题!编辑 views / note_preview_panel.py 文件:
# 修改 __init__
再次测试一下,预览面板可以同步更新了:
总结
创建和保存笔记功能已经实现了,但还可以优化:例如在编辑器中连续输入文字时,会不停的触发回调方法来执行文件保存操作,理想的状态应该是连续输入文字的间隔时间大于2秒或者更长时,才执行回调方法,以便节省程序开销。当然实际上可以通过JavaScript防抖动函数来实现,这里就不再介绍了。
后面的文章中将会涉及到如何管理笔记列表。