之前看書看一看021 ImageSwicher 和 GridView 介面元件覺得講得有點含糊,Adapter 是什麼都沒解釋就繼承來寫 ImageAdapter,
然後就跑去看了 Android Developers 的 LayoutGridView,筆記在這邊:Layouts(Adapter) & GridView Example

範例的程式碼和註解

最後再回來看書做一遍範例,以下是範例的程式碼和註解。
activity_main.xml

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >


<GridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:numColumns="3"
android:layout_weight="0.4"
xmlns:android="http://schemas.android.com/apk/res/android" />


<ImageSwitcher
android:id="@+id/imgSwitcher"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="0.6"
android:layout_gravity="center"
android:layout_marginTop="10dp"
>

</ImageSwitcher>
</LinearLayout>

MainActivity.java

package com.example.gallery021;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.ViewSwitcher;

// 這邊多了 implements ViewSwitcher.ViewFactory 實做了 ViewFactory 這個介面
// ViewFactory 裡面的 makeView() 抽象方法,用來幫 ViewSwitcher 建立 View
public class MainActivity extends ActionBarActivity implements ViewSwitcher.ViewFactory {

// 宣告 layout 裡的兩個東西
private GridView mGridView;
private ImageSwitcher mImgSwitcher;

// 用來放圖片id的陣列
private Integer[] iThumbImgArr = {R.drawable.sample_0,
R.drawable.sample_1, R.drawable.sample_2,
R.drawable.sample_3, R.drawable.sample_4,
R.drawable.sample_5, R.drawable.sample_6,
R.drawable.sample_7};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 用findViewById 把 xml 中設定的 ImageSwitcher 元件
mImgSwitcher = (ImageSwitcher) findViewById(R.id.imgSwitcher);
// 傳入ViewSwitcher.ViewFactory 用在 ViewSwitcher 翻轉之間建立兩個 View
mImgSwitcher.setFactory(this);
// 設定 View 進入和離開時要用哪種動畫
mImgSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
mImgSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));

// 宣告一個 ImageAdapter 給 GridView 用
ImageAdapter imgAdap = new ImageAdapter(this, iThumbImgArr);
mGridView = (GridView) findViewById(R.id.gridView);
mGridView.setAdapter(imgAdap);

// 傳入 OnItemClickListener 裡面會寫當按下 GridView 項目被點擊時要呼叫的 Callback
mGridView.setOnItemClickListener(gridViewOnItemClick);
}

// menu 用,這個範例中沒用
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

// menu 用,這個範例中沒用
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}

// 建立 OnItemClickListener 裡面寫 GridView 項目被按下時要呼叫的 Callback
private AdapterView.OnItemClickListener gridViewOnItemClick = new AdapterView.OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 設定 ImageSwitcher 要顯示的圖片 id
mImgSwitcher.setImageResource(iThumbImgArr[position]);
}
};

// ViewFactory 的抽象方法 用來幫 ViewSwitcher 建立 View
// return View
@Override
public View makeView() {
ImageView imageView = new ImageView(this);
// 書上不知道為什麼做了兩次 setBackgroundColor
// 0xFF000000 這種顏色格式,前面的 FF 是不透明度 100%,再來 R G B 都是 00,所以把後面的setBackgroundColor註解調圖片背景就會變黑的
imageView.setBackgroundColor(0xFF000000);
// 設定如果影像需要被改變大小時要怎麼做
// 有: matrix, fitXY, fitStart, fitCenter, fitEnd, center, centerCrop, centerInside
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setLayoutParams(new ImageSwitcher.LayoutParams(ImageSwitcher.LayoutParams.MATCH_PARENT, ImageSwitcher.LayoutParams.MATCH_PARENT));
// 書上不知道為什麼做了兩次 setBackgroundColor 第一次的設定會被第二次蓋掉,所以最後圖片背景是白的
imageView.setBackgroundColor(Color.WHITE);
return imageView;
}
}

這邊多了 implements ViewSwitcher.ViewFactory 實做了 ViewFactory 這個介面,所以必須要實做 ViewFactory 裡面的 makeView() 抽象方法,用來幫 ViewSwitcher 建立 View。

ImageAdapter.java

package com.example.gallery021;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

// Adapter 可以把資料填入 AdapterView(像是ListView 或 GridView)
// 這邊繼承 BaseAdapter 做出 ImageAdapter 來把影像加入 GridView
public class ImageAdapter extends BaseAdapter {
private Context mContext; // 執行環境
private Integer[] mImgArr; // 影像 id 陣列

// 建構式 要把 Context 和 影像陣列丟進來
public ImageAdapter(Context context, Integer[] imgArr) {
mContext = context;
mImgArr = imgArr;
}

// 用來回傳項目數量,陣列的話就用 .length 回傳長度
@Override
public int getCount() {
return mImgArr.length;
}

// 傳入 position 回傳 Item
@Override
public Object getItem(int position) {
return null;
}

// 傳入 position 回傳 Item 的 id
@Override
public long getItemId(int position) {
return 0;
}

// 用來建立有放進 GridView 的 ImageView,回傳 ImageView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(150, 150));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(10, 10, 10, 10);
} else {
imageView = (ImageView) convertView;
}

imageView.setImageResource(mImgArr[position]);
return imageView;
}
}

執行結果:

有個莫名其妙的白色背景,看起來很醜,還有上面的縮圖區,影像看起來靠左而且大小很不整齊。
所以自己做了一些調整:

  • layout xml GridView 多加一個 android:gravity="center" 讓裡面的影像會置中。
  • ImageAdapter.javagetView() 中設定 imageView 的地方加上一行 imageView.setCropToPadding(true);
    才不會長邊沒 padding,設定好以後每個縮圖看起來都是方方的。
  • MainActivity.java 把兩個 setBackgroundColor() 都註解掉,這樣下面大圖區就不會有白白的背景了。

最後就變這樣:

第六屆黑客松的Node.js Workshop 筆記,目的是做出聊天室後端。

環境

重點放在後端所以講師有先弄好一個聊天室前端
做好的聊天室伺服器可以放在 cloud9 可以在雲端編輯、執行server
或是直接localhost跑 (推薦,因為這邊網路很爛把node.js裝好、聊天室前端 zip 下載下來、裝好 socket.io 就不用網路了)

先裝好 Node.js
開一個資料夾
進去以後

$ npm init

然後就會問你一些專案設定的東西,這邊就先一直 Enter

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (nodeWorkshop)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/yucing617/Documents/nodeWorkshop/package.json:

{
"name": "nodeWorkshop",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}


Is this ok? (yes)

OK以後再來安裝 socket.io

$ npm install socket.io --save

請求資料

server 和 client 溝通

polling 輪詢

ㄟ 你有沒有話要跟我說
ㄟ 你有沒有話要跟我說
ㄟ 你有沒有話要跟我說
ㄟ 你有沒有話要跟我說

long polling 長輪詢

ㄟ 你有沒有話要跟我說



會等一陣子,不要求馬上回應
(像是facebook 某些服務 一次等10秒)

WebSocket

在 server 或 client 建立一個穩定的連線,有訊息可以馬上告訴對方,減少不斷問的成本,反應速度更快。

Hello World

在專案裡面開一個 server.js

var socketio = require('socket.io');
var io = socketio(7000);

執行 server,指令下完以後會看起來卡住,正常,代表 server 在跑了。

$ node server.js

把下載下來的前端 index.html 直接點兩下打開
Server: 的地方填入 http://127.0.0.1:7000 按下連線

下面看到已連線就代表成功了。

Callback 基本觀念

等LOL讀取的時候會先去滑手機,不會卡在那邊讀取

等LOL讀取(function(){
殺敵
})

滑手機()

像是下面這樣,ha ha ha 會先跑出來。

setTimeout(function(){
console.log('hello World');
}, 2000);

console.log('ha ha ha');

連線聊天實做

當有人連線進來時顯示訊息

var socketio = require('socket.io');

var io = socketio(7000);

io.on('connection', function(someone){
console.log('someone here', someone.id);
});

當有人傳訊息時可以用emit()送訊息出來,就可以看到聊天室有訊息了。

var socketio = require('socket.io');

// 啟動socket.io server 這個server叫做io
var io = socketio(7000);

// socket.io 事件 每調薪連線進來時會做事件綁定
io.on('connection', function(someone){
console.log('someone here', someone.id);

// message是自訂的 收到訊息時的事件 然後發送給所有人
someone.on('message', function(form, msg){
// 有人送訊息時 用io這個伺服器把訊息送出去
// emit是送訊息的動作
io.emit('message', form, msg);

});
});

實做使用者列表

當有人連線或離現時更新使用者列表
上面要加一個userList記錄所有使用者

var userList = {};

下面這些要寫在 io.on('connection', function(someone) 裡面:

// 使用者上線通知
someone.on('user_in', function(nickname){
userList[someone.id] = nickname;
io.emit('user_list', userList);
})

// 使用者離線通知
someone.on('disconnect', function(nickname){
delete userList[someone.id];
io.emit('user_list', userList);
})

發悄悄話給某人

上面要加一個clients記錄所有client(連線物件)

var clients = {};

因為我們多記錄了client,所以登入和離線的地方也要改,有人連線要紀錄他的連線物件,有人離線要多刪除一個client

// 使用者上線通知
someone.on('user_in', function(nickname){
userList[someone.id] = nickname;
clients[someone.id] = someone;
io.emit('user_list', userList);
})

// 使用者離線通知
someone.on('disconnect', function(nickname){
delete userList[someone.id];
delete clients[someone.id];
io.emit('user_list', userList);
})

主要的功能寫在 io.on('connection', function(someone) 裡面:

// 發悄悄話給某人
someone.on('private message', function(form, sendToId, msg){
// 不能用io.emit()了 因為會送給io伺服器上所有人
// 要改成送給client[]裡面的某人,從sendToId來選人
clients[sendToId].emit('private message', form, msg);
});

完成以後就可以開三個網頁用不同 nickname 用悄悄話看看了。

完整的code

var socketio = require('socket.io');

// 啟動socket.io server 這個server叫做io
var io = socketio(7000);

var clients = {};
var userList = {};

// socket.io 事件 每調薪連線進來時會做事件綁定
io.on('connection', function(someone){
console.log('someone here', someone.id);

// 使用者上線通知
someone.on('user_in', function(nickname){
userList[someone.id] = nickname;
clients[someone.id] = someone;
io.emit('user_list', userList);
})

// 使用者離線通知
someone.on('disconnect', function(nickname){
delete userList[someone.id];
delete clients[someone.id];
io.emit('user_list', userList);
})


// message是自訂的 收到訊息時的事件 然後發送給所有人
someone.on('message', function(form, msg){
// 有人送訊息時 用io這個伺服器把訊息送出去
// emit是送訊息的動作
io.emit('message', form, msg);

});

// 發悄悄話給某人
someone.on('private message', function(form, sendToId, msg){
// 不能用io.emit()了 因為會送給io伺服器上所有人
// 要改成送給clients[]裡面的某人,從sendToId來選人
clients[sendToId].emit('private message', form, msg);
});
});

為了看懂 GridView 所以回去看 Layouts 的 Adapter 部分
所以這篇有一部份的 Layouts + GridView

Layouts

Layout 用來定義 UI 的視覺結構,有兩種方法可以定義 Layout:

  • XML
  • 用編程的方式,讓 Layout 在執行期實現

可以兩種方式一起用,用 XML 定義 Layout 和他的特性,之後可以在執行期被修改。

Write the XML

直接寫 XML ,定義完 layout 將 XML 檔(.xml)放在 res/layout/ 路徑下,就會被正確的編譯。

Load the XML Resource

當你編譯你的 APP,每個 XML layout 檔被編譯到 View resource 中,你可以在你的 Activity.onCreate() Callback 中用程式碼讀取 layout resource。
例如你的 XML layout 儲存為 main_layout.xml,你應該要讓你的 Activity 這樣讀取:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
}

你的 Activity 中的 onCreate() callback 方法,會在你的 Activity 啟動時,由 Android framework 呼叫。

…中略…

目標是下面的 Adapter

Building Layouts with an Adapter

當 layout 的內容是動態的或是不能預先決定,你可以用 layout 的子類別 AdapterView 在執行期時將 View 填入 layout。
子類別 AdapterView 使用 Adapter 來結合資料到 layouts。
Adapter 是資料來源和 AdapterView layout 的中間人,Adapter 取回資料(資料來源像是 array 或是資料庫查詢)
並轉換成資料到 View 中,讓他們可以被加入到 AdapterView layout 中。

支持 Adapter 的 layout 有:
List View
顯示可以捲動的一列清單
Grid View
顯示可以捲動的行列網格

Filling an adapter view with data

你可以藉由結合 AdapterView 到一個 Adapter 來填入像是 ListView 或 GridView 的 AdapterView,他會從額外來源獲取資料並且建立一個 View。

Android 提供幾個 Adapter 子類別,來取回不同種類的資料並且結合 views 到 AdapterView。下面兩個是最常見的 Adapter:

ArrayAdapter

用這個 Adapter 可以用陣列當作資料來源。
ArrayAdapter 建立一個 view 時預設會把陣列中每一個項目呼叫 toString() 並把他們放到 TextView。

例如,如果你有一個字串陣列,你想要用 ListView來顯示他,用建構式初始化一個 ArrayAdapter:

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, myStringArray);

建構式的參數是:

  • 你的 app Context
  • layout,用來容納陣列中每個字串的 TextView
  • 字串陣列

之後在你的 ListView 簡單的呼叫 setAdapter():

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

要自訂每個項目的外表,可以為你的陣列物件 override toString() 方法,
或是要為一些 TextView 以外其他種類的東西建立 view(例如想要為陣列的每個項目建立 ImageView),可以 extend ArrayAdapter 並 override getView() 來為每個項目回傳你想要的型態的 veiw。

SimpleCursorAdapter

當你的資料來自 Cursor(不是滑鼠游標,是讀取資料庫用的 android.database.Cursor)可以使用這個 Adapter。
你必須指定一個 layout 給每個 Cursor 的 Row 使用,而且每個 Cursor 的 Columns 必須加入到 layout 中的 view。
例如你要建立某些人的姓名與電話清單,你可以回傳 Cursor 做的查詢結果,包含每個人的姓名與電話,然後建立一個字串陣列指定哪個查詢結果的 column 要放到哪裡,
建立一個整數陣列指定相對每個 coulmn 要被放進去的 view:

String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};

翻譯感覺不太順,簡單來說就是把查詢結果放到 String Array 中,然後在另一個 int Array 把這些資料要放進去哪個 view 的 id 指定好。

當你實做 SimpleCursorAdapter 時,傳入要使用的 layout,包含查詢結果的 Cursor,還有上面兩個陣列:

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);

如果 Course 在 APP 執行中,你更改 adapter 讀取的底層資料,你必須呼叫 notifyDataSetChanged()。
這個方法會通知所屬的 view 資料被更動了,必須更新。

Handling click events

你可以藉由實現 AdapterView.OnItemClickListener 介面,回應 AdapterView 每個項目的點擊事件,例如:

// Create a message handling object as an anonymous class.
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int position, long id) {
// Do something in response to the click
}
};

listView.setOnItemClickListener(mMessageClickedHandler);

GridView

GridView 是 ViewGroup 用二維可捲動的格狀方式顯示項目。這些項目可以用 ListAdapter 自動加入 layout。

Example

在這個教學中,你將建立一個格狀的影像縮圖。當一個項目被選擇時,一個 toast message 機會顯示該位置的影像。

  • 開啟一個新專案叫做 HelloGridView
  • 找一些你喜歡圖片,或是下載這些範例圖片。把圖片儲存到專案中的 res/drawable/ 目錄下。
  • 打開 res/layout/activity_main.xml 把這些打進去:

    <?xml version="1.0" encoding="utf-8"?>
    <GridView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnWidth="90dp"
    android:numColumns="auto_fit"
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center"
    />

    這個 GridView 將會填滿整個畫面。

  • 打開 MainActivity.java 把這些程式碼家到 onCreate() 方法中:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    GridView gridView = (GridView) findViewById(R.id.gridview);
    gridView.setAdapter(new ImageAdapter(this));

    gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Toast.makeText(MainActivity.this, "" + position, Toast.LENGTH_SHORT).show();
    }
    });
    }

    在把 ContentView 設為 activity_main 之後,GridView layout 被 findViewById(int) 抓進來。
    之後 setAdapter() 方法設定一個自訂的 Adapter (ImageAdapter) 作為資料來源,讓所有項目被顯示在表格中,ImageAdapter 會在下一步驟建立。
    要讓表格中的項目被點擊時能做些什麼,要用 setOnItemClickListener 方法,傳入一個 new AdapterView.OnItemClickListener。
    這個匿名實例定義了 onItemClick() callback 方法來顯示一個 Toast 來展示被選擇項目的 index position(從0開始)(實際的情況是,position 被用在其他的任務中取得全尺寸的影像)。

  • 建立一個叫做 ImageAdapter 的新 class,extends BaseAdapter:

    public class ImageAdapter extends BaseAdapter {
    private Context mContext;

    public ImageAdapter(Context c) {
    mContext = c;
    }

    public int getCount() {
    return mThumbIds.length;
    }

    public Object getItem(int position) {
    return null;
    }

    public long getItemId(int position) {
    return 0;
    }

    // create a new ImageView for each item referenced by the Adapter
    public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;
    if (convertView == null) {
    // if it's not recycled, initialize some attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
    } else {
    imageView = (ImageView) convertView;
    }

    imageView.setImageResource(mThumbIds[position]);
    return imageView;
    }

    // references to our images
    private Integer[] mThumbIds = {
    R.drawable.sample_2, R.drawable.sample_3,
    R.drawable.sample_4, R.drawable.sample_5,
    R.drawable.sample_6, R.drawable.sample_7,
    R.drawable.sample_0, R.drawable.sample_1,
    R.drawable.sample_2, R.drawable.sample_3,
    R.drawable.sample_4, R.drawable.sample_5,
    R.drawable.sample_6, R.drawable.sample_7,
    R.drawable.sample_0, R.drawable.sample_1,
    R.drawable.sample_2, R.drawable.sample_3,
    R.drawable.sample_4, R.drawable.sample_5,
    R.drawable.sample_6, R.drawable.sample_7
    };
    }

    首先實現一些繼承自 BaseAdapter 的方法,通常 getCount(int) 必須能傳入 position 回傳項目,但是在這個範例中就先忽略了,
    同樣的 getItemId(int) 需要回傳某個項目的 row id,但是在這裡我們也用不到。
    第一個必須的方法是 getView()。這個方法建立一個 View,把每個影像加入 ImageAdapter。當這個方法被呼叫時 view 會被傳入,通常這是可以回收在利用的物件(被呼叫一次以後),
    所以這裡會檢查如果這個物件是不是空的(null),如果是的話會建立一個 ImageView 並把特性設定成影像想要展現的樣子。

    • setLayoutParams(ViewGroup.LayoutParams) 設定 view 的高和寬已確保不論 drawable 的大小,每個影像可以適當的被調整和裁剪成適合的尺寸。
    • setScaleType(ImageView.ScaleType) 宣告這些影像如果需要時會被朝中心裁剪。
    • setPadding(int, int, int, int) 定義每一邊的 padding。(注意,當影像有不同的常寬比時,當他不符合ImageView的尺寸時,較少的 padding 會造成更多的剪裁。)
      如果傳入的 View 不是空值,被傳入的 position 會被用來在 mThumbIds 陣列中選擇影像,他會為 ImageView 設定影像來源。
  • 執行APP!

嘗試看看調整 GridView 和 ImageView 的特性,例如不要用 setLayoutParams(ViewGroup.LayoutParams) 改用 setAdjustViewBounds(boolean)。

先到 Google Analytics 登入或建立帳戶,新增一個資源,填完以後按取得追蹤編號。

就會看到追蹤程式碼,把這段程式碼複製起來

開啟主題中的 after-footer.ejs,以我使用預設主題為例,這個檔案在 themes/landscape/layout/_partial 中。
把剛剛複製的程式碼貼進去最底下就可以了。

用了 Hexo 以後,要寫筆記都是在 Terminal 下指令來新增 markdown 檔案,
之前都 hexo new 以後去 Finder 把文章找出來點兩下,Sublime Text 就會打開讓我編輯文章,
就覺得如果可以直接從 Terminal 開啟 Sublime Text 就好了,Google 一下,果然有人教怎麼做。

可以參考這兩篇:
這篇 Launch Sublime Text 3 from the Mac OS X Terminal 是從這篇 Launch Sublime Text 2 from the Mac OS X Terminal Fork 改成 ST3 用的。
因為我是用 Sublime Text 3,所以這邊就用 ST3的方法寫。

先試試看下面這樣能不能啟動 ST3

$ open /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl

可以的話,用下面的指令,把 sublime 連結到 /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl

$ ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime

以後就可以直接在 Terminal 用 sublime 指令來開啟檔案了。

$ sublime <filename>


裝好 Hexo 部屬到 GitHub 以後,如果你的 GitHub Pages 是用 Project Site ,幫 Repo 開一個 Pages 的話,會發現 Header中這些連結都怪怪的。

像是標題和 Home 會連結到 /,而不是 /projectname/
Archives 和訂閱會連到 /archives/atom.xml 而不是 /projectname/archives/projectname/atom.xml
跟你的網誌實際位置不一樣就出現404了Orz
結果是因為沒去修改預設主題的_config.yml ,位置在 theme/landscape/_config.yml

# Header
menu:
  Home: /projectname/
  Archives: /projectname/archives
rss: /projectname/atom.xml

# Content
excerpt_link: Read More
fancybox: true

# Sidebar
sidebar: right
widgets:
- category
- tag
- tagcloud
- archive
- recent_posts

# Miscellaneous
google_analytics:
favicon: /projectname/favicon.png
twitter:
google_plus:
fb_admins:
fb_app_id:

打開_config.yml 把所有路徑都加上/projectname,這樣大致上連結就正常了。

除了你的 Logo 連結…

這東西_config.yml 裡沒得設定,還是連到/啊…
後來去 Google了 一下有人說是預設主題 landscape 有一些 Bug。
可以修改 blog/themes/landscape/layout/_partial/header.ejs 來解決,不過我不會用EJS,所以直接看了一下程式碼:

<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="<%- url_for() %>" id="logo"><%= config.title %></a>
</h1>
<% if (theme.subtitle){ %>
<h2 id="subtitle-wrap">
<a href="<%- url_for() %>" id="subtitle"><%= theme.subtitle %></a>


把第6行的 href="<%- url_for() %>" 直接改成 href="/projectname/" 就可以正確連到首頁了。

變成這樣:

<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="/projectname/" id="logo"><%= config.title %></a>
</h1>
<% if (theme.subtitle){ %>
<h2 id="subtitle-wrap">
<a href="<%- url_for() %>" id="subtitle"><%= theme.subtitle %></a>


之後應該會換別的主題用用看,就先將就吧。

一開始我的 Tags 中的 Hexo 是小寫的,後來把他改成大寫,結果 Tags/Hexo 的頁面就變成404了…
原因是 git 預設對於檔名的大小寫無感,把資料夾名稱改成大寫或小寫並不算是有更動。
因此改了大小寫、生成檔案、部屬以後 Repository 上的資料夾是還是小寫的 tags/hexo/
但是 Hexo 幫我們生成的檔案,連結已經變成大寫的 tags/Hexo/ ,就找不到網頁啦ˊ_>ˋ

後來看了 hexo-deployer-git 發現有個資料夾隱藏叫做.deploy_git
原來 Hexo 生成的靜態網站.git 是在.deploy_git 裡,所以要這樣做:

$ cd .deploy_git
$ git config core.ignorecase false
$ hexo clean
$ hexo generate
$ hexo deploy

git config 中有個設定可以讓 git 對大小寫有感,指令是這樣 $ git config core.ignorecase false
但是要進去.deploy_git才能改到要發佈到 GitHub Pages 的 git config
之後把產生檔案 clean 再重新產生、發佈一次,Repo的 tags/Hexo/ 終於變大寫了!!

遇到一個問題,在 hexo generate 的時候會出現錯誤:

$ hexo generate
ERROR Process failed: _posts/yourpost.md
JS-YAML: can not read a block mapping entry; a multiline key may not be an implicit key at line 5, column 5:
tags:
^

結果是因為一開始沒注意到 Front-matter(就是Markdown檔案最上面---以上的東西)是 YAML 格式,List 格式應該的- XXX ,減號後面要有空格,我寫成像下面這樣:

title: "yourpost"
date: 2015-04-01 08:16:27
categories:
-Tech
tags:
-Hexo

正確的應該是這樣:

title: "yourpost"
date: 2015-04-01 08:16:27
categories:
- Tech
tags:
- Hexo

真是個蠢問題XD()