使用 Pow 和 Anvil 管理本地网站

做网页开发的(特别是 Rails 开发者)应该听过甚至在使用 Pow 了吧?Pow 让开发者从 localhost:port 地狱中解放出来,她让每个项目都可以通过以项目名(实际是文件夹名)和 .dev 组成的域名来代替 localhost:port 的方式,如 http://project_name.dev/,不管在易用性还是视觉上都是非常赞的。

使用方法很简单,通过 curl get.pow.cx | sh 安装完后,把项目用 symbol link 连到 ~/.pow 下就可以:

cd ~/.pow
ln -s /path/to/myapp

但是每次都要跑命令行还是有点麻烦,而 Anvil 就是为了解决这个问题而诞生的。

Anvil 是一个只在菜单栏占一个位置的程序,如果要添加项目,只需要把文件夹拖放到菜单栏上的图标即可,Anvil 就为你做剩下的工作;删除项目时只需点击图标,在菜单里点击对应的删除按钮即可:

Anvil

Anvil 实在简单得没什么可以说的,不过有一点需要注意,因为Anvil 本质上相当于是 Pow 的图形界面,所以是需要安装 Pow 的,运行时 Anvil 也会提示需要安装 Pow;需要注意的是,Anvil(0.61)不能识别通过 Homebrew 安装的 Pow,需要通过 curl get.pow.cx | sh 或点击 Anvil 的安装按钮来安装 Pow。

发布 Data URI Converter 1.0

Data URI Converter 是我写的第一个 正式对外发布的 Mac App,写这货的动机是因为有段时间常用的拖曳式生成 DataURI 网站不是挂了就是被墙,剩下的都是不理想的,比如是传统 <input type=file>,所以萌生了写一个本地软件的想法。

只是完成了原先计划的基础功能,如拖曳自动识别 mime 类型历史记录等,还有一些暂时没时间,先搁置,历史记录也做得相对简单了,预览图:

Data URI Converter Preview

欢迎任何意见或建议 :)

Alfred 扩展:Open in Sublime Text 2

做了两枚用 Sublime Text 2 打开目录或文件的 Alfred 扩展:

图标来自于 dmatarazzoSublime-Text-2-Icon

一些很有用但不常见的 JavaScript APIs

Nicholas Zakas 发布了一个幻灯片,讲的是一些知名度低的 JavaScript API,我把它们都整理出来,并适当加了一些个人见解。

这篇文章里介绍的 API 大部分都是被普遍支持的(大部分 IE6~7 也能用),但是像是被我当成速查手册的 javascriptkit.com 也没完全包含进去,这就有点怪异了,我是认为像这类入门网站,是该好好完善兼容性高的基础 API,这样新人入行时才不至于漏掉一些实用知识。

下面的例子如无特殊说明,都是基于这个 HTML 结构:

<nav id="nav">
  <!-- comment -->
  <a>Home</a>
  <a>Blog</a>
</nav>
<script>
  var nav = document.getElementById('nav')
</script>

元素遍历

Element.children[]:这个 API 比起 Element.childNodes[] 的优点就是只包含 1 === nodeType 亦即 ELEMENT_NODE 的子节点,对于元素遍历来说方便很多,和 jQuery.children() 是一样效果的。注:IE8- 会包含 8 === nodeTypeCOMMENT_NODE,需要手动判断。

nav.childNodes[0] // '#text'
nav.children[0]   // <a>

Element.firstElementChildElement.lastElementChildElement.nextElementSiblingElement.previousElementSibling:和 children[] 一样,都会跳过非 ELEMENT_NODE 的子节点。注:IE8- 不支持。

nav.firstChild                              // '#text'
nav.lastChild                               // '#text'
nav.firstElementChild                       // <a>Home</a>
nav.lastElementChild                        // <a>Blog</a>
nav.firstElementChild.nextSibling           // '#text'
nav.lastElementChild.previousSibling        // '#text'
nav.firstElementChild.nextElementSibling    // <a>Blog>
nav.lastElementChild.previousElementSibling // <a>Home</a>

Element.contains():判断一个元素是否包含另一个元素。相比之下,jQuery.contains() 实现得很别扭,只能对比原生 DOM 元素,自己的 jQuery 对象无法对比。

document.body.contains(nav)         // true
jQuery.contains(document.body, nav) // true
jQuery.contains($('body'), nav)     // false

元素修改

Element.insertAdjacentHTML(location, html_string):把 html_string 插入到指定的地方,html_string 必须是合法的 HTML 片段,location 有四个值,分别是 beforebeginbeforeendafterbeginafterend,假设往 nav 插入 HTML,对应的位置如下:

<!-- beforebegin --><nav><!-- afterbegin -->
  <a>Home</a>
  <a>Blog</a>
<!-- beforeend --></nav><!-- afterend -->

需要注意的是 beforebeginafterend 是只有元素拥有父元素且在 DOM 树中时才有效,用在特定元素上也有可能出现非预期结果:

// 用在 body 上会产生两个 <head> 和 <body>(仅在 Chrome 上测试过)
document.body.insertAdjacentHTML('beforebegin', '<p>hello')
document.body.insertAdjacentHTML('afterend', '<p>hello')

// <p> 会消失
var d = document.createElement('div')
d.insertAdjacentHTML('afterend', '<p>hello')
nav.appendChild(d)

// <p> 会出现在 <nav> 里,和 <div> 同级
var d2 = document.createElement('div')
nav.appendChild(d2)
d2.insertAdjacentHTML('afterend', '<p>hello')

性能insertAdjacentHTMLinnerHTML 相近,选择用谁要看需求,如果插入的位置依赖于某个元素,insertAdjacentHTML 通常会便利一些。

Element.outerHTMLinnerHTML 的变种,也就不必过多说明了。应用起来和上两者一样,某些情况下会很便利。

document.implementation.createHTMLDocument(): 创建一个新的 DOCUMENT_NODE,也就是 document。虽然似乎没什么用,不过可以做的东西有很多,比如:

var new_doc = document.implementation.createHTMLDocument()
new_doc.body.innerHTML = html_string
// 删除特定标签
[].forEach.call(new_doc.body.querySelectorAll('script,link,object'), function(el){
  el.parentNode.removeChild(el)
})
// 将处理过、干净安全的 HTML 代码添加到真正的 DOM
document.body.innerHTML = new_doc.body.innerHTML

需要注意的是,创建出来文档里的元素用 getComputedStyle() 不一定能得到样式值的,所以要用于计算的话,请谨慎(仅在 Chrome 测试过):

var new_doc = document.implementation.createHTMLDocument()
  , p = new_doc.createElement('p')
p.innerHTML = 'hello world'
p.style.cssText = 'color: red;'
new_doc.body.appendChild(p)
console.log(p.style.color)                                 // 'red'
console.log(getComputedStyle(new_doc.querySelector('p')))  // all empty

文本选择

Element.select():选择文本框内的文本。

Element.setSelectionRange(position, length):选择指定范围内的文本,position0 开始算,小于 0 的也算 0length 如果小于或等于 position 则不会选择任何字符。

Element.selectionStartElement.selectionEnd:标识文本选择的起始位置。

document.activeElement:选择文档内获得焦点的元素。

XMLHttpRequest

XMLHttpRequest 很多人都会选择使用已经封装好的库吧,毕竟有兼容的问题,不过我喜欢从简,一般会挑个轻量的,之前也写了一个 jQuery 风格的 ajax 函数。如果你也想自己写,可以试试以下几个能加强 XHR 的玩意。

new FormData():实际就是模拟一个 <form> 提交时封装的数据包,不过是用类似 key: value 方式来处理,同时也能直接接受 <input> 的值,相当便利。

xhr.upload.onprogress:通常请求时都是用动态 GIF 来表示进行中,不过我曾经遇过一个客户,想要显示进度,又不希望用 Flash,当时因为难度问题,说服了他放弃,不过现在倒是有了这东西可以用。注:IE9- 不支持。

xhr.timeoutxhr.ontimeout:控制超时。注:IE9- 不支持。

xhr.responseType:目前支持四种,textdocumentblobarraybuffer,配合 xhr.response 使用。原来是只有 responseTextresponseXML 的,不过由于是字符串,下载后的处理比较麻烦,通过这些扩展类型,会方便很多,比如 documentblob 就能直接处理了:

var xhr = new XMLHttpRequest()
  , data = new FormData() // or FormData(document.form[index])
data.append('key', 'value')
data.append('key2', fileInput.file[0]) // 获取 <input> 元素的值

xhr.open('get', url, true)
xhr.timeout = 5000
xhr.ontimeout = function(event){
  console.log(arguments)
}
xhr.responseType = 'document'
xhr.upload.onprogress = function(event){
  console.log(arguments)
}
xhr.onload = function(event){
  var doc = event.currentTarget.response
  // do something like
  doc.querySelector('body')
}
xhr.send()

CSS 相关的

Element.matchesSelector()jQuery.is() 的原生实现。注:IE8- 不支持,其他的都要前缀,如 webkitMatchesSelector

Element.getBoundingClientRect():获取指定元素的矩形区域信息,简单地说,就是这个元素在文档里的座标、长高分别是多少,比起 getComputedStyle() 更方便,因为值是纯数字。注:IE7- 会给每个座标加 2,就像是被一个 padding: 2px 的容器包裹。

Element.document.elementFromPoint(x, y):获取指定座标的元素,如有多个,取 z-index 最大的。用在游戏或互动界面上应该不错,比如说球是不是进入了球门的座标里,或者结合 window.innerWidthwindow.innerHeight 来判断一个元素是否进入了可视范围,不过我更喜欢用 getBoundingClientRect()

下面是「加载更多」的不完整实现:

var footer = document.querySelector('footer')
window.addEventListener('scroll', function(){
  var rect = footer.getBoundingClientRect()
  if (rect.top <= window.innerHeight) {
    // do something...
  }
}, false)

window.matchMedia():判断 window 符不符合 CSS Media Query 的条件,比如 window.matchMedia("(max-width: 320px)")。主要用途是为移动设备启用不同的 JS 效果

JavaScript 原生的 API 是越来越强大、好用,很多时候都可以不需要库的加持,不过如果仍然要苦逼地支持 IE7-(其实我想说 IE8-……)的话,库还是最好的选择,毕竟解决了很多兼容性的问题。

BTW,最近看《松本行弘的程序世界》,Matz 对动态类型语言的一个观点觉得很实用:「无论如何都想检查(参数类型)的时候,也不要检查对象是否属于某个类,而是要检查对象是否有某个方法」,也就是这样:

var respond_to = function (o, f) {
  return !!(o != null && o[f]);
}
// 假设需转换标题为大写字母,但是传入参数不一定是 string
function title (o) {
  if (respond_to(o, 'toUpperCase')) {
    return o.toUpperCase()
  }
  // 传统做法
  if (typeof o === 'string') {
    return o.toUpperCase()
  }
}

这种做法原本是针对支持继承和多态的语言,比如父类和子类就有可能拥有同名的方法,如果单纯判断是不是某个类,就不能应对所有情况,而直接判断是否有这个方法的话,就可以适应各种情况,在 JavaScript 里也可以实现类似的效果:

var text_post = { title: "Hello World", toUpperCase: function(){
      return this.title.toUpperCase()
    }
  }
  , music_post = { name: "Hey Jade", toUpperCase: function(){
      return this.name.toUpperCase()
    }
  }
console.log(title(text_post)) // 'HELLO WORLD'
console.log(title(music_post)) // 'HEY JADE'

参考资料:

Alfred 扩展:移除 OS X 右键菜单的重复项

升级到 Mountain Lion 之后,最恼人的是这个 bug:

Duplicate items in contextual menu

只要安装、升级程序都有可能造成重复项,实在烦人,但是并不是没有办法,可以用以下命名去重建右键菜单:

/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -domain local -domain system -domain user

复制到 Terminal 回车然后重启 Finder 就可以了。

如果你是 Alfred 的 powerpack 用户,可以用我做的扩展,注意里面有重启 Finder 的命令,killall FinderFinder 的工作还没做好之前,不要执行这个扩展,如有任何问题,本人不负责,你也可以在安装后删除那行命令:

http://d.pr/f/VsSF

Image via MacRumors

这是一篇老文,从旧 blog 里搬过来的,因为有点用。

Zurui Design

TAE 在日本「第一回 プログラマ向けデザイン勉強会」(面向程序员的设计学习班)上做了「Zurui Design」的演讲,TAE 称之为「Zurui Design」。「Zurui」在日文的意思是「狡猾的」,大概是因为大部分情况下都可以用,很少出问题的缘故吧。

演示内的例子使用了高阶的 CSS 属性,大部分在 IE8 或以下会有支持问题,如 rgba(),这里就不多说明怎么处理,请善用 Google。

以下整理一些实用的:

背景

  • 使用接近纯白或纯黑的颜色;
  • Pattern 的选择上尽量接近自然或构图简单的;
  • 避免选择大图案的 pattern;
  • 避免选择色度很浓或亮度差异很大的 pattern。

渐变

  • 起始色和结束色之间尽量不要有太大差距,如 #434343#222
  • 起始色和结束色之间尽量不要混合多种不同的颜色,如红黄蓝搭配。

圆角

  • 非圆形设计,尽量限制 border-radius6px 内;

阴影

box-shadow 需避免:

  • 太浓的颜色
  • 较大的淡化值(blur)
  • 彩色的阴影

彩色的阴影指非黑白的其他颜色,使用上尽量使用黑色,如果背景色为黑色系,可以把阴影的颜色设置为比背景还要黑一些,别因为要凸显而采用其他颜色。

按钮

如果想要做有立体感的按钮,可以配合渐变,但光源是上到下为佳,即上部浅色,底部深色。使用渐变时配合 border 会有较好的效果,但注意避免彩色的 border

适用于中日韩的正则表达式

之前我在 Twitter 发过用于匹配中文的正则表达式,但是发的表达式并不是很严谨,只是一个粗略的匹配,今天就进行补完吧。

FYI,如果是用 Ruby,用内置的集合吧,如 /\p{Han}/,最省事,详情看文档

汉字匹配

[一 -鿌]4E00–9FCC 段。这个是最常用的汉字集。

[㐀-䶵]3400–4DBF 段。

[𠀀-𪛖]20000–2A6D6 段。

[𪜀-𫠝]2A700–2B81F 段。

[⺀-⿕]2E80–2FDF 段。

[豈-舘]F900-FA6D 段。

[你-鼖]2F800-2FA1B 段。

在 Objective-C 里可以任意用上面的集合进行组合。

Python 不会用,简单的测试发现 u"[𠀀-𪛖]" 会导致报错,不加 u 匹配出来的会是别的字符,

# -*- coding:utf-8 -*-
import re
prog = re.compile(u'[𠀀-𫠚]')
result = prog.match(u'𠀀')
print result

For JavaScript

JS 对五位的 Unicode 编码支持有问题,比如 𠀀 不管是以单字还是 \uD840\uDC00 方式是可以匹配,但是作为范围,如 [𠀀-𪛖] 会抛出 Range out of order in character class 异常。

如有需要,建议使用 [一 -鿋][㐀-𫠚]

日文五十音匹配

相对于汉字,这个难度就大降:

[ぁ - ヶ゛゜ー]

JavaScript、Objective-C、Python 和 Ruby 测试通过。

韩文匹配

[가-힝]

不懂韩文,大致就是这样吧?

JavaScript、Objective-C、Python 和 Ruby 测试通过。

备注

  1. 为什么我会选择用「字」而不是 Unicode 编码来匹配呢?因为五位 Unicode 编码的支持实在不能统一,用字的话,我常用的语言都可以兼容,省事。
  2. 如果看到的是方块字,那是正常的,虽然显示不出来,但仍旧是那个编码。

参考资料:

从 Tumblr 迁移到 Octopress

以前一直在用 Tumblr 作为 blog 托管,因为简单,还不用管流量、图片托管等问题,也支持自定义域名,不过最近开始满足不了我的一些需求:

  • 非 Photo 类型的文章难插入多张图片,通常都是用别的图床来放置图片,比如 Droplr;
  • CSS/JS 的处理比较郁闷。

所以就打算迁移到 GitHub + Octopress 上,虽然所有东西都是公开的。

附 Octopress 简单教程:

第一步,在 GitHub 创建一个新的 repo,将其命名为 "username.github.com","username" 是你的用户名。

第二步,创建好之后,去 admin 页面选择 "Automatic Page Generator"。

第三步,安装 Octopress:

git clone git://github.com/imathis/octopress.git octopress
cd octopress # 保证安装了 Ruby 和 rbenv/RVM,进入目录时会问你是否信任 .rvmrc,选是
gem install bundler
# rbenv 需要跑 `rbenv rehash`
bundle install
rake install
rake setup_github_pages # 填入你的 repo 地址
rake generate

一些常用的操作如下:

rake new_post["title"]:创建新的文章,title 尽量用英文吧,Mac Terminal 打中文会报错。创建的文章会用时间戳的方式,如 2012-11-02-moved-from-tumblr.markdown 保存到 source/_post 下,用任意编辑器打开即可。

rake new_page[page-name]:创建新的页面,比如个人简介什么的,会放在 source/page-name 下,默认主页是 index.markdown

rake generate:把所有文件转换并复制到 public/ 下面。rake watch 会监视文件并实时转换。

rake preview:启动一个 localhost:4000 服务器供预览。

rake deploy:如果没问题了,就用这个命令把文件提交到 GitHub。

因为这个 repo(按照上面的例子是 octopress)和你的 GitHub pages 并不是同一个 repo,所以你可以再建一个 repo,把东西提交上去,这样也方便你写草稿,还可以用 Bitbucket 的免费私有 repo。

如果执行 rake new_postrake generate 时碰到了类似 invalid byte sequence in US-ASCII (ArgumentError) 的问题,编辑一下这两个文件:

# rvm-path/gems/ruby-version/gems/jekyll-version/lib/jekyll/tags/include.rb
# 把 source = File.read(@file) 改成:
source = File.read(@file, :encoding => "utf-8")

# rvm-path/gems/ruby-version/gems/jekyll-version/lib/jekyll/convertible.rb
# 把 self.content = File.read(File.join(base, name)) 改成:
self.content = File.read(File.join(base, name), :encoding => "utf-8")

根据你的配置,把 rvm-pathruby-versionjekyll-version 改成实际的路径。一般情况,rvm-path~/.rvm/

或者在你的 .bashrc.zshrc 等文件加入以下两个变量:

export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8

这个问题是由于 Python 2.X 并没有把 Unicode 作为默认编码格式,Jekyll 也没有声明要用 Unicode 处理文件。