├── cmd ├── libs │ ├── php │ │ ├── .gitignore │ │ ├── _config.php │ │ ├── put.php │ │ └── S3.php │ ├── node │ │ ├── demo │ │ │ ├── .gitignore │ │ │ ├── _config.js │ │ │ ├── finish.js │ │ │ ├── rollback.js │ │ │ ├── argvs.js │ │ │ ├── choice.js │ │ │ ├── pushBr.js │ │ │ ├── plugin.js │ │ │ ├── s3Info.js │ │ │ ├── newBr.js │ │ │ ├── dirsInfo.js │ │ │ ├── check.js │ │ │ ├── minify.js │ │ │ └── putS3.js │ │ ├── zip │ │ │ ├── compress.js │ │ │ ├── listFiles.js │ │ │ └── dirsInfo.js │ │ ├── watch │ │ │ ├── livereload.js │ │ │ ├── scss.js │ │ │ └── icon.js │ │ └── Ginkgo.js │ ├── plugin │ │ └── cover.php │ └── scss │ │ ├── _Ginkgo.scss │ │ ├── _GinkgoFont.scss │ │ ├── _GinkgoMixin.scss │ │ └── _GinkgoUi.scss ├── .gitignore ├── _dirs.yaml ├── package.json ├── config.rb ├── zip ├── doc │ └── WindowInstall.md ├── watch └── demo ├── img └── og.png ├── font ├── .gitignore └── icomoon │ ├── fonts │ ├── icomoon.eot │ ├── icomoon.ttf │ ├── icomoon.woff │ └── icomoon.svg │ └── style.css ├── .gitignore ├── css ├── icon.css └── public.css ├── readme.md ├── scss ├── icon.scss └── public.scss ├── LICENSE └── index.html /cmd/libs/php/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | config.php -------------------------------------------------------------------------------- /cmd/libs/node/demo/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | config.js -------------------------------------------------------------------------------- /img/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oawu/LiveTaiwan/HEAD/img/og.png -------------------------------------------------------------------------------- /cmd/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .sass-cache/* 3 | node_modules/* 4 | package-lock.json 5 | npm-debug.log -------------------------------------------------------------------------------- /font/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | */demo-files/* 3 | */demo.html 4 | */Read Me.txt 5 | */selection.json -------------------------------------------------------------------------------- /font/icomoon/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oawu/LiveTaiwan/HEAD/font/icomoon/fonts/icomoon.eot -------------------------------------------------------------------------------- /font/icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oawu/LiveTaiwan/HEAD/font/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /font/icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oawu/LiveTaiwan/HEAD/font/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | sftp-config* 3 | 4 | **/npm-debug.log 5 | **/node_modules/* 6 | **/package-lock.json 7 | -------------------------------------------------------------------------------- /cmd/libs/php/_config.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 6 | * @license http://opensource.org/licenses/MIT MIT License 7 | * @link https://www.ioa.tw/ 8 | */ 9 | 10 | return [ 11 | 'bucket' => null, 12 | 'access' => null, 13 | 'secret' => null, 14 | 'folder' => null, 15 | 'domain' => null, 16 | ]; -------------------------------------------------------------------------------- /cmd/libs/node/demo/_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | module.exports.s3Info = function() { 9 | const Path = require('path'); 10 | 11 | return { 12 | bucket: null, 13 | access: null, 14 | secret: null, 15 | folder: Path.resolve(__dirname, '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..').split(Path.sep).pop(), 16 | domain: null, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /cmd/libs/plugin/cover.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 6 | * @license http://opensource.org/licenses/MIT MIT License 7 | * @link https://www.ioa.tw/ 8 | */ 9 | 10 | mb_regex_encoding('UTF-8'); 11 | mb_internal_encoding('UTF-8'); 12 | date_default_timezone_set('Asia/Taipei'); 13 | 14 | try { 15 | throw new Exception('錯誤原因 1'); 16 | throw new Exception(json_encode(['錯誤原因 2', '錯誤原因 3'])); 17 | throw new Exception(json_encode(['錯誤原因' => '4', '錯誤原因' => '5'])); 18 | } catch (Exception $e) { 19 | echo $e->getMessage(); 20 | exit(1); 21 | } 22 | 23 | exit(0); 24 | -------------------------------------------------------------------------------- /cmd/libs/scss/_Ginkgo.scss: -------------------------------------------------------------------------------- 1 | // 2 | // @author OA Wu 3 | // @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | // @license http://opensource.org/licenses/MIT MIT License 5 | // @link https://www.ioa.tw/ 6 | // 7 | 8 | @import "GinkgoMixin"; 9 | @import "GinkgoUi"; 10 | 11 | $_InitGinkgo: false !global; 12 | 13 | @mixin init-ginkgo() { 14 | @if ($_InitGinkgo == false) { 15 | $_InitGinkgo: true !global; 16 | 17 | * { 18 | &, &:after, &:before { 19 | vertical-align: top; 20 | @include box-sizing(border-box); 21 | @include font-smoothing(subpixel-antialiased); 22 | } 23 | } 24 | } 25 | } 26 | 27 | @include init-ginkgo; 28 | -------------------------------------------------------------------------------- /css/icon.css: -------------------------------------------------------------------------------- 1 | @font-face { font-family: "icon"; src: url('../font/icomoon/fonts/icomoon.eot?1563416454') format('embedded-opentype'), url('../font/icomoon/fonts/icomoon.woff?1563416454') format('woff'), url('../font/icomoon/fonts/icomoon.ttf?1563416454') format('truetype'), url('../font/icomoon/fonts/icomoon.svg?1563416454') format('svg'); }*[class^="icon-"]:before, *[class*=" icon-"]:before { font-family: "icon"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; }.icon-user-secret:before { content: "\e900"; }.icon-004:before { content: "\f0004"; }.icon-005:before { content: "\f0005"; }.icon-00d:before { content: "\f000d"; }.icon-016:before { content: "\f0016"; }.icon-github:before { content: "\eab0"; } -------------------------------------------------------------------------------- /cmd/libs/node/zip/compress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const pr = Ginkgo.pr; 14 | const AdmZip = rq('adm-zip'); 15 | 16 | module.exports.run = function(path, dirs, closure) { 17 | pr(cc(' ➤ ', 'C') + '壓縮檔案'); 18 | 19 | const zip = new AdmZip(); 20 | 21 | dirs.forEach(function(dir) { 22 | return pr() && zip.addFile(dir.name, dir.buffer); 23 | }); 24 | 25 | zip.writeZip(path); 26 | return pr('') && closure(path); 27 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 歡迎來到 LiveTaiwan 2 | 讓我們大家一起來搜集全台灣即時影像,組成一個全台灣的各個景點的直播地圖吧! 3 | 4 | --- 5 | 6 | ## 內容說明 7 | * 這是一個將全台灣各地的 [Youtube](https://www.youtube.com/) 直播集合起來的網站,並且採用 [Google Maps](https://www.google.com.tw/maps/) 方式將他們呈現出來! 8 | * 目前資料是儲存在 [Google 試算表](https://www.google.com.tw/intl/zh-TW/sheets/about/) 上面,所以不需要後端維護。 9 | * 有興趣的夥伴,可以按 [地圖](https://works.ioa.tw/LiveTaiwan/index.html) 右上角的「我要新增」來增加新景點喔,如果資訊有誤或者不正確,也歡迎在「備註欄位」告知一下! 10 | 11 | ## 技術說明 12 | * 本專案採用 [Ginkgo](https://github.com/comdan66/Ginkgo) 框架實作。 13 | * 專案內使用 [Youtube](https://www.youtube.com/) 作為主要直播來源。 14 | * 專案內使用 [GoogleMaps](https://www.google.com.tw/maps/) 作為地圖呈現方式。 15 | * 主要語言為 [HTML](https://zh.wikipedia.org/zh-tw/HTML)、[SCSS](https://sass-lang.com/guide)、[JsvaScript](https://zh.wikipedia.org/wiki/JavaScript) 的框架。 16 | -------------------------------------------------------------------------------- /cmd/_dirs.yaml: -------------------------------------------------------------------------------- 1 | # # 2 | # - path: "" # 路徑,專案目錄開始的相對目錄 3 | # formats: [".html", ".text"] # 接受的副檔名,[] 為全部的副檔名都接受 4 | # hidden: No # 是否包含隱藏檔,Yes 與 No 兩種選項 5 | # recursive: No # 是否包含子資料夾,Yes 與 No 兩種選項 6 | # # 7 | 8 | - path: '' 9 | formats: ['.html', '.txt'] 10 | hidden: No 11 | recursive: No 12 | 13 | - path: "js" 14 | formats: [".js"] 15 | hidden: No 16 | recursive: Yes 17 | 18 | - path: "css" 19 | formats: [".css"] 20 | hidden: No 21 | recursive: Yes 22 | 23 | - path: "font" 24 | formats: [".eot", ".svg", ".ttf", ".woff"] 25 | hidden: No 26 | recursive: Yes 27 | 28 | - path: "img" 29 | formats: [".png", ".jpg", ".jpeg", ".gif", ".svg"] 30 | hidden: No 31 | recursive: Yes 32 | -------------------------------------------------------------------------------- /cmd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ginkgo", 3 | "version": "4.0.0", 4 | "description": "這是一套 OA Wu 所製作的個人網頁前端框架!", 5 | "private": true, 6 | "scripts": { 7 | "watch": "node watch" 8 | }, 9 | "keywords": [ 10 | "html", 11 | "scss", 12 | "css", 13 | "JavaScript", 14 | "compass", 15 | "node" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/comdan66/Ginkgo" 20 | }, 21 | "author": "OA Wu", 22 | "license": "MIT", 23 | "dependencies": { 24 | "adm-zip": "^0.4.11", 25 | "aws-sdk": "^2.315.0", 26 | "chokidar": "^2.0.4", 27 | "command-exists": "^1.2.7", 28 | "html-minifier": "^3.5.20", 29 | "js-yaml": "^3.12.0", 30 | "livereload": "^0.7.0", 31 | "md5-file": "^4.0.0", 32 | "node-notifier": "^5.2.1", 33 | "sprintf-js": "^1.1.1", 34 | "uglify-js": "^3.4.9" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scss/icon.scss: -------------------------------------------------------------------------------- 1 | // 2 | // @author OA Wu 3 | // @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | // @license http://opensource.org/licenses/MIT MIT License 5 | // @link https://www.ioa.tw/ 6 | // 7 | 8 | @import "compass/css3/font-face"; 9 | 10 | @include font-face("icon", font-files( 11 | "icomoon/fonts/icomoon.eot", 12 | "icomoon/fonts/icomoon.woff", 13 | "icomoon/fonts/icomoon.ttf", 14 | "icomoon/fonts/icomoon.svg")); 15 | 16 | *[class^="icon-"]:before, *[class*=" icon-"]:before { 17 | font-family: "icon"; 18 | 19 | speak: none; 20 | font-style: normal; 21 | font-weight: normal; 22 | font-variant: normal; 23 | } 24 | 25 | .icon-user-secret:before { content: "\e900"; } 26 | .icon-004:before { content: "\f0004"; } 27 | .icon-005:before { content: "\f0005"; } 28 | .icon-00d:before { content: "\f000d"; } 29 | .icon-016:before { content: "\f0016"; } 30 | .icon-github:before { content: "\eab0"; } -------------------------------------------------------------------------------- /cmd/libs/node/demo/finish.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | 16 | const Exec = rq('child_process').exec; 17 | 18 | module.exports.run = function(_v, closure) { 19 | pp((title = cc(' ➤ ', 'C') + '分支切換回 ' + cc(_v.gitOri.branch, 'w2') + ' 分支') + cc('… ', 'w0')); 20 | 21 | Exec('git checkout ' + _v.gitOri.branch + ' --quiet', function(err, stdout, stderr) { 22 | if (err) 23 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 24 | 25 | if (stdout.length) 26 | return er(title, ['執行指令 ' + cc('git checkout ' + _v.gitOri.branch + ' --quiet', 'w2') + ' 失敗!']); 27 | 28 | return su(title) && closure(); 29 | }); 30 | }; -------------------------------------------------------------------------------- /cmd/config.rb: -------------------------------------------------------------------------------- 1 | require 'compass/import-once/activate' 2 | 3 | # 預設編碼 4 | Encoding.default_external = "utf-8" 5 | 6 | # 網域(domain)後面的目錄 7 | http_path = "/Ginkgo" 8 | 9 | # 字體目錄與網址下的字體目錄 10 | fonts_dir = "font" 11 | fonts_path = "../font" 12 | 13 | # 圖片目錄與網址下的圖片目錄 14 | images_dir = "img" 15 | images_path = "../img" 16 | 17 | # css 目錄與 scss 目錄 18 | css_dir = "../css" 19 | sass_dir = "../scss" 20 | 21 | # js 目錄與網址下的 js 目錄,目前沒發現在哪邊用到.. 22 | javascripts_dir = "js" 23 | javascripts_path = "../js" 24 | 25 | # 其他要匯入的資源 26 | # add_import_path = "./libs" 27 | additional_import_paths = ["./libs/scss"] 28 | 29 | # 選擇輸出的 css 類型,:expanded or :nested or :compact or :compressed 30 | # nested 有縮排 沒壓縮,會有 @charset "UTF-8"; 31 | # expanded 沒縮排 沒壓縮,會有 @charset "UTF-8"; 32 | # compact 有換行 有壓縮(半壓縮),會有 @charset "UTF-8"; 33 | # compressed 沒縮排 有壓縮(全壓縮),沒有 @charset "UTF-8"; 34 | output_style = :compact 35 | 36 | # 在 css 中加入註解相對應於 scss 的第幾行,false、true 37 | # false 不需加入註解 38 | # true 需要加入註解 39 | line_comments = false 40 | 41 | # 是否使用相對位置,若是仰賴 http_path 則設為 false 42 | # relative_assets = false 43 | relative_assets = true 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 OA Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /font/icomoon/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src: url('fonts/icomoon.eot?i7iel4'); 4 | src: url('fonts/icomoon.eot?i7iel4#iefix') format('embedded-opentype'), 5 | url('fonts/icomoon.ttf?i7iel4') format('truetype'), 6 | url('fonts/icomoon.woff?i7iel4') format('woff'), 7 | url('fonts/icomoon.svg?i7iel4#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icomoon' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-user-secret:before { 28 | content: "\e900"; 29 | } 30 | .icon-004:before { 31 | content: "\f0004"; 32 | } 33 | .icon-005:before { 34 | content: "\f0005"; 35 | } 36 | .icon-00d:before { 37 | content: "\f000d"; 38 | } 39 | .icon-016:before { 40 | content: "\f0016"; 41 | } 42 | .icon-github:before { 43 | content: "\eab0"; 44 | } 45 | -------------------------------------------------------------------------------- /cmd/zip: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('./libs/node/Ginkgo'); 10 | 11 | const cc = Ginkgo.cc; 12 | const ln = Ginkgo.ln; 13 | const pp = Ginkgo.pp; 14 | const Path = rq('path'); 15 | const root = '..' + Path.sep; 16 | const path = Path.resolve(__dirname, root + Path.resolve(__dirname, root).split(Path.sep).pop() + '.zip'); 17 | 18 | Ginkgo.init('Ginkgo 打包工具', function() { 19 | pp(cc(' ➤ ', 'C') + '注意喔,過程中請勿隨意結束!' + ln + cc(' ➤ ', 'C') + Ginkgo.ctrlC()); 20 | 21 | pp(ln + cc(' 【讀取設定檔案】', 'y') + ln); 22 | rq('./libs/node/zip/dirsInfo').run(function(dirs) { 23 | 24 | pp(ln + cc(' 【壓縮檔案】', 'y') + ln); 25 | rq('./libs/node/zip/listFiles').run(dirs, function(dirs) { 26 | 27 | rq('./libs/node/zip/compress').run(path, dirs, function(path) { 28 | 29 | pp(ln + cc(' 【壓縮完成】', 'r2') + ln); 30 | pp(cc(' ➤ ', 'C') + '已經成功壓縮專案!' + ln); 31 | pp(cc(' ➤ ', 'C') + '依據 ' + cc('cmd/_dirs.yaml', 'w2') + ' 所指定的目錄檔案做壓縮!' + ln); 32 | pp(cc(' ➤ ', 'C') + '壓縮檔位置:' + cc(path, 'b2', undefined, 'underline') + ln + ln); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /cmd/doc/WindowInstall.md: -------------------------------------------------------------------------------- 1 | # Ginkgo in Windows 2 | 3 | ## 安裝 Git 4 | * 若電腦已安裝,則可以跳過此步驟 5 | * git 主要是在專案部屬到 demo 才會需要的指令,可視需求開發 6 | * 首先至[官網下載](https://git-scm.com/download/win) 7 | * 一樣無腦的下一步,不過其中有一步驟是選擇 git 編輯器,你可以選擇 other,然後再從資料目錄去選擇 sublime text 8 | 9 | ## 安裝 Node.js 10 | * 無腦安裝,直接去 [官網](https://nodejs.org/en/) 下載,通常我們會選擇比較多人安裝的下載(上面會寫`Recommended For Most Users`)。 11 | * 下載完後點兩下,無腦的下一步下一步的按下去,安裝過程中,若跳出防火牆是否同意,請記得按 `同意`。 12 | 13 | ## 安裝 compass 14 | * [官網](http://compass-style.org/) 15 | * 主要是參考此篇: [https://blog.wu-boy.com/2011/10/install-compass-css-authoring-framework-on-windows/](https://blog.wu-boy.com/2011/10/install-compass-css-authoring-framework-on-windows/) 16 | 17 | #### 1. 先安裝 Ruby 18 | * 其實在 [Ruby 下載官網](http://www.ruby-lang.org/zh_TW/downloads/) 有製作好懶人包了,我們只要下載 [RubyInstaller](http://rubyinstaller.org/) 網站別人包好的執行檔即可。 19 | * 這邊我選擇 Ruby 2.3.3 版本下載安裝。 20 | * 下載過程稍微注意一下 `安裝路徑` 位置。 21 | * 然後就一樣無腦安裝,一步一步步的下一步。 22 | 23 | #### 2. 開始安裝 Compass 24 | * 安裝好上述執行檔,你會發現在 C 槽多了 `Ruby23` 目錄(會依據你安裝不同版本名稱不同)。 25 | 26 | * 接著 `Windows`,輸入 `cmd` 然後 `Enter` 27 | * 進入你剛剛安裝時所選的路徑,輸入指令:`cd C:\\Ruby23\bin` 28 | * 更新 Gem 套件,輸入指令:`gem update --system` 29 | * 使用 Gem 安裝 Compass,輸入指令:`gem install compass` 30 | 31 | #### 3. 修改環境變數 32 | * `我的電腦` > `右鍵` > `內容` > 左邊的`進階系統設定` > `環境變數` 33 | * `點擊兩下` 變數名稱為`PATH` 的 `值`,在原本的設定值後面加上 `;C:\\Ruby23\bin` 34 | * 然後按 `確定`,即可完成囉! 35 | 36 | > 注意喔! C 前面有個分號!!! 37 | > 記得,安裝完以上步驟後,若要使用 compass 的話,記得重開 Compass -------------------------------------------------------------------------------- /cmd/libs/scss/_GinkgoFont.scss: -------------------------------------------------------------------------------- 1 | // 2 | // @author OA Wu 3 | // @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | // @license http://opensource.org/licenses/MIT MIT License 5 | // @link https://www.ioa.tw/ 6 | // 7 | 8 | @function str-last-index($string, $substr) { 9 | $index: null; 10 | $length: str-length($string); 11 | 12 | @for $n from $length through 1 { 13 | $index: str-index(str-slice($string, $n, $length), $substr); 14 | 15 | @if $index { 16 | @return $index + $n - 1; 17 | } 18 | } 19 | 20 | @return $index; 21 | } 22 | 23 | @function font-files($files...) { 24 | $srcs: null; 25 | 26 | $formats: ( 27 | eot: 'embedded-opentype', 28 | woff: 'woff', 29 | ttf: 'truetype', 30 | svg: 'svg', 31 | woff2: 'woff2', 32 | otf: 'opentype', 33 | opentype: 'opentype', 34 | truetype: 'truetype', 35 | ); 36 | 37 | @if(length($files) >= 1) { 38 | $srcs: append($srcs, url(quote(nth($files, 1) + '?' + unique-id()))); 39 | } 40 | 41 | @each $file in $files { 42 | $ext: str-slice($file, str-last-index($file, '.') + 1); 43 | $format: if(map-has-key($formats, $ext), map-get($formats, $ext), ''); 44 | @if ($ext == 'eot') { 45 | $srcs: append($srcs, url(quote($file + '?' + unique-id() + '#iefix')) if($format == '', null, format(quote($format)))); 46 | } @else { 47 | $srcs: append($srcs, url(quote($file + '?' + unique-id())) if($format == '', null, format(quote($format)))); 48 | } 49 | } 50 | 51 | @return $srcs; 52 | } 53 | 54 | @mixin font-face($name, $srcs) { 55 | @font-face { 56 | font-family: quote($name); 57 | src: $srcs; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cmd/libs/node/demo/rollback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = require('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | 16 | const Exec = require('child_process').exec; 17 | 18 | function checkout(_v) { 19 | pp((title = cc(' ➤ ', 'r2') + '分支切換回 ' + cc(_v.gitOri.branch, 'w2') + ' 分支') + cc('… ', 'w0')); 20 | 21 | Exec('git checkout ' + _v.gitOri.branch + ' --quiet', function(err, stdout, stderr) { 22 | if (err) 23 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 24 | 25 | if (stdout.length) 26 | return er(title, ['執行指令 ' + cc('git checkout ' + _v.gitOri.branch + ' --quiet', 'w2') + ' 失敗!']); 27 | 28 | return su(title) && 29 | pp(ln + 30 | cc(' '.repeat(54), 'w0', 'r') + ln + 31 | cc(' 上傳失敗!', 'y2', 'r') + cc('請先檢查以上步驟的錯誤原因,再重新執行! ', 'w2', 'r') + ln + 32 | cc(' '.repeat(54), 'w0', 'r') + ln + ln) && process.exit(1); 33 | }); 34 | } 35 | 36 | function gitClear(_v) { 37 | pp((title = cc(' ➤ ', 'r2') + '清除恢復修改') + cc('… ', 'w0')); 38 | 39 | Exec('git checkout .. --quiet', function(err, stdout, stderr) { 40 | if (err) 41 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 42 | 43 | if (stdout.length) 44 | return er(title, ['執行指令 ' + cc('git checkout .. --quiet', 'w2') + ' 失敗!']); 45 | 46 | return su(title) && checkout(_v); 47 | }); 48 | } 49 | 50 | module.exports.run = function(_v) { 51 | return pp(cc(' 【退回原本分支】', 'R') + ln) && gitClear(_v); 52 | }; -------------------------------------------------------------------------------- /cmd/libs/node/demo/argvs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | module.exports.run = function(argv, _v) { 9 | const argvs = argv.slice(2); 10 | 11 | for(let i = 0; i < argvs.length; i++) { 12 | if (['-g', '--goal'].indexOf(argvs[i].toLowerCase()) !== -1) 13 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-' && ['aws-s3', 'gh-pages'].indexOf(argvs[i + 1].toLowerCase()) !== -1) 14 | _v.cho.goal = argvs[i + 1]; 15 | 16 | if (['-m', '--minify'].indexOf(argvs[i].toLowerCase()) !== -1) 17 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-' && ['0', '1', 'true', 'false'].indexOf(argvs[i + 1].toLowerCase()) !== -1) 18 | _v.cho.minify = argvs[i + 1] === '0' || argvs[i + 1] === 'false' ? false : true; 19 | 20 | if (['-b', '--bucket'].indexOf(argvs[i].toLowerCase()) !== -1) 21 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-') 22 | _v.s3Info.bucket = argvs[i + 1]; 23 | 24 | if (['-a', '--access'].indexOf(argvs[i].toLowerCase()) !== -1) 25 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-') 26 | _v.s3Info.access = argvs[i + 1]; 27 | 28 | if (['-s', '--secret'].indexOf(argvs[i].toLowerCase()) !== -1) 29 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-') 30 | _v.s3Info.secret = argvs[i + 1]; 31 | 32 | if (['-f', '--folder'].indexOf(argvs[i].toLowerCase()) !== -1) 33 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-') 34 | _v.s3Info.folder = argvs[i + 1]; 35 | 36 | if (['-d', '--domain'].indexOf(argvs[i].toLowerCase()) !== -1) 37 | if (typeof argvs[i + 1] !== 'undefined' && argvs[i + 1][0] != '-') 38 | _v.s3Info.domain = argvs[i + 1]; 39 | } 40 | }; -------------------------------------------------------------------------------- /cmd/libs/node/demo/choice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const qu = Ginkgo.qu; 14 | 15 | function minify(_v, closure) { 16 | pp(ln + cc(' 【是否壓縮】', 'y') + ln); 17 | pp(cc(_v.cho.minify === true ? ' ➜' : ' ', 'g1') + cc(' 1. 要!我要將一切壓縮', _v.cho.minify === true ? 'g2' : undefined) + cc(' - Yes, compress them', _v.cho.minify === true ? 'g0' : 'w0') + ln); 18 | pp(cc(_v.cho.minify === false ? ' ➜' : ' ', 'g1') + cc(' 2. 不要,我要保持原樣', _v.cho.minify === false ? 'g2' : undefined) + cc(' - No, keep them original', _v.cho.minify === false ? 'g0' : 'w0') + ln); 19 | 20 | const switcher = function(c) { 21 | _v.cho.minify = c == '1' ? true : false; 22 | return closure(_v.cho); 23 | }; 24 | 25 | if (_v.cho.minify === true || _v.cho.minify === false) { 26 | switcher(_v.cho.minify ? '1' : '2'); 27 | } else { 28 | pp(ln); 29 | qu(['1', '2'], switcher); 30 | } 31 | } 32 | 33 | module.exports.run = function(_v, closure) { 34 | pp(ln + cc(' 【選擇目標】', 'y') + ln); 35 | pp(cc(_v.cho.goal == 'gh-pages' ? ' ➜' : ' ', 'g1') + cc(' 1. GitHub Pages', _v.cho.goal == 'gh-pages' ? 'g2' : undefined) + cc(' - ' + 'gh-pages branch', _v.cho.goal == 'gh-pages' ? 'g0' : 'w0') + ln); 36 | pp(cc(_v.cho.goal == 'aws-s3' ? ' ➜' : ' ', 'g1') + cc(' 2. Amazon S3 ', _v.cho.goal == 'aws-s3' ? 'g2' : undefined) + cc(' - ' + 'Simple Storage Service', _v.cho.goal == 'aws-s3' ? 'g0' : 'w0') + ln); 37 | 38 | const switcher = function(c) { 39 | _v.cho.goal = c == '1' ? 'gh-pages' : 'aws-s3'; 40 | return minify(_v, closure); 41 | }; 42 | 43 | if (['gh-pages', 'aws-s3'].indexOf(_v.cho.goal) !== -1) { 44 | switcher(_v.cho.goal == 'gh-pages' ? '1' : '2'); 45 | } else { 46 | pp(ln); 47 | qu(['1', '2'], switcher); 48 | } 49 | }; -------------------------------------------------------------------------------- /cmd/libs/node/zip/listFiles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | 11 | const cc = Ginkgo.cc; 12 | const ln = Ginkgo.ln; 13 | const pp = Ginkgo.pp; 14 | const pr = Ginkgo.pr; 15 | 16 | const FileSystem = rq('fs'); 17 | const Path = rq('path'); 18 | const root = '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep; 19 | const rootDiv = Path.resolve(__dirname, root) + Path.sep; 20 | const cmdDiv = Path.resolve(__dirname, root + 'cmd') + Path.sep; 21 | const gitDiv = Path.resolve(__dirname, root + '.git') + Path.sep; 22 | 23 | 24 | var mapDir = function(dir, filelist, options) { 25 | const files = FileSystem.readdirSync(dir); 26 | 27 | filelist = filelist || []; 28 | files.forEach(function(file) { 29 | if ([cmdDiv, gitDiv].indexOf(dir + file + Path.sep) !== -1) 30 | return; 31 | 32 | if (!FileSystem.statSync(dir + file).isDirectory()) 33 | if ( 34 | (!options.includes.length || options.includes.indexOf((dir + file).replace(rootDiv, '')) !== -1) && 35 | (!options.formats.length || options.formats.indexOf('.' + file.split('.').pop().toLowerCase()) !== -1) 36 | ) 37 | if ((stats = FileSystem.statSync(dir + file)) && (stats.size > 0)) 38 | return filelist.push(dir + file); 39 | 40 | if (FileSystem.statSync(dir + file).isDirectory() && options.recursive) 41 | filelist = mapDir(dir + file + Path.sep, filelist, options); 42 | }); 43 | 44 | return filelist; 45 | }; 46 | 47 | var localFilesFunc = function(dirs, closure) { 48 | let tmps = dirs.map(function(dir) { return mapDir(dir.path, [], dir); }).reduce(function(a, b) { return a.concat(b); }); 49 | 50 | pr(tmps.length); 51 | 52 | tmps = tmps.map(function(dir) { 53 | pr(); 54 | return { 55 | name: dir.replace(rootDiv, ''), 56 | buffer: FileSystem.readFileSync(dir) 57 | }; 58 | }); 59 | 60 | return pr('') && closure(tmps); 61 | }; 62 | 63 | module.exports.run = function(dirs, closure) { 64 | pr(cc(' ➤ ', 'C') + '整理本機內檔案'); 65 | return localFilesFunc(dirs, closure); 66 | }; -------------------------------------------------------------------------------- /cmd/watch: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('./libs/node/Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | 14 | const Path = require('path'); 15 | const root = '..' + Path.sep; 16 | 17 | const _v = { 18 | font: { 19 | isListen: false, 20 | isReady: false, 21 | timer: 1000, 22 | }, 23 | scss: { 24 | isListen: false, 25 | isReady: false, 26 | timer: 2500, 27 | }, 28 | live: { 29 | isListen: false, 30 | isReady: false, 31 | divs: [] 32 | }, 33 | divs: { 34 | _: Path.resolve(__dirname, root + '') + Path.sep, 35 | cmd: Path.resolve(__dirname, root + 'cmd') + Path.sep, 36 | css: Path.resolve(__dirname, root + 'css') + Path.sep, 37 | font: Path.resolve(__dirname, root + 'font') + Path.sep, 38 | js: Path.resolve(__dirname, root + 'js') + Path.sep, 39 | scss: Path.resolve(__dirname, root + 'scss') + Path.sep, 40 | img: Path.resolve(__dirname, root + 'img') + Path.sep, 41 | } 42 | }; 43 | 44 | _v.live.divs = [ 45 | _v.divs.css, 46 | _v.divs.js, 47 | _v.divs.img, 48 | _v.divs._ + '**' + Path.sep + '*.html' 49 | ]; 50 | 51 | Ginkgo.init('Ginkgo 開發工具', function() { 52 | 53 | pp(cc(' ➤ ', 'C') + Ginkgo.ctrlC() + ln + cc(' 【監控目錄】', 'y') + ln); 54 | 55 | rq('./libs/node/watch/icon').run(_v, function() { 56 | rq('./libs/node/watch/scss').run(_v, function() { 57 | rq('./libs/node/watch/livereload').run(_v, function() { 58 | pp(ln + cc(' 【開發工具狀態】', 'y') + ln); 59 | 60 | if (!(_v.font.isListen || _v.scss.isListen || _v.live.isListen)) 61 | return pp(cc(' ➤ ', 'C') + '開發工具沒有開啟..' + ln); 62 | 63 | pp(cc(' ➤ ', 'C') + 'Watch Icon Fonts' + cc(' ─ ', 'w0') + (_v.font.isListen ? cc('已經啟動', 'g') : cc('尚未啟動', 'r')) + ln); 64 | pp(cc(' ➤ ', 'C') + 'Watch Scss Files' + cc(' ─ ', 'w0') + (_v.scss.isListen ? cc('已經啟動', 'g') : cc('尚未啟動', 'r')) + ln); 65 | pp(cc(' ➤ ', 'C') + 'Watch Livereload' + cc(' ─ ', 'w0') + (_v.live.isListen ? cc('已經啟動', 'g') : cc('尚未啟動', 'r')) + ln); 66 | 67 | pp(ln + cc(' 【完成】', 'y') + ln); 68 | pp(cc(' ➤ ', 'C') + '您可以開始開發囉!' + ln); 69 | pp(cc(' ➤ ', 'C') + Ginkgo.ctrlC()); 70 | 71 | pp(ln + cc(' 【開發紀錄】', 'R') + ln); 72 | 73 | return; 74 | }); 75 | }); 76 | }); 77 | 78 | }); -------------------------------------------------------------------------------- /cmd/libs/node/demo/pushBr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | 16 | const Exec = rq('child_process').exec; 17 | 18 | function pushBr(_v, closure) { 19 | pp((title = cc(' ➤ ', 'C') + '推送分支 ' + cc(_v.cho.goal, 'w2') + ' 至 ' + cc('origin remote', 'w2')) + cc('… ', 'w0')); 20 | 21 | Exec('git push origin ' + _v.cho.goal + ' --force', function(err, stdout, stderr) { 22 | if (err) 23 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 24 | 25 | if (stdout.length) 26 | return er(title, ['執行指令 ' + cc('git push origin ' + _v.cho.goal + ' --force', 'w2') + ' 失敗!']); 27 | 28 | return su(title) && closure(_v.cho.goal); 29 | }); 30 | } 31 | 32 | function commitBr(_v, closure) { 33 | pp((title = cc(' ➤ ', 'C') + '變更紀錄提交 ' + cc(_v.cho.goal, 'w2') + ' 分支') + cc('… ', 'w0')); 34 | 35 | Exec('git commit --message "上傳前壓縮紀錄。" --quiet', function(err, stdout, stderr) { 36 | if (err) 37 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 38 | 39 | if (stdout.length) 40 | return er(title, ['執行指令 ' + cc('git commit --message "上傳前壓縮紀錄。" --quiet', 'w2') + ' 失敗!']); 41 | 42 | su(title); 43 | 44 | return pushBr(_v, closure); 45 | // _v.cho.goal == 'aws-s3' ? 46 | // rq('./putS3').run(_v, function() { return pushBr(_v, closure); }) : 47 | // pushBr(_v, closure); 48 | }); 49 | } 50 | 51 | function addAllBr(_v, closure) { 52 | pp((title = cc(' ➤ ', 'C') + '添加變更檔案 ' + cc(_v.cho.goal, 'w2') + ' 分支') + cc('… ', 'w0')); 53 | 54 | Exec('git add --all', function(err, stdout, stderr) { 55 | if (err) 56 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 57 | 58 | if (stdout.length) 59 | return er(title, ['執行指令 ' + cc('git add --all', 'w2') + ' 失敗!']); 60 | 61 | return su(title) && commitBr(_v, closure); 62 | }); 63 | } 64 | 65 | module.exports.run = function(_v, closure) { 66 | pp((title = cc(' ➤ ', 'C') + '檢查是否變更') + cc('… ', 'w0')); 67 | 68 | Exec('git status --porcelain', function(err, stdout, stderr) { 69 | if (err) 70 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 71 | 72 | su(title); 73 | 74 | if (stdout.length) 75 | return addAllBr(_v, closure); 76 | 77 | return pushBr(_v, closure); 78 | }); 79 | }; -------------------------------------------------------------------------------- /cmd/libs/node/demo/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | const ij = Ginkgo.ij; 16 | 17 | const FileSystem = rq('fs'); 18 | const Path = rq('path'); 19 | const root = '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep; 20 | 21 | const CmdExists = rq('command-exists'); 22 | const Exec = rq('child_process').exec; 23 | let cmds = []; 24 | 25 | var check = function(_v, closure) { 26 | closure(_v.plugins.map(function(plugin) { 27 | plugin.file = Path.resolve(__dirname, root + 'cmd' + Path.sep + 'libs' + Path.sep + 'plugin' + Path.sep + plugin.file); 28 | return plugin; 29 | }).filter(function(plugin) { 30 | return FileSystem.existsSync(plugin.file); 31 | })); 32 | }; 33 | 34 | var exec = function(_v, i, closure) { 35 | pp((title = cc(' ➤ ', 'C') + '執行 ' + cc(_v.plugins[i].title, 'w2') + ' 指令') + cc('… ', 'w0')); 36 | 37 | Exec(_v.plugins[i].cmd + ' ' + _v.plugins[i].file + ' ' + _v.plugins[i].argv, function(err, stdout, stderr) { 38 | if (err) 39 | return er(title, ['錯誤原因:' + cc(err, 'w2'), ij(stdout) ? JSON.parse(stdout) : ('錯誤原因:' + cc(stdout, 'w2'))]) && rq('./rollback').run(_v); 40 | 41 | return su(title) && runPlugin(_v, i + 1, closure); 42 | }); 43 | }; 44 | 45 | var runPlugin = function(_v, i, closure) { 46 | if (i >= _v.plugins.length) 47 | return closure(); 48 | 49 | pp((title = cc(' ➤ ', 'C') + '確認是否可以執行 ' + cc(_v.plugins[i].cmd, 'w2') + ' 指令') + cc('… ', 'w0')); 50 | 51 | if (cmds.indexOf(_v.plugins[i].cmd) !== -1) 52 | return su(title) && exec(_v, i, closure); 53 | 54 | return CmdExists(_v.plugins[i].cmd, function(err, exists) { 55 | if (err) 56 | return er(title, ['錯誤原因:' + cc(err, 'w2')]) && rq('./rollback').run(_v); 57 | 58 | if(!exists) 59 | return er(title, [ 60 | '無法執行 ' + cc(_v.plugins[i].cmd, 'w2') + ' 指令。', 61 | '請確認終端機是否可以執行 ' + _v.plugins[i].cmd + ' 指令。']) && rq('./rollback').run(_v); 62 | 63 | cmds.push(_v.plugins[i].cmd); 64 | 65 | return su(title) && exec(_v, i, closure); 66 | }); 67 | }; 68 | 69 | module.exports.run = function(_v, closure) { 70 | pp((title = cc(' ➤ ', 'C') + '檢驗外掛') + cc('… ', 'w0')); 71 | 72 | return check(_v, function(plugin) { 73 | return su(title) && runPlugin(_v, 0, closure); 74 | }); 75 | }; -------------------------------------------------------------------------------- /cmd/libs/node/watch/livereload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const nt = Ginkgo.nt; 15 | const su = Ginkgo.su; 16 | const qq = Ginkgo.qq; 17 | const wp = Ginkgo.wp; 18 | const Path = rq('path'); 19 | 20 | const port = 35729; 21 | 22 | function openLivereload(_v, closure) { 23 | pp((title = cc(' ➤ ', 'C') + '開啟 LiveReload') + cc('… ', 'w0')); 24 | 25 | const server = rq('livereload').createServer({ 26 | applyCSSLive: false, 27 | applyImgLive: false, 28 | }); 29 | const sendAllClients = server.sendAllClients; 30 | const pattern = new RegExp(qq(_v.divs._)); 31 | 32 | server.sendAllClients = function(data) { 33 | sendAllClients.bind(server)(data); 34 | 35 | return _v.font.isReady && 36 | _v.scss.isReady && 37 | _v.live.isReady && 38 | su(cc(' ➤ ', 'c2') + cc('[Livereload] ', 'y2') + cc('刷新', 'w') + ' ' + cc(JSON.parse(data).path.replace(/\//g, Path.sep).replace(pattern, ''), 'w2')); 39 | }; 40 | 41 | server.watch(_v.live.divs.map(wp)); 42 | 43 | _v.live.isReady = true; 44 | _v.live.isListen = true; 45 | return su(title) 46 | && closure(); 47 | } 48 | 49 | function isPortUsed(port, closure) { 50 | const tester = rq('net').createServer(); 51 | 52 | tester.once('error', function(err) { 53 | return err.code != 'EADDRINUSE' ? 54 | closure(err) : 55 | closure(null, true); 56 | }).once('listening', function() { 57 | return tester.once('close', function() { 58 | closure(null, false); 59 | }).close(); 60 | }).listen(port); 61 | } 62 | 63 | module.exports.run = function(_v, closure) { 64 | pp((title = cc(' ➤ ', 'C') + '檢查是否可以啟用') + cc('… ', 'w0')); 65 | 66 | isPortUsed(port, function(err, used) { 67 | if (err) 68 | return nt('[開啟 LiveReload] 警告!', '啟動 LiveReload 時發生錯誤', '無法執行 LiveReload,不明原因錯誤!\n錯誤原因:' + err) && 69 | er(title, ['錯誤原因:' + cc(err, 'w2'), '未啟動 LiveReload ' + cc('不會影響', 'w2') + '其他功能!']) && 70 | closure(); 71 | 72 | if (used) 73 | return nt('[開啟 LiveReload] 警告!', '啟動 LiveReload 時發生錯誤', '您有其他正在執行 LiveReload 的專案。\n本專案無法執行 LiveReload,但不影響其他功能。') && 74 | er(title, ['有別的專案開啟了 ' + cc('LiveReload', 'w2') + '!', '未啟動 LiveReload ' + cc('不會影響', 'w2') + '其他功能!']) && 75 | closure(); 76 | 77 | return su(title) && 78 | openLivereload(_v, closure); 79 | }); 80 | }; -------------------------------------------------------------------------------- /cmd/libs/node/demo/s3Info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = require('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const qu = Ginkgo.qu; 14 | const Path = rq('path'); 15 | 16 | let ask = false; 17 | let reask = false; 18 | 19 | const titles = { 20 | bucket: 'Bucket Name:', 21 | access: 'Access Key :', 22 | secret: 'Secret Key :', 23 | folder: 'Folder Name:', 24 | domain: 'Domain Url :', 25 | }; 26 | 27 | function askS3(title, d4, closure, space) { 28 | if (reask === false && d4 !== null) { 29 | pp(cc(' ➤ ', 'C') + title + cc(d4, 'w2') + ln); 30 | return closure(d4); 31 | } 32 | 33 | ask = true; 34 | 35 | const readline = rq('readline').createInterface; 36 | const rl = readline({ input: process.stdin, output: process.stdout }); 37 | 38 | rl.question(cc(' ➤ ', 'C') + title, function(input) { 39 | rl.close(); 40 | input = input.trim(); 41 | return space || input.length ? closure(input) : askS3(title, d4, closure, space); 42 | }); 43 | 44 | if (d4 !== null) 45 | rl.write(d4); 46 | else if (title == titles.folder) 47 | rl.write(Path.resolve(__dirname, '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep).split(Path.sep).pop()); 48 | else; 49 | } 50 | 51 | function check(_v, closure) { 52 | pp(ln + cc(' 【以上是否正確】', 'y') + ln); 53 | pp(cc(' ', 'g1') + cc(' 1. 是的,資訊沒錯', undefined) + cc(' - Yes, the information is correct', 'w0') + ln); 54 | pp(cc(' ', 'g1') + cc(' 2. 不對,我要重寫', undefined) + cc(' - No, the information should be rewritten', 'w0') + ln); 55 | pp(ln); 56 | 57 | qu(['1', '2'], function(cho) { 58 | return cho == '2' && (reask = true) ? 59 | info(_v, closure) : 60 | closure(); 61 | }); 62 | } 63 | 64 | var info = function(_v, closure) { 65 | if (reask) 66 | pp(ln + cc(' 【請重新輸入 S3 設定】', 'y') + ln); 67 | 68 | askS3(titles.bucket, _v.s3Info.bucket, function(input) { 69 | _v.s3Info.bucket = input; 70 | 71 | askS3(titles.access, _v.s3Info.access, function(input) { 72 | _v.s3Info.access = input; 73 | 74 | askS3(titles.secret, _v.s3Info.secret, function(input) { 75 | _v.s3Info.secret = input; 76 | 77 | askS3(titles.folder, _v.s3Info.folder, function(input) { 78 | _v.s3Info.folder = input; 79 | 80 | askS3(titles.domain, _v.s3Info.domain, function(input) { 81 | _v.s3Info.domain = input; 82 | 83 | ask ? check(_v, closure) : closure(); 84 | 85 | }, false); 86 | }, true); 87 | }, false); 88 | }, false); 89 | }, false); 90 | }; 91 | 92 | module.exports = { 93 | run: info 94 | }; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 直播地圖 - 全台灣最即時的影像紀錄

GitHub 專案

這是一個將全台灣各地的 Youtube 直播集合起來的網站,並且採用 Google Maps 方式將他們呈現出來!

目前資料是儲存在「Google 試算表」上面,所以不需要後端維護XD

有興趣的夥伴,可以按下右上角的「我要新增」來增加新景點喔,如果資訊有誤或者不正確,也歡迎在「備註欄位」告知一下!

作者GitHub
-------------------------------------------------------------------------------- /cmd/libs/node/demo/newBr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | 16 | const Exec = rq('child_process').exec; 17 | 18 | function checkoutBr(_v, closure) { 19 | pp((title = cc(' ➤ ', 'C') + '分支切換至 ' + cc(_v.cho.goal, 'w2') + ' 分支') + cc('… ', 'w0')); 20 | 21 | Exec('git checkout ' + _v.cho.goal + ' --quiet', function(err, stdout, stderr) { 22 | if (err) 23 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 24 | 25 | if (stdout.length) 26 | return er(title, ['執行指令 ' + cc('git checkout ' + _v.cho.goal + ' --quiet', 'w2') + ' 失敗!']); 27 | 28 | return su(title) && closure(_v.cho); 29 | }); 30 | } 31 | 32 | function createBr(_v, closure) { 33 | pp((title = cc(' ➤ ', 'C') + '新增本地端 ' + cc(_v.cho.goal, 'w2') + ' 分支') + cc('… ', 'w0')); 34 | 35 | Exec('git branch --verbose ' + _v.cho.goal, function(err, stdout, stderr) { 36 | if (err) 37 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 38 | 39 | if (stdout.length) 40 | return er(title, ['執行指令 ' + cc('git branch --verbose ' + _v.cho.goal, 'w2') + ' 失敗!']); 41 | 42 | return su(title) && checkoutBr(_v, closure); 43 | }); 44 | } 45 | 46 | function deleteBr(_v, closure) { 47 | pp((title = cc(' ➤ ', 'C') + '刪除本地端 ' + cc(_v.cho.goal, 'w2') + ' 分支') + cc('… ', 'w0')); 48 | 49 | Exec('git branch --delete --force ' + _v.cho.goal, function(err, stdout, stderr) { 50 | if (err) 51 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 52 | 53 | if (!stdout.length) 54 | return er(title, ['執行指令 ' + cc('git branch --delete --force ' + _v.cho.goal, 'w2') + ' 失敗!']); 55 | 56 | return su(title) && createBr(_v, closure); 57 | }); 58 | } 59 | 60 | function setGitOriBr(branches, _v, stdout) { 61 | _v.gitOri.branch = branches.filter(function(t) { return t.match(/^\*\s+/g); }); 62 | 63 | if (!_v.gitOri.branch.length) 64 | return true; 65 | 66 | _v.gitOri.branch = _v.gitOri.branch.shift().replace(/^\*\s+/g, ''); 67 | 68 | return !_v.gitOri.branch.length; 69 | } 70 | 71 | module.exports.run = function(_v, closure) { 72 | pp((title = cc(' ➤ ', 'C') + '檢查本地端 ' + cc(_v.cho.goal, 'w2') + ' 分支') + cc('… ', 'w0')); 73 | 74 | Exec('git branch --list', function(err, stdout, stderr) { 75 | if (err) 76 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 77 | 78 | if (!stdout.length) 79 | return er(title, ['執行指令 ' + cc('git branch --list', 'w2') + ' 失敗!']); 80 | 81 | const branches = stdout.split(ln).map(Function.prototype.call, String.prototype.trim).filter(function(t) { return t !== ''; }); 82 | 83 | if (setGitOriBr(branches, _v, stdout)) 84 | return er(title, ['錯誤原因:' + cc('取不到目前的分支名稱', 'w2')]); 85 | 86 | su(title); 87 | 88 | return branches.indexOf(_v.cho.goal) === -1 ? 89 | createBr(_v, closure) : 90 | deleteBr(_v, closure); 91 | }); 92 | } -------------------------------------------------------------------------------- /cmd/libs/node/zip/dirsInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | const qq = Ginkgo.qq; 16 | 17 | const jsYaml = rq('js-yaml'); 18 | const FileSystem = rq('fs'); 19 | 20 | const Path = rq('path'); 21 | const root = '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep; 22 | const dirsFilePath = Path.resolve(__dirname, root + 'cmd' + Path.sep + '_dirs.yaml'); 23 | const sepRegExp = new RegExp(qq('^' + Path.sep.replace('/', '\/') + '*|' + Path.sep.replace('/', '\/') + '*$'), 'g'); 24 | const rootDiv = Path.resolve(__dirname, root) + Path.sep; 25 | 26 | var check = function(closure) { 27 | pp((title = cc(' ➤ ', 'C') + '檢查 ' + cc('_dirs.yaml', 'w2') + ' 是否存在') + cc('… ', 'w0')); 28 | 29 | FileSystem.exists(dirsFilePath, function(exists) { 30 | return !exists ? 31 | er(title, ['錯誤原因:' + cc('cmd/_dirs.yaml', 'w2') + ' 不存在!']) : 32 | su(title) && closure(); 33 | }); 34 | }; 35 | 36 | var read = function(closure) { 37 | pp((title = cc(' ➤ ', 'C') + '讀取 ' + cc('_dirs.yaml', 'w2') + ' 檔案內容') + cc('… ', 'w0')); 38 | FileSystem.readFile('_dirs.yaml', 'utf8', function(err, data) { 39 | return err ? 40 | er(title, ['錯誤原因:' + cc(err, 'w2')]) : 41 | su(title) && closure(data); 42 | }); 43 | }; 44 | 45 | var yaml = function(data, closure) { 46 | pp((title = cc(' ➤ ', 'C') + '轉換 ' + cc('_dirs.yaml', 'w2') + ' 檔案內容') + cc('… ', 'w0')); 47 | 48 | try { 49 | data = jsYaml.safeLoad(data); 50 | data = Array.isArray(data) ? data : []; 51 | data = data.map(function(t) { 52 | if (typeof t.path === 'undefined') 53 | return null; 54 | 55 | t.path = t.path.replace(sepRegExp, ''); 56 | t.path = Path.resolve(__dirname, root + t.path.replace(sepRegExp, '')) + Path.sep; 57 | 58 | if (!FileSystem.existsSync(t.path)) 59 | return null; 60 | 61 | t = Object.assign({formats: [], recursive: false, hidden: false, includes: []}, t); 62 | t.formats = Array.isArray(t.formats) ? t.formats : []; 63 | t.includes = Array.isArray(t.includes) ? t.includes : []; 64 | t.recursive = t.recursive.toLowerCase() === 'yes' ? true: false; 65 | t.hidden = t.hidden.toLowerCase() === 'yes' ? true: false; 66 | t.includes = t.includes.map(function(t) { return t.replace(sepRegExp, ''); }); 67 | t.formats = t.formats.map(function(t) { return t.toLowerCase(); }); 68 | 69 | return t; 70 | }).filter(function(t) { 71 | return t !== null; 72 | }); 73 | return su(title) && closure(data); 74 | } catch (err) { 75 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 76 | } 77 | }; 78 | 79 | module.exports.run = function(closure) { 80 | return check(function() { 81 | read(function(data) { 82 | yaml(data, function(data) { 83 | return closure(data); 84 | }); 85 | }); 86 | }); 87 | }; -------------------------------------------------------------------------------- /cmd/libs/node/demo/dirsInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | const qq = Ginkgo.qq; 16 | 17 | const jsYaml = rq('js-yaml'); 18 | const FileSystem = rq('fs'); 19 | 20 | const Path = rq('path'); 21 | const root = '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep; 22 | const dirsFilePath = Path.resolve(__dirname, root + 'cmd' + Path.sep + '_dirs.yaml'); 23 | const sepRegExp = new RegExp(qq('^' + Path.sep.replace('/', '\/') + '*|' + Path.sep.replace('/', '\/') + '*$'), 'g'); 24 | const rootDiv = Path.resolve(__dirname, root) + Path.sep; 25 | 26 | var check = function(_v, closure) { 27 | pp((title = cc(' ➤ ', 'C') + '檢查 ' + cc('_dirs.yaml', 'w2') + ' 是否存在') + cc('… ', 'w0')); 28 | 29 | FileSystem.exists(dirsFilePath, function(exists) { 30 | return !exists ? 31 | er(title, ['錯誤原因:' + cc('cmd/_dirs.yaml', 'w2') + ' 不存在!']) && rq('./rollback').run(_v) : 32 | su(title) && closure(); 33 | }); 34 | }; 35 | 36 | var read = function(_v, closure) { 37 | pp((title = cc(' ➤ ', 'C') + '讀取 ' + cc('_dirs.yaml', 'w2') + ' 檔案內容') + cc('… ', 'w0')); 38 | FileSystem.readFile('_dirs.yaml', 'utf8', function(err, data) { 39 | return err ? 40 | er(title, ['錯誤原因:' + cc(err, 'w2')]) && rq('./rollback').run(_v) : 41 | su(title) && closure(data); 42 | }); 43 | }; 44 | 45 | var yaml = function(data, closure) { 46 | pp((title = cc(' ➤ ', 'C') + '轉換 ' + cc('_dirs.yaml', 'w2') + ' 檔案內容') + cc('… ', 'w0')); 47 | 48 | try { 49 | data = jsYaml.safeLoad(data); 50 | data = Array.isArray(data) ? data : []; 51 | data = data.map(function(t) { 52 | if (typeof t.path === 'undefined') 53 | return null; 54 | 55 | t.path = t.path.replace(sepRegExp, ''); 56 | t.path = Path.resolve(__dirname, root + t.path.replace(sepRegExp, '')) + Path.sep; 57 | 58 | if (!FileSystem.existsSync(t.path)) 59 | return null; 60 | 61 | t = Object.assign({formats: [], recursive: false, hidden: false, includes: []}, t); 62 | t.formats = Array.isArray(t.formats) ? t.formats : []; 63 | t.includes = Array.isArray(t.includes) ? t.includes : []; 64 | t.recursive = t.recursive.toLowerCase() === 'yes' ? true: false; 65 | t.hidden = t.hidden.toLowerCase() === 'yes' ? true: false; 66 | t.includes = t.includes.map(function(t) { return t.replace(sepRegExp, ''); }); 67 | t.formats = t.formats.map(function(t) { return t.toLowerCase(); }); 68 | 69 | return t; 70 | }).filter(function(t) { 71 | return t !== null; 72 | }); 73 | return su(title) && closure(data); 74 | } catch (err) { 75 | return er(title, ['錯誤原因:' + cc(err, 'w2')]) && rq('./rollback').run(_v); 76 | } 77 | }; 78 | 79 | module.exports.run = function(_v, closure) { 80 | return check(_v, function() { 81 | read(_v, function(data) { 82 | yaml(data, function(data) { 83 | return closure(_v.dirs = data); 84 | }); 85 | }); 86 | }); 87 | }; -------------------------------------------------------------------------------- /cmd/demo: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('./libs/node/Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | 14 | 15 | const _v = { 16 | cho: { 17 | goal: null, 18 | minify: null, 19 | }, 20 | s3Info: rq('fs').existsSync('./libs/node/demo/config.js') ? rq('./libs/node/demo/config').s3Info() : { 21 | bucket: null, 22 | access: null, 23 | secret: null, 24 | folder: null, 25 | domain: null, 26 | }, 27 | gitOri: { 28 | url: [], 29 | branch: null, 30 | }, 31 | dirs: [], 32 | plugins: [ 33 | // {title: '轉換1', cmd: 'php', file: 'cover.php', argv: ''}, 34 | ] 35 | }; 36 | 37 | Ginkgo.init('Ginkgo 上傳工具', function() { 38 | 39 | function finish() { 40 | rq('./libs/node/demo/finish').run(_v, function() { 41 | pp(cc(' ➤ ', 'C') + '趕緊去看最新版的吧!' + ln); 42 | 43 | if (_v.cho.goal == 'gh-pages' && _v.gitOri.url.length == 2) 44 | pp(cc(' ➤ ', 'C') + '因為快取問題,請稍待 ' + cc('1 分鐘', 'w2') + ' 後再重新整理頁面。' + ln + 45 | cc(' ➤ ', 'C') + '網址:' + cc('https://' + _v.gitOri.url[0] + '.github.io/' + _v.gitOri.url[1] + '/', 'b2', undefined, 'underline') + ln); 46 | if (_v.cho.goal == 'aws-s3') 47 | pp(cc(' ➤ ', 'C') + '若有設定 CDN 快取的話,請等 Timeout 後再重新整理頁面。' + ln + 48 | cc(' ➤ ', 'C') + '網址:' + cc('https://' + _v.s3Info.domain.replace(new RegExp('^\/*|\/*$', 'g'), '') + '/' + (_v.s3Info.folder.length ? _v.s3Info.folder + '/' : ''), 'b2', undefined, 'underline') + ln); 49 | 50 | pp(ln + ln); 51 | }); 52 | } 53 | 54 | function minifyFiles() { 55 | rq('./libs/node/demo/minify').run(_v, upload); 56 | } 57 | 58 | function pushNewBr() { 59 | rq('./libs/node/demo/pushBr').run(_v, function(goal) { 60 | return pp(ln + cc(' 【上傳完成】', 'R') + ln) && finish(); 61 | }); 62 | } 63 | 64 | function upload() { 65 | const tmp = function() { 66 | return pp(ln + cc(' 【記錄與更新】', 'y') + ln) && pushNewBr(); 67 | }; 68 | 69 | return _v.cho.goal == 'aws-s3' ? 70 | rq('./libs/node/demo/putS3').run(_v, tmp) : 71 | tmp(); 72 | } 73 | 74 | function plugin() { 75 | rq('./libs/node/demo/plugin').run(_v, function() { 76 | return _v.cho.minify ? 77 | pp(ln + cc(' 【壓縮檔案】', 'y') + ln) && minifyFiles() : 78 | upload(); 79 | }); 80 | } 81 | 82 | function newBranch() { 83 | return rq('./libs/node/demo/newBr').run(_v, function(cho) { 84 | return pp(ln + cc(' 【執行外掛】', 'y') + ln) && plugin(); 85 | }); 86 | } 87 | 88 | function checkEnv() { 89 | rq('./libs/node/demo/check').run(_v, function(cho) { 90 | return pp(ln + cc(' 【設定分支】', 'y') + ln) && newBranch(); 91 | }); 92 | } 93 | 94 | function s3Info() { 95 | rq('./libs/node/demo/s3Info').run(_v, function(cho) { 96 | return pp(ln + cc(' 【檢查環境】', 'y') + ln) && checkEnv(); 97 | }); 98 | } 99 | 100 | pp(cc(' ➤ ', 'C') + '注意喔,過程中請勿隨意結束!' + ln + cc(' ➤ ', 'C') + Ginkgo.ctrlC()); 101 | 102 | rq('./libs/node/demo/argvs').run(process.argv, _v); 103 | 104 | rq('./libs/node/demo/choice').run(_v, function(cho) { 105 | return cho.goal == 'gh-pages' ? 106 | pp(ln + cc(' 【檢查環境】', 'y') + ln) && checkEnv() : 107 | pp(ln + cc(' 【S3 設定】', 'y') + ln) && s3Info(); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /cmd/libs/node/demo/check.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const su = Ginkgo.su; 15 | 16 | const CmdExists = rq('command-exists'); 17 | const Exec = rq('child_process').exec; 18 | 19 | function checkPhpCmd(_v, closure) { 20 | pp((title = cc(' ➤ ', 'C') + '檢查專案是否有 ' + cc('PHP', 'w2') + ' 指令') + cc('… ', 'w0')); 21 | 22 | CmdExists('php', function(err, exists) { 23 | if (err) 24 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 25 | 26 | if(!exists) 27 | return er(title, [ 28 | '無法執行 ' + cc('PHP', 'w2') + ' 指令。', 29 | '請確認終端機是否可以執行 PHP 指令。']); 30 | 31 | return su(title) && closure(_v.cho); 32 | }); 33 | } 34 | 35 | function setGitOriUrl(_v, stdout) { 36 | _v.gitOri.url = []; 37 | if (stdout.match(/^git@github\.com:(.*)\/(.*)\.git/gi)) { 38 | stdout = stdout.split(/^git@github\.com:(.*)\/(.*)\.git/g).map(Function.prototype.call, String.prototype.trim).filter(function(t) { return t !== ''; }) 39 | if (stdout.length == 2) 40 | _v.gitOri.url = stdout; 41 | } else if (stdout.match(/^https:\/\/github\.com\/.*\/.*\.git/gi)) { 42 | stdout = stdout.split(/^https:\/\/github\.com\/(.*)\/(.*)\.git/g).map(Function.prototype.call, String.prototype.trim).filter(function(t) { return t !== ''; }) 43 | if (stdout.length == 2) 44 | _v.gitOri.url = stdout; 45 | } 46 | 47 | return !_v.gitOri.url.length; 48 | } 49 | function checkRemoteOriginUrl(_v, closure) { 50 | pp((title = cc(' ➤ ', 'C') + '檢查專案是否為 ' + cc('GitHub', 'w2') + ' 的專案') + cc('… ', 'w0')); 51 | 52 | Exec('git remote get-url origin', function(err, stdout, stderr) { 53 | if (err) 54 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 55 | 56 | if (!stdout.length) 57 | return er(title, ['執行指令 ' + cc('git remote get-url origin', 'w2') + ' 失敗!']); 58 | 59 | if (setGitOriUrl(_v, stdout)) 60 | return er(title, [ 61 | '找不到你的 ' + cc('origin remote url', 'w2'), 62 | '請確認專案內有 ' + cc('origin', 'w2') + ' remote', 63 | '以及其 url 為 ' + cc('git@github.com', 'w2') + ' 或 ' + cc('https://github.com/', 'w2') + ' 開頭!']); 64 | 65 | return su(title) && closure(_v.cho); 66 | }); 67 | } 68 | 69 | function checkGitStatus(_v, closure) { 70 | pp((title = cc(' ➤ ', 'C') + '檢查專案狀態是否已經 ' + cc('Commit', 'w2')) + cc('… ', 'w0')); 71 | 72 | Exec('git status --porcelain', function(err, stdout, stderr) { 73 | if (err) 74 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 75 | 76 | if (stdout.length) 77 | return er(title, [ 78 | '此專案尚未做 Git Commit。', 79 | '請檢查哪些檔案有變更但尚未紀錄。', 80 | '建議先執行以下指令:', [ 81 | '紀錄變更,指令:' + cc('git add --all', 'w2'), 82 | '再做提交,指令:' + cc('git commit --message "你的訊息"', 'w2')]]); 83 | 84 | su(title); 85 | 86 | return _v.cho.goal == 'gh-pages' ? 87 | checkRemoteOriginUrl(_v, closure) : 88 | su(title) && closure(_v.cho); 89 | }); 90 | } 91 | 92 | module.exports.run = function(_v, closure) { 93 | pp((title = cc(' ➤ ', 'C') + '檢查專案是否有 ' + cc('Git', 'w2') + ' 指令') + cc('… ', 'w0')); 94 | 95 | CmdExists('git', function(err, exists) { 96 | if (err) 97 | return er(title, ['錯誤原因:' + cc(err, 'w2')]); 98 | 99 | if(!exists) 100 | return er(title, [ 101 | '無法執行 ' + cc('Git', 'w2') + ' 指令。', 102 | '請確認終端機是否可以執行 git 指令。']); 103 | 104 | return su(title) && 105 | checkGitStatus(_v, closure); 106 | }); 107 | }; -------------------------------------------------------------------------------- /cmd/libs/scss/_GinkgoMixin.scss: -------------------------------------------------------------------------------- 1 | // 2 | // @author OA Wu 3 | // @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | // @license http://opensource.org/licenses/MIT MIT License 5 | // @link https://www.ioa.tw/ 6 | // 7 | 8 | @import "compass/css3/box-sizing"; 9 | @import "compass/css3/selection"; 10 | @import "compass/css3/box-shadow"; 11 | @import "compass/css3/text-shadow"; 12 | @import "compass/css3/border-radius"; 13 | @import "compass/css3/animation"; 14 | @import "compass/css3/transform"; 15 | @import "compass/css3/opacity"; 16 | @import "compass/css3/images"; 17 | @import "compass/css3/user-interface"; 18 | @import "compass/css3/transition"; 19 | @import "compass/css3/background-size"; 20 | @import "compass/css3/filter"; 21 | @import "compass/css3/background-clip"; 22 | @import "compass/css3/appearance"; 23 | @import "compass/utilities/color/contrast"; 24 | @import "compass/css3/hyphenation"; 25 | @import "compass/css3/user-interface"; 26 | 27 | @mixin range-width($min: 0, $max: 0) { 28 | @if $max == 0 { 29 | @media screen and (min-width: $min) { 30 | @content; 31 | } 32 | } @else { 33 | @media screen and (max-width: $max - 1px) and (min-width: $min) { 34 | @content; 35 | } 36 | } 37 | } 38 | 39 | @mixin range-height($min: 0, $max: 0) { 40 | @if $max == 0 { 41 | @media screen and (min-height: $min) { 42 | @content; 43 | } 44 | } @else { 45 | @media screen and (max-height: $max - 1px) and (min-height: $min) { 46 | @content; 47 | } 48 | } 49 | } 50 | 51 | @mixin font-smoothing($val: antialiased) { 52 | -moz-osx-font-smoothing: $val; 53 | -webkit-font-smoothing: $val; 54 | -moz-font-smoothing: $val; 55 | -ms-font-smoothing: $val; 56 | -o-font-smoothing: $val; 57 | } 58 | 59 | @mixin overflow-docx3() { 60 | overflow: hidden; 61 | text-overflow: ellipsis; 62 | white-space: nowrap; 63 | } 64 | 65 | @mixin clearfix() { 66 | *zoom: 1; 67 | 68 | &:after { 69 | display: table; 70 | content: ""; 71 | line-height: 0; 72 | clear: both; 73 | } 74 | } 75 | 76 | @mixin cursor-zoomIn { 77 | cursor: url("data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAcABwAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAACIAAABCAAAAhAAAAwgAAPwQAAOHIAAGMaAABEiAAAzMwAAJAkAACQJAAAzMwAAESIAABjGAAAOHAAAA/AAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////j////wf///4H///8D//8AB//8AA//+AAf//BIH//xhj//4QIf/+IBH//iAR//4QIf//GGP//wSD//+AB///wA////A////////8="), all-scroll; 78 | cursor: -webkit-zoom-in; 79 | cursor: -moz-zoom-in; 80 | cursor: -o-zoom-in; 81 | cursor: -ms-zoom-in; 82 | cursor: zoom-in; 83 | } 84 | 85 | @mixin cursor-grab { 86 | // http://www.google.com/intl/en_ALL/mapfiles/openhand.cur 87 | cursor: url('data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAcABQAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAA/AAAAfwAAAP+AAAH/gAAB/8AAA//AAAd/wAAGf+AAAH9gAADbYAAA2yAAAZsAAAGbAAAAGAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////gH///4B///8Af//+AD///AA///wAH//4AB//8AAf//AAD//5AA///gAP//4AD//8AF///AB///5A////5///8='), all-scroll; 88 | cursor: -webkit-grab; 89 | cursor: -moz-grab; 90 | cursor: -o-grab; 91 | cursor: -ms-grab; 92 | cursor: grab; 93 | } 94 | 95 | @mixin cursor-grabbing { 96 | // http://www.google.com/intl/en_ALL/mapfiles/closedhand.cur 97 | cursor: url('data:image/vnd.microsoft.icon;base64,AAACAAEAICACAAcABQAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAA/AAAAfwAAAP+AAAH/gAAB/8AAAH/AAAB/wAAA/0AAANsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////////////////////////////////////////////////////////gH///4B///8Af//+AD///AA///wAH//+AB///wAf//4AH//+AD///yT/////////////////////////////8='), all-scroll; 98 | cursor: -webkit-grabbing; 99 | cursor: -moz-grabbing; 100 | cursor: -o-grabbing; 101 | cursor: -ms-grabbing; 102 | cursor: grabbing; 103 | } -------------------------------------------------------------------------------- /cmd/libs/node/watch/scss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const nt = Ginkgo.nt; 15 | const su = Ginkgo.su; 16 | const wp = Ginkgo.wp; 17 | 18 | const Exec = rq('child_process').exec; 19 | const CmdExists = rq('command-exists');; 20 | const Chokidar = rq('chokidar'); 21 | const FileSystem = rq('fs'); 22 | const Path = rq('path'); 23 | 24 | function compileScss(_v, event, path) { 25 | const css = path.replace(_v.divs.scss, _v.divs.css).replace(/\.scss$/, '.css'); 26 | let title = cc(' ➤ ', 'c2') + cc('[Scss Files] ', 'b2') + cc(event, 'w') + ' '; 27 | 28 | if (event == '刪除') 29 | return FileSystem.exists(css, function(exists) { 30 | 31 | if (!exists) 32 | return _v.scss.isReady && 33 | su(title); 34 | 35 | FileSystem.unlink(css, function(err) { 36 | if (err) 37 | return _v.scss.isReady && 38 | notifier('[Scss Files] 錯誤!', '檔案無法刪除', '請至終端機確認錯誤原因!') && 39 | er(title, ['錯誤原因:' + cc(err, 'w2')]); 40 | 41 | return _v.scss.isReady && 42 | su(title); 43 | }); 44 | }); 45 | 46 | Exec('compass compile', function(err, stdout, stderr) { 47 | stdout = stdout.replace(/\x1b[[][^A-Za-z]*[A-Za-z]/g, '').split(/\s/).map(Function.prototype.call, String.prototype.trim).filter(function(t) { return t !== ''; }); 48 | 49 | if (!stdout.length) 50 | return; 51 | 52 | const action = stdout.shift(); 53 | const file = stdout.shift(); 54 | 55 | if (action == 'write') 56 | return _v.scss.isReady && 57 | su(title + cc(file.replace(/\//g, Path.sep).replace(_v.divs.css, ''), 'w2')); 58 | 59 | if (action == 'error') { 60 | 61 | stdout = /\(Line\s*(\d+):\s*(.*)\)/g.exec(stdout.join(' ')); 62 | title = title + cc(file.replace(/\//g, Path.sep).replace(_v.divs.scss, ''), 'w2') + cc(' ─ ', 'w0') + cc('失敗', 'r'); 63 | 64 | if (!Array.isArray(stdout)) 65 | return _v.scss.isReady && 66 | nt('[Scss Files] 錯誤!', '編譯 SCSS 檔案發生錯誤', '請至終端機確認錯誤原因!') && 67 | er(title, ['錯誤原因:' + cc(stdout.join(' '), 'w2')]); 68 | 69 | title += stdout.length >= 2 ? cc(' ─ ', 'w0') + '在第 ' + cc(stdout[1], 'y2') + ' 行左右' : ''; 70 | title += stdout.length >= 3 ? '\n ' + cc(' ◎ ', 'p2') + '錯誤原因:' + cc(stdout[2], 'w2') + ln : ''; 71 | 72 | return _v.scss.isReady && 73 | nt('[Scss Files] 錯誤!', '編譯 SCSS 檔案發生錯誤', '請至終端機確認錯誤原因!') && 74 | pp(title + ln); 75 | } 76 | 77 | if (action == 'delete') 78 | return; 79 | }); 80 | } 81 | 82 | module.exports.run = function(_v, closure) { 83 | pp((title = cc(' ➤ ', 'C') + '監控 Scss 檔案') + cc('… ', 'w0')); 84 | 85 | CmdExists('compass', function(err, exists) { 86 | if (err) 87 | return nt('[監控 Scss 檔案] 錯誤!', '偵測不到 Compass 指令', '請至終端機確認錯誤原因!') && 88 | er(title, ['錯誤原因:' + cc(err, 'w2')]) && 89 | closure(); 90 | 91 | if(!exists) 92 | return nt('[監控 Scss 檔案] 警告!', '不存在 Compass 指令', '請至終端機確認錯誤原因!') && 93 | er(title, ['無法執行 ' + cc('compass', 'w2') + ' 指令。', '請確認終端機是否可以執行 compass 指令。']) && 94 | closure(); 95 | 96 | let timer = null; 97 | 98 | Chokidar.watch(wp(_v.divs.scss + '**' + Path.sep + '*.scss')) 99 | .on('change', function(path) { clearTimeout(timer); timer = setTimeout(compileScss.bind(null, _v, '修改', path), 250); }) 100 | .on('add', function(path) { clearTimeout(timer); timer = setTimeout(compileScss.bind(null, _v, '新增', path), 250); }) 101 | .on('unlink', function(path) { setTimeout(compileScss.bind(null, _v, '刪除', path), 250); }) 102 | .on('error', function(err) { 103 | return nt('[監控 Scss 檔案] 警告!', '監控 Scss 檔案發生錯誤', '請至終端機確認錯誤原因!') && 104 | er(title, ['錯誤原因:' + cc('err', 'w2')]) && 105 | closure(); 106 | }) 107 | .on('ready', function() { 108 | _v.scss.isListen = true; 109 | setTimeout(function() { _v.scss.isReady = true; }, _v.scss.timer); 110 | return su(title) && 111 | closure(); 112 | }); 113 | }); 114 | }; -------------------------------------------------------------------------------- /font/icomoon/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /cmd/libs/node/watch/icon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const cc = Ginkgo.cc; 11 | const ln = Ginkgo.ln; 12 | const pp = Ginkgo.pp; 13 | const er = Ginkgo.er; 14 | const nt = Ginkgo.nt; 15 | const su = Ginkgo.su; 16 | const wp = Ginkgo.wp; 17 | 18 | const Chokidar = rq('chokidar'); 19 | const FileSystem = rq('fs'); 20 | const Path = rq('path'); 21 | 22 | function parseData(name, data) { 23 | data = data.match(/\.icon-[a-zA-Z_\-0-9]*:before\s?\{\s*content:\s*"[\\A-Za-z0-9]*";(\s*color:\s*#[A-Za-z0-9]*;)?\s*}/g); 24 | data = Array.isArray(data) ? data.map(function(v) { return v.replace(/^\.icon-/g, '.icon-' + (name ? name + '-' : '')).replace(/\n/g, ' ').replace(/\{\s*/g, '{ '); }) : []; 25 | data = '//\n// @author OA Wu \n// @copyright Copyright (c) 2015 - 2019, Ginkgo\n// @license http://opensource.org/licenses/MIT MIT License\n// @link https://www.ioa.tw/\n//\n\n' + (data.length ? '@import "compass/css3/font-face";\n\n@include font-face("icon' + (name ? '-' + name : '') + '", font-files(\n "' + (name ? name : 'icomoon') + '/fonts/icomoon.eot",\n "' + (name ? name : 'icomoon') + '/fonts/icomoon.woff",\n "' + (name ? name : 'icomoon') + '/fonts/icomoon.ttf",\n "' + (name ? name : 'icomoon') + '/fonts/icomoon.svg"));\n\n*[class^="icon' + (name ? '-' + name : '') +'-"]:before, *[class*=" icon' + (name ? '-' + name : '') +'-"]:before {\n font-family: "icon' + (name ? '-' + name : '') + '";\n\n speak: none;\n font-style: normal;\n font-weight: normal;\n font-variant: normal;\n}\n\n' + data.join(ln) : '@import "compass/css3/font-face";'); 26 | return data; 27 | } 28 | 29 | function build(_v, event, path, dir) { 30 | const name = dir === 'icomoon' ? false : dir; 31 | const title = cc(' ➤ ', 'c2') + cc('[Icon Fonts] ', 'P') + cc(event, 'w') + ' ' + cc(dir, 'w2') + ' 目錄'; 32 | 33 | if (event == '刪除') { 34 | return FileSystem.exists(path = Path.resolve(__dirname, _v.divs.scss + 'icon' + (name ? '-' + name : '') + '.scss'), function(exists) { 35 | if (!exists) 36 | return _v.font.isReady && 37 | su(title); 38 | 39 | FileSystem.unlink(path, function(err) { 40 | if (err) 41 | return _v.font.isReady && 42 | nt('[Icon Fonts] 錯誤!', '檔案無法刪除', '請至終端機確認錯誤原因!') && 43 | er(title, ['錯誤原因:' + cc(err, 'w2')]); 44 | 45 | return _v.font.isReady && 46 | su(title); 47 | }); 48 | }); 49 | } 50 | 51 | FileSystem.readFile(path, 'utf8', function(err, data) { 52 | if (err) 53 | return _v.font.isReady && 54 | nt('[Icon Fonts] 錯誤!', '檔案無法讀取', '請至終端機確認錯誤原因!') && 55 | er(title, ['錯誤原因:' + cc(err, 'w2')]); 56 | 57 | FileSystem.writeFile(Path.resolve(__dirname, _v.divs.scss + 'icon' + (name ? '-' + name : '') + '.scss'), parseData(name,data), function(err) { 58 | if (err) 59 | return _v.font.isReady && 60 | nt('[Icon Fonts] 錯誤!', '檔案無法寫入', '請至終端機確認錯誤原因!') && 61 | er(title, ['錯誤原因:' + cc(err, 'w2')]); 62 | 63 | return _v.font.isReady && 64 | su(title); 65 | }); 66 | }); 67 | } 68 | module.exports.run = function(_v, closure) { 69 | pp((title = cc(' ➤ ', 'C') + '監控 Font 目錄') + cc('… ', 'w0')); 70 | 71 | Chokidar.watch(wp(_v.divs.font)) 72 | .on('change', function(path) { 73 | const token = path.replace(_v.divs.font, '').split(Path.sep).map(Function.prototype.call, String.prototype.trim).filter(function(v) { return v.length; }); 74 | return token.length == 2 && token[1] == 'style.css' && build(_v, '修改', path, token[0]); 75 | }) 76 | .on('add', function(path) { 77 | const token = path.replace(_v.divs.font, '').split(Path.sep).map(Function.prototype.call, String.prototype.trim).filter(function(v) { return v.length; }); 78 | return token.length == 2 && token[1] == 'style.css' && build(_v, '新增', path, token[0]); 79 | }) 80 | .on('unlink', function(path) { 81 | const token = path.replace(_v.divs.font, '').split(Path.sep).map(Function.prototype.call, String.prototype.trim).filter(function(v) { return v.length; }); 82 | return token.length == 2 && token[1] == 'style.css' && build(_v, '刪除', path, token[0]); 83 | }) 84 | .on('error', function(err) { 85 | return nt('[監控 Font 目錄] 警告!', '監控 Font 目錄發生錯誤', '請至終端機確認錯誤原因!') && 86 | er(title, ['錯誤原因:' + cc('err', 'w2')]) && 87 | closure(); 88 | }) 89 | .on('ready', function() { 90 | _v.font.isListen = true; 91 | setTimeout(function() { _v.font.isReady = true; }, _v.font.timer); 92 | return su(title) && 93 | closure(); 94 | }); 95 | }; -------------------------------------------------------------------------------- /cmd/libs/node/demo/minify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | 10 | const Ginkgo = rq('../Ginkgo'); 11 | const cc = Ginkgo.cc; 12 | const ln = Ginkgo.ln; 13 | const pp = Ginkgo.pp; 14 | const er = Ginkgo.er; 15 | const su = Ginkgo.su; 16 | const qq = Ginkgo.qq; 17 | 18 | const Exec = rq('child_process').exec; 19 | const FileSystem = rq('fs'); 20 | const Path = rq('path'); 21 | const root = '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep; 22 | const cmdDiv = Path.resolve(__dirname, root + 'cmd') + Path.sep; 23 | 24 | var mapDir = function(dir, filelist) { 25 | const files = FileSystem.readdirSync(dir); 26 | 27 | filelist = filelist || []; 28 | files.forEach(function(file) { 29 | if (!FileSystem.statSync(dir + file).isDirectory()) 30 | return filelist.push(dir + file); 31 | 32 | if (!(dir + file + Path.sep).match(qq(cmdDiv))) 33 | filelist = mapDir(dir + file + Path.sep, filelist); 34 | }); 35 | return filelist; 36 | }; 37 | 38 | function minifyCSS(_v, closure) { 39 | pp((title = cc(' ➤ ', 'C') + '壓縮 ' + cc('.css', 'w2') + ' 檔案') + cc('… ', 'w0')); 40 | const files = mapDir(Path.resolve(__dirname, root + Path.sep + 'css') + Path.sep).filter(function(file) { return file.match(/\.css$/g); }); 41 | 42 | if (!files.length) 43 | return su(title) && closure(_v.cho); 44 | 45 | let i = 0; 46 | 47 | try { 48 | for (; i < files.length; i++) 49 | FileSystem.writeFileSync(files[i], FileSystem.readFileSync(files[i], 'utf8').replace(/\n*/g, ''), 'utf8'); 50 | } catch(e) { 51 | let message = cc(e.message, 'w2'); 52 | 53 | er(title, [ 54 | '錯誤發生在:' + cc(files[i].replace(Path.resolve(__dirname, root) + Path.sep, ''), 'w2'), 55 | '以下可能是錯誤原因:' + ln + ln + cc('─'.repeat(30), 'w0') + ln + ln + message + ln + ln + cc('─'.repeat(30), 'w0')]); 56 | 57 | return rq('./rollback').run(_v); 58 | } 59 | 60 | return su(title) && closure(_v.cho); 61 | } 62 | 63 | function minifyHTML(_v, closure) { 64 | pp((title = cc(' ➤ ', 'C') + '壓縮 ' + cc('.html', 'w2') + ' 檔案') + cc('… ', 'w0')); 65 | 66 | const files = mapDir(Path.resolve(__dirname, root) + Path.sep).filter(function(file) { return file.match(/\.html$/g); }); 67 | 68 | if (!files.length) 69 | return su(title) && minifyCSS(_v, closure); 70 | 71 | const minify = rq('html-minifier').minify; 72 | 73 | let i = 0; 74 | try { 75 | for (; i < files.length; i++) 76 | FileSystem.writeFileSync(files[i], minify(FileSystem.readFileSync(files[i], 'utf8'), {collapseWhitespace: true}), 'utf8'); 77 | } catch(e) { 78 | let message = cc(e.message, 'w2'); 79 | 80 | if (e.message.match(/^Parse Error:/g)) { 81 | message = e.message.split(/^Parse Error:/gi).map(Function.prototype.call, String.prototype.trim).filter(function(t) { return t.length; }).join(''); 82 | message = message.length > 100 ? cc(message.slice(0, 100), 'w2') + cc('…', 'w0') : cc(message, 'w2'); 83 | } 84 | 85 | er(title, [ 86 | '錯誤發生在:' + cc(files[i].replace(Path.resolve(__dirname, root) + Path.sep, ''), 'w2'), 87 | '以下可能是錯誤原因:' + ln + ln + cc('─'.repeat(30), 'w0') + ln + ln + message + ln + ln + cc('─'.repeat(30), 'w0')]); 88 | 89 | return rq('./rollback').run(_v); 90 | } 91 | 92 | return su(title) && minifyCSS(_v, closure); 93 | } 94 | 95 | function uglifyJS(_v, closure) { 96 | pp((title = cc(' ➤ ', 'C') + '壓縮 ' + cc('.js', 'w2') + ' 檔案') + cc('… ', 'w0')); 97 | 98 | const files = mapDir(Path.resolve(__dirname, root + 'js') + Path.sep).filter(function(file) { return file.match(/\.js$/g); }); 99 | 100 | if (!files.length) 101 | return su(title) && minifyHTML(_v, closure); 102 | 103 | const UglifyJS = rq('uglify-js'); 104 | 105 | let i = 0; 106 | try { 107 | for (; i < files.length; i++) { 108 | let code = FileSystem.readFileSync(files[i], 'utf8'); 109 | const result = UglifyJS.minify(code, { mangle: { toplevel: true } }); 110 | 111 | if (result.error) 112 | throw result.error; 113 | 114 | FileSystem.writeFileSync(files[i], result.code, 'utf8'); 115 | } 116 | } catch(e) { 117 | let message = cc(e.message, 'w2'); 118 | 119 | er(title, [ 120 | '錯誤發生在:' + cc(files[i].replace(Path.resolve(__dirname, root) + Path.sep, ''), 'w2'), 121 | '以下可能是錯誤原因:' + ln + ln + cc('─'.repeat(30), 'w0') + ln + ln + message + ln + ln + cc('─'.repeat(30), 'w0')]); 122 | 123 | return rq('./rollback').run(_v); 124 | } 125 | 126 | return su(title) && minifyHTML(_v, closure); 127 | } 128 | 129 | module.exports.run = function(_v, closure) { 130 | return uglifyJS(_v, closure); 131 | }; -------------------------------------------------------------------------------- /cmd/libs/node/Ginkgo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | 10 | const Path = rq('path'); 11 | const Exec = rq('child_process').exec; 12 | const FileSystem = rq('fs'); 13 | 14 | const ln = '\n'; 15 | 16 | let notifierEnable = true; 17 | let sprintf = null; 18 | 19 | const ij = function(str) { 20 | try { 21 | JSON.parse(str); 22 | return true; 23 | } catch (e) { 24 | return false; 25 | } 26 | }; 27 | const wp = function(str) { 28 | return str.replace(/\\/g, "/"); 29 | }; 30 | 31 | const qq = function(str) { 32 | return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); 33 | }; 34 | 35 | const cc = function(str, fontColor, backgroundColor, options) { 36 | if (str === '') 37 | return ''; 38 | 39 | const colors = { n: '30', r: '31', g: '32', y: '33', b: '34', p: '35', c: '36', w: '37' }; 40 | const styles = { underline: '4', blink: '5', reverse: '7', hidden: '8', u: '4', b: '5', r: '7', h: '8' }; 41 | 42 | let tmps = []; 43 | 44 | if (typeof options === 'undefined') 45 | options = []; 46 | 47 | if (typeof options === 'string') 48 | options = options.split(',').map(Function.prototype.call, String.prototype.trim); 49 | 50 | if (Array.isArray(options) && (options = options.map(Function.prototype.call, String.prototype.toLowerCase)).length) 51 | for(let i = 0; i < options.length; i++) 52 | if (typeof styles[options[i]] !== 'undefined') 53 | tmps.push(['\033[' + styles[options[i]] + 'm', '\033[0m']); 54 | 55 | if (typeof backgroundColor !== 'undefined') { 56 | let c = backgroundColor[0].toLowerCase(); 57 | if (typeof colors[c] !== 'undefined') 58 | tmps.push(['\033[' + (parseInt(colors[c], 10) + 10) + 'm', '\033[0m']); 59 | } 60 | 61 | if (typeof fontColor !== 'undefined') { 62 | if (fontColor.length < 2) 63 | fontColor += '_'; 64 | 65 | let c = fontColor[0], w = fontColor[1]; 66 | 67 | w = w === '_' ? c === c.toUpperCase() ? '2' : w : w; 68 | c = c.toLowerCase(); 69 | 70 | if (!['0', '1', '2'].includes(w)) 71 | w = '1'; 72 | 73 | w = w !== '0' ? w === '1' ? '0' : '1' : '2'; 74 | 75 | if (typeof colors[c] !== 'undefined') 76 | tmps.push(['\033[' + w + ';' + colors[c] + 'm', '\033[0m']); 77 | } 78 | 79 | for(let i = 0; i < tmps.length; i++) 80 | str = tmps[i][0] + str + tmps[i][1]; 81 | 82 | return str; 83 | }; 84 | 85 | const er = function(title, details) { 86 | let str = title + cc(' ─ ', 'w0') + cc('失敗', 'r') + ln; 87 | 88 | details = details.filter(function(detail) { 89 | return typeof detail === 'string' || Array.isArray(detail) ? detail.length : true; 90 | }); 91 | 92 | for (var i in details) { 93 | if (Array.isArray(details[i])) 94 | for (var j in details[i]) 95 | str += cc(' ➜ ', 'g2') + details[i][j].replace(/\n.*$/, '') + ln; 96 | else if (typeof details[i] === 'object') 97 | for (var k in details[i]) 98 | str += cc(' ➜ ', 'g2') + cc(k + ':', 'w2') + details[i][k].replace(/\n.*$/, '') + ln; 99 | else if (typeof details[i] === 'string') 100 | str += cc(' ◎ ', 'p2') + details[i].replace(/\n.*$/, '') + ln; 101 | else; 102 | } 103 | 104 | return pp(str + ln); 105 | }; 106 | 107 | const pp = function(str) { 108 | process.stdout.write('\r' + str); 109 | return true; 110 | }; 111 | 112 | const su = function(title) { 113 | return pp(title + cc(' ─ ', 'w0') + cc('成功', 'g') + ln); 114 | }; 115 | 116 | const ctrlC = function() { 117 | return '過程中若要關閉請直接按鍵盤上的 ' + cc('control', 'W') + cc(' + ', 'w0') + cc('c', 'W') + ln + ' '.repeat(37) + cc('^^^^^^^^^^^', 'c1'); 118 | }; 119 | 120 | const init = function(header, closure) { 121 | process.stdout.write('\x1b[2J'); 122 | process.stdout.write('\x1b[0f'); 123 | 124 | const tmp = function() { 125 | rq('livereload'); 126 | rq('node-notifier'); 127 | rq('chokidar'); 128 | rq('command-exists'); 129 | rq('uglify-js'); 130 | rq('html-minifier'); 131 | rq('js-yaml'); 132 | rq('md5-file'); 133 | rq('sprintf-js'); 134 | sprintf = rq("sprintf-js").sprintf; 135 | return true; 136 | }; 137 | 138 | const errMsg = function(err) { 139 | return ln + cc(' '.repeat(30), 'w', 'r') + ln + cc(' 發生錯誤', 'y2', 'r') + cc(',以下為錯誤原因', 'w2', 'r') + cc(': ', 'w', 'r') + ln + cc(' '.repeat(30), 'w', 'r') + ln + cc('─'.repeat(30), 'w0') + ln + err + ln + cc('─'.repeat(30), 'w0') + ln + ln; 140 | }; 141 | 142 | try { 143 | return tmp() && pp(ln + cc(' ' + '【' + header + '】', 'r2') + ln) && closure(true); 144 | } catch(e) { 145 | if (!(e + '').match(/^Error: Cannot find module/)) 146 | return pp(errMsg(e)) && process.exit(1); 147 | 148 | pp(ln + cc(' ' + '【' + header + '】', 'r2') + ln); 149 | pp(cc(' ➤ ', 'C') + '首次使用,所以建立初始化環境!' + ln + cc(' ➤ ', 'C') + '注意喔,過程中請勿隨意結束!' + ln + ln + cc(' 【建立環境】', 'y') + ln); 150 | pp((title = cc(' ➤ ', 'C') + '執行 ' + cc('npm install .', 'w2') + ' 指令') + cc('… ', 'w0')); 151 | 152 | Exec('npm install .', function(err, stdout, stderr) { 153 | try { 154 | return su(title) && tmp() && pp(ln + cc(' ' + '【開始' + header + '】', 'y') + ln) && closure(false); 155 | } catch(e) { 156 | if (!(e + '').match(/^Error: Cannot find module/)) 157 | return pp(errMsg(e)) && process.exit(1); 158 | 159 | pp(title + cc(' ─ ', 'w0') + cc('失敗', 'r') + ln + cc(' ◎ ', 'p2') + cc('cmd 目錄', 'w2') + '無法寫入' + ln); 160 | pp((title = cc(' ➤ ', 'C') + '改用最高權限執行 ' + cc('sudo npm install .', 'w2') + ' 指令') + cc('… ', 'w0')); 161 | 162 | Exec('sudo npm install .', function(err, stdout, stderr) { 163 | try { 164 | return su(title) && tmp() && pp(ln + cc(' ' + '【開始' + header + '】', 'y') + ln) && closure(false); 165 | } catch(e) { 166 | if (!(e + '').match(/^Error: Cannot find module/)) 167 | return pp(errMsg(e)) && process.exit(1); 168 | 169 | pp(title + cc(' ─ ', 'w0') + cc('失敗', 'r') + ln + cc(' ◎ ', 'p2') + cc('cmd 目錄', 'w2') + '無法寫入' + ln); 170 | pp(ln + cc(' '.repeat(50), 'r', 'r') + ln + cc(' 錯誤!', 'y2', 'r') + cc('執行', 'n', 'r') + cc(' npm install . ', 'w2', 'r') + cc('失敗', 'n', 'r') + cc(' '.repeat(19), 'r', 'r') + ln + cc(' '.repeat(8), 'y2', 'r') + cc('請在終端機手動輸入指令', 'n1', 'r') + cc(' npm install . ', 'w2', 'r') + cc('吧! ', 'n1', 'r') + ln + cc(' '.repeat(31), 'r', 'r') + cc('^^^^^^^^^^^^^', 'y2', 'r') + cc(' '.repeat(6), 'r', 'r') + ln + ln); 171 | } 172 | }); 173 | } 174 | }); 175 | } 176 | }; 177 | 178 | const qu = function(items, closure) { 179 | const readline = rq('readline').createInterface; 180 | const rl = readline({ input: process.stdin, output: process.stdout }); 181 | 182 | rl.question(cc(' ➜', 'r2') + ' 請輸入您的選項:', function(answer) { 183 | rl.close(); 184 | 185 | cho = answer.toLowerCase().trim(); 186 | 187 | if (items.indexOf(cho) === -1) 188 | return qu(items, closure); 189 | 190 | closure(cho); 191 | }); 192 | }; 193 | 194 | 195 | const nt = function(title, subtitle, message) { 196 | let Notifier = rq('node-notifier').NotificationCenter; 197 | 198 | notifierEnable && new Notifier().notify({ 199 | title: title, 200 | subtitle: subtitle, 201 | message: message, 202 | sound: true, 203 | wait: false, 204 | timeout: 5, 205 | closeLabel: '關閉', 206 | actions: ['不再顯示'], 207 | dropdownLabel: '其他', 208 | }, function(e, r, m) { 209 | if (r == 'activate' && m.activationValue == '不再顯示') 210 | notifierEnable = false; 211 | }); 212 | 213 | return true; 214 | }; 215 | 216 | const progressInfo = { 217 | title: null, 218 | total: 0, 219 | index: 0, 220 | present: 0, 221 | }; 222 | 223 | var pr = function(total, err) { 224 | if (typeof total === 'string') { 225 | if (total === '') 226 | return pp(progressInfo.title + cc('(' + progressInfo.total + '/' + progressInfo.total + ')', 'w0') + cc(' ─ ', 'w0') + '100%' + cc(' ─ ', 'w0') + cc("完成", 'g') + ln); 227 | else if (total === '_') 228 | return pp(progressInfo.title + cc('(' + progressInfo.index + '/' + progressInfo.total + ')', 'w0') + cc(' ─ ', 'w0') + sprintf('%3d%%', progressInfo.present) + cc(' ─ ', 'w0') + cc("失敗", 'r') + ln + (err ? err.map(function(t) { 229 | return cc(' ◎ ', 'p2') + t + ln; 230 | }).join('') : '') + ln); 231 | else 232 | return pp((progressInfo.title = total) + cc('… ', 'w0')); 233 | } 234 | 235 | if (!isNaN(total)) { 236 | progressInfo.total = total; 237 | progressInfo.index = -1; 238 | } 239 | 240 | progressInfo.present = progressInfo.total ? Math.ceil((progressInfo.index + 1) * 100) / progressInfo.total : 100; 241 | progressInfo.present = progressInfo.present <= 100 ? progressInfo.present >= 0 ? progressInfo.present : 0 : 100; 242 | 243 | return pp(progressInfo.title + cc('(' + (++progressInfo.index) + '/' + progressInfo.total + ')', 'w0') + cc(' ─ ', 'w0') + sprintf('%3d%%', progressInfo.present)); 244 | }; 245 | 246 | module.exports = { 247 | ln: ln, 248 | cc: cc, 249 | pp: pp, 250 | er: er, 251 | qu: qu, 252 | nt: nt, 253 | su: su, 254 | pr: pr, 255 | qq: qq, 256 | wp: wp, 257 | ij: ij, 258 | init: init, 259 | ctrlC: ctrlC, 260 | }; -------------------------------------------------------------------------------- /cmd/libs/php/put.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 6 | * @license http://opensource.org/licenses/MIT MIT License 7 | * @link https://www.ioa.tw/ 8 | */ 9 | 10 | mb_regex_encoding('UTF-8'); 11 | mb_internal_encoding('UTF-8'); 12 | date_default_timezone_set('Asia/Taipei'); 13 | 14 | define('PATH_CMD_LIB_PHP', dirname(__FILE__) . DIRECTORY_SEPARATOR); 15 | define('PATH_CMD_LIB', dirname(PATH_CMD_LIB_PHP) . DIRECTORY_SEPARATOR); 16 | define('PATH_CMD', dirname(PATH_CMD_LIB) . DIRECTORY_SEPARATOR); 17 | define('PATH', dirname(PATH_CMD) . DIRECTORY_SEPARATOR); 18 | define('DIRNAME', basename(PATH)); 19 | 20 | define('METHOD', php_sapi_name () === 'cli' ? 'cli' : 'post'); 21 | 22 | if (!function_exists('arrayFlatten')) { function arrayFlatten($arr) { $new = []; foreach ($arr as $key => $value) if (is_array($value)) $new = array_merge($new, $value); else array_push($new, $value); return $new; } } 23 | 24 | $steps = []; 25 | 26 | try { 27 | array_push($steps, '讀取 config.php'); 28 | $dirs = is_readable($dirs = PATH_CMD . 'libs' . DIRECTORY_SEPARATOR . 'php' . DIRECTORY_SEPARATOR . 'config.php') ? include_once($dirs) : ['bucket' => null, 'access' => null, 'secret' => null, 'folder' => null, 'domain' => null]; 29 | 30 | array_push($steps, '分析參數'); 31 | switch (METHOD) { 32 | case 'get': $key = '_GET'; 33 | case 'post': isset($key) || $key = '_POST'; 34 | isset($$key['bucket']) && ($$key['bucket'] = trim($$key['bucket'])) && $dirs['bucket'] = $$key['bucket']; 35 | isset($$key['access']) && ($$key['access'] = trim($$key['access'])) && $dirs['access'] = $$key['access']; 36 | isset($$key['secret']) && ($$key['secret'] = trim($$key['secret'])) && $dirs['secret'] = $$key['secret']; 37 | isset($$key['folder']) && $dirs['folder'] = trim($$key['folder']); 38 | isset($$key['domain']) && $dirs['domain'] = trim($$key['domain']); 39 | break; 40 | 41 | case 'cli': 42 | if (!function_exists('params')) { function params($params, $keys) { $ks = $return = $result = []; if (!($params && $keys)) return $return; foreach ($keys as $key) if (is_array($key)) foreach ($key as $k) array_push($ks, $k); else array_push($ks, $key); $key = null; foreach ($params as $param) if (in_array($param, $ks)) if (!isset($result[$key = $param])) $result[$key] = []; else ; else if (isset($result[$key])) array_push($result[$key], $param); else ; foreach ($keys as $key) if (is_array($key)) foreach ($key as $k) if (isset($result[$k])) $return[$key[0]] = isset($return[$key[0]]) ? array_merge($return[$key[0]], $result[$k]) : $result[$k]; else; else if (isset($result[$key])) $return[$key] = isset($return[$key]) ? array_merge($return[$key], $result[$key]) : $result[$key]; else ; return $return; } } 43 | $file = array_shift($argv); 44 | $file = params($argv, [['-b', '--bucket'], ['-a', '--access'], ['-s', '--secret'], ['-f', '--folder'], ['-d', '--domain']]); 45 | 46 | isset($file['-b'][0]) && $dirs['bucket'] = $file['-b'][0]; 47 | isset($file['-a'][0]) && $dirs['access'] = $file['-a'][0]; 48 | isset($file['-s'][0]) && $dirs['secret'] = $file['-s'][0]; 49 | isset($file['-f']) && $dirs['folder'] = isset($file['-f'][0]) ? $file['-f'][0] : ''; 50 | isset($file['-d'][0]) && $dirs['domain'] = $file['-d'][0]; 51 | break; 52 | 53 | default: 54 | break; 55 | } 56 | 57 | array_push($steps, '定義參數'); 58 | define('BUCKET', $dirs['bucket']); 59 | define('ACCESS', $dirs['access']); 60 | define('SECRET', $dirs['secret']); 61 | define('FOLDER', $dirs['folder'] !== null ? trim($dirs['folder'], '/') : DIRNAME); 62 | define('DOMAIN', $dirs['domain'] !== null ? $dirs['domain'] : BUCKET); 63 | 64 | array_push($steps, '檢查參數'); 65 | if (BUCKET === null || ACCESS === null || SECRET === null) 66 | throw new Exception('缺少必填參數!'); 67 | 68 | array_push($steps, '載入 S3'); 69 | if (!is_readable($dirs = PATH_CMD . 'libs' . DIRECTORY_SEPARATOR . 'php' . DIRECTORY_SEPARATOR . 'S3.php')) 70 | throw new Exception('找不到 cmd/libs/php 目錄內的 S3.php 檔案!'); 71 | 72 | include_once $dirs; 73 | 74 | array_push($steps, '載入 Spyc'); 75 | if (!is_readable($dirs = PATH_CMD . 'libs' . DIRECTORY_SEPARATOR . 'php' . DIRECTORY_SEPARATOR . 'Spyc.php')) 76 | throw new Exception('找不到 cmd/libs/php 目錄內的 Spyc.php 檔案!'); 77 | 78 | include_once $dirs; 79 | 80 | array_push($steps, '載入 _dirs.yaml'); 81 | if (!is_readable($dirs = PATH_CMD . '_dirs.yaml')) 82 | throw new Exception('找不到 cmd 目錄內的 _dirs.yaml 檔案!'); 83 | 84 | array_push($steps, '解譯 _dirs.yaml'); 85 | $dirs = array_map(function($dir) { 86 | $dir = array_merge(['formats' => [], 'recursive' => false, 'hidden' => false, 'includes' => []], $dir); 87 | is_array($dir['formats']) || $dir['formats'] = []; 88 | is_array($dir['includes']) || $dir['includes'] = []; 89 | $dir['recursive'] = boolval($dir['recursive']); 90 | $dir['hidden'] = boolval($dir['hidden']); 91 | $dir['includes'] = array_map(function($include) { return trim($include, DIRECTORY_SEPARATOR); }, $dir['includes']); 92 | 93 | $dir['path'] = trim($dir['path'], DIRECTORY_SEPARATOR); 94 | $dir['path'] = PATH . ($dir['path'] ? $dir['path'] . DIRECTORY_SEPARATOR : $dir['path']); 95 | return $dir; 96 | }, array_filter(Spyc::YAMLLoad($dirs), function($dir) { return isset($dir['path']) && is_string($dir['path']); })); 97 | 98 | array_push($steps, '定義函式'); 99 | function mapDir($path, $options) { 100 | $files = @scandir($path); 101 | $files !== false || $files = []; 102 | return arrayFlatten(array_filter(array_map(function($file) use ($path, $options) { 103 | $path = $path . $file; 104 | if ($file === '.') return null; 105 | if ($file === '..') return null; 106 | if ($path === PATH . '.git') return null; 107 | if ($path === PATH . 'cmd') return null; 108 | 109 | if (is_dir($path)) return $options['recursive'] ? mapDir($path . DIRECTORY_SEPARATOR, $options) : null; 110 | 111 | if (is_file($path)) 112 | return 113 | filesize($path) && 114 | ($options['hidden'] || $file[0] !== '.') && 115 | (!$options['includes'] || in_array(preg_replace('/^(' . preg_replace('/\//', '\/', PATH) . ')/', '', $path), $options['includes'])) && 116 | (!$options['formats'] || in_array('.' . pathinfo($file, PATHINFO_EXTENSION), $options['formats'])) 117 | ? $path : null; 118 | 119 | return null; 120 | }, $files), function($file) { 121 | return $file !== null; 122 | })); 123 | } 124 | 125 | $localFilesFunc = function($dirs) { 126 | return array_map(function($dir) { 127 | return [ 128 | 'name' => (FOLDER ? FOLDER . '/' : '') . preg_replace('/^(' . preg_replace('/\//', '\/', PATH) . ')/', '', $dir), 129 | 'hash' => md5_file($dir), 130 | 'path' => $dir, 131 | ]; 132 | }, arrayFlatten(array_map(function($dir) { return mapDir($dir['path'], $dir); }, $dirs))); 133 | }; 134 | 135 | $s3FilesFunc = function(&$s3) { 136 | $s3 = new S3(ACCESS, SECRET); 137 | 138 | if (!$s3->test()) 139 | return 'S3 測試失敗!'; 140 | 141 | $files = $s3->bucket(BUCKET, FOLDER === '' ? null : FOLDER); 142 | 143 | if ($files === false) 144 | return 'S3 取得 Bucket 資料失敗!'; 145 | 146 | return array_values(array_map(function($file) { 147 | unset($file['time'], $file['size']); 148 | return $file; 149 | }, $files)); 150 | }; 151 | 152 | $filterLocalFilesFunc = function(array $localFiles, array $s3Files) { 153 | return array_filter($localFiles, function($localFile) use ($s3Files) { 154 | foreach ($s3Files as $s3File) 155 | if ($s3File['name'] == $localFile['name'] && $s3File['hash'] == $localFile['hash']) 156 | return false; 157 | return true; 158 | }); 159 | }; 160 | 161 | $uploadFilesFunc = function($uploadFiles, $s3) { 162 | return array_filter(array_map(function($uploadFile) use ($s3) { 163 | return !$s3->putObject($uploadFile['path'], BUCKET, $uploadFile['name']); 164 | }, $uploadFiles)); 165 | }; 166 | 167 | $filterS3FilesFunc = function(array $s3Files, array $localFiles) { 168 | return array_filter($s3Files, function($s3File) use ($localFiles) { 169 | foreach ($localFiles as $localFile) 170 | if ($localFile['name'] == $s3File['name']) 171 | return false; 172 | return true; 173 | }); 174 | }; 175 | 176 | $deleteFilesFunc = function(array $deleteFiles, $s3) { 177 | return array_filter(array_map(function($deleteFile) use ($s3) { 178 | return !$s3->deleteObject(BUCKET, $deleteFile['name']); 179 | }, $deleteFiles)); 180 | }; 181 | 182 | 183 | array_push($steps, '整理本機內檔案'); 184 | $localFiles = $localFilesFunc($dirs); 185 | unset($localFilesFunc); 186 | 187 | array_push($steps, '取得 S3 上檔案'); 188 | if (is_string($s3Files = $s3FilesFunc($s3))) 189 | throw new Exception($s3Files); 190 | unset($s3FilesFunc); 191 | 192 | array_push($steps, '過濾上傳的檔案'); 193 | $uploadFiles = $filterLocalFilesFunc($localFiles, $s3Files); 194 | unset($filterLocalFilesFunc); 195 | 196 | array_push($steps, '上傳檔案至 S3'); 197 | if ($uploadFilesFunc($uploadFiles, $s3)) 198 | throw new Exception('S3 上傳失敗!'); 199 | unset($uploadFilesFunc); 200 | 201 | array_push($steps, '過濾刪除的檔案'); 202 | $deleteFiles = $filterS3FilesFunc($s3Files, $localFiles); 203 | unset($filterS3FilesFunc); 204 | 205 | array_push($steps, '刪除 S3 的檔案'); 206 | if ($deleteFilesFunc($deleteFiles, $s3)) 207 | throw new Exception('S3 刪除失敗!'); 208 | unset($deleteFilesFunc); 209 | 210 | array_push($steps, '完成'); 211 | @header('Content-Type: application/json; charset=UTF-8', true); 212 | echo json_encode(['status' => true, 'steps' => $steps]); 213 | } catch (Exception $e) { 214 | @header('Content-Type: application/json; charset=UTF-8', true); 215 | echo json_encode(['status' => false, 'steps' => $steps, 'error' => $e->getMessage()]); 216 | } 217 | -------------------------------------------------------------------------------- /cmd/libs/node/demo/putS3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author OA Wu 3 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @link https://www.ioa.tw/ 6 | */ 7 | 8 | const rq = require; 9 | const Ginkgo = rq('../Ginkgo'); 10 | const qq = Ginkgo.qq; 11 | const pp = Ginkgo.pp; 12 | const cc = Ginkgo.cc; 13 | const ln = Ginkgo.ln; 14 | const pr = Ginkgo.pr; 15 | const wp = Ginkgo.wp; 16 | 17 | const Path = rq('path'); 18 | const root = '..' + Path.sep + '..' + Path.sep + '..' + Path.sep + '..' + Path.sep; 19 | const FileSystem = rq('fs'); 20 | const rootDiv = Path.resolve(__dirname, root) + Path.sep; 21 | const cmdDiv = Path.resolve(__dirname, root + 'cmd') + Path.sep; 22 | const gitDiv = Path.resolve(__dirname, root + '.git') + Path.sep; 23 | const md5File = rq('md5-file'); 24 | const S3 = rq('aws-sdk/clients/s3'); 25 | const sepRegExp = new RegExp(qq('^' + Path.sep.replace('/', '\/') + '*|' + Path.sep.replace('/', '\/') + '*$'), 'g'); 26 | const exts = { jpg: ['image/jpeg', 'image/pjpeg'], gif: ['image/gif'], png: ['image/png', 'image/x-png'], pdf: ['application/pdf', 'application/x-download'], gz: ['application/x-gzip'], zip: ['application/x-zip', 'application/zip', 'application/x-zip-compressed'], swf: ['application/x-shockwave-flash'], tar: ['application/x-tar'], bz: ['application/x-bzip'], bz2: ['application/x-bzip2'], txt: ['text/plain'], html: ['text/html'], htm: ['text/html'], ico: ['image/x-icon'], css: ['text/css'], js: ['application/x-javascript'], xml: ['text/xml'], ogg: ['application/ogg'], wav: ['audio/x-wav', 'audio/wave', 'audio/wav'], avi: ['video/x-msvideo'], mpg: ['video/mpeg'], mov: ['video/quicktime'], mp3: ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'], mpeg: ['video/mpeg'], flv: ['video/x-flv'], php: ['application/x-httpd-php'], bin: ['application/macbinary'], psd: ['application/x-photoshop'], ai: ['application/postscript'], ppt: ['application/powerpoint', 'application/vnd.ms-powerpoint'], wbxml: ['application/wbxml'], tgz: ['application/x-tar', 'application/x-gzip-compressed'], jpeg: ['image/jpeg', 'image/pjpeg'], jpe: ['image/jpeg', 'image/pjpeg'], bmp: ['image/bmp', 'image/x-windows-bmp'], shtml: ['text/html'], text: ['text/plain'], doc: ['application/msword'], docx: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'], xlsx: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip'], word: ['application/msword', 'application/octet-stream'], json: ['application/json', 'text/json'], svg: ['image/svg+xml'], mp2: ['audio/mpeg'], exe: ['application/octet-stream', 'application/x-msdownload'], tif: ['image/tiff'], tiff: ['image/tiff'], asc: ['text/plain'], xsl: ['text/xml'], hqx: ['application/mac-binhex40'], cpt: ['application/mac-compactpro'], csv: ['text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel'], dms: ['application/octet-stream'], lha: ['application/octet-stream'], lzh: ['application/octet-stream'], class: ['application/octet-stream'], so: ['application/octet-stream'], sea: ['application/octet-stream'], dll: ['application/octet-stream'], oda: ['application/oda'], eps: ['application/postscript'], ps: ['application/postscript'], smi: ['application/smil'], smil: ['application/smil'], mif: ['application/vnd.mif'], xls: ['application/excel', 'application/vnd.ms-excel', 'application/msexcel'], wmlc: ['application/wmlc'], dcr: ['application/x-director'], dir: ['application/x-director'], dxr: ['application/x-director'], dvi: ['application/x-dvi'], gtar: ['application/x-gtar'], php4: ['application/x-httpd-php'], php3: ['application/x-httpd-php'], phtml: ['application/x-httpd-php'], phps: ['application/x-httpd-php-source'], sit: ['application/x-stuffit'], xhtml: ['application/xhtml+xml'], xht: ['application/xhtml+xml'], mid: ['audio/midi'], midi: ['audio/midi'], mpga: ['audio/mpeg'], aif: ['audio/x-aiff'], aiff: ['audio/x-aiff'], aifc: ['audio/x-aiff'], ram: ['audio/x-pn-realaudio'], rm: ['audio/x-pn-realaudio'], rpm: ['audio/x-pn-realaudio-plugin'], ra: ['audio/x-realaudio'], rv: ['video/vnd.rn-realvideo'], log: ['text/plain', 'text/x-log'], rtx: ['text/richtext'], rtf: ['text/rtf'], mpe: ['video/mpeg'], qt: ['video/quicktime'], movie: ['video/x-sgi-movie'], xl: ['application/excel'], eml: ['message/rfc822']}; 27 | 28 | let s3 = null; 29 | 30 | var mapDir = function(dir, filelist, options) { 31 | const files = FileSystem.readdirSync(dir); 32 | 33 | filelist = filelist || []; 34 | files.forEach(function(file) { 35 | const path = dir + file; 36 | if ([cmdDiv, gitDiv].indexOf(path + Path.sep) !== -1) 37 | return; 38 | 39 | if (!FileSystem.statSync(path).isDirectory()) 40 | 41 | if ( 42 | (options.hidden || file[0] !== '.') && 43 | (!options.includes.length || options.includes.indexOf((path).replace(rootDiv, '')) !== -1) && 44 | (!options.formats.length || options.formats.indexOf('.' + file.split('.').pop().toLowerCase()) !== -1) 45 | ) 46 | if ((stats = FileSystem.statSync(path)) && (stats.size > 0)) 47 | return filelist.push(path); 48 | 49 | if (FileSystem.statSync(path).isDirectory() && options.recursive) 50 | filelist = mapDir(path + Path.sep, filelist, options); 51 | }); 52 | 53 | return filelist; 54 | }; 55 | 56 | 57 | var localFilesFunc = function(_v, closure) { 58 | 59 | let tmps = _v.dirs.map(function(dir) { return mapDir(dir.path, [], dir); }).reduce(function(a, b) { return a.concat(b); }); 60 | 61 | pr(tmps.length); 62 | 63 | tmps = tmps.map(function(dir) { 64 | return pr() && { 65 | name: (_v.s3Info.folder.length ? _v.s3Info.folder + '/' : '') + wp(dir.replace(rootDiv, '')), 66 | hash: md5File.sync(dir), 67 | path: dir, 68 | }; 69 | }); 70 | 71 | return pr('') && closure(tmps); 72 | }; 73 | 74 | var listObjects = function(_v, closure, opts, items) { 75 | s3.listObjectsV2(opts, function(err, data) { 76 | if (err) 77 | return pr('_', ['錯誤原因:' + cc(err.message, 'w2')]) && rq('./rollback').run(_v); 78 | 79 | items = items.concat(data.Contents.map(function(t) { 80 | return { 81 | name: t.Key, 82 | hash: t.ETag.replace(/^('|")(.*)\1/g, '$2'), 83 | }; 84 | })); 85 | 86 | if (!data.IsTruncated) 87 | return pr(items.length) && pr('') && closure(items); 88 | 89 | opts.ContinuationToken = data.NextContinuationToken; 90 | return listObjects(_v, closure, opts, items); 91 | }); 92 | }; 93 | 94 | var s3FilesFunc = function(_v, closure) { 95 | if (!s3) 96 | s3 = new S3({ accessKeyId: _v.s3Info.access, secretAccessKey: _v.s3Info.secret }); 97 | 98 | if (!s3) 99 | return pr('_', ['錯誤原因:' + cc('初始 S3 物件失敗!', 'w2')]) && rq('./rollback').run(_v); 100 | 101 | return listObjects(_v, closure, { 102 | Bucket: _v.s3Info.bucket, 103 | Prefix: _v.s3Info.folder, 104 | }, []); 105 | }; 106 | 107 | var filterLocalFilesFunc = function(localFiles, s3Files, closure) { 108 | pr(localFiles.length); 109 | 110 | const tmps = localFiles.filter(function(localFile) { 111 | pr(); 112 | 113 | for (let i = 0; i < s3Files.length; i++) 114 | if (s3Files[i].name == localFile.name && s3Files[i].hash == localFile.hash) 115 | return false; 116 | 117 | return true; 118 | }); 119 | 120 | return pr('') && closure(tmps); 121 | }; 122 | 123 | var uploadFilesFunc = function(_v, uploadFiles, closure) { 124 | pr(uploadFiles.length); 125 | 126 | if (!s3) 127 | s3 = new S3({ accessKeyId: _v.s3Info.access, secretAccessKey: _v.s3Info.secret }); 128 | 129 | if (!s3) 130 | return pr('_', ['錯誤原因:' + cc('初始 S3 物件失敗!', 'w2')]) && rq('./rollback').run(_v); 131 | 132 | Promise.all(uploadFiles.map(function(uploadFile) { 133 | return new Promise(function(resolve, reject) { 134 | s3.putObject({ 135 | Bucket: _v.s3Info.bucket, 136 | Key: uploadFile.name, 137 | Body: FileSystem.readFileSync(uploadFile.path), 138 | ACL: 'public-read', 139 | ContentType: extensions(uploadFile.path), 140 | // ContentMD5: Buffer.from(uploadFile.hash).toString('base64'), 141 | // CacheControl: 'max-age=5' 142 | }, function(err, data) { 143 | if (err) reject(err); 144 | else pr() && resolve(data); 145 | }); 146 | }); 147 | })).then(function() { 148 | return pr('') && closure(true); 149 | }).catch(function(err) { 150 | return pr('_', ['錯誤原因:' + cc(err.message, 'w2')]) && rq('./rollback').run(_v); 151 | }); 152 | }; 153 | 154 | var filterS3FilesFunc = function(s3Files, localFiles, closure) { 155 | pr(s3Files.length); 156 | 157 | const tmps = s3Files.filter(function(s3File) { 158 | pr(); 159 | 160 | for (let i = 0; i < localFiles.length; i++) 161 | if (localFiles[i].name == s3File.name) 162 | return false; 163 | 164 | return true; 165 | }); 166 | 167 | return pr('') && closure(tmps); 168 | }; 169 | 170 | var deleteFilesFunc = function(_v, deleteFiles, closure) { 171 | pr(deleteFiles.length); 172 | 173 | if (!s3) 174 | s3 = new S3({ accessKeyId: _v.s3Info.access, secretAccessKey: _v.s3Info.secret }); 175 | 176 | if (!s3) 177 | return pr('_', ['錯誤原因:' + cc('初始 S3 物件失敗!', 'w2')]) && rq('./rollback').run(_v); 178 | 179 | Promise.all(deleteFiles.map(function(deleteFile) { 180 | return new Promise(function(resolve, reject) { 181 | s3.deleteObject({ 182 | Bucket: _v.s3Info.bucket, 183 | Key: deleteFile.name, 184 | }, function(err, data) { 185 | if (err) reject(err); 186 | else pr() && resolve(data); 187 | }); 188 | }); 189 | })).then(function() { 190 | return pr('') && closure(true); 191 | }).catch(function(err) { 192 | return pr('_', ['錯誤原因:' + cc(err.message, 'w2')]) && rq('./rollback').run(_v); 193 | }); 194 | }; 195 | 196 | var extensions = function(name) { 197 | return typeof exts[name.split('.').pop().toLowerCase()] === 'undefined' ? 'text/plain' : exts[name.split('.').pop().toLowerCase()][0]; 198 | }; 199 | 200 | var start = function(_v, closure) { 201 | pr(cc(' ➤ ', 'C') + '整理本機內檔案'); 202 | localFilesFunc(_v, function(localFiles) { 203 | 204 | pr(cc(' ➤ ', 'C') + '取得 S3 上檔案'); 205 | s3FilesFunc(_v, function(s3Files) { 206 | 207 | pr(cc(' ➤ ', 'C') + '過濾上傳的檔案'); 208 | filterLocalFilesFunc(localFiles, s3Files, function(uploadFiles) { 209 | 210 | pr(cc(' ➤ ', 'C') + '上傳檔案至 S3 '); 211 | uploadFilesFunc(_v, uploadFiles, function(ok) { 212 | 213 | pr(cc(' ➤ ', 'C') + '過濾刪除的檔案'); 214 | filterS3FilesFunc(s3Files, localFiles, function(deleteFiles) { 215 | 216 | pr(cc(' ➤ ', 'C') + '刪除 S3 的檔案'); 217 | deleteFilesFunc(_v, deleteFiles, closure); 218 | }); 219 | }); 220 | }); 221 | }); 222 | }); 223 | }; 224 | 225 | module.exports.run = function(_v, closure) { 226 | pp(ln + cc(' 【讀取設定檔案】', 'y') + ln); 227 | 228 | rq('./dirsInfo').run(_v, function(dirs) { 229 | pp(ln + cc(' 【上傳至 AWS S3】', 'y') + ln); 230 | return start(_v, closure); 231 | }); 232 | }; 233 | -------------------------------------------------------------------------------- /cmd/libs/scss/_GinkgoUi.scss: -------------------------------------------------------------------------------- 1 | // 2 | // @author OA Wu 3 | // @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | // @license http://opensource.org/licenses/MIT MIT License 5 | // @link https://www.ioa.tw/ 6 | // 7 | 8 | $_CircleAnimation: false !global; 9 | @mixin CircleAnimation() { 10 | @if ($_CircleAnimation == false) { 11 | $_CircleAnimation: true !global; 12 | @include keyframes(_CircleAnimation) { 0% { @include rotate(0); } 100% { @include rotate(360deg); } } 13 | } 14 | } 15 | 16 | // 17 | // @name Ginkgo's Button UI 18 | // 19 | // @param $color 按鈕顏色 20 | // @param $height 按鈕高度 21 | // 22 | // @example: 23 | // 24 | // 25 | @mixin button($color: rgba(69, 139, 244, 1.00), $height: 24px) { 26 | display: inline-block; 27 | width: auto; 28 | height: $height; 29 | line-height: $height; 30 | font-size: $height / 2; 31 | 32 | padding-top: 0; 33 | padding-bottom: 0; 34 | 35 | border: 0; 36 | cursor: pointer; 37 | 38 | @include contrasted($color, rgba(55, 55, 55, 1.00), rgba(255, 255, 255, 1.00)); 39 | @include box-shadow(inset 1px 1px 1px rgba(255, 255, 255, 0.15), inset -1px -1px 1px rgba(0, 0, 0, 0.15)); 40 | @include border-radius(2px); 41 | @include transition(box-shadow .1s); 42 | 43 | &:hover { 44 | @include box-shadow(inset -1px -1px 2px rgba(0, 0, 0, 0.25), inset 1px 1px 2px rgba(255, 255, 255, 0.25)); 45 | @include text-shadow(0 0 1px rgba(0, 0, 0, 0.25)); 46 | } 47 | 48 | &:active { 49 | @include box-shadow(inset 1px 1px 1px rgba(0, 0, 0, 0.1)); 50 | } 51 | 52 | &:focus { 53 | outline: 0; 54 | } 55 | 56 | $amount: 15%; 57 | &[disabled] { 58 | cursor: not-allowed; 59 | @include text-shadow(none); 60 | @include box-shadow(none); 61 | @include contrasted(adjust-lightness($color, $amount), adjust-lightness(rgba(55, 55, 55, 1.00), $amount), adjust-lightness(rgba(255, 255, 255, 1.00), $amount)); 62 | } 63 | } 64 | 65 | // 66 | // @name Ginkgo's Checkbox UI 67 | // 68 | // @param $bgColor 底色 69 | // @param $size 寬高 70 | // @param $loadingSize Loading 寬高 71 | // 72 | // @example: 73 | // 77 | // 78 | @mixin checkbox($bgColor: rgba(69, 139, 244, 1.00), $size: 20px, $loadingSize: 4px) { 79 | position: relative; 80 | 81 | display: inline-block; 82 | height: $size; 83 | line-height: $size; 84 | 85 | cursor: pointer; 86 | font-size: $size - 6px; 87 | padding-left: $size + 4px; 88 | 89 | > * { 90 | position: absolute; 91 | left: 0; 92 | top: 0; 93 | display: inline-block; 94 | } 95 | 96 | input { 97 | cursor: pointer; 98 | z-index: 2; 99 | @include opacity(0); 100 | 101 | &[disabled] { 102 | cursor: not-allowed; 103 | } 104 | 105 | &:not([disabled]):checked ~ span { 106 | &:before { 107 | @include box-shadow(inset 1px 1px 1px rgba(0, 0, 0, .1), inset 0 0 1px rgba(0, 0, 0, .1)); 108 | } 109 | &:after { 110 | color: rgba(255, 255, 255, 1.00); 111 | @include text-shadow(.5px .5px 2px rgba(0, 0, 0, .3)); 112 | @include box-shadow(none); 113 | } 114 | } 115 | } 116 | 117 | span { 118 | width: $size; 119 | height: $size; 120 | background-color: rgba(255, 255, 255, 1.00); 121 | z-index: 1; 122 | 123 | &:before, &:after { 124 | content: ''; 125 | 126 | position: absolute; 127 | left: 0; 128 | top: 0; 129 | 130 | display: inline-block; 131 | width: 100%; 132 | height: 100%; 133 | 134 | z-index: 1; 135 | 136 | @include border-radius(2px); 137 | @include transition(border-radius .3s); 138 | } 139 | 140 | &:before { 141 | @include box-shadow(inset 0 0 1px rgba(0, 0, 0, .1)); 142 | } 143 | 144 | &:after { 145 | content: "✔"; 146 | @content; 147 | 148 | left: 2px; 149 | top: 2px; 150 | 151 | width: calc(100% - #{2px * 2}); 152 | height: calc(100% - #{2px * 2}); 153 | line-height: $size - 2px * 2; 154 | color: rgba(255, 255, 255, 1.00); 155 | text-align: center; 156 | 157 | background-image: none; 158 | background-color: rgba(255, 255, 255, 1.00); 159 | z-index: 2; 160 | 161 | @include box-shadow(.5 .5 1px rgba(0, 0, 0, .1)); 162 | @include transition(background-color .3s, color .3s); 163 | } 164 | } 165 | 166 | input:not([disabled]):checked ~ span:after { 167 | background-color: $bgColor; 168 | } 169 | 170 | span:before { 171 | background-color: $bgColor; 172 | } 173 | 174 | &.loading { 175 | span { 176 | &:before { 177 | @include border-radius(50%); 178 | @include background-image(linear-gradient(rgba(184, 241, 148, 1.00) 0%, rgba(89, 178, 169, 1.00) 100%)); 179 | @include animation(_CircleAnimation 1s linear infinite); 180 | } 181 | &:after { 182 | content: ""; 183 | left: $loadingSize; 184 | top: $loadingSize; 185 | width: calc(100% - #{$loadingSize} * 2); 186 | height: calc(100% - #{$loadingSize} * 2); 187 | @include border-radius(50%); 188 | } 189 | } 190 | 191 | input:checked ~ span:after { 192 | background-color: white; 193 | } 194 | } 195 | @include CircleAnimation; 196 | } 197 | 198 | // 199 | // @name Ginkgo's Hyperlink UI 200 | // 201 | // @param $underLineHeight 底線高度 202 | // @param $d4UnderLine 預設出現底線 203 | // @param $color 顏色 204 | // @param $hoverColor hover 時的顏色 205 | // 206 | // @example: 207 | // 208 | // 209 | @mixin a($underLineHeight: 0, $d4UnderLine: false, $color: rgba(66, 133, 244, 1.00), $hoverColor: null) { 210 | color: $color; 211 | display: inline; 212 | font-weight: normal; 213 | text-decoration: none; 214 | @include transition(color .3s, border-bottom .3s); 215 | 216 | @if $hoverColor == null { 217 | $hoverColor: adjust-lightness($color, -15%); 218 | } 219 | 220 | @if $underLineHeight > 0 { 221 | @if($d4UnderLine) { 222 | border-bottom: $underLineHeight solid adjust-lightness($color, 5%); 223 | } 224 | @else { 225 | border-bottom: $underLineHeight solid transparent; 226 | } 227 | } 228 | &.active, &:hover { 229 | color: $hoverColor; 230 | 231 | @if($underLineHeight > 0) { 232 | border-bottom: $underLineHeight solid adjust-lightness($hoverColor, 5%); 233 | } 234 | } 235 | } 236 | 237 | // 238 | // @name Ginkgo's Input UI 239 | // 240 | // @param $borderFocusColor 文字顏色 241 | // 242 | // @example: 243 | // 244 | // 245 | @mixin input($borderFocusColor: rgba(100, 175, 235, 1), $height: 30px) { 246 | height: $height; 247 | line-height: $height - 2px - 1px; 248 | 249 | color: rgba(120, 120, 120, 1.00); 250 | font-size: $height * 0.5; 251 | padding: 0 8px; 252 | border: 1px solid rgba(200, 200, 200, 1.00); 253 | 254 | @include border-radius (2px); 255 | @include transition(box-shadow .3s, border-color .3s); 256 | 257 | @include input-placeholder { 258 | font-weight: normal; 259 | color: rgba(175, 175, 175, 1); 260 | font-size: $height * 0.475; 261 | } 262 | 263 | &:focus { 264 | outline: 0; 265 | border: 1px solid $borderFocusColor; 266 | @include box-shadow(0 0 8px rgba(red($borderFocusColor), green($borderFocusColor), blue($borderFocusColor), .6)); 267 | } 268 | 269 | &[readonly] { 270 | cursor: not-allowed; 271 | background-color: rgba(250, 250, 250, 1); 272 | border: 1px solid rgba(225, 225, 225, 1.00); 273 | 274 | @include input-placeholder { 275 | color: rgba(200, 200, 200, 1); 276 | } 277 | 278 | &:focus { 279 | border: 1px solid rgba(225, 225, 225, 1.00); 280 | @include box-shadow(none); 281 | } 282 | } 283 | 284 | &[type="file"] { 285 | $tmp: $height / 10 + 1; 286 | font-size: $height / 3; 287 | padding: $tmp; 288 | line-height: 0; 289 | width: auto; 290 | 291 | &::-webkit-file-upload-button { 292 | @include button(rgba(69, 139, 244, 1.00), $height - ($tmp + 1px) * 2); 293 | } 294 | } 295 | 296 | &[type='number']::-webkit-inner-spin-button { 297 | } 298 | 299 | &[type='date']::-webkit-datetime-edit { 300 | font-size: $height * 0.5; 301 | } 302 | 303 | @content; 304 | } 305 | 306 | // 307 | // @name Ginkgo's Radio UI 308 | // 309 | // @param $bgColor 底色 310 | // @param $size 寬高 311 | // @param $dotSpaceSize 內點空隙大小 312 | // @param $loadingSize Loading 寬高 313 | // 314 | // @example: 315 | // 319 | // 320 | @mixin radio($bgColor: rgba(69, 139, 244, 1.00), $size: 20px, $dotSpaceSize: 6px, $loadingSize: 4px) { 321 | position: relative; 322 | display: inline-block; 323 | 324 | min-width: $size; 325 | height: $size; 326 | line-height: $size; 327 | 328 | cursor: pointer; 329 | font-size: $size - 6px; 330 | padding-left: $size + 4px; 331 | 332 | > * { 333 | position: absolute; 334 | left: 0; 335 | top: 0; 336 | 337 | display: inline-block; 338 | z-index: 1; 339 | } 340 | 341 | input { 342 | @include opacity(0); 343 | } 344 | 345 | span { 346 | width: $size; 347 | height: $size; 348 | z-index: 2; 349 | 350 | @include border-radius(50%); 351 | @include transition(background-color .3s); 352 | @include box-shadow(inset 0 0 0 .5px rgba(100, 100, 100, .500)); 353 | 354 | &:before, &:after { 355 | position: absolute; 356 | display: inline-block; 357 | } 358 | 359 | &:after { 360 | content: ''; 361 | 362 | left: $dotSpaceSize; 363 | top: $dotSpaceSize; 364 | 365 | width: calc(100% - #{$dotSpaceSize * 2}); 366 | height: calc(100% - #{$dotSpaceSize * 2}); 367 | background-color: white; 368 | 369 | @include scale(0); 370 | @include border-radius(50%); 371 | @include transition(transform .3s); 372 | } 373 | } 374 | 375 | input:checked ~ span { 376 | background-color: $bgColor; 377 | @include box-shadow(inset 1px 1px 1px rgba(0, 0, 0, .200)); 378 | 379 | &:after { 380 | @include box-shadow(1px 1px 1px rgba(0, 0, 0, .200)); 381 | @include scale(1); 382 | } 383 | } 384 | 385 | &.loading { 386 | cursor: not-allowed; 387 | 388 | span { 389 | &:before { 390 | content: ''; 391 | left: 0; 392 | top: 0; 393 | z-index: 1; 394 | 395 | width: 100%; 396 | height: 100%; 397 | 398 | @include box-shadow(inset 0 0 1px rgba(0, 0, 0, .15)); 399 | @include border-radius(50%); 400 | @include background-image(linear-gradient(rgba(184, 241, 148, 1.00) 0%, rgba(89, 178, 169, 1.00) 100%)); 401 | @include animation(_CircleAnimation 1s linear infinite); 402 | } 403 | 404 | &:after { 405 | left: $loadingSize; 406 | top: $loadingSize; 407 | z-index: 2; 408 | 409 | width: calc(100% - #{$loadingSize * 2}); 410 | height: calc(100% - #{$loadingSize * 2}); 411 | 412 | background-color: rgba(255, 255, 255, 1.00); 413 | 414 | @include scale(1); 415 | @include border-radius(50%); 416 | @include box-shadow(0 0 1px rgba(0, 0, 0, .1)); 417 | } 418 | } 419 | } 420 | 421 | input[disabled] { 422 | cursor: not-allowed; 423 | 424 | ~ span { 425 | cursor: not-allowed; 426 | @include box-shadow(inset 0 0 0 .5px rgba(100, 100, 100, .300)); 427 | } 428 | } 429 | 430 | @include CircleAnimation; 431 | } 432 | 433 | // 434 | // @name Ginkgo's Switch UI 435 | // 436 | // @param $bgColor 底色 437 | // @param $height 高度 438 | // @param $width 寬高 439 | // @param $loadingSize Loading 寬高 440 | // 441 | // @example: 442 | // 446 | // 447 | @mixin switch($bgColor: rgba(69, 139, 244, 1.00), $height: 20px, $width: 40px, $loadingSize: 4px) { 448 | position: relative; 449 | display: inline-block; 450 | 451 | height: $height; 452 | line-height: $height; 453 | 454 | cursor: pointer; 455 | font-size: $height - 6px; 456 | padding-left: $height + 4px; 457 | 458 | input { 459 | position: absolute; 460 | @include opacity(0); 461 | } 462 | 463 | span { 464 | position: relative; 465 | display: inline-block; 466 | width: $width; 467 | height: $height; 468 | 469 | overflow: hidden; 470 | @include border-radius($height / 2); 471 | 472 | &:before, &:after { 473 | content: ''; 474 | position: absolute; 475 | display: inline-block; 476 | } 477 | 478 | &:before { 479 | left: 0; 480 | top: 0; 481 | 482 | width: 100%; 483 | height: 100%; 484 | 485 | border: 1px solid rgba(223, 223, 223, 1.00); 486 | background-color: white; 487 | 488 | @include border-radius($height / 2); 489 | @include box-shadow(inset 0px 0px 0px 0px rgba(223, 223, 223, 1)); 490 | @include transition(box-shadow 0.4s, background-color .2s); 491 | } 492 | &:after { 493 | left: 1px; 494 | top: 1px; 495 | 496 | width: $height - 1px * 2; 497 | height: $height - 1px * 2; 498 | 499 | background-color: rgba(255, 255, 255, 1.00); 500 | @include border-radius(50%); 501 | @include transition(background-color 0.4s, left 0.2s); 502 | @include box-shadow(0 1px 3px rgba(0, 0, 0, 0.4)); 503 | } 504 | } 505 | 506 | &:not(.loading) input:checked ~ span { 507 | &:before { 508 | background-color: $bgColor; 509 | border-color: adjust-lightness($bgColor, -5%); 510 | @include box-shadow(inset 0px 0px 0px 11px $bgColor); 511 | } 512 | &:after { 513 | left: $width + 1px - $height; 514 | } 515 | } 516 | 517 | &.loading { 518 | cursor: not-allowed; 519 | 520 | span { 521 | width: $height; 522 | height: $height; 523 | 524 | &:before { 525 | z-index: 1; 526 | border-width: 0; 527 | 528 | @include box-shadow(inset 0 0 1px rgba(0, 0, 0, .15)); 529 | @include border-radius(50%); 530 | @include background-image(linear-gradient(rgba(184, 241, 148, 1.00) 0%, rgba(89, 178, 169, 1.00) 100%)); 531 | @include animation(_CircleAnimation 1s linear infinite); 532 | } 533 | 534 | &:after { 535 | z-index: 2; 536 | 537 | left: $loadingSize; 538 | top: $loadingSize; 539 | 540 | width: calc(100% - #{$loadingSize * 2}); 541 | height: calc(100% - #{$loadingSize * 2}); 542 | 543 | background-color: rgba(255, 255, 255, 1.00); 544 | @include border-radius(50%); 545 | @include box-shadow(0 0 1px rgba(0, 0, 0, .1)); 546 | } 547 | } 548 | } 549 | 550 | input[disabled] { 551 | cursor: not-allowed; 552 | 553 | ~ span { 554 | cursor: not-allowed; 555 | 556 | &:before { 557 | border: 1px solid rgba(215, 215, 215, 1.00); 558 | background-color: rgba(235, 235, 235, 1.00); 559 | } 560 | &:after { 561 | @include box-shadow(0 0 0 1px rgba(215, 215, 215, 1)); 562 | background-color: rgba(245, 245, 245, 1.00); 563 | } 564 | } 565 | } 566 | 567 | @include CircleAnimation; 568 | } 569 | 570 | // 571 | // @name Ginkgo's Textarea UI 572 | // 573 | // @param $resize 是否可以移動大小 574 | // @param $borderColor 框色 575 | // 576 | // @example: 577 | // 578 | // 579 | @mixin textarea($resize: false, $borderColor: rgba(100, 175, 235, 1)) { 580 | padding: 4px; 581 | font-size: 16px; 582 | color: rgba(120, 120, 120, 1.00); 583 | border: 1px solid rgba(200, 200, 200, 1.00); 584 | @include border-radius (2px); 585 | @include transition(box-shadow .3s, border-color .3s); 586 | 587 | @if $resize == false { 588 | resize: none; 589 | } 590 | 591 | @include input-placeholder { 592 | font-weight: normal; 593 | color: rgba(175, 175, 175, 1); 594 | font-size: 14px; 595 | } 596 | 597 | &:focus { 598 | outline: 0; 599 | border: 1px solid $borderColor; 600 | @include box-shadow(0 0 8px rgba(red($borderColor), green($borderColor), blue($borderColor), .6)); 601 | } 602 | 603 | &[readonly] { 604 | cursor: not-allowed; 605 | background-color: rgba(250, 250, 250, 1); 606 | border: 1px solid rgba(225, 225, 225, 1.00); 607 | 608 | @include input-placeholder { 609 | color: rgba(200, 200, 200, 1); 610 | } 611 | 612 | &:focus { 613 | border: 1px solid rgba(225, 225, 225, 1.00); 614 | @include box-shadow(none); 615 | } 616 | } 617 | 618 | @content; 619 | } 620 | 621 | -------------------------------------------------------------------------------- /cmd/libs/php/S3.php: -------------------------------------------------------------------------------- 1 | 5 | * @copyright Copyright (c) 2015 - 2019, Ginkgo 6 | * @license http://opensource.org/licenses/MIT MIT License 7 | * @link https://www.ioa.tw/ 8 | */ 9 | 10 | final class S3Request { 11 | private $s3 = null, 12 | $verb, 13 | $bucket, 14 | $uri, 15 | $resource = '', 16 | $parameters = [], 17 | $amzHeaders = [], 18 | $headers = [ 19 | 'Host' => '', 20 | 'Date' => '', 21 | 'Content-MD5' => '', 22 | 'Content-Type' => '' 23 | ]; 24 | 25 | public $fp = null, 26 | $data = null, 27 | $size = 0, 28 | $response = null; 29 | 30 | public function __construct($verb, $s3, $bucket = '', $uri = '') { 31 | $this->s3 = $s3; 32 | $this->verb = strtoupper($verb); 33 | $this->bucket = strtolower($bucket); 34 | $this->uri = $uri ? '/' . str_replace('%2F', '/', rawurlencode($uri)) : '/'; 35 | $this->resource = ($this->bucket ? '/' . $this->bucket : '') . $this->uri; 36 | $this->headers['Host'] = ($this->bucket ? $this->bucket . '.' : '') . 's3.amazonaws.com'; 37 | $this->headers['Date'] = gmdate('D, d M Y H:i:s T'); 38 | $this->response = new STDClass; 39 | $this->response->error = null; 40 | $this->response->body = ''; 41 | $this->response->code = null; 42 | } 43 | 44 | public function setParameter($key, $value) { 45 | $value && $this->parameters[$key] = $value; 46 | return $this; 47 | } 48 | 49 | public function setHeaders($arr) { 50 | foreach ($arr as $key => $value) 51 | $this->setHeader($key, $value); 52 | return $this; 53 | } 54 | 55 | public function setHeader($key, $value) { 56 | $value && $this->headers[$key] = $value; 57 | return $this; 58 | } 59 | 60 | public function setAmzHeaders($arr) { 61 | foreach ($arr as $key => $value) 62 | $this->setAmzHeader($key, $value); 63 | return $this; 64 | } 65 | 66 | public function setAmzHeader($key, $value) { 67 | $value && $this->amzHeaders[preg_match('/^x-amz-.*$/', $key) ? $key : 'x-amz-meta-' . $key] = $value; 68 | return $this; 69 | } 70 | 71 | public function setData($data) { 72 | $this->data = $data; 73 | $this->setSize(strlen($data)); 74 | return $this; 75 | } 76 | 77 | public function setFile($file, $mode = 'rb', $autoSetSize = true) { 78 | $this->fp = @fopen($file, $mode); 79 | $autoSetSize && $this->setSize(filesize($file)); 80 | return $this; 81 | } 82 | 83 | public function setSize($size) { 84 | $this->size = $size; 85 | return $this; 86 | } 87 | 88 | public function getSize() { 89 | return $this->size; 90 | } 91 | 92 | public function getFile() { 93 | return $this->fp; 94 | } 95 | 96 | public function isSuccessResponse(&$response, $codes = [200]) { 97 | $response = $this->getResponse(); 98 | return $response->error === null && in_array($response->code, $codes); 99 | } 100 | 101 | public function isFailResponse(&$response, $codes = [200]) { 102 | return !$this->isSuccessResponse($response, $codes); 103 | } 104 | 105 | public static function create($verb, $s3, $bucket = '', $uri = '') { 106 | return new S3Request($verb, $s3, $bucket, $uri); 107 | } 108 | 109 | private function makeAmz() { 110 | $amz = []; 111 | foreach ($this->amzHeaders as $header => $value) 112 | $value && array_push($amz, strtolower($header) . ':' . $value); 113 | 114 | if (!$amz) 115 | return ''; 116 | 117 | sort($amz); 118 | 119 | return "\n" . implode("\n", $amz); 120 | } 121 | 122 | private function makeHeader() { 123 | $headers = []; 124 | foreach ($this->amzHeaders as $header => $value) 125 | $value && array_push($headers, $header . ': ' . $value); 126 | 127 | foreach ($this->headers as $header => $value) 128 | $value && array_push($headers, $header . ': ' . $value); 129 | 130 | array_push($headers, 'Authorization: ' . $this->s3->getSignature($this->headers['Host'] == 'cloudfront.amazonaws.com' ? $this->headers['Date'] : $this->verb . "\n" . $this->headers['Content-MD5'] . "\n" . $this->headers['Content-Type'] . "\n" . $this->headers['Date'] . $this->makeAmz() . "\n" . $this->resource)); 131 | 132 | return $headers; 133 | } 134 | 135 | private function responseWriteCallback(&$curl, &$data) { 136 | if ($this->response->code == 200 && $this->fp !== null) 137 | return fwrite($this->fp, $data); 138 | 139 | $this->response->body .= $data; 140 | 141 | return strlen($data); 142 | } 143 | 144 | private function responseHeaderCallback(&$curl, &$data) { 145 | if (($strlen = strlen($data)) <= 2) 146 | return $strlen; 147 | 148 | if (substr($data, 0, 4) == 'HTTP') { 149 | $this->response->code = (int)substr($data, 9, 3); 150 | } else { 151 | list($header, $value) = explode(': ', trim($data), 2); 152 | $header == 'Last-Modified' && $this->response->headers['time'] = strtotime($value); 153 | $header == 'Content-Length' && $this->response->headers['size'] = (int)$value; 154 | $header == 'Content-Type' && $this->response->headers['type'] = $value; 155 | $header == 'ETag' && $this->response->headers['hash'] = $value{0} == '"' ? substr($value, 1, -1) : $value; 156 | preg_match('/^x-amz-meta-.*$/', $header) && $this->response->headers[$header] = is_numeric($value) ? (int)$value : $value; 157 | } return $strlen; 158 | } 159 | 160 | public function getResponse() { 161 | $query = ''; 162 | if ($this->parameters) { 163 | $query = substr($this->uri, -1) !== '?' ? '?' : '&'; 164 | foreach ($this->parameters as $var => $value) 165 | $query .= ($value == null) || ($value == '') ? $var . '&' : $var . '=' . rawurlencode($value) . '&'; 166 | 167 | $this->uri .= $query = substr($query, 0, -1); 168 | 169 | if (isset($this->parameters['acl']) || isset($this->parameters['location']) || isset($this->parameters['torrent']) || isset($this->parameters['logging'])) 170 | $this->resource .= $query; 171 | } 172 | 173 | $url = ($this->s3->isUseSsl() && extension_loaded('openssl') ? 'https://' : 'http://') . $this->headers['Host'] . $this->uri; 174 | 175 | $curlSetopts = [ 176 | CURLOPT_URL => $url, 177 | CURLOPT_USERAGENT => 'S3/php', 178 | CURLOPT_HTTPHEADER => $this->makeHeader(), 179 | CURLOPT_HEADER => false, 180 | CURLOPT_RETURNTRANSFER => false, 181 | CURLOPT_WRITEFUNCTION => [&$this, 'responseWriteCallback'], 182 | CURLOPT_HEADERFUNCTION => [&$this, 'responseHeaderCallback'], 183 | CURLOPT_FOLLOWLOCATION => true 184 | ]; 185 | 186 | $this->s3->isUseSsl() && $curlSetopts[CURLOPT_SSL_VERIFYHOST] = 1; 187 | $this->s3->isUseSsl() && $curlSetopts[CURLOPT_SSL_VERIFYPEER] = $this->s3->isVerifyPeer() ? 1 : FALSE; 188 | 189 | switch ($this->verb) { 190 | case 'PUT': case 'POST': 191 | if ($this->fp !== null) { 192 | $curlSetopts[CURLOPT_PUT] = true; 193 | $curlSetopts[CURLOPT_INFILE] = $this->fp; 194 | $this->size && $curlSetopts[CURLOPT_INFILESIZE] = $this->size; 195 | break; 196 | } 197 | 198 | $curlSetopts[CURLOPT_CUSTOMREQUEST] = $this->verb; 199 | 200 | if ($this->data !== null) { 201 | $curlSetopts[CURLOPT_POSTFIELDS] = $this->data; 202 | $this->size && $curlSetopts[CURLOPT_BUFFERSIZE] = $this->size; 203 | } 204 | break; 205 | 206 | case 'HEAD': 207 | $curlSetopts[CURLOPT_CUSTOMREQUEST] = 'HEAD'; 208 | $curlSetopts[CURLOPT_NOBODY] = true; 209 | break; 210 | 211 | case 'DELETE': 212 | $curlSetopts[CURLOPT_CUSTOMREQUEST] = 'DELETE'; 213 | break; 214 | 215 | case 'GET': default: break; 216 | } 217 | 218 | $curl = curl_init(); 219 | curl_setopt_array($curl, $curlSetopts); 220 | 221 | if (curl_exec($curl)) 222 | $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); 223 | else 224 | $this->response->error = [ 225 | 'code' => curl_errno($curl), 226 | 'message' => curl_error($curl), 227 | 'resource' => $this->resource 228 | ]; 229 | 230 | curl_close($curl); 231 | 232 | if ($this->response->error === null && isset($this->response->headers['type']) && $this->response->headers['type'] == 'application/xml' && isset($this->response->body) && ($this->response->body = simplexml_load_string($this->response->body))) 233 | if (!in_array($this->response->code, [200, 204]) && isset($this->response->body->Code, $this->response->body->Message)) 234 | $this->response->error = [ 235 | 'code' => (string)$this->response->body->Code, 236 | 'message' => (string)$this->response->body->Message, 237 | 'resource' => isset($this->response->body->Resource) ? (string)$this->response->body->Resource : null 238 | ]; 239 | 240 | $this->fp !== null && is_resource($this->fp) && fclose($this->fp); 241 | 242 | return $this->response; 243 | } 244 | } 245 | 246 | class S3 { 247 | const ACL_PRIVATE = 'private', 248 | ACL_PUBLIC_READ = 'public-read', 249 | ACL_PUBLIC_READ_WRITE = 'public-read-write', 250 | ACL_AUTHENTICATED_READ = 'authenticated-read'; 251 | 252 | private $accessKey = null, 253 | $secretKey = null, 254 | $isUseSsl = false, 255 | $isVerifyPeer = true; 256 | 257 | public static $extensions = ['jpg' => ['image/jpeg', 'image/pjpeg'], 'gif' => ['image/gif'], 'png' => ['image/png', 'image/x-png'], 'pdf' => ['application/pdf', 'application/x-download'], 'gz' => ['application/x-gzip'], 'zip' => ['application/x-zip', 'application/zip', 'application/x-zip-compressed'], 'swf' => ['application/x-shockwave-flash'], 'tar' => ['application/x-tar'], 'bz' => ['application/x-bzip'], 'bz2' => ['application/x-bzip2'], 'txt' => ['text/plain'], 'html' => ['text/html'], 'htm' => ['text/html'], 'ico' => ['image/x-icon'], 'css' => ['text/css'], 'js' => ['application/x-javascript'], 'xml' => ['text/xml'], 'ogg' => ['application/ogg'], 'wav' => ['audio/x-wav', 'audio/wave', 'audio/wav'], 'avi' => ['video/x-msvideo'], 'mpg' => ['video/mpeg'], 'mov' => ['video/quicktime'], 'mp3' => ['audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'], 'mpeg' => ['video/mpeg'], 'flv' => ['video/x-flv'], 'php' => ['application/x-httpd-php'], 'bin' => ['application/macbinary'], 'psd' => ['application/x-photoshop'], 'ai' => ['application/postscript'], 'ppt' => ['application/powerpoint', 'application/vnd.ms-powerpoint'], 'wbxml' => ['application/wbxml'], 'tgz' => ['application/x-tar', 'application/x-gzip-compressed'], 'jpeg' => ['image/jpeg', 'image/pjpeg'], 'jpe' => ['image/jpeg', 'image/pjpeg'], 'bmp' => ['image/bmp', 'image/x-windows-bmp'], 'shtml' => ['text/html'], 'text' => ['text/plain'], 'doc' => ['application/msword'], 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'], 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip'], 'word' => ['application/msword', 'application/octet-stream'], 'json' => ['application/json', 'text/json'], 'svg' => ['image/svg+xml'], 'mp2' => ['audio/mpeg'], 'exe' => ['application/octet-stream', 'application/x-msdownload'], 'tif' => ['image/tiff'], 'tiff' => ['image/tiff'], 'asc' => ['text/plain'], 'xsl' => ['text/xml'], 'hqx' => ['application/mac-binhex40'], 'cpt' => ['application/mac-compactpro'], 'csv' => ['text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel'], 'dms' => ['application/octet-stream'], 'lha' => ['application/octet-stream'], 'lzh' => ['application/octet-stream'], 'class' => ['application/octet-stream'], 'so' => ['application/octet-stream'], 'sea' => ['application/octet-stream'], 'dll' => ['application/octet-stream'], 'oda' => ['application/oda'], 'eps' => ['application/postscript'], 'ps' => ['application/postscript'], 'smi' => ['application/smil'], 'smil' => ['application/smil'], 'mif' => ['application/vnd.mif'], 'xls' => ['application/excel', 'application/vnd.ms-excel', 'application/msexcel'], 'wmlc' => ['application/wmlc'], 'dcr' => ['application/x-director'], 'dir' => ['application/x-director'], 'dxr' => ['application/x-director'], 'dvi' => ['application/x-dvi'], 'gtar' => ['application/x-gtar'], 'php4' => ['application/x-httpd-php'], 'php3' => ['application/x-httpd-php'], 'phtml' => ['application/x-httpd-php'], 'phps' => ['application/x-httpd-php-source'], 'sit' => ['application/x-stuffit'], 'xhtml' => ['application/xhtml+xml'], 'xht' => ['application/xhtml+xml'], 'mid' => ['audio/midi'], 'midi' => ['audio/midi'], 'mpga' => ['audio/mpeg'], 'aif' => ['audio/x-aiff'], 'aiff' => ['audio/x-aiff'], 'aifc' => ['audio/x-aiff'], 'ram' => ['audio/x-pn-realaudio'], 'rm' => ['audio/x-pn-realaudio'], 'rpm' => ['audio/x-pn-realaudio-plugin'], 'ra' => ['audio/x-realaudio'], 'rv' => ['video/vnd.rn-realvideo'], 'log' => ['text/plain', 'text/x-log'], 'rtx' => ['text/richtext'], 'rtf' => ['text/rtf'], 'mpe' => ['video/mpeg'], 'qt' => ['video/quicktime'], 'movie' => ['video/x-sgi-movie'], 'xl' => ['application/excel'], 'eml' => ['message/rfc822']]; 258 | 259 | public function __construct($accessKey, $secretKey, $isUseSsl = false, $isVerifyPeer = true) { 260 | $this->accessKey = $accessKey; 261 | $this->secretKey = $secretKey; 262 | $this->isUseSsl = $isUseSsl; 263 | $this->isVerifyPeer = $isVerifyPeer; 264 | } 265 | 266 | public function getSignature($string) { 267 | return 'AWS ' . $this->accessKey . ':' . $this->getHash($string); 268 | } 269 | 270 | private function getHash($string) { 271 | return base64_encode(extension_loaded('hash') ? hash_hmac('sha1', $string, $this->secretKey, true) : pack('H*', sha1((str_pad($this->secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . pack('H*', sha1((str_pad($this->secretKey, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . $string))))); 272 | } 273 | 274 | public function isUseSsl() { 275 | return $this->isUseSsl; 276 | } 277 | 278 | public function isVerifyPeer() { 279 | return $this->isVerifyPeer; 280 | } 281 | 282 | public function test() { 283 | return S3Request::create('GET', $this)->isSuccessResponse($rest); 284 | } 285 | 286 | public static function fileMD5($filePath) { 287 | return base64_encode(md5_file($filePath, true)); 288 | } 289 | 290 | public function deleteObject($bucket, $uri) { 291 | return $rest = S3Request::create('DELETE', $this, $bucket, $uri)->isSuccessResponse($rest, [200, 204]) ? true : false; 292 | } 293 | 294 | public function bucket($bucket, $prefix = null, $marker = null, $maxKeys = null, $delimiter = null, $returnCommonPrefixes = false) { 295 | if (S3Request::create('GET', $this, $bucket)->setParameter('prefix', $prefix)->setParameter('marker', $marker)->setParameter('max-keys', $maxKeys)->setParameter('delimiter', $delimiter)->isFailResponse($rest)) 296 | return false; 297 | 298 | $nextMarker = null; 299 | $results = []; 300 | 301 | if (isset($rest->body, $rest->body->Contents)) 302 | foreach ($rest->body->Contents as $content) 303 | $results[$nextMarker = (string)$content->Key] = [ 304 | 'name' => (string)$content->Key, 305 | 'time' => date('Y-m-d H:i:s', strtotime((string)$content->LastModified)), 306 | 'size' => (int)$content->Size, 307 | 'hash' => substr((string)$content->ETag, 1, -1) 308 | ]; 309 | if ($returnCommonPrefixes && isset($rest->body, $rest->body->CommonPrefixes)) 310 | foreach ($rest->body->CommonPrefixes as $content) 311 | $results[(string)$content->Prefix] = ['prefix' => (string)$content->Prefix]; 312 | 313 | if (isset($rest->body, $rest->body->IsTruncated) && (((string)$rest->body->IsTruncated) == 'false')) 314 | return $results; 315 | 316 | if (isset($rest->body, $rest->body->NextMarker)) 317 | $nextMarker = (string)$rest->body->NextMarker; 318 | 319 | if ($maxKeys || !$nextMarker || (((string)$rest->body->IsTruncated) != 'true')) 320 | return $results; 321 | 322 | do { 323 | if (S3Request::create('GET', $this, $bucket)->setParameter('marker', $nextMarker)->setParameter('prefix', $prefix)->setParameter('delimiter', $delimiter)->isFailResponse($rest)) 324 | break; 325 | 326 | if (isset($rest->body, $rest->body->Contents)) 327 | foreach ($rest->body->Contents as $content) 328 | $results[$nextMarker = (string)$content->Key] = [ 329 | 'name' => (string)$content->Key, 330 | 'time' => date('Y-m-d H:i:s', strtotime((string)$content->LastModified)), 331 | 'size' => (int)$content->Size, 332 | 'hash' => substr((string)$content->ETag, 1, -1) 333 | ]; 334 | 335 | if ($returnCommonPrefixes && isset($rest->body, $rest->body->CommonPrefixes)) 336 | foreach ($rest->body->CommonPrefixes as $content) 337 | $results[(string)$content->Prefix] = ['prefix' => (string)$content->Prefix]; 338 | 339 | if (isset($rest->body, $rest->body->NextMarker)) 340 | $nextMarker = (string)$rest->body->NextMarker; 341 | 342 | } while (($rest !== false) && (((string)$rest->body->IsTruncated) == 'true')); 343 | 344 | return $results; 345 | } 346 | 347 | private static function getMimeByExtension($file) { 348 | $extension = strtolower(substr(strrchr($file, '.'), 1)); 349 | $mime = isset(S3::$extensions[$extension][0]) ? S3::$extensions[$extension][0] : null; 350 | 351 | return $extensions[$extension] = $mime !== null ? $mime : 'text/plain'; 352 | } 353 | 354 | public function putObject($filePath, $bucket, $s3Path, $acl = self::ACL_PUBLIC_READ, $amzHeaders = [], $headers = []) { 355 | if (!(is_file($filePath) && is_readable($filePath))) 356 | return false; 357 | 358 | $request = S3Request::create('PUT', $this, $bucket, $s3Path)->setHeaders(array_merge(['Content-Type' => self::getMimeByExtension($filePath), 'Content-MD5' => self::fileMD5($filePath)], $headers))->setAmzHeaders(array_merge(['x-amz-acl' => $acl], $amzHeaders))->setFile($filePath); 359 | 360 | if ($request->getSize() < 0 || $request->getFile() === null) 361 | return false; 362 | 363 | return $request->isSuccessResponse($rest) ? true : false; 364 | } 365 | } -------------------------------------------------------------------------------- /scss/public.scss: -------------------------------------------------------------------------------- 1 | // 2 | // @author OA Wu 3 | // @copyright Copyright (c) 2015 - 2019, Ginkgo 4 | // @license http://opensource.org/licenses/MIT MIT License 5 | // @link https://www.ioa.tw/ 6 | // 7 | 8 | @import "Ginkgo"; 9 | 10 | @import "compass/css3/font-face"; 11 | 12 | @include font-face("icon", font-files( 13 | "icomoon/fonts/icomoon.eot", 14 | "icomoon/fonts/icomoon.woff", 15 | "icomoon/fonts/icomoon.ttf", 16 | "icomoon/fonts/icomoon.svg")); 17 | 18 | .-pf { position: fixed; } 19 | .-pa { position: absolute; } 20 | .-pr { position: relative; } 21 | .-dib { display: inline-block; } 22 | .-dn { display: none; } 23 | .-t0 { top: 0; } 24 | .-b0 { bottom: 0; } 25 | .-l0 { left: 0; } 26 | .-r0 { right: 0; } 27 | .-w0 { width: 0; } 28 | .-h0 { height: 0; } 29 | .-w50 { width: 50%; } 30 | .-w100 { width: 100%; } 31 | .-h100 { height: 100%; } 32 | .-fl-l { float: left; } 33 | .-fl-r { float: right; } 34 | .-co-w { color: white; } 35 | .-ta-l { text-align: left; } 36 | .-ta-c { text-align: center; } 37 | .-td-n { text-decoration: none; } 38 | .-bg-w { background-color: white; } 39 | .-m0 { margin: 0; } 40 | .-p0 { padding: 0; } 41 | .-oh { overflow: hidden; } 42 | .-pf-fu { @extend .-pf; @extend .-dib; @extend .-t0; @extend .-l0; @extend .-w100; @extend .-h100; } 43 | .-pa-fu { @extend .-pa; @extend .-dib; @extend .-t0; @extend .-l0; @extend .-w100; @extend .-h100; } 44 | .-ff0 { font-family: "微軟正黑體", "Open sans", "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif; } 45 | .-b-ffi:before { font-family: "icon"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; } 46 | .-cu-p { cursor: pointer; } 47 | .-cu-n { cursor: not-allowed; } 48 | 49 | .-op1 { @include opacity(1); } 50 | .-op0 { @include opacity(0); } 51 | .-fl-x { @include clearfix(); } 52 | .-doc3 { @include overflow-docx3(); } 53 | .-br1 { @include border-radius(1px); } 54 | .-br3 { @include border-radius(3px); } 55 | .-br4 { @include border-radius(4px); } 56 | .-br5 { @include border-radius(5px); } 57 | .-br12 { @include border-radius(12px); } 58 | .-br50 { @include border-radius(50%); } 59 | 60 | $i: 10; 61 | .-fs#{$i} { font-size: #{$i}px; } 62 | 63 | $i: 14; 64 | .-fs#{$i} { font-size: #{$i}px; } 65 | 66 | $i: 18; 67 | .-fs#{$i} { font-size: #{$i}px; } 68 | 69 | 70 | .-cxh { 71 | @extend .-pf; 72 | @extend .-t0; 73 | @extend .-l0; 74 | @extend .-dn; 75 | @extend .-w0; 76 | @extend .-h0; 77 | @extend .-op0; 78 | z-index: -999; 79 | } 80 | $linkColor: rgba(24, 144, 255, 1.00); 81 | .-link { color: $linkColor; @include transition(color .3s); &:hover { color: adjust-lightness($linkColor, -10%); } } 82 | .-bg-cv { 83 | background-size: cover; 84 | background-position: center center; 85 | background-repeat: no-repeat; 86 | } 87 | 88 | .-uox, .-uox-c { 89 | @extend .-pf; 90 | @extend .-dib; 91 | @extend .-oh; 92 | @extend .-bg-w; 93 | @extend .-op0; 94 | 95 | top: -99999px; 96 | left: -99999px; 97 | z-index: -99999; 98 | @include transition(opacity .3s); 99 | } 100 | .-uox-c { 101 | @extend .-t0; 102 | @extend .-w100; 103 | @extend .-h100; 104 | left: -100%; 105 | 106 | background-color: rgba(0, 0, 0, 0.3); 107 | 108 | &[for] { 109 | @extend .-cu-p; 110 | } 111 | } 112 | 113 | .-slt { 114 | @extend .-pr; 115 | @extend .-dib; 116 | @extend .-bg-w; 117 | @extend .-cu-p; 118 | @extend .-b-ffi; 119 | 120 | &:before { 121 | @extend .-pa; 122 | @extend .-dib; 123 | content: '\f000e'; 124 | right: 4px; 125 | width: 20px; 126 | height: 20px; 127 | line-height: 20px; 128 | top: calc(50% - #{20px / 2}); 129 | color: rgba(190, 190, 190, 1); 130 | } 131 | 132 | select { 133 | @extend .-dib; 134 | @extend .-w100; 135 | @extend .-h100; 136 | @extend .-doc3; 137 | @extend .-bg-w; 138 | @extend .-cu-p; 139 | @extend .-fs14; 140 | @extend .-br4; 141 | 142 | color: rgba(75, 75, 75, 1); 143 | padding: 0 8px; 144 | padding-right: 20px + 4px; 145 | border: 1px solid rgba(210, 210, 210, 1.00); 146 | color: rgba(100, 100, 100, 1.00); 147 | 148 | -moz-appearance: none; 149 | -webkit-appearance: none; 150 | &::-ms-expand { display: none; } 151 | @include transition(border-color .3s, box-shadow .3s); 152 | 153 | &:hover { 154 | border-color: rgba(63, 169, 255, 1.00); 155 | } 156 | &:focus { 157 | outline: 0; 158 | border-color: rgba(63, 169, 255, 1.00); 159 | @include box-shadow(0 0 0 2px rgba(24, 144, 255, .2)); 160 | } 161 | } 162 | } 163 | 164 | #{elements-of-type(text-input)} { 165 | @extend .-br3; 166 | color: rgba(0, 0, 0, .65); 167 | font-size: 14px; 168 | border: 1px solid rgba(217, 217, 217, 1.00); 169 | background-color: rgba(255, 255, 255, 1.00); 170 | @include box-shadow(inset 1px 1px 1px rgba(0, 0, 0, .075), 1px 1px 1px rgba(255, 255, 255, 1)); 171 | @include transition(box-shadow .3s, border-color .3s); 172 | 173 | @include input-placeholder { 174 | color: rgba(191, 191, 191, 1.00); 175 | } 176 | 177 | &:focus { 178 | outline: 0; 179 | border-color: rgba(63, 169, 255, 1.00); 180 | @include box-shadow(0 0 0 2px rgba(24, 144, 255, .2)); 181 | } 182 | 183 | &[readonly] { 184 | @extend .-cu-n; 185 | color: rgba(0, 0, 0, .25); 186 | background-color: rgba(245, 245, 245, 1.00); 187 | border-color: rgb(217, 217, 217); 188 | @include box-shadow(none); 189 | } 190 | } 191 | 192 | html { 193 | @extend .-pr; 194 | } 195 | 196 | body { 197 | @extend .-pr; 198 | @extend .-dib; 199 | @extend .-w100; 200 | @extend .-ff0; 201 | @extend .-ta-c; 202 | @extend .-m0; 203 | @extend .-p0; 204 | 205 | color: rgba(90, 90, 90, 1.00); 206 | font-size: medium; 207 | background-color: rgba(241, 242, 244, 1.00); 208 | @include selection(rgba(120, 186, 255, 1.00), rgba(255, 255, 255, 1)); 209 | } 210 | 211 | input { 212 | padding: 0 11px; 213 | } 214 | 215 | textarea { 216 | padding: 7px 11px; 217 | resize: none; 218 | } 219 | 220 | @include keyframes(rotate){ 221 | 100% { 222 | @include rotate(360deg); 223 | } 224 | } 225 | 226 | 227 | body > * { 228 | position: fixed; 229 | display: inline-block; 230 | z-index: 2; 231 | } 232 | #map { 233 | @extend .-pf-fu; 234 | position: fixed !important; 235 | z-index: 1 !important; 236 | } 237 | .marker { 238 | @extend .-dib; 239 | 240 | span { 241 | @extend .-pa; 242 | @extend .-dib; 243 | @extend .-co-w; 244 | @extend .-doc3; 245 | @extend .-br12; 246 | left: calc(50% - #{80px / 2}); 247 | top: calc(50% - #{24px / 2}); 248 | width: 80px; 249 | height: 24px; 250 | line-height: 24px - 4px; 251 | 252 | margin: 0 auto; 253 | background-color: rgba(245, 98, 98, 1.00); 254 | white-space: nowrap; 255 | border: 1px solid white; 256 | padding: 2px 8px; 257 | @include box-shadow(0 0 2px rgba(0, 0, 0, .3), 0 0 1px rgba(0, 0, 0, .1)); 258 | } 259 | b { 260 | @extend .-pa; 261 | @extend .-dib; 262 | @extend .-br50; 263 | @extend .-co-w; 264 | left: calc(50% - #{28px / 2}); 265 | top: calc(50% - #{28px / 2}); 266 | width: 28px; 267 | height: 28px; 268 | 269 | background-color: rgba(205, 62, 62, 1.00); 270 | line-height: 28px - 2px; 271 | border: 1px solid white; 272 | @include box-shadow(0 0 2px rgba(0, 0, 0, .3), 0 0 1px rgba(0, 0, 0, .1)); 273 | } 274 | } 275 | 276 | #create { 277 | @extend .-cu-p; 278 | @extend .-bg-w; 279 | @extend .-br4; 280 | top: 8px + 32px + 8px; 281 | right: 8px; 282 | padding: 0 16px; 283 | height: 36px; 284 | line-height: 36px - 2px; 285 | border: 0; 286 | z-index: 2; 287 | font-size: 15px; 288 | letter-spacing: 1px; 289 | color: rgba(0, 0, 0, .5); 290 | 291 | @include text-shadow(0 0 1px rgba(0, 0, 0, .3)); 292 | @include transition(color .3s); 293 | 294 | &:hover { 295 | color: rgba(0, 0, 0, .75); 296 | } 297 | &:focus { 298 | outline: 0; 299 | } 300 | @include box-shadow(0px 1px 4px rgba(0,0,0,0.3)); 301 | } 302 | 303 | #in, #out { 304 | @extend .-b-ffi; 305 | @extend .-cu-p; 306 | @extend .-br3; 307 | right: 8px; 308 | width: 32px; 309 | height: 32px; 310 | line-height: 32px; 311 | background-color: rgba(255, 255, 255, .85); 312 | @include transition(background-color .3s); 313 | @include box-shadow(0px 1px 4px rgba(0,0,0,0.3)); 314 | &:hover { 315 | @extend .-bg-w; 316 | } 317 | } 318 | #in { 319 | bottom: 16px + 8px + 32px + 8px; 320 | 321 | &:before { 322 | content: '\f0016'; 323 | } 324 | } 325 | #out { 326 | bottom: 16px + 8px; 327 | 328 | &:before { 329 | content: '\f000d'; 330 | } 331 | } 332 | 333 | h1 { 334 | @extend .-bg-w; 335 | @extend .-fs18; 336 | @extend .-m0; 337 | @extend .-br3; 338 | left: 8px; 339 | top: 8px; 340 | height: 48px; 341 | line-height: 48px; 342 | @include box-shadow(0px 1px 4px rgba(0,0,0,0.3)); 343 | 344 | label { 345 | @extend .-cu-p; 346 | @extend .-dib; 347 | @extend .-w100; 348 | @extend .-h100; 349 | padding: 0 20px; 350 | } 351 | } 352 | .gb { 353 | @extend .-link; 354 | @extend .-b-ffi; 355 | @extend .-td-n; 356 | @extend .-bg-w; 357 | @extend .-br3; 358 | 359 | right: 8px; 360 | top: 8px; 361 | padding: 0 16px; 362 | height: 32px; 363 | line-height: 32px; 364 | font-size: 14px; 365 | 366 | @include box-shadow(0px 1px 4px rgba(0,0,0,0.3)); 367 | &:before { content: '\eab0'; margin-right: 4px; } 368 | } 369 | 370 | #readme, #add, #video { 371 | @extend .-uox; 372 | @extend .-br5; 373 | background-color: rgba(245, 245, 245, 1); 374 | @include box-shadow(0 7px 8px -4px rgba(0,0,0,0.2), 0 13px 19px 2px rgba(0,0,0,0.14), 0 5px 24px 4px rgba(0,0,0,0.12)); 375 | 376 | &:before { 377 | @extend .-dib; 378 | @extend .-w100; 379 | @extend .-bg-w; 380 | content: attr(data-title); 381 | height: 44px; 382 | line-height: 44px; 383 | border-bottom: 1px solid rgba(216, 216, 216, 1); 384 | } 385 | 386 | label[for], button { 387 | @extend .-cu-p; 388 | @extend .-pa; 389 | @extend .-b0; 390 | @extend .-l0; 391 | @extend .-dib; 392 | @extend .-w50; 393 | @extend .-fs14; 394 | @extend .-bg-w; 395 | height: 44px; 396 | line-height: 44px; 397 | border: 0; 398 | border-top: 1px solid rgba(216, 216, 216, 1); 399 | 400 | + button { 401 | left: 50%; 402 | border-left: 1px solid rgba(222, 222, 222, 1); 403 | } 404 | &:focus { 405 | outline: 0; 406 | } 407 | } 408 | label:not([for]) { 409 | @extend .-pr; 410 | @extend .-dib; 411 | 412 | width: calc(100% - #{8px * 2}); 413 | height: 32px; 414 | margin: 8px 8px; 415 | padding-left: 56px + 8px; 416 | padding-right: 8px; 417 | margin-top: 16px; 418 | 419 | + label { 420 | margin-top: 8px; 421 | } 422 | 423 | &:before { 424 | @extend .-pa; 425 | @extend .-l0; 426 | @extend .-t0; 427 | @extend .-dib; 428 | @extend .-fs14; 429 | text-align: right; 430 | content: attr(data-title) ':'; 431 | width: 56px; 432 | height: 32px; 433 | line-height: 32px; 434 | } 435 | } 436 | input { 437 | @extend .-w100; 438 | height: 32px; 439 | } 440 | +label { 441 | @extend .-uox-c; 442 | } 443 | } 444 | #add { 445 | width: 450px; 446 | height: 348px; 447 | @include range-width(0, 500px) { 448 | width: calc(100% - #{8px * 2}); 449 | }; 450 | } 451 | #_add { 452 | @extend .-cxh; 453 | &:checked { 454 | ~ #add { 455 | @extend .-op1; 456 | top: calc(50% - #{348px / 2}); 457 | left: calc(50% - #{450px / 2}); 458 | z-index: 999; 459 | 460 | @include transition(transform .3s cubic-bezier(.68, -0.275, .825, 0.115)); 461 | @include range-width(0, 500px) { 462 | left: 8px; 463 | } 464 | + .-uox-c { 465 | @extend .-t0; 466 | @extend .-l0; 467 | @extend .-op1; 468 | z-index: 999 - 5; 469 | } 470 | } 471 | } 472 | } 473 | #_readme { 474 | @extend .-cxh; 475 | &:checked { 476 | ~ #readme { 477 | @extend .-op1; 478 | top: calc(50% - #{348px / 2}); 479 | left: calc(50% - #{450px / 2}); 480 | z-index: 999; 481 | 482 | @include transition(transform .3s cubic-bezier(.68, -0.275, .825, 0.115)); 483 | @include range-width(0, 500px) { 484 | left: 8px; 485 | } 486 | + .-uox-c { 487 | @extend .-t0; 488 | @extend .-l0; 489 | @extend .-op1; 490 | z-index: 999 - 5; 491 | } 492 | } 493 | } 494 | } 495 | #readme { 496 | width: 450px; 497 | height: 348px; 498 | @include range-width(0, 500px) { 499 | width: calc(100% - #{8px * 2}); 500 | }; 501 | p { 502 | @extend .-ta-l; 503 | padding: 0 16px; 504 | } 505 | a { 506 | @extend .-link; 507 | @extend .-td-n; 508 | } 509 | label { 510 | width: 100% !important; 511 | } 512 | span { 513 | @extend .-dib; 514 | @extend .-fl-x; 515 | margin: 0 auto; 516 | margin-top: 12px; 517 | 518 | > * { 519 | @extend .-fl-l; 520 | + * { 521 | margin-left: 20px; 522 | } 523 | } 524 | a { 525 | @extend .-b-ffi; 526 | @extend .-dib; 527 | height: 20px; 528 | line-height: 20px; 529 | &:before { 530 | content: '\e900'; 531 | margin-right: 3px; 532 | @include scale(.75); 533 | @include opacity(.5); 534 | } 535 | } 536 | i { 537 | @extend .-dib; 538 | width: 1px; 539 | height: 16px; 540 | margin-top: 3px; 541 | border-left: 1px solid rgba(200, 200, 200, 1); 542 | + a { 543 | &:before { 544 | content: '\eab0'; 545 | } 546 | } 547 | } 548 | } 549 | } 550 | #video { 551 | width: 650px; 552 | height: 480px; 553 | @include range-width(0, 670px) { 554 | width: calc(100% - #{8px * 2}); 555 | }; 556 | iframe { 557 | width: 100%; 558 | height: 100%; 559 | max-height: calc(100% - 44px); 560 | } 561 | a { 562 | @extend .-link; 563 | @extend .-cu-p; 564 | @extend .-pa; 565 | @extend .-r0; 566 | @extend .-t0; 567 | @extend .-dib; 568 | padding: 0 10px; 569 | height: 44px; 570 | line-height: 44px; 571 | z-index: 2; 572 | } 573 | &.show { 574 | @extend .-op1; 575 | top: calc(50% - #{480px / 2}); 576 | left: calc(50% - #{650px / 2}); 577 | @include range-width(0, 670px) { 578 | left: 8px; 579 | } 580 | z-index: 999; 581 | @include transition(transform .3s cubic-bezier(.68, -0.275, .825, 0.115)); 582 | 583 | + .-uox-c { 584 | @extend .-t0; 585 | @extend .-l0; 586 | @extend .-op1; 587 | z-index: 999 - 5; 588 | } 589 | } 590 | } 591 | 592 | $notifyHeight: 85px; 593 | #notify { 594 | @extend .-pf; 595 | @extend .-dib; 596 | 597 | bottom: 12px; 598 | right: 12px; 599 | width: 320px; 600 | z-index: 700; 601 | 602 | @include range-width(450px) { 603 | .notify { 604 | @for $i from 1 through 20 { 605 | &:nth-child(#{$i}) { 606 | bottom: ($notifyHeight + 12px) * ($i - 1); 607 | z-index: 20 - $i; 608 | } 609 | } 610 | } 611 | } 612 | 613 | @include range-width(0, 450px) { 614 | top: 0; 615 | left: 0; 616 | right: auto; 617 | bottom: auto; 618 | width: 100%; 619 | 620 | .notify { 621 | right: auto; 622 | bottom: auto; 623 | top: -$notifyHeight; 624 | 625 | left: 8px; 626 | width: calc(100% - 8px * 2); 627 | @include opacity(1); 628 | @include border-radius(3px); 629 | @include box-shadow(0 7px 8px -4px rgba(0,0,0,0.2), 0 13px 19px 2px rgba(0,0,0,0.14), 0 5px 24px 4px rgba(0,0,0,0.12)); 630 | @include transition(top .3s); 631 | 632 | &.show { 633 | top: 8px; 634 | 635 | @for $i from 1 through 20 { 636 | &:nth-last-child(#{$i}) { 637 | top: 8px + ($i - 1) * 4px; 638 | } 639 | } 640 | } 641 | &:nth-last-child(n + 4) { 642 | display: none; 643 | } 644 | } 645 | }; 646 | } 647 | 648 | .notify { 649 | @extend .-pa; 650 | @extend .-dib; 651 | @extend .-w100; 652 | @extend .-p0; 653 | @extend .-bg-w; 654 | @extend .-oh; 655 | @extend .-op0; 656 | @extend .-fl-x; 657 | @extend .-br1; 658 | 659 | bottom: -$notifyHeight; 660 | right: -100%; 661 | height: $notifyHeight; 662 | 663 | @include box-shadow(0 1px 8px -4px rgba(0,0,0,0.2), 0 0 1px rgba(0,0,0,0.3), 0 2px 4px rgba(0, 0, 0, .15)); 664 | @include transition(bottom .3s, right .3s, opacity .3s); 665 | 666 | &.pointer { 667 | @extend .-cu-p; 668 | } 669 | 670 | &.show { 671 | @extend .-r0; 672 | @extend .-op1; 673 | } 674 | } 675 | 676 | .notify-img { 677 | @extend .-fl-l; 678 | @extend .-dib; 679 | @extend .-bg-cv; 680 | @extend .-oh; 681 | @extend .-b-ffi; 682 | @extend .-m0; 683 | 684 | height: $notifyHeight; 685 | width: $notifyHeight; 686 | 687 | background-color: rgba(245, 245, 245, 1.00); 688 | border-right: 1px solid rgba(240, 240, 240, 1.00); 689 | @include transition(background-color .3s); 690 | 691 | &:before { 692 | line-height: $notifyHeight; 693 | font-size: $notifyHeight / 2; 694 | @include text-shadow(1px 1px rgba(255, 255, 255, 1)); 695 | @include transition(color .3s); 696 | @include text-shadow(1px 1px white); 697 | } 698 | 699 | ~ .notify-title, ~ .notify-content { 700 | width: calc(100% - #{$notifyHeight}); 701 | } 702 | 703 | &.failure:before { content: '\f0005'; color: rgba(197, 56, 40, 1.00); } 704 | &.success:before { content: '\f0004'; color: rgba(24, 181, 141, 1.00); } 705 | } 706 | 707 | .notify-title, .notify-content { 708 | @extend .-w100; 709 | @extend .-fl-r; 710 | @extend .-ta-l; 711 | padding: 0 12px; 712 | @include transition(color .3s); 713 | } 714 | 715 | .notify-title { 716 | @extend .-dib; 717 | @extend .-doc3; 718 | @extend .-fs14; 719 | 720 | margin-top: 8px + 4px; 721 | padding-right: 36px; 722 | 723 | ~ .notify-content { 724 | margin-top: 8px; 725 | height: 16px * 2; 726 | -webkit-line-clamp: 2; 727 | } 728 | ~ .notify-close { 729 | top: 8px; 730 | right: 8px; 731 | width: 24px; 732 | height: 24px; 733 | line-height: 24px; 734 | } 735 | } 736 | 737 | .notify-content { 738 | @extend .-oh; 739 | @extend .-fs10; 740 | 741 | display: -webkit-box; 742 | margin-top: 18px; 743 | height: 16px * 3; 744 | line-height: 16px; 745 | 746 | color: rgba(135, 135, 135, 1); 747 | 748 | -webkit-line-clamp: 3; 749 | -webkit-box-orient: vertical; 750 | @include word-break(break-all); 751 | } 752 | 753 | .notify-close { 754 | @extend .-pa; 755 | @extend .-t0; 756 | @extend .-r0; 757 | @extend .-dib; 758 | @extend .-cu-p; 759 | @extend .-b-ffi; 760 | 761 | width: 18px; 762 | height: 18px; 763 | line-height: 18px; 764 | 765 | font-weight: bold; 766 | color: rgba(175, 175, 175, 1.00); 767 | @include transition(color .3s); 768 | 769 | &:hover { 770 | color: rgba(105, 105, 105, 1.00); 771 | } 772 | &:before { 773 | content: '\f0005'; 774 | } 775 | } 776 | 777 | #loading { 778 | @extend .-uox; 779 | @extend .-fs14; 780 | @extend .-ta-c; 781 | @extend .-doc3; 782 | @extend .-br12; 783 | 784 | $width: 136px; 785 | $size: 24px; 786 | $border: 4px; 787 | $height: 52px; 788 | $space: ($height - $size) / 2; 789 | 790 | width: $width; 791 | height: $height; 792 | line-height: $height; 793 | 794 | padding-left: $space + $size + $space / 2; 795 | padding-right: $space + $space / 2; 796 | z-index: -1; 797 | 798 | @include scale(0); 799 | @include box-shadow(0 7px 8px -4px rgba(0,0,0,0.2), 0 13px 19px 2px rgba(0,0,0,0.14), 0 5px 24px 4px rgba(0,0,0,0.12)); 800 | 801 | &.show { 802 | top: calc(50% - #{$height / 2}); 803 | left: calc(50% - #{$width / 2}); 804 | z-index: 999; 805 | @include opacity(1); 806 | @include transition(transform .3s cubic-bezier(.68, -0.275, .825, 0.115)); 807 | 808 | + .-uox-c { 809 | @extend .-t0; 810 | @extend .-l0; 811 | @extend .-op1; 812 | z-index: 999 - 5; 813 | } 814 | 815 | &.ani { 816 | @include scale(1); 817 | @include transition(transform .3s cubic-bezier(.175, .885, .32, 1.275)); 818 | } 819 | } 820 | 821 | &:before { 822 | content: ''; 823 | @extend .-pa; 824 | @extend .-dib; 825 | @extend .-bg-w; 826 | @extend .-br50; 827 | left: $space; 828 | top: calc(50% - #{$size / 2}); 829 | width: $size; 830 | height: $size; 831 | 832 | @include background-image(linear-gradient( 833 | rgba(59, 243, 156, 1.00) 0%, 834 | rgba(51, 224, 175, 1.00) 33%, 835 | rgba(42, 202, 200, 1.00) 67%, 836 | rgba(34, 181, 228, 1.00) 100%)); 837 | 838 | @include animation(rotate 1s linear infinite); 839 | @include box-shadow(inset 0 0 2px rgba(0, 0, 0, .3)); 840 | } 841 | &:after { 842 | content: ''; 843 | @extend .-pa; 844 | @extend .-dib; 845 | @extend .-bg-w; 846 | @extend .-br50; 847 | left: $space + $border; 848 | top: calc(50% - #{($size - $border * 2) / 2}); 849 | width: $size - $border * 2; 850 | height: $size - $border * 2; 851 | @include box-shadow(0 0 3px rgba(0, 0, 0, .3)); 852 | } 853 | } 854 | 855 | @include keyframes(rotate){ 856 | 100% { 857 | @include rotate(360deg); 858 | } 859 | } 860 | -------------------------------------------------------------------------------- /css/public.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";*, *:after, *:before { vertical-align: top; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; -moz-osx-font-smoothing: subpixel-antialiased; -webkit-font-smoothing: subpixel-antialiased; -moz-font-smoothing: subpixel-antialiased; -ms-font-smoothing: subpixel-antialiased; -o-font-smoothing: subpixel-antialiased; }@font-face { font-family: "icon"; src: url('../font/icomoon/fonts/icomoon.eot?1563416454') format('embedded-opentype'), url('../font/icomoon/fonts/icomoon.woff?1563416454') format('woff'), url('../font/icomoon/fonts/icomoon.ttf?1563416454') format('truetype'), url('../font/icomoon/fonts/icomoon.svg?1563416454') format('svg'); }.-pf, .-pf-fu, #map, .-cxh, #_add, #_readme, .-uox, #readme, #add, #video, #loading, .-uox-c, #readme + label, #add + label, #video + label, #notify { position: fixed; }.-pa, .-pa-fu, .-slt:before, .marker span, .marker b, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button, #readme label:not([for]):before, #add label:not([for]):before, #video label:not([for]):before, #video a, .notify, .notify-close, #loading:before, #loading:after { position: absolute; }.-pr, .-slt, html, body, #readme label:not([for]), #add label:not([for]), #video label:not([for]) { position: relative; }.-dib, .-pf-fu, #map, .-pa-fu, .-uox, #readme, #add, #video, #loading, .-uox-c, #readme + label, #add + label, #video + label, .-slt, .-slt:before, .-slt select, body, .marker, .marker span, .marker b, h1 label, #readme:before, #add:before, #video:before, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button, #readme label:not([for]), #add label:not([for]), #video label:not([for]), #readme label:not([for]):before, #add label:not([for]):before, #video label:not([for]):before, #readme span, #readme span a, #readme span i, #video a, #notify, .notify, .notify-img, .notify-title, .notify-close, #loading:before, #loading:after { display: inline-block; }.-dn, .-cxh, #_add, #_readme { display: none; }.-t0, .-pf-fu, #map, .-pa-fu, .-cxh, #_add, #_readme, .-uox-c, #readme + label, #add + label, #video + label, #readme label:not([for]):before, #add label:not([for]):before, #video label:not([for]):before, #_add:checked ~ #add + .-uox-c, #_add:checked ~ #add + label, #_readme:checked ~ #readme + .-uox-c, #_readme:checked ~ #readme + label, #video a, #video.show + .-uox-c, #video.show + label, .notify-close, #loading.show + .-uox-c { top: 0; }.-b0, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button { bottom: 0; }.-l0, .-pf-fu, #map, .-pa-fu, .-cxh, #_add, #_readme, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button, #readme label:not([for]):before, #add label:not([for]):before, #video label:not([for]):before, #_add:checked ~ #add + .-uox-c, #_add:checked ~ #add + label, #_readme:checked ~ #readme + .-uox-c, #_readme:checked ~ #readme + label, #video.show + .-uox-c, #video.show + label, #loading.show + .-uox-c { left: 0; }.-r0, #video a, .notify.show, .notify-close { right: 0; }.-w0, .-cxh, #_add, #_readme { width: 0; }.-h0, .-cxh, #_add, #_readme { height: 0; }.-w50, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button { width: 50%; }.-w100, .-pf-fu, #map, .-pa-fu, .-uox-c, #readme + label, #add + label, #video + label, .-slt select, body, h1 label, #readme:before, #add:before, #video:before, #readme input, #add input, #video input, .notify, .notify-title, .notify-content { width: 100%; }.-h100, .-pf-fu, #map, .-pa-fu, .-uox-c, #readme + label, #add + label, #video + label, .-slt select, h1 label { height: 100%; }.-fl-l, #readme span > *, .notify-img { float: left; }.-fl-r, .notify-title, .notify-content { float: right; }.-co-w, .marker span, .marker b { color: white; }.-ta-l, #readme p, .notify-title, .notify-content { text-align: left; }.-ta-c, body, #loading { text-align: center; }.-td-n, .gb, #readme a { text-decoration: none; }.-bg-w, .-uox, #readme, #add, #video, #loading, .-uox-c, #readme + label, #add + label, #video + label, .-slt, .-slt select, #create, #in:hover, #out:hover, h1, .gb, #readme:before, #add:before, #video:before, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button, .notify, #loading:before, #loading:after { background-color: white; }.-m0, body, h1, .notify-img { margin: 0; }.-p0, body, .notify { padding: 0; }.-oh, .-uox, #readme, #add, #video, #loading, .-uox-c, #readme + label, #add + label, #video + label, .notify, .notify-img, .notify-content { overflow: hidden; }.-ff0, body { font-family: "微軟正黑體", "Open sans", "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif; }.-b-ffi:before, .-slt:before, #in:before, #out:before, .gb:before, #readme span a:before, .notify-img:before, .notify-close:before { font-family: "icon"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; }.-cu-p, .-uox-c[for], #readme + label[for], #add + label[for], #video + label[for], .-slt, .-slt select, #create, #in, #out, h1 label, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button, #video a, .notify.pointer, .notify-close { cursor: pointer; }.-cu-n, input[readonly], textarea[readonly] { cursor: not-allowed; }.-op1, #_add:checked ~ #add, #_add:checked ~ #add + .-uox-c, #_add:checked ~ #add + label, #_readme:checked ~ #readme, #_readme:checked ~ #readme + .-uox-c, #_readme:checked ~ #readme + label, #video.show, #video.show + .-uox-c, #video.show + label, .notify.show, #loading.show + .-uox-c { filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); opacity: 1; }.-op0, .-cxh, #_add, #_readme, .-uox, #readme, #add, #video, #loading, .-uox-c, #readme + label, #add + label, #video + label, .notify { filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); opacity: 0; }.-fl-x, #readme span, .notify { *zoom: 1; }.-fl-x:after, #readme span:after, .notify:after { display: table; content: ""; line-height: 0; clear: both; }.-doc3, .-slt select, .marker span, .notify-title, #loading { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.-br1, .notify { -moz-border-radius: 1px; -webkit-border-radius: 1px; border-radius: 1px; }.-br3, input, textarea, #in, #out, h1, .gb { -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; }.-br4, .-slt select, #create { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }.-br5, #readme, #add, #video { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }.-br12, .marker span, #loading { -moz-border-radius: 12px; -webkit-border-radius: 12px; border-radius: 12px; }.-br50, .marker b, #loading:before, #loading:after { -moz-border-radius: 50%; -webkit-border-radius: 50%; border-radius: 50%; }.-fs10, .notify-content { font-size: 10px; }.-fs14, .-slt select, #readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button, #readme label:not([for]):before, #add label:not([for]):before, #video label:not([for]):before, .notify-title, #loading { font-size: 14px; }.-fs18, h1 { font-size: 18px; }.-cxh, #_add, #_readme { z-index: -999; }.-link, .gb, #readme a, #video a { color: #1890ff; -moz-transition: color 0.3s; -o-transition: color 0.3s; -webkit-transition: color 0.3s; transition: color 0.3s; }.-link:hover, .gb:hover, #readme a:hover, #video a:hover { color: #0076e4; }.-bg-cv, .notify-img { background-size: cover; background-position: center center; background-repeat: no-repeat; }.-uox, #readme, #add, #video, #loading, .-uox-c, #readme + label, #add + label, #video + label { top: -99999px; left: -99999px; z-index: -99999; -moz-transition: opacity 0.3s; -o-transition: opacity 0.3s; -webkit-transition: opacity 0.3s; transition: opacity 0.3s; }.-uox-c, #readme + label, #add + label, #video + label { left: -100%; background-color: rgba(0, 0, 0, 0.3); }.-slt:before { content: '\f000e'; right: 4px; width: 20px; height: 20px; line-height: 20px; top: calc(50% - 20px/2); color: #bebebe; }.-slt select { color: #4b4b4b; padding: 0 8px; padding-right: 24px; border: 1px solid #d2d2d2; color: #646464; -moz-appearance: none; -webkit-appearance: none; -moz-transition: border-color 0.3s, box-shadow 0.3s; -o-transition: border-color 0.3s, box-shadow 0.3s; -webkit-transition: border-color 0.3s, box-shadow 0.3s; transition: border-color 0.3s, box-shadow 0.3s; }.-slt select::-ms-expand { display: none; }.-slt select:hover { border-color: #3fa9ff; }.-slt select:focus { outline: 0; border-color: #3fa9ff; -moz-box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -webkit-box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); }input, textarea { color: rgba(0, 0, 0, 0.65); font-size: 14px; border: 1px solid #d9d9d9; background-color: white; -moz-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.075), 1px 1px 1px white; -webkit-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.075), 1px 1px 1px white; box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.075), 1px 1px 1px white; -moz-transition: box-shadow 0.3s, border-color 0.3s; -o-transition: box-shadow 0.3s, border-color 0.3s; -webkit-transition: box-shadow 0.3s, border-color 0.3s; transition: box-shadow 0.3s, border-color 0.3s; }input:-moz-placeholder, textarea:-moz-placeholder { color: #bfbfbf; }input::-moz-placeholder, textarea::-moz-placeholder { color: #bfbfbf; }input:-ms-input-placeholder, textarea:-ms-input-placeholder { color: #bfbfbf; }input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { color: #bfbfbf; }input:focus, textarea:focus { outline: 0; border-color: #3fa9ff; -moz-box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -webkit-box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); }input[readonly], textarea[readonly] { color: rgba(0, 0, 0, 0.25); background-color: whitesmoke; border-color: #d9d9d9; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }body { color: #5a5a5a; font-size: medium; background-color: #f1f2f4; }body::-moz-selection { color: white; background-color: #78baff; }body::selection { color: white; background-color: #78baff; }input { padding: 0 11px; }textarea { padding: 7px 11px; resize: none; }@-moz-keyframes rotate { 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } }@-webkit-keyframes rotate { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }@keyframes rotate { 100% { -moz-transform: rotate(360deg); -ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg); } }body > * { position: fixed; display: inline-block; z-index: 2; }#map { position: fixed !important; z-index: 1 !important; }.marker span { left: calc(50% - 80px/2); top: calc(50% - 24px/2); width: 80px; height: 24px; line-height: 20px; margin: 0 auto; background-color: #f56262; white-space: nowrap; border: 1px solid white; padding: 2px 8px; -moz-box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1); -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1); box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1); }.marker b { left: calc(50% - 28px/2); top: calc(50% - 28px/2); width: 28px; height: 28px; background-color: #cd3e3e; line-height: 26px; border: 1px solid white; -moz-box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1); -webkit-box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1); box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1); }#create { top: 48px; right: 8px; padding: 0 16px; height: 36px; line-height: 34px; border: 0; z-index: 2; font-size: 15px; letter-spacing: 1px; color: rgba(0, 0, 0, 0.5); text-shadow: 0 0 1px rgba(0, 0, 0, 0.3); -moz-transition: color 0.3s; -o-transition: color 0.3s; -webkit-transition: color 0.3s; transition: color 0.3s; -moz-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); }#create:hover { color: rgba(0, 0, 0, 0.75); }#create:focus { outline: 0; }#in, #out { right: 8px; width: 32px; height: 32px; line-height: 32px; background-color: rgba(255, 255, 255, 0.85); -moz-transition: background-color 0.3s; -o-transition: background-color 0.3s; -webkit-transition: background-color 0.3s; transition: background-color 0.3s; -moz-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); }#in { bottom: 64px; }#in:before { content: '\f0016'; }#out { bottom: 24px; }#out:before { content: '\f000d'; }h1 { left: 8px; top: 8px; height: 48px; line-height: 48px; -moz-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); }h1 label { padding: 0 20px; }.gb { right: 8px; top: 8px; padding: 0 16px; height: 32px; line-height: 32px; font-size: 14px; -moz-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); }.gb:before { content: '\eab0'; margin-right: 4px; }#readme, #add, #video { background-color: whitesmoke; -moz-box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); -webkit-box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); }#readme:before, #add:before, #video:before { content: attr(data-title); height: 44px; line-height: 44px; border-bottom: 1px solid #d8d8d8; }#readme label[for], #readme button, #add label[for], #add button, #video label[for], #video button { height: 44px; line-height: 44px; border: 0; border-top: 1px solid #d8d8d8; }#readme label[for] + button, #readme button + button, #add label[for] + button, #add button + button, #video label[for] + button, #video button + button { left: 50%; border-left: 1px solid #dedede; }#readme label[for]:focus, #readme button:focus, #add label[for]:focus, #add button:focus, #video label[for]:focus, #video button:focus { outline: 0; }#readme label:not([for]), #add label:not([for]), #video label:not([for]) { width: calc(100% - 16px); height: 32px; margin: 8px 8px; padding-left: 64px; padding-right: 8px; margin-top: 16px; }#readme label:not([for]) + label, #add label:not([for]) + label, #video label:not([for]) + label { margin-top: 8px; }#readme label:not([for]):before, #add label:not([for]):before, #video label:not([for]):before { text-align: right; content: attr(data-title) ":"; width: 56px; height: 32px; line-height: 32px; }#readme input, #add input, #video input { height: 32px; }#add { width: 450px; height: 348px; }@media screen and (max-width: 499px) and (min-width: 0) { #add { width: calc(100% - 16px); } }#_add:checked ~ #add { top: calc(50% - 348px/2); left: calc(50% - 450px/2); z-index: 999; -moz-transition: -moz-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -o-transition: -o-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); transition: transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); }@media screen and (max-width: 499px) and (min-width: 0) { #_add:checked ~ #add { left: 8px; } }#_add:checked ~ #add + .-uox-c, #_add:checked ~ #add + label { z-index: 994; }#_readme:checked ~ #readme { top: calc(50% - 348px/2); left: calc(50% - 450px/2); z-index: 999; -moz-transition: -moz-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -o-transition: -o-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); transition: transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); }@media screen and (max-width: 499px) and (min-width: 0) { #_readme:checked ~ #readme { left: 8px; } }#_readme:checked ~ #readme + .-uox-c, #_readme:checked ~ #readme + label { z-index: 994; }#readme { width: 450px; height: 348px; }@media screen and (max-width: 499px) and (min-width: 0) { #readme { width: calc(100% - 16px); } }#readme p { padding: 0 16px; }#readme label { width: 100% !important; }#readme span { margin: 0 auto; margin-top: 12px; }#readme span > * + * { margin-left: 20px; }#readme span a { height: 20px; line-height: 20px; }#readme span a:before { content: '\e900'; margin-right: 3px; -moz-transform: scale(0.75, 0.75); -ms-transform: scale(0.75, 0.75); -webkit-transform: scale(0.75, 0.75); transform: scale(0.75, 0.75); filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); opacity: 0.5; }#readme span i { width: 1px; height: 16px; margin-top: 3px; border-left: 1px solid #c8c8c8; }#readme span i + a:before { content: '\eab0'; }#video { width: 650px; height: 480px; }@media screen and (max-width: 669px) and (min-width: 0) { #video { width: calc(100% - 16px); } }#video iframe { width: 100%; height: 100%; max-height: calc(100% - 44px); }#video a { padding: 0 10px; height: 44px; line-height: 44px; z-index: 2; }#video.show { top: calc(50% - 480px/2); left: calc(50% - 650px/2); z-index: 999; -moz-transition: -moz-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -o-transition: -o-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); transition: transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); }@media screen and (max-width: 669px) and (min-width: 0) { #video.show { left: 8px; } }#video.show + .-uox-c, #video.show + label { z-index: 994; }#notify { bottom: 12px; right: 12px; width: 320px; z-index: 700; }@media screen and (min-width: 450px) { #notify .notify:nth-child(1) { bottom: 0px; z-index: 19; } #notify .notify:nth-child(2) { bottom: 97px; z-index: 18; } #notify .notify:nth-child(3) { bottom: 194px; z-index: 17; } #notify .notify:nth-child(4) { bottom: 291px; z-index: 16; } #notify .notify:nth-child(5) { bottom: 388px; z-index: 15; } #notify .notify:nth-child(6) { bottom: 485px; z-index: 14; } #notify .notify:nth-child(7) { bottom: 582px; z-index: 13; } #notify .notify:nth-child(8) { bottom: 679px; z-index: 12; } #notify .notify:nth-child(9) { bottom: 776px; z-index: 11; } #notify .notify:nth-child(10) { bottom: 873px; z-index: 10; } #notify .notify:nth-child(11) { bottom: 970px; z-index: 9; } #notify .notify:nth-child(12) { bottom: 1067px; z-index: 8; } #notify .notify:nth-child(13) { bottom: 1164px; z-index: 7; } #notify .notify:nth-child(14) { bottom: 1261px; z-index: 6; } #notify .notify:nth-child(15) { bottom: 1358px; z-index: 5; } #notify .notify:nth-child(16) { bottom: 1455px; z-index: 4; } #notify .notify:nth-child(17) { bottom: 1552px; z-index: 3; } #notify .notify:nth-child(18) { bottom: 1649px; z-index: 2; } #notify .notify:nth-child(19) { bottom: 1746px; z-index: 1; } #notify .notify:nth-child(20) { bottom: 1843px; z-index: 0; } }@media screen and (max-width: 449px) and (min-width: 0) { #notify { top: 0; left: 0; right: auto; bottom: auto; width: 100%; } #notify .notify { right: auto; bottom: auto; top: -85px; left: 8px; width: calc(100% - 8px * 2); filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); opacity: 1; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; -moz-box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); -webkit-box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); -moz-transition: top 0.3s; -o-transition: top 0.3s; -webkit-transition: top 0.3s; transition: top 0.3s; } #notify .notify.show { top: 8px; } #notify .notify.show:nth-last-child(1) { top: 8px; } #notify .notify.show:nth-last-child(2) { top: 12px; } #notify .notify.show:nth-last-child(3) { top: 16px; } #notify .notify.show:nth-last-child(4) { top: 20px; } #notify .notify.show:nth-last-child(5) { top: 24px; } #notify .notify.show:nth-last-child(6) { top: 28px; } #notify .notify.show:nth-last-child(7) { top: 32px; } #notify .notify.show:nth-last-child(8) { top: 36px; } #notify .notify.show:nth-last-child(9) { top: 40px; } #notify .notify.show:nth-last-child(10) { top: 44px; } #notify .notify.show:nth-last-child(11) { top: 48px; } #notify .notify.show:nth-last-child(12) { top: 52px; } #notify .notify.show:nth-last-child(13) { top: 56px; } #notify .notify.show:nth-last-child(14) { top: 60px; } #notify .notify.show:nth-last-child(15) { top: 64px; } #notify .notify.show:nth-last-child(16) { top: 68px; } #notify .notify.show:nth-last-child(17) { top: 72px; } #notify .notify.show:nth-last-child(18) { top: 76px; } #notify .notify.show:nth-last-child(19) { top: 80px; } #notify .notify.show:nth-last-child(20) { top: 84px; } #notify .notify:nth-last-child(n + 4) { display: none; } }.notify { bottom: -85px; right: -100%; height: 85px; -moz-box-shadow: 0 1px 8px -4px rgba(0, 0, 0, 0.2), 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.15); -webkit-box-shadow: 0 1px 8px -4px rgba(0, 0, 0, 0.2), 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.15); box-shadow: 0 1px 8px -4px rgba(0, 0, 0, 0.2), 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.15); -moz-transition: bottom 0.3s, right 0.3s, opacity 0.3s; -o-transition: bottom 0.3s, right 0.3s, opacity 0.3s; -webkit-transition: bottom 0.3s, right 0.3s, opacity 0.3s; transition: bottom 0.3s, right 0.3s, opacity 0.3s; }.notify-img { height: 85px; width: 85px; background-color: whitesmoke; border-right: 1px solid #f0f0f0; -moz-transition: background-color 0.3s; -o-transition: background-color 0.3s; -webkit-transition: background-color 0.3s; transition: background-color 0.3s; }.notify-img:before { line-height: 85px; font-size: 42.5px; text-shadow: 1px 1px white; -moz-transition: color 0.3s; -o-transition: color 0.3s; -webkit-transition: color 0.3s; transition: color 0.3s; text-shadow: 1px 1px white; }.notify-img ~ .notify-title, .notify-img ~ .notify-content { width: calc(100% - 85px); }.notify-img.failure:before { content: '\f0005'; color: #c53828; }.notify-img.success:before { content: '\f0004'; color: #18b58d; }.notify-title, .notify-content { padding: 0 12px; -moz-transition: color 0.3s; -o-transition: color 0.3s; -webkit-transition: color 0.3s; transition: color 0.3s; }.notify-title { margin-top: 12px; padding-right: 36px; }.notify-title ~ .notify-content { margin-top: 8px; height: 32px; -webkit-line-clamp: 2; }.notify-title ~ .notify-close { top: 8px; right: 8px; width: 24px; height: 24px; line-height: 24px; }.notify-content { display: -webkit-box; margin-top: 18px; height: 48px; line-height: 16px; color: #878787; -webkit-line-clamp: 3; -webkit-box-orient: vertical; word-break: break-all; word-break: break-word; }.notify-close { width: 18px; height: 18px; line-height: 18px; font-weight: bold; color: #afafaf; -moz-transition: color 0.3s; -o-transition: color 0.3s; -webkit-transition: color 0.3s; transition: color 0.3s; }.notify-close:hover { color: dimgray; }.notify-close:before { content: '\f0005'; }#loading { width: 136px; height: 52px; line-height: 52px; padding-left: 45px; padding-right: 21px; z-index: -1; -moz-transform: scale(0, 0); -ms-transform: scale(0, 0); -webkit-transform: scale(0, 0); transform: scale(0, 0); -moz-box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); -webkit-box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); }#loading.show { top: calc(50% - 26px); left: calc(50% - 68px); z-index: 999; filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); opacity: 1; -moz-transition: -moz-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -o-transition: -o-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); transition: transform 0.3s cubic-bezier(0.68, -0.275, 0.825, 0.115); }#loading.show + .-uox-c { z-index: 994; }#loading.show.ani { -moz-transform: scale(1, 1); -ms-transform: scale(1, 1); -webkit-transform: scale(1, 1); transform: scale(1, 1); -moz-transition: -moz-transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -o-transition: -o-transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }#loading:before { content: ''; left: 14px; top: calc(50% - 12px); width: 24px; height: 24px; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzNiZjM5YyIvPjxzdG9wIG9mZnNldD0iMzMlIiBzdG9wLWNvbG9yPSIjMzNlMGFmIi8+PHN0b3Agb2Zmc2V0PSI2NyUiIHN0b3AtY29sb3I9IiMyYWNhYzgiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMyMmI1ZTQiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #3bf39c), color-stop(33%, #33e0af), color-stop(67%, #2acac8), color-stop(100%, #22b5e4)); background-image: -moz-linear-gradient(#3bf39c 0%, #33e0af 33%, #2acac8 67%, #22b5e4 100%); background-image: -webkit-linear-gradient(#3bf39c 0%, #33e0af 33%, #2acac8 67%, #22b5e4 100%); background-image: linear-gradient(#3bf39c 0%, #33e0af 33%, #2acac8 67%, #22b5e4 100%); -moz-animation: rotate 1s linear infinite; -webkit-animation: rotate 1s linear infinite; animation: rotate 1s linear infinite; -moz-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3); -webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3); box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3); }#loading:after { content: ''; left: 18px; top: calc(50% - 8px); width: 16px; height: 16px; -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); box-shadow: 0 0 3px rgba(0, 0, 0, 0.3); }@-moz-keyframes rotate { 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } }@-webkit-keyframes rotate { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }@keyframes rotate { 100% { -moz-transform: rotate(360deg); -ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg); } } --------------------------------------------------------------------------------