CloudNotes之桌面客户端篇:笔记撰写样式的支持

最近在CloudNotes桌面客户端中新增了笔记撰写样式的功能。当用户新建笔记的时候,可以在输入笔记标题的同时,选择笔记撰写样式,由安装包默认提供的样式主要有默认样式(Default)、羊皮纸样式(Leather Paper)以及Word 2013样式(Microsoft Word 2013)。选择笔记样式的时候,还提供了预览功能,用户可以直接预览样式效果:

当然,为了方便操作,用户可以在设置界面选择默认使用的样式,从而每次新建笔记时,默认使用的样式就会自动被选中,减少用户的操作次数。设置界面中有关默认样式的设定如下:

现在,我就简单介绍一下,整个样式系统是如何设计实现的。

样式系统

与插件系统类似,样式系统也需要读取外部的样式文件,然后把所有读取的样式罗列在设置界面中。因此,在CloudNotes桌面客户端中,样式是可以自定义扩展的。对于如何自定义样式,下一节会作详细介绍。就样式系统本身而言,它有着插件系统相似的行为特征:需要有一个中心组件,对所有的样式进行管理,并在需要的时候,能够通过这个中心组件找到所需的样式定义。于是,在实现样式系统的第一步,我对现有的插件系统进行了重构。

外部资源管理器(ExternalResourceManager)

正如上述分析,插件系统和样式系统都需要读取外部文件,因此,我将其抽象成一个外部资源管理器组件,主要负责通过读取外部文件,将文件数据转换成资源(插件或者样式)数据,并对这些数据进行管理。因此,插件管理器(ExtensionManager)和样式管理器(StyleManager)都属于外部资源管理器(ExternalResourceManager)的一种实现,不同之处在于两者对外部文件的读取和使用方式不同,而所处理的数据也不一样。

首先,在ExternalResourceManager抽象类的Load方法中,会根据构造函数传入的路径,去搜索所有匹配搜索条件的文件,并通过LoadResources抽象方法,从每个符合搜索条件的文件中载入资源:

/// <summary>
/// Loads the resources into the current manager.
/// </summary>
public void Load()
{
    if (Directory.Exists(this.path))
    {
        var resourceFiles = Directory.EnumerateFiles(this.path, this.searchPattern, SearchOption.AllDirectories);

        foreach (var resourceFile in resourceFiles)
        {
            try
            {
                var res = LoadResources(resourceFile);
                if (res != null && res.Any())
                {
                    foreach (var resource in res)
                    {
                        this.resources.Add(resource.ID, resource);
                    }
                }
            }
            catch
            {
            }
        }
    }
}

然后,ExtensionManager在LoadResources方法的实现中,通过Assembly.LoadFrom调用,将Assembly读入内存,同时使用Activator.CreateInstance创建Extension对象,然后返回给基类进行管理:

protected override IEnumerable<Extension> LoadResources(string fileName)
{
    var assembly = Assembly.LoadFrom(fileName);
    var result = new List<Extension>();
    foreach (var type in assembly.GetExportedTypes())
    {
        if (type.IsDefined(typeof (ExtensionAttribute)) &&
            type.IsSubclassOf(typeof (Extension)))
        {
            try
            {
                var extensionLoaded = (Extension) Activator.CreateInstance(type);
                this.OnResourceLoaded(extensionLoaded);
                result.Add(extensionLoaded);
            }
            catch
            {
            }
        }
    }
    return result;
}

而在StyleManager的LoadResources方法的实现中,则将传入的文件以zip解压缩,从中读取metadata.json文件以获得样式的基本定义信息,并从中读取style.css文件以获得样式的详细内容,之后,会使用Json库对metadata.json反序列化,并得到Style对象。最后,再将Style对象返回给基类进行管理:

protected override IEnumerable<Style> LoadResources(string fileName)
{
    try
    {
        var extractedContent = new Dictionary<string, string>();
        var zipFile = new ZipFile(fileName);
        foreach (ZipEntry entry in zipFile)
        {
            if (!entry.IsFile ||
                (string.Compare(entry.Name, MetadataFileName, StringComparison.InvariantCultureIgnoreCase) != 0 &&
                string.Compare(entry.Name, StyleFileName, StringComparison.InvariantCultureIgnoreCase) != 0))
                continue;

            var buffer = new byte[4000];
            var entryStream = zipFile.GetInputStream(entry);
            using (MemoryStream memoryStream = new MemoryStream())
            {
                StreamUtils.Copy(entryStream, memoryStream, buffer);
                extractedContent[entry.Name] = Encoding.ASCII.GetString(memoryStream.ToArray());
            }
        }
        if (extractedContent.ContainsKey(MetadataFileName) &&
            extractedContent.ContainsKey(StyleFileName))
        {
            var style = JsonConvert.DeserializeObject<Style>(extractedContent[MetadataFileName]);
            if (style.ID == Guid.Empty)
                return null;
            style.Content = extractedContent[StyleFileName];
            return new[] { style };
        }
    }
    catch
    {
    }

    return null;
}

就样式数据而言,它包含ID、样式名称、样式描述、创建日期、作者以及样式代码(定义在style.css文件中),Style对象保存了这些数据,并被用于设置页面以及新笔记创建等场景中。

样式的应用原理

其实,样式的应用原理非常简单,就是在创建新笔记时,使用样式文件中的style.css的内容去替换空Html文档的head下的style标签部分的占位符即可。在CloudNotes中,空Html文档的定义如下:

<html>
	<head>
		<style>
			$style$
		</style>
	</head>
	<body>

	</body>
</html>

说它是空Html文档,并不是说它是一个空字符串,而是因为body部分是没有任何内容的。假设style.css中的样式内容如下:

body {
    color: red;
}

那么,最终生成的笔记内容就是:

<html>
	<head>
		<style>
			body {
                             color: red;
                     }
		</style>
	</head>
	<body>

	</body>
</html>

值得一提的是,在CloudNotes桌面客户端中,样式系统的实现还是相对简单的,因此,除了metadata.json和style.css两个文件外,其它的文件都会被忽略。如果需要使用外部文件作为样式的资源,比如希望能够指定背景图片,那么就应该将图片数据转成base64,然后使用data:image的方式指定图片的URL。

自定义样式

CloudNotes桌面客户端笔记撰写样式的自定义也是非常简单的,样式文件本身就是一个以style为扩展名的zip压缩文件。要自定义样式,首先新建一个metadata.json文件,该文件内容如下:

{
    "ID": "F04F9079-EC4E-4739-93C5-473607BEA79E",   // 样式的Guid
    "Name": "Default",                              // 样式名称
    "Description": "CloudNotes Default Style",      // 样式描述
    "Author": "Sunny Chen",                         // 样式作者
    "CreationDate": "8/8/2015"                      // 样式创建日期
}

然后在metadata.json相同的路径下,新建一个style.css文件,该文件的内容相信大家都知道是什么,就是css样式。两个文件准备好以后,将它们压缩成zip文件,然后将扩展名改为style,并拷贝到CloudNotes桌面客户端安装路径的Styles子目录下即可。在重启CloudNotes桌面客户端之后,就可以在新建笔记和设置界面中看到并选择这个自定义样式了。说明一点,zip文件中仅能包含metadata.json和style.css两个文件,不能将它们放到子目录中,否则桌面客户端将无法读取样式信息。

以下是一个使用了羊皮纸样式的笔记效果,这在以前版本的桌面客户端中是无法看到的。

时间: 2024-10-10 11:29:29

CloudNotes之桌面客户端篇:笔记撰写样式的支持的相关文章

CloudNotes之桌面客户端篇:插件系统的实现

[CloudNotes版本更新历史与各版本下载地址请点击此处] [CloudNotes中文系列文章汇总列表请点击此处] [查看CloudNotes源代码请点击此处] 有时候,同一个名词,针对不同的人群,应该采用不同的表达方式.比如插件的概念,对于程序员而言,可以将其称为插件,或者扩展.对于用户而言,或许"扩展功能"一词会更加贴切.本文还是脱离不了码农的气质,继续讨论技术问题,因此,我会以"插件"一词进行描述. 概述 从1.0.5504.38654版本开始,Cloud

CloudNotes之桌面客户端篇:增强的笔记列表

今天,我发布了CloudNotes的一个更新版本:1.0.5484.36793.这个版本与1.0.5472.20097不同的是,它拥有增强的笔记列表,与之前单调的列表系统相比,新的笔记列表不仅可以显示笔记的摘要内容,而且还可以从笔记中抽取第一张图片,并显示图片的详细信息: 怎么样?相比之前的笔记列表,现在的设计是不是能够展示更丰富的信息呢? 升级到最新版本 如果在读完我的第一篇关于CloudNotes的文章,<CloudNotes:一个云端个人笔记系统>之后,已经安装并体验了上一个版本的Clo

Mina airQQ聊天 客户端篇(三)

开发工具 (FlashBuilder4.7) 程序类型(Adobe Air) Flex Air做的桌面程序,效果还挺好看的,最主要是Socket这一块,它也是异步的,并且在Flex中的事件机制比较强大(个人认为) 有改一些样式,重新看看新的效果吧: 大致的实现方式: 在WindowedApplication中包含登陆窗口和主界面,用Flex中的状态来切换,聊天窗口时Window组件,好友列表用树菜单 实现好友分组,好友上线时改成在线图标,收到消息时头像抖动,聊天显示实现图文混排,系统托盘,其它貌

互联网江湖,桌面客户端框架技术比武大会

By 技术怪咖 欧阳森林 导读:在互联网时代,如何将一个好的idea快速的转化为产品,如何在原有产品中增加新的特性,是产品能够快速的推向市场.快速占领的关键.因此,作为客户端的架构选型,面临哪几方面的需求?资深前端工程师欧阳森林针对其专业领域的经验,有着自己独到的见解. 前言 自从互联网江湖上出现了一部叫做"云"的秘籍,大大小小的门派,纷纷对外宣称自己掌握了"云"的核心,各种"云"应用层出不穷,Service的各种架构在一次次的华山论剑上大放异彩

&quot;xaml+cs&quot;桌面客户端跨平台初体验

"Xaml+C#"桌面客户端跨平台初体验 前言 ??随着 .Net 5的到来,微软在 .Net 跨平台路上又开始了一个更高的起点.回顾.Net Core近几年的成果,可谓是让.Net重生了一次. ??Asp .Net Core跨平台解决了Windows服务器昂贵的费用和不能长时间待机的问题,让Asp程序能够跑在Linux甚至Mac上.从博客园里.Net分类可以看到,每天都可以涌现大批Asp .Net Core的技术文章,越来越多的开发者或者公司开始尝试这个船新的跨平台框架. ??然鹅,

在连接windows2008时,报错:由于这台计算机没有远程桌面客户端访问许可证,远程会话被中断

前两天连接服务器的时候弹出:由于这台计算机没有远程桌面客户端访问许可证,远程会话被中断的对话框提示.前一段还在用都是好好的,怎么一下子就不行了呢?然后找了一下解决方案,解决方案很简单. 解决方案: 打开本机注册表,注册表子项:HKEY_LOCAL_MACHINESOFTWAREMicrosoftMSLicensing,对,你没有看错,就是这个,不管他下面有没有东西,将整个子项删除.将MSLicensing删除. 但,原因是什么呢???

python采用pika库使用rabbitmq总结,多篇笔记和示例(转)

add by zhj:作者的几篇文章参考了Rabbitmq的Tutorials中的几篇文章. 原文:http://www.01happy.com/python-pika-rabbitmq-summary/ 这一段时间学习了下rabbitmq,在学习的过程中,发现国内关于python采用pika库使用rabbitmq的资料很少,官网有这方面的资料,不过是都英文的.于是笔者结合自己的理解,就这方面内容写了一些示例,总共有七篇笔记,分享出来. 笔记依次是循序渐进的,笔记内贴出的代码笔者都实际运行过,运

使用node-webkit开发Clover桌面客户端的一些记录(一)

首先说一说Clover. 这是一个OA产品,是我们公司组建我们技术部以来最最重要的一项工作 -- 开发企业内部使用的管理系统."Clover"这个名字是我们老大起的,拆开看是"C"+"lover"."lover"都懂是啥,而这个"C"呢,其实就是"Code",因为我们老大是一个(狂热的)编码爱好者(汗...),精通很多门语言,所以就隐晦的把自己的爱好写进项目名称,就连我们测试服务器的地址

网盘桌面客户端的技术难点

最近在做网盘桌面客户端,遇到如下技术难点: 1.网盘如何本地虚拟化(类似金山网盘等桌面快捷方式等功能) 2.如何使用Shell扩展技术 3.是否需要使用COM组件编程技术 4.如何做文件实时同步功能 以上难点到目前还有好多未知的情况,如果有人知道或者做过类似的桌面shell扩展,麻烦告知 (*^__^*) 嘻嘻…… http://blog.csdn.net/esonpo/article/details/11128719