第 8 章 - 網誌系統設計

簡易伺服器

檔案: staticServer.js

var koa = require('koa');
var fs = require('fs');
var app = module.exports = koa();
var path = require('path');
var extname = path.extname;

app.use(function *() {
  var path = __dirname + this.path;
  var fstat = yield stat(path);

  if (fstat.isFile()) {
    this.type = extname(path);
    this.body = fs.createReadStream(path);
  }
});

if (!module.parent) app.listen(3000);

function stat(file) {
  return function (done) {
    fs.stat(file, done);
  };
}

markdown 格式

Markdown 是目前程式人最常用的書寫格式,這種格式容易寫有容易讀,最後通常會被轉換為 HTML 呈現,目前已經有很多網誌系統都支援 markdown 格式,您現在所看到的這本 gitbook 書,也是用 markdown格式撰寫的!

以下是一個 markdown 的範例

markdown顯示結果
# 標題

## 子標題

### 子子標題

內文, __底線__ , 
*斜體*, **粗體** .

水平線

---

無號項目

  * 項目1
  * 項目2
  * 項目3

編號項目

  1. 項目1
  2. 項目2
  3. 項目3

yahoo: [Yahoo](http://tw.yahoo.com/).

標題

子標題

子子標題

內文, 底線 , 斜體, 粗體 .

水平線


無號項目

  • 項目1
  • 項目2
  • 項目3

編號項目

  1. 項目1
  2. 項目2
  3. 項目3

yahoo: Yahoo.

若您想更進一步了解 markdown 的寫法,請參考下列連結。

showdown 套件: 將 markdown轉為html

範例: showdown_embed.html

<html>
<body>
<table width="100%"><tr><td style="vertical-align:top">
<textarea id="mdBox" style="width:90%; height:300px">
# 標題

## 子標題

### 子子標題

內文, __底線__ , 
*斜體*, **粗體** .

水平線

---

無號項目

  * 項目1
  * 項目2
  * 項目3

編號項目

  1. 項目1
  2. 項目2
  3. 項目3

yahoo: [Yahoo](http://tw.yahoo.com/).

</textarea>
</td><td>
<div id="htmlBox">
htmlBox
</div>
</td></tr></table>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.5.0/showdown.js"></script>
<script>
var mdBox = document.getElementById('mdBox');
var htmlBox = document.getElementById('htmlBox');

var converter = new showdown.Converter();
converter.setOption('tables', true);
var html = converter.makeHtml(mdBox.value);

htmlBox.innerHTML = html;
</script>
</body>
</html>

呈現結果:

showdown 套件 AJAX 顯示檔案

檔案: showdown_ajax.html

<html>
<body>
<table width="100%"><tr><td style="vertical-align:top">
<textarea id="mdBox" style="width:90%; height:300px">
</textarea>
</td><td>
<div id="htmlBox">
htmlBox
</div>
</td></tr></table>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.5.0/showdown.js"></script>
<script>
var load=function(path) {
  if (!path.startsWith("/")) path="/"+path;
  return $.ajax({
    type: "GET",
    url: path,
    timeout: this.timeout,
    data: {}
  });    
}

var loadFile = function(path) {
  load(path).done(function(content) {
    var md;
    if (typeof content === 'string')
        md = content;
    else
        md = JSON.stringify(content, null, ' ');
    $('#mdBox').val(md);
    var html = converter.makeHtml(md);
    $('#htmlBox').html(html);

  })
  .fail(function() {
    $('#mdBox').val("error!");
  });
}

window.onhashchange = function() {
  var filepath = window.location.hash.substring(1);
  loadFile(filepath);
}

var converter = new showdown.Converter();
converter.setOption('tables', true);
window.location = '#test.md';
</script>
</body>
</html>

呈現結果

專案:wikidown 網誌系統

專案網址: https://github.com/ccckmit/wikidown

安裝方法:

$ git clone https://github.com/ccckmit/wikidown.git
$ cd wikidown
$ node wdserver

Server started: http://localhost:80
Ssl Server started: https://localhost:443
db connect fail

Wikidown 系統用到了上一章的 fdbserver,執行時由於沒有啟動 mongod ,所以只能使用檔案系統,不能用資料庫。

不過 wikidown 並沒有使用到資料庫,而是將資料完全儲存在檔案系統中,所以上述執行方法,雖然沒有啟動 mongodb 伺服器,但功能卻是完全正常的。(因為根本沒用到)

以下是我在 windows 8 上執行此程式的結果:

1 瀏覽畫面

2 https 進入時會顯示可編輯的瀏覽畫面

3 點選 Edit 後會顯示 markdown 格式原始碼的編輯畫面

關於完整的原始碼,請直接閱讀 https://github.com/ccckmit/wikidown 專案。

wikidown 是一個單頁式應用,由於伺服端是採用上一章的 fdbserver 模組,所以在此我們僅列出 client 重要的程式碼部分。

檔案: wd.html

<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="web/favicon.ico" rel="icon">
  <link href="web/css/bootstrap.min.css" rel="stylesheet">
  <link href="web/css/highlight.default.min.css" rel="stylesheet">
  <link href="web/css/fileinput.css" media="all" rel="stylesheet"/>
  <link href="web/editor.css" rel="stylesheet">
  <title></title>
</head>
<body onload="E.init()">
  <div id="fb-root"></div>
  <nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
                <span id="titleHead" class="titleLink"></span>  
      </div>
      <div id="navbar" class="navbar-collapse collapse">
        <ul class="nav navbar-nav navbar-right hide" id="menuLogin">
          <li class="dropdown">
            <a class="dropdown-toggle" data-toggle="dropdown">Login</a>
            <ul class="dropdown-menu" role="menu">
              <li><a onclick="E.login()">Login</a></li>
              <li><a onclick="E.logout()">Logout</a></li>
            </ul>
          </li>
        </ul>
        <ul class="nav navbar-nav navbar-right">
          <li class="dropdown">
            <a class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-share"></span>
            <ul class="dropdown-menu" role="menu">
              <li><a onclick="E.facebookShare()">Facebook</a></li>
            </ul>
          </li>
        </ul>
        <form class="navbar-form navbar-right">
          <input id="staticUrl" type="text" placeholder="static url" class="form-control">
        </form>                
        <form class="navbar-form navbar-right hide" id="menuEdit">
          <div class="form-group">
            <input id="filepath" type="hidden" class="form-control" placeholder="filepath" aria-describedby="basic-addon1">
            <button class="btn btn-success" type="button" onclick="E.show()">Show</button>
            <button class="btn btn-success" type="button" onclick="E.edit()">Edit</button>
            <button class="btn btn-success" type="button" onclick="E.save()">Save</button>
            <button class="btn btn-success" type="button" onclick="E.upload()">Upload</button>
          </div>
        </form>     
      </div>
    </div>
  </nav>

  <div id="panelShow" class="tab-pane panel container-fluid"> <!-- Show -->
    <div class="row">
      <div id="sidebar"  class="col-sm-3 col-md-2 sidebar">
        <ul id="side" class="nav nav-sidebar"></ul>
      </div>  
      <div id="htmlBox" class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" style="height:90%;padding:10px;">
      {%=html%}
      </div>
      <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
      </div>
    </div>
  </div>
  <div id="panelEdit" class="tab-pane panel container-fluid"> <!-- 編輯 -->
    <br/>
    <textarea id="editBox" class="form-control" style="width:100%; height:80%">
    {%=text%}
    </textarea>
  </div>
  <div id="panelUpload" class="tab-pane panel container-fluid" style="height:75%">
    <center>
    <div class="kv-main">
      <form enctype="multipart/form-data">
        <div class="form-group">
      <input id="imageUpload" name="imageUpload" type="file" multiple=true class="file-loading">
        </div>
      </form>
    </div>
    </center>
  </div>
  <div id="panelLogin" class="tab-pane panel container-fluid"> <!-- 登入 -->
    <form class="form-signin" role="form">
      <input type="text" id="loginUser" class="form-control" required autofocus data-mt="User" placeholder="Account"/>
      <input type="password" id="loginPassword" class="form-control" required data-mt="Password" placeholder="Password"/>
      <button class="btn btn-lg btn-primary btn-block" type="button" data-mt="Login" onclick="E.Server.login()">Login</button>
    </form>
  </div>
  <script src="web/js/jquery.min.js"></script>
  <script src="web/js/bootstrap.min.js"></script>
  <script src="web/js/fileinput.js"></script>
  <script src="web/js/showdown.js"></script>
  <script src="web/js/highlight.min.js"></script>
  <script src="web/editor.js"></script>
  <script src="web/config.js"></script>
  <script src="web/wdlib.js"></script>
  <script src="web/wd.js"></script>
</body>
</html>

檔案: web/editor.js

var E = {
  path:null,
  msgNewFile:'File not found!',
  loadCompleted:false,
}

E.init = function() {
  $('.panel').css( "display", "none");
  E.onhashchange();
  if (window.location.protocol === 'https:') {
    $('#menuLogin').removeClass('hide');
    $('#menuEdit').removeClass('hide');
  }
}

E.isLogin = function() {
  if (localStorage.wd_login !== "true") { 
    // 注意:sessionStorage 不能跨頁面持續,所以得用 localStorage
    alert('You can not save & edit before login. Please login now !');
    E.Server.login();
    return false;
  }
  return true;
}

E.Server = {
  timeout : 4000
};

E.Server.save=function(path, text) {
  if (!path.startsWith("/")) path="/"+path;
  console.log("save");
  $.ajax({
    type: "POST",
    url: "/file"+path,
    timeout: this.timeout,
    data: { text: text },
    statusCode: {
      401: function() { // 401:Unauthorized
        localStorage.wd_login = "false";
        E.isLogin();
      }
    }
  })
  .done(function(data) {
    console.log("save success");
    alert("Save success!");
  })
  .fail(function() {
    alert("Save fail!");
  });
}

E.Server.load=function(path) {
  if (!path.startsWith("/")) path="/"+path;
  return $.ajax({
    type: "GET",
    url: "/file"+path,
    timeout: this.timeout,
    data: {}
  });
}

E.Server.login=function() {
  $.ajax({
    type: "POST",
    url: "/login",
    timeout: this.timeout,
    data: { user:$('#loginUser').val(), password:$('#loginPassword').val() },
  })
  .done(function(data) {
    localStorage.wd_login = "true";
    alert( "Login success!");
    $('#loginPassword').val('');
    E.edit();
  })
  .fail(function() {
    localStorage.wd_login = "false";
    alert("Login fail!\nDefault : user=root , password=123\nModify or add (user, password) in setting.js for security");
    E.login();
  });
}

E.Server.logout=function() {
  $.ajax({
    type: "POST",
    url: "/logout",
    timeout: this.timeout,
    data: {},
  })
  .done(function(data) {
    localStorage.wd_login = "false";
    alert( "Logout success!");
    E.switchPanel('panelShow');
  })
  .fail(function() {
    alert( "Logout fail!" );
  });
}

E.switchPanel=function(name) {
  $('.panel').css( "display", "none");
  $('#'+name).css( "display", "block");
}

E.login=function() {
  E.switchPanel('panelLogin');
}

E.logout=function() {
  E.Server.logout();
}

E.edit=function () {
  E.switchPanel('panelEdit');
}

E.upload=function() {
  if (!E.isLogin()) return;
  E.switchPanel('panelUpload');
  $("#imageUpload").fileinput({
    uploadUrl: "/upload/"+E.path,
    maxFileCount: 10,
    uploadAsync: false,
    uploadExtraData: { path: E.path }
  });
}

E.show=function() {
  var editText = $('#editBox').val();
  var content;
  try {
    content = JSON.parse(editText);
  } catch (e) {
    content = editText;
  }
  var html = E.render(content);
  // http://stackoverflow.com/questions/12449890/reload-content-in-modal-twitter-bootstrap
  $('#htmlBox').html(html);
  $("#htmlBox").animate({ scrollTop: 0 }, "fast");
  E.switchPanel('panelShow');
}

E.render=function(content) {
  if (typeof content === 'string') {
    return "<pre>\n"+content+"</pre>\n";
  } else if (content.type==="directory"){
    var files = content.files;
    var html = "<UL>\n";
    var path = E.path;
    if (!path.endsWith("/")) path=path+"/";
    for (var i=0; i<files.length; i++) {
      var rewritePath = path;
      html += "<LI><a href='#"+rewritePath+files[i]+"\'>"+files[i]+"</a></LI>\n";
    }
    html += "</UL>\n";
    return html;
  } 
}

E.loadFile=function(path) {
  E.path = path;
  E.Server.load(path)
  .done(function(content) {
    if (typeof content === 'string')
      editText = content;
    else
      editText = JSON.stringify(content, null, ' ');
    $('#editBox').val(editText);
    E.show();
  })
  .fail(function() {
    $('#editBox').val(E.msgNewFile);
    E.show();
  });
}

E.onhashchange = window.onhashchange = function() {
  var filepath = window.location.hash.substring(1);
  E.loadFile(filepath);
}

E.save=function() {
  if (!E.isLogin()) return;
  var editText = $('#editBox').val();
  E.Server.save(E.path, editText);
}

results matching ""

    No results matching ""