CloudNotes之桌面客戶端篇:筆記撰寫樣式的支持


最近在CloudNotes桌面客戶端中新增了筆記撰寫樣式的功能。當用戶新建筆記的時候,可以在輸入筆記標題的同時,選擇筆記撰寫樣式,由安裝包默認提供的樣式主要有默認樣式(Default)、羊皮紙樣式(Leather Paper)以及Word 2013樣式(Microsoft Word 2013)。選擇筆記樣式的時候,還提供了預覽功能,用戶可以直接預覽樣式效果:

image

當然,為了方便操作,用戶可以在設置界面選擇默認使用的樣式,從而每次新建筆記時,默認使用的樣式就會自動被選中,減少用戶的操作次數。設置界面中有關默認樣式的設定如下:

image

現在,我就簡單介紹一下,整個樣式系統是如何設計實現的。

樣式系統

與插件系統類似,樣式系統也需要讀取外部的樣式文件,然后把所有讀取的樣式羅列在設置界面中。因此,在CloudNotes桌面客戶端中,樣式是可以自定義擴展的。對於如何自定義樣式,下一節會作詳細介紹。就樣式系統本身而言,它有着插件系統相似的行為特征:需要有一個中心組件,對所有的樣式進行管理,並在需要的時候,能夠通過這個中心組件找到所需的樣式定義。於是,在實現樣式系統的第一步,我對現有的插件系統進行了重構。

外部資源管理器(ExternalResourceManager)

正如上述分析,插件系統和樣式系統都需要讀取外部文件,因此,我將其抽象成一個外部資源管理器組件,主要負責通過讀取外部文件,將文件數據轉換成資源(插件或者樣式)數據,並對這些數據進行管理。因此,插件管理器(ExtensionManager)和樣式管理器(StyleManager)都屬於外部資源管理器(ExternalResourceManager)的一種實現,不同之處在於兩者對外部文件的讀取和使用方式不同,而所處理的數據也不一樣。

image

首先,在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兩個文件,不能將它們放到子目錄中,否則桌面客戶端將無法讀取樣式信息。

以下是一個使用了羊皮紙樣式的筆記效果,這在以前版本的桌面客戶端中是無法看到的。

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM