ASK AND LEARN

记日记:从 ohlife 到 littlememo

发布于

以前一直觉得记日记比较麻烦,无论是用专业的日记软件、Evernote 还是纸质的日记本,都有不便的地方, 要么比较贵,要么编辑器难用,要用携带和检索不便,因为这样那样的原因,日记总是时段时续。 自从遇见了 ohlife,一切就改变了,它太方便了,每天早晨固定发给你一封邮件, 你只要不断的回复这封邮件,当天的日记都会自动叠加起来,汇成一篇日记。

久而久之,习惯了这种方式,有时寥寥几句,记录下日常,有时则滔滔不绝,好似和老友谈心。 丝毫不会觉得别扭,也没有什么心里负担,如果不想记日记,把邮件归档就好了。

ohlife 还可以在每天的邮件中随机寄一封以前的日记,这样偶尔回忆下以前的事情也挺有趣的。 因为邮件已经太成熟了,所以也不存在什么跨平台的障碍,电脑,手机,平板都有好用的邮件客户端可以用。

因为长年使用 Gmail,也早已习惯了它的编辑器,写起日记来也不会有什么不适感(Evernote 的编辑器实在太烂)。

然后可惜的是,ohlife 却关闭了,这一度让我非常失落,网友推荐的几个替代品也都不怎么满意。 所以日记就停了一段时间,直到在推上找到了它的替代品 little memory

同样支持邮件,网站界面同样极简,ohlife 有的功能它都有了,而且使用方式也几乎完全一致,就是它了。 于是我的日记生活就又恢复了。

我不知道 little memory 这个服务能够坚持多久,也曾想过自己实现一个, 但回想起之前写的一篇博文《不折腾》,又放弃了这个想法,专业的事儿还是让专业的人去做吧, 我只管用就好了。之前 Google Wave 倒下的时候,依然担心过,这么好的协作编辑服务,怎么就关闭了呢, 而现在看,伟大的 Google 已经将协作编辑的特性融入了 Google Docs 的全线产品中,比 Wave 更加实用。

好的东西总会留存下来,以各种形式。

同步 Chrome 扩展的用户偏好

发布于

Chrome Dev

我在之前介绍过一些 Chrome 扩展的特性,而 Storage 就是其中之一, Storage 允许将一些数据同步到用户的 Google 帐户中,这是一个非常棒的特性, 即使它的存储容量非常有限

Storage 一个非常典型的应用场景就是保存用户偏好,如果我们开发的应用定制性很强, 有了 Storage 这个利器,用户更换了机器,就不需要再重新定制一次了,真是太方便了。

那么如何使用 Storage 来同步用户的偏好数据呢?本文就以 TransIt 为例,抛砖引玉。

需要实现的效果

扩展中的用户数据同步,包括两个方面:

  1. 用户不同设备间的数据同步
  2. 扩展内部不同页面的数据同步

第一点直接使用 Storage 的 API 就能实现,但第二点却需要自己做许多工作了,它包括:

  • 背景页面 (Event Page / Background Page)
  • 设定页面 (Options Page)
  • 弹出窗口 (Browser Action Popup)
  • 页面脚本 (Content Script)
  • 其它非标准页面

其中任意一个页面的偏好数据更改后,其它页面会自动同步,还有一个好处就是, 扩展在一个页面改了偏好后,扩展的其它页面不需要刷新就可以用到最新的偏好。

定义用户数据的默认值

默认值很重要,因为扩展数据同步也会有时间花费,这此之前,扩展要正常工作, 在数据同步完之前,用户就会使用默认值作为偏好运行扩展。

默认值会被定义在全局的命名空间上,方便直接引用。

// application.js

var options = {
    notifyTimeout: 5,     // 页面划词结果显示时间
    pageInspect: true,    // 是否启用页面划词
    linkInspect: true,    // 是否启用链接划词
    pushItem: false,      // 是否推送单词到服务端
    notifyMode: 'margin', // 结果默认显示在右上角
};

将这个脚本在每个扩展页面都引用,这样在任意页面都可以直接使用 options.optionName 来取得偏好值了。要让 options 的值为用户的最新设置,下面会介绍到。

同步数据到服务器

扩展运行时,应该先从服务器取得最新的数据,再与默认值合并,然后重新推回给服务器。 这样做有个好处:如果我们的扩展增加了一个配置项(在默认值里),那新的配置项不会被服务器数据覆盖掉, 新增加的项也会立即作为更新后的数据重新推送并与其它设备保持同步。

// 从 storage 中读取配置,如果没有配置,则初始化为默认值
function initOptions(callback) {
    chrome.storage.sync.get(null, function(data) {
        $.extend(options, data);
        chrome.storage.sync.set(options);
        callback && callback();
    });
}

为了确保使用最新的配置数据运行扩展,读取服务器数据的方法中可以加入一个回调, 保证在取完数据后再执行扩展里的逻辑,类似 jQuery.ready

initOptions(function() {
   // do something with options 
});

同步用户数据到扩展各页面

因为 Storage API 的取值方法是异步的回调模式,没有办法像取 localStorage 那样直接使用 localStorage['optionName'] 来获取数据, 但是如果每次如果使用回调的方法,又不免太麻烦了。

chrome.storage.sync.get('pageInspect', function(data) {
    console.log(data.pageInspect);
});

试想每次要取个值都要来上这么一段,还必须在回调里面再处理后面的逻辑!

所以,需要这样一种机制,任务页面修改一个配置项,则直接将其推送服务器,像这样。

options.optionName = ‘new Value;
chrome.storage.sync.set(options);

然后其它页面利用 storage.onChanged 事件来获取变更内容,覆盖页面中的 options 对应的项。 这样不需要手动从服务器取值。

// 监听设置项的变化
chrome.storage.onChanged.addListener(function(changes) {
    for (var name in changes) {
        var change = changes[name];
        options[name] = change.newValue;
    }
});

这样直接引用 options.optionName 就可以拿到最新的内容了。简直就和使用 localStorage 一样, 甚至更加便利。

在扩展各页面中加入同步的处理

当然,这段监听的调用需要遍布扩展的各个页面。将处理同步的相关代码放入一个单独的 JS 文件中, 比如 application.js

var options = {
    notifyTimeout: 5,     // 页面划词结果显示时间
    pageInspect: true,    // 是否启用页面划词
    linkInspect: true,    // 是否启用链接划词
    pushItem: false,      // 是否推送单词到服务端,
    notifyMode: 'margin', // 结果默认显示在右上角
};

// 从 storage 中读取配置,如果没有配置,则初始化为默认值
function initOptions(callback) {
    chrome.storage.sync.get(null, function(data) {
        $.extend(options, data);
        chrome.storage.sync.set(options);
        callback && callback();
    });
}

// 监听设置项的变化
chrome.storage.onChanged.addListener(function(changes) {
    for (var name in changes) {
        var change = changes[name];
        options[name] = change.newValue;
    }
});

对于 Background Page 和 Content Scripts,直接在 manifest.json 中指定就可以了。

{
  "background": {
    "scripts": [
      "js/application.js",
      "js/event.js"
    ],
    "persistent": true
  },

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": [
        "application.js",
        "contentscript.js"
      ],
      "all_frames": true
    }
  ],

  "permissions": [
    "tabs",
    "storage"
  ]
}

不要忘记在 permissions 里加入 storage 权限。

其它页面,则可以直接在其 HTML 文件中引用脚本。

<script type="text/javascript" src="application.js"></script>

然后在各个页面的处理脚本中,这样使用

initOptions(function() {
   // do something with options 
});

Chrome 划词翻译扩展 TransIt V1.2 发布

发布于

非常荣幸的宣布,TransIt 又升级了一个版本,这是自开源以来的第一个版本。 也算是对即将到来的双节的一个献礼,感谢大家对 TransIt 的支持,我们会一如继往的改进这个产品。

这个版本版本包括下面的更新:

页面划词翻译结果支持在选中的文本附近显示

TransIt 的页面划词翻译现在支持两种显示方式了:窗口右侧显示就近显示, 可以同时照顾到大屏幕和小屏幕的用户了,大家选择自己喜欢的方式就好了。

设置页面

就近显示

支持在框架页面内划词

之前的 TransIt 对框架页内的内容无能为力,现在这个问题也已经解决了。 可以畅快的在框架页面内划词翻译了,由于框架页面类型的限制,划词翻译的显示方式并不能完全兼顾。

  • 对于顶层是 FrameSet 的页面,只能使用就近显示
  • 普通页面内嵌框架页面,可以切换使用就近显示窗口右侧显示

下面是在 RunJS 上的效果

边缘显示

就近显示

支持 Tuborlinks 技术的网站

对于使用了 Turbolinks 技术的网站,网站在跳页时,会忽略掉划词脚本的加载, 导致跳页后划词脚本就失效了,现在 TransIt 也能搞定这种网站了。

下面是 Ruby China 上的效果

RubyChina

遵循有道 API 的调用规则,增加 Logo,去除缓存

TransIt 使用了有道翻译API来提供翻译服务,之前没有细心阅读其使用条款, 擅自在扩展里面对已经翻译的词进行缓存,以加快查过的词的翻译速度,这显示违反了条款。

所以这一版本, TransIt 去除了缓存的功能,其实有道的速度已经很快了,所以影响也不是很大。

此外,有道要求使用其 API,需要在适当的地方进行品牌露出, 所以目前在扩展弹出窗口中,也加入了有道的 Logo。


TransIt 以 MIT 协议开源在 https://github.com/GDG-Xian/crx-transit

欢迎喜欢或者不喜欢 TransIt 的朋友提出意见或者建议, 同时也欢迎热爱 Chrome 扩展开发的朋友 Fork 或者提交 PR.

前往 Webstore 安装 TransIt

不折腾

发布于

突然心血来潮,想要将博客从目前的 Acrylamid 迁移到 Jekyll,于是经历了一番折腾。

两个 Static Site Generator 所使用的文件结构和格式各不相同:

  • 文件结构不同 Acrylamid 使用 content/:year/:month/:slug.md 的文件结构存储文章源文件, 而 Jekyll 使用的是 _source/:year-:month-:day-:slug.md 的形式
  • YAML Front Matter 不同 Acrylamid 中会包含 slug 而 Jekyll 不会,而且 Jekyll 还要指定 layout

而 Acrylamid 又没有提供导出的功能,而手工修改肯定不是咱大程序员的作风呀,因为只能借助 脚本了,鉴于这两年来接触 Ruby On Rails 比较多,所以考虑使用 Ruby 脚本来操作迁移的过程。

成功迁移!不过发现 Jekyll 编译的速度好慢呀,已经超出了忍受的界限,在编译速度上 Acrylamid 处理的还是很赞的,只编译更改的部分,速度会快很多,尤其是在文章比较多的时候,尤为明显。

另外,我还需要移植我目前博客的皮肤,两个工具的模板引擎也不相同,要移植起来也不轻松。 这时候我突然又想移植到 Middleman 上面,因为前段时间看到 叶玎玎 的博客用的就是这个。

当然,又是一番折腾,结果发现 middleman 配置和移植起来也挺麻烦,静下以来吃个雪糕顺便想一想, Acrylamid 已经满足我所有的需要了,我为什么要折腾,不如洗洗睡吧。

No Zuo No Die!

Chrome 扩展 Tower.im Plus 发布

发布于

Tower.im 富文本编辑器的缺陷

最近与朋友合作开发一些小东西,作为远程工作的体验,工具嘛,选择了国内的一个协作工具 Tower.im

Tower 的确是一个非常不错的协作工具,彩程一贯的好设计,任务、讨论及上传文件都很方便。

因为 Tower 不是专门为开发人员设计的,所以并没有全面的支持 Markdown 编辑,而是使用了传统的 富文本编辑器,而这个富文本编辑器的功能还相当基础,支持的功能也比较少。

甚至不支持为选中的文字插入链接,虽然 Tower 为自己内部的地址做了短链接,但学是感觉不方便, 尤其是一些带有中文的链接,直接贴在正文里简直无法直视。我曾经向 Tower 反馈过这个问题, 我有向他们反馈过这个问题,但 Tower 更新的速度自然没有办法满足我的预期的,于是就自己动手了。

编辑器功能增强

目前实现了以下的扩展功能:

  • 添加插入链接的功能
  • 添加缩进和取消缩进的功能
  • 为讨论的编辑器中也添加水平分豁线的功能

此扩展仅是暂时补完一些 Tower.im 目前不完善的功能,希望彩程能够慢慢的把 Tower.im 做的更好, 这个扩展的也将功能越来越少。

讨论

文档

安装与反馈:

在 Webstore 中安装:

https://chrome.google.com/webstore/detail/twoerim-plus/dfhmgoomjkcdlfclkpjpmhjgpdakijke

安装后打开 Tower.im 就可以使用了,本扩展只是一个内容增强脚本, 所以并不会在你浏览器中占据一个图标的位置。

本扩展是开源的:

https://github.com/GDG-Xian/crx-tower

有问题欢迎在 Issue 中提出。

Chrome 划词翻译扩展 TransIt V1.1 发布

发布于

自从发布 TransIt 以来以来,收到了大家的许多反馈,又得到了玩儿法博客的推荐,取得了还算不错的成绩。

今天,TransIt 更新到了 1.1

  1. 页面划词时,如果一个单词的翻译还未消失,再次对该词划词,不会重复翻译
  2. 修正部分单词没有查找到翻译仍然显示翻译结果的问题
  3. 页面划词翻译结果增加透明和淡入淡出效果
  4. 为翻译结果添加音标(如果有)
  5. 更新链接文本划词翻译的描述
  6. 解决频繁划词导致翻译结果跑出屏幕的问题(支持使用鼠标滚轮滚动)

大家的反馈很多,目前这一版本只更新了一部分,毕竟精力有限。大家关心的 翻译语言设置大分辨率适配部分网站上划词无法使用自定义翻译结果样式 的问题,我们会逐个在以后的版本中处理。TransIt 会不断进步。

在商店中安装:

https://chrome.google.com/webstore/detail/transit/pfjipfdmbpbkcadkdpmacdcefoohagdc

发布 Chrome 代码高亮扩展 Hiliteme

发布于

最近一段时间将我平时做的笔记Sphinx 迁移到了 Evernote,虽然搜索和编辑方便了许多,但 Evernote 毕竟不是专门给开发人员用的,它网页版的编辑器限制很多,比如粘贴内容时会剔除其中的样式,只保留白底黑字, 这样我们就没有办法粘贴漂亮的代码了。

虽然 Evernote 桌面版提供了粘贴带样式的内容的选项,但是 Web 版本一直没有支持,所以只能是通过浏览器扩展来解决了。

Hiliteme

http://hilite.me/ 是我经常使用的一个代码高亮的服务,它基于 Pygments,能提供非常丰富的解析器和样式, 而且它生成的结果样式完全是通过 style="..." 来内嵌到 HTML 元素中的,可以方便的复制到任何支持 HTML 的地方。

更方便的是,hilite.me 还提供 api,这样极大的扩展了 hilite.me 的使用场景,于是 Hiliteme 诞生了。

我尽量延用了 hilite.me 的界面风格,简单方便。

弹出窗口

用法

如果在编辑器中选中了代码片断,点击图标后会自动将选择的代码片断带到弹出窗口的代码框中,点击高亮按钮, 会自动替换页面中选择的部分。

选择代码片断

高亮结果

如果没有选择代码片断,只要将光标置到编辑器中,点击图标手动输入代码,点击高亮按钮后会将高亮的结果插入光标位置。

注意:Hiliteme 可以在 Gmail 和 Evernote 中使用哦,妈妈再也不用担心我贴代码了。

拥抱开源

Hiliteme 当然是开源的,遵循 MIT 协议,

安装 dbus-python

发布于

写一个 python 脚本需要用到 dbus,但因为 dbus-python 这个包并没有提供 setup.py , 所以无法通过 pip 直接安装,唯有下载源码手动编译安装一途了。

wget https://pypi.python.org/packages/source/d/dbus-python/dbus-python-0.84.0.tar.gz
tar zxvf dbus-python-0.84.0.tar.gz
cd dbus-python-0.84.0

但事有不顺,在 ./configure 的过程中,还是出了一些错。

configure: error: Package requirements (dbus-1 >= 1.0) were not met:

No package 'dbus-1' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables DBUS_CFLAGS
and DBUS_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

这显然是缺失了依赖库

sudo apt-get install libdbus-glib-1-dev

然后安装就就可以顺利进行了

./configure
make
sudo make install

用 Python 批量创建云梯 VPN 连接配置

发布于

缘起

大家都知道,最近的网络不怎么和谐,速度慢不说,VPN 还总断,好在云梯 提供了挺多的服务器可以切换, 但云梯的服务器又挺多,Linux 的 Network Manager 又不支持批量添加配置,甚至配置文件都不能复制新建, 每个服务器的配置都得手动加,非常麻烦。

当然,也可以每次切换时打开配置,光改地址,但是这也非常不方便。

作为一个合格的开发人员,当然会想到用程序批量生成配置,我选择使用 Python。

寻找配置文件的位置

要批量创建配置,首先得知道配置文件在哪里,比如自己的云梯 VPN 地址中包含 example 字样,这样找起来就方便了。

grep 'example'  ~/.config -r
grep 'example'  /etc/ -r

于是轻松的定位到了配置文件的位置

grep: /etc/NetworkManager/system-connections/yunti.pptp.a: Permission denied
grep: /etc/NetworkManager/system-connections/yunti.pptp.b: Permission denied
grep: /etc/NetworkManager/system-connections/yunti.pptp.c: Permission denied

了解配置文件结构

拿一个配置文件出来看看:

[connection]
id=yunti.pptp.tw1
uuid=063db9b5-5915-4f3e-8bb4-2fe58abf5be5
type=vpn
permissions=user:greatghoul:;
autoconnect=false

[vpn]
service-type=org.freedesktop.NetworkManager.pptp
gateway=tw1.example.com
require-mppe=yes
user=greatghoul
refuse-chap=yes
refuse-eap=yes
password-flags=1
refuse-pap=yes

[ipv4]
method=auto
dns=8.8.8.8;8.8.4.4;
ignore-auto-dns=true

显然,有这么几个部分需要动态生成的

  • connection.id 这个需要是唯一的
  • connection.uuid 就是 uuid 生成一个就好了
  • connection.permissions 要添加上你的用户名嘛
  • vpn.gateway VPN 服务器的地址
  • vpn.user VPN 服务的帐户名
  • ipv4.dns 按你喜好配置就好

既然了解了,就开工吧

准备配置信息及模板

首先,让我们准备好材料:

VPN_SERVERS = [
    { 'id': 'yunti.pptp.a',  'gateway': 'a.example.com' },
    { 'id': 'yunti.pptp.b',  'gateway': 'b.example.com' },
    { 'id': 'yunti.pptp.c',  'gateway': 'c.example.com' },
]

配置中 uuid 需要动态生成了

>>> import uuid
>>> str(uuid.uuid1())
'0621ba62-888a-11e3-805c-44334c786649'

至于 connection.permissionsvpn.useripv4.dns 直接写在配置模板中即可。

tpl.cfg

[connection]
id=%(id)s
uuid=%(uuid)s
type=vpn
permissions=user:greatghoul:;
autoconnect=false

[vpn]
service-type=org.freedesktop.NetworkManager.pptp
gateway=%(gateway)s
require-mppe=yes
user=greatghoul
refuse-chap=yes
refuse-eap=yes
password-flags=1
refuse-pap=yes

[ipv4]
method=auto
dns=8.8.8.8;8.8.4.4;
ignore-auto-dns=true

生成 VPN 连接配置文件

剩下的事,就只有遍历 VPN 服务器信息,生成模板了

def add_connection(tpl, conn_info):
    filename = os.path.join(CFG_DIR, conn_info['id'])
    print '  Creating file:', filename 
    out = open(filename, 'w')
    out.write(tpl % conn_info)
    out.close()
    os.chmod(filename, 0600)

def create_all():
    tpl = open(os.path.join(CURRENT_DIR, 'tpl.cfg'), 'r').read()

    print 'Creating yunti connection files under', CFG_DIR
    for conn_info in VPN_SERVERS:
        conn_info.update(uuid=str(uuid.uuid1()))
        add_connection(tpl, conn_info)

我测试过,虽然 VPN 配置文件的文件名怎么写都行,但是如果在 NetworkManager 中修改了该连接的信息,NetworkManager 会自动将该配置文件重命为 Connection Name (也就是配置文件中 id),所以在创建文件时,还是保持文件名与 id 一致才好。

还有一个注意点是,连接配置文件必须属于 root:root 并且权限设置为 600, 因为我们需要通过 sudo 执行脚本,所以这里只需要控制 chmod 就行了。

os.chmod(filename, 0600)

完整的脚本

https://gist.github.com/greatghoul/9066705

享受成果

修改 tpl.cfg 中相关的用户名为自己的,然后执行下面的命令。

$ sudo python create_yunti_config.py 
Cleaning up yunti connection files...
  Removing file: /etc/NetworkManager/system-connections/yunti.pptp.a
  Removing file: /etc/NetworkManager/system-connections/yunti.pptp.b
  Removing file: /etc/NetworkManager/system-connections/yunti.pptp.c
Creating yunti connection files under /etc/NetworkManager/system-connections
  Creating file: /etc/NetworkManager/system-connections/yunti.pptp.a
  Creating file: /etc/NetworkManager/system-connections/yunti.pptp.b
  Creating file: /etc/NetworkManager/system-connections/yunti.pptp.c

开始使用云梯吧 :)

连接

PS: 第一次点击 VPN 连接会要求输入密码。

参考资料

在 XFCE 中使用截图工具

发布于

在 Ubuntu 中,我一直以来都是使用的 Shutter 作为主要的截图工具,它拥有超级多的截图方案, 还内置了非常棒的图片编辑工具,可以方便的添加标注。但公司的机器比较垃圾,我甚至从 GNOME 换到了 XFCE,所以尽量去挑选一些比较精致的工具来使用,而且我对 Shutter 大部分的功能也用不上, 于是卸载了 Shutter,准备换成 XFCE 的截图工具。

xfce-screenshoter

在安装 XFCE 截图之前,先卸载掉 Shutter 和 GNOME 自带的截图工具

sudo apt-get purge shutter
sudo apt-get purge gnome-screenshot

然后顺手安装 XFCE 截图工具

sudo apt-get install xfce4-screenshooter-plugin

XFCE 并没有默认的截图快捷键绑定,需要我们自己配置,可以使用 Application Finder 打开 Keyboard 工具,在 Application Shortcuts 选项卡下添加下面的配置:

xfce4-screenshooter -f  Print
xfce4-screenshooter -w  Ctrl + Print
xfce4-screenshooter -r  Shift + Print

shortcuts

参考资料: