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

dax.net發表於2015-08-19

最近在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

相關文章