summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--app.js17
-rw-r--r--app.json21
-rw-r--r--config.json.example9
-rw-r--r--lib/config/default.js9
-rw-r--r--lib/config/environment.js9
-rw-r--r--lib/config/index.js4
-rwxr-xr-xlib/response.js11
-rw-r--r--locales/zh-CN.json104
-rw-r--r--locales/zh-TW.json104
l---------[-rw-r--r--]locales/zh.json105
-rw-r--r--package.json4
-rw-r--r--public/docs/features.md15
-rw-r--r--public/js/extra.js2
-rw-r--r--public/js/locale.js3
-rw-r--r--public/views/hackmd/header.ejs16
-rw-r--r--public/views/index/body.ejs3
17 files changed, 315 insertions, 131 deletions
diff --git a/README.md b/README.md
index bff6ef7e..718f6863 100644
--- a/README.md
+++ b/README.md
@@ -153,15 +153,20 @@ There are some configs you need to change in the files below
| HMD_LDAP_SEARCHFILTER | `(uid={{username}})` | LDAP filter to search with |
| HMD_LDAP_SEARCHATTRIBUTES | no example | LDAP attributes to search with |
| HMD_LDAP_TLS_CA | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) |
-| HMD_LDAP_PROVIDERNAME | `My institution` | Optional name to be displayed at login form indicating the LDAP provider |
+| HMD_LDAP_PROVIDERNAME | `My institution` | Optional name to be displayed at login form indicating the LDAP provider |
| HMD_IMGUR_CLIENTID | no example | Imgur API client id |
| HMD_EMAIL | `true` or `false` | set to allow email signin |
+| HMD_ALLOW_PDF_EXPORT | `true` or `false` | Enable or disable PDF exports |
| HMD_ALLOW_EMAIL_REGISTER | `true` or `false` | set to allow email register (only applied when email is set, default is `true`) |
| HMD_IMAGE_UPLOAD_TYPE | `imgur`, `s3` or `filesystem` | Where to upload image. For S3, see our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) |
| HMD_S3_ACCESS_KEY_ID | no example | AWS access key id |
| HMD_S3_SECRET_ACCESS_KEY | no example | AWS secret key |
| HMD_S3_REGION | `ap-northeast-1` | AWS S3 region |
| HMD_S3_BUCKET | no example | AWS S3 bucket name |
+| HMD_HSTS_ENABLE | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) |
+| HMD_HSTS_INCLUDE_SUBDOMAINS | `true` | set to include subdomains in HSTS (default is `true`) |
+| HMD_HSTS_MAX_AGE | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) |
+| HMD_HSTS_PRELOAD | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) |
## Application settings `config.json`
@@ -173,6 +178,7 @@ There are some configs you need to change in the files below
| port | `80` | web app port |
| alloworigin | `['localhost']` | domain name whitelist |
| usessl | `true` or `false` | set to use ssl server (if true will auto turn on `protocolusessl`) |
+| hsts | `{"enable": "true", "maxAgeSeconds": "31536000", "includeSubdomains": "true", "preload": "true"}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) |
| protocolusessl | `true` or `false` | set to use ssl protocol for resources path (only applied when domain is set) |
| urladdport | `true` or `false` | set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set) |
| usecdn | `true` or `false` | set to use CDN resources or not (default is `true`) |
@@ -203,7 +209,7 @@ There are some configs you need to change in the files below
| email | `true` or `false` | set to allow email signin |
| allowemailregister | `true` or `false` | set to allow email register (only applied when email is set, default is `true`) |
| imageUploadType | `imgur`(default), `s3` or `filesystem` | Where to upload image
-| s3 | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageUploadType` be setted to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) |
+| s3 | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageUploadType` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](docs/guides/s3-image-upload.md) |
| s3bucket | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` |
## Third-party integration api key settings
diff --git a/app.js b/app.js
index 1508781c..c3f1fe8e 100644
--- a/app.js
+++ b/app.js
@@ -97,14 +97,19 @@ var sessionStore = new SequelizeStore({
app.use(compression())
// use hsts to tell https users stick to this
-app.use(helmet.hsts({
- maxAge: 31536000 * 1000, // 365 days
- includeSubdomains: true,
- preload: true
-}))
+if (config.hsts.enable) {
+ app.use(helmet.hsts({
+ maxAge: config.hsts.maxAgeSeconds * 1000,
+ includeSubdomains: config.hsts.includeSubdomains,
+ preload: config.hsts.preload
+ }))
+} else if (config.usessl) {
+ logger.info('Consider enabling HSTS for extra security:')
+ logger.info('https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security')
+}
i18n.configure({
- locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da'],
+ locales: ['en', 'zh', 'zh-CN', 'zh-TW', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da'],
cookie: 'locale',
directory: path.join(__dirname, '/locales')
})
diff --git a/app.json b/app.json
index e06720f4..d1804c52 100644
--- a/app.json
+++ b/app.json
@@ -23,7 +23,22 @@
"description": "Specify database type. See sequelize available databases. Default using postgres",
"value": "postgres"
},
-
+ "HMD_HSTS_ENABLE": {
+ "description": "whether to also use HSTS if HTTPS is enabled",
+ "required": false
+ },
+ "HMD_HSTS_MAX_AGE": {
+ "description": "max duration, in seconds, to tell clients to keep HSTS status",
+ "required": false
+ },
+ "HMD_HSTS_INCLUDE_SUBDOMAINS": {
+ "description": "whether to tell clients to also regard subdomains as HSTS hosts",
+ "required": false
+ },
+ "HMD_HSTS_PRELOAD": {
+ "description": "whether to allow at all adding of the site to HSTS preloads (e.g. in browsers)",
+ "required": false
+ },
"HMD_DOMAIN": {
"description": "domain name",
"required": false
@@ -112,6 +127,10 @@
"HMD_IMGUR_CLIENTID": {
"description": "Imgur API client id",
"required": false
+ },
+ "HMD_ALLOW_PDF_EXPORT": {
+ "description": "Enable or disable PDF exports",
+ "required": false
}
},
"addons": [
diff --git a/config.json.example b/config.json.example
index 87c04ed0..e2d774c7 100644
--- a/config.json.example
+++ b/config.json.example
@@ -6,6 +6,9 @@
}
},
"development": {
+ "hsts": {
+ "enable": false
+ },
"db": {
"dialect": "sqlite",
"storage": "./db.hackmd.sqlite"
@@ -13,6 +16,12 @@
},
"production": {
"domain": "localhost",
+ "hsts": {
+ "enable": "true",
+ "maxAgeSeconds": "31536000",
+ "includeSubdomains": "true",
+ "preload": "true"
+ },
"db": {
"username": "",
"password": "",
diff --git a/lib/config/default.js b/lib/config/default.js
index a14a4294..e7e2e4b3 100644
--- a/lib/config/default.js
+++ b/lib/config/default.js
@@ -7,6 +7,12 @@ module.exports = {
urladdport: false,
alloworigin: ['localhost'],
usessl: false,
+ hsts: {
+ enable: true,
+ maxAgeSeconds: 31536000,
+ includeSubdomains: true,
+ preload: true
+ },
protocolusessl: false,
usecdn: true,
allowanonymous: true,
@@ -88,5 +94,6 @@ module.exports = {
tlsca: undefined
},
email: true,
- allowemailregister: true
+ allowemailregister: true,
+ allowpdfexport: true
}
diff --git a/lib/config/environment.js b/lib/config/environment.js
index c108a6f9..6f33d140 100644
--- a/lib/config/environment.js
+++ b/lib/config/environment.js
@@ -8,6 +8,12 @@ module.exports = {
port: process.env.HMD_PORT,
urladdport: toBooleanConfig(process.env.HMD_URL_ADDPORT),
usessl: toBooleanConfig(process.env.HMD_USESSL),
+ hsts: {
+ enable: toBooleanConfig(process.env.HMD_HSTS_ENABLE),
+ maxAgeSeconds: process.env.HMD_HSTS_MAX_AGE,
+ includeSubdomains: toBooleanConfig(process.env.HMD_HSTS_INCLUDE_SUBDOMAINS),
+ preload: toBooleanConfig(process.env.HMD_HSTS_PRELOAD)
+ },
protocolusessl: toBooleanConfig(process.env.HMD_PROTOCOL_USESSL),
alloworigin: process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : undefined,
usecdn: toBooleanConfig(process.env.HMD_USECDN),
@@ -63,5 +69,6 @@ module.exports = {
tlsca: process.env.HMD_LDAP_TLS_CA
},
email: toBooleanConfig(process.env.HMD_EMAIL),
- allowemailregister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER)
+ allowemailregister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER),
+ allowpdfexport: toBooleanConfig(process.env.HMD_ALLOW_PDF_EXPORT)
}
diff --git a/lib/config/index.js b/lib/config/index.js
index bea5a6af..dfad28ed 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -1,3 +1,4 @@
+
'use strict'
const fs = require('fs')
@@ -90,6 +91,7 @@ config.isEmailEnable = config.email
config.isGitHubEnable = config.github.clientID && config.github.clientSecret
config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
config.isLDAPEnable = config.ldap.url
+config.isPDFExportEnable = config.allowpdfexport
// generate correct path
config.sslcapath = path.join(appRootPath, config.sslcapath)
@@ -106,7 +108,7 @@ config.errorpath = path.join(appRootPath, config.errorpath)
config.prettypath = path.join(appRootPath, config.prettypath)
config.slidepath = path.join(appRootPath, config.slidepath)
-// maek config readonly
+// make config readonly
config = deepFreeze(config)
module.exports = config
diff --git a/lib/response.js b/lib/response.js
index a22d1e70..9e39ffb5 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -69,6 +69,7 @@ function showIndex (req, res, next) {
ldap: config.isLDAPEnable,
email: config.isEmailEnable,
allowemailregister: config.allowemailregister,
+ allowpdfexport: config.allowpdfexport,
signin: req.isAuthenticated(),
infoMessage: req.flash('info'),
errorMessage: req.flash('error')
@@ -98,7 +99,8 @@ function responseHackMD (res, note) {
google: config.isGoogleEnable,
ldap: config.isLDAPEnable,
email: config.isEmailEnable,
- allowemailregister: config.allowemailregister
+ allowemailregister: config.allowemailregister,
+ allowpdfexport: config.allowpdfexport
})
}
@@ -382,7 +384,12 @@ function noteActions (req, res, next) {
actionInfo(req, res, note)
break
case 'pdf':
- actionPDF(req, res, note)
+ if (config.allowpdfexport) {
+ actionPDF(req, res, note)
+ } else {
+ logger.error('PDF export failed: Disabled by config. Set "allowpdfexport: true" to enable. Check the documentation for details')
+ response.errorForbidden(res)
+ }
break
case 'gist':
actionGist(req, res, note)
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
new file mode 100644
index 00000000..97602c82
--- /dev/null
+++ b/locales/zh-CN.json
@@ -0,0 +1,104 @@
+{
+ "Collaborative markdown notes": "Markdown 协作笔记",
+ "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即时协作笔记",
+ "Best way to write and share your knowledge in markdown.": "您使用 Markdown 写作与分享知识的最佳方式",
+ "Intro": "简介",
+ "History": "历史",
+ "New guest note": "建立访客笔记",
+ "Collaborate with URL": "使用网址协作",
+ "Support charts and MathJax": "支持图表与 MathJax",
+ "Support slide mode": "支持简报模式",
+ "Sign In": "登录",
+ "Below is the history from browser": "以下为来自浏览器的历史",
+ "Welcome!": "欢迎!",
+ "New note": "建立笔记",
+ "or": "或",
+ "Sign Out": "登出",
+ "Explore all features": "探索所有功能",
+ "Select tags...": "选择标签...",
+ "Search keyword...": "搜索关键字...",
+ "Sort by title": "用标题排序",
+ "Title": "标题",
+ "Sort by time": "用时间排序",
+ "Time": "时间",
+ "Export history": "导出历史",
+ "Import history": "导入历史",
+ "Clear history": "清空历史",
+ "Refresh history": "刷新历史",
+ "No history": "没有历史",
+ "Import from browser": "从浏览器导入",
+ "Releases": "版本",
+ "Are you sure?": "你确定吗?",
+ "Cancel": "取消",
+ "Yes, do it!": "没错,就这样办!",
+ "Choose method": "选择方式",
+ "Sign in via %s": "通过 %s 登录",
+ "New": "新增",
+ "Publish": "发表",
+ "Extra": "增益",
+ "Revision": "修订版本",
+ "Slide Mode": "简报模式",
+ "Export": "导出",
+ "Import": "导入",
+ "Clipboard": "剪贴板",
+ "Download": "下载",
+ "Raw HTML": "纯 HTML",
+ "Edit": "编辑",
+ "View": "检视",
+ "Both": "双栏",
+ "Help": "帮助",
+ "Upload Image": "上传图片",
+ "Menu": "菜单",
+ "This page need refresh": "此页面需要重新整理",
+ "You have an incompatible client version.": "您使用的是不相容的客户端",
+ "Refresh to update.": "请重新整理来更新",
+ "New version available!": "新版本来了!",
+ "See releases notes here": "请由此查阅更新纪录",
+ "Refresh to enjoy new features.": "请重新整理来享受最新功能",
+ "Your user state has changed.": "您的使用者状态已变更",
+ "Refresh to load new user state.": "请重新整理来载入新的使用者状态",
+ "Refresh": "重新整理",
+ "Contacts": "联络方式",
+ "Report an issue": "报告问题",
+ "Send us email": "寄信给我们",
+ "Documents": "文件",
+ "Features": "功能简介",
+ "YAML Metadata": "YAML Metadata",
+ "Slide Example": "简报范例",
+ "Cheatsheet": "快速简表",
+ "Example": "范例",
+ "Syntax": "语法",
+ "Header": "标题",
+ "Unordered List": "无序清单",
+ "Ordered List": "有序清单",
+ "Todo List": "待办事项",
+ "Blockquote": "引用",
+ "Bold font": "粗体",
+ "Italics font": "斜体",
+ "Strikethrough": "删除线",
+ "Inserted text": "插入文字",
+ "Marked text": "标记文字",
+ "Link": "链接",
+ "Image": "图片",
+ "Code": "代码",
+ "Externals": "外部",
+ "This is a alert area.": "这是警告区块",
+ "Revert": "还原",
+ "Import from clipboard": "从剪贴板导入",
+ "Paste your markdown or webpage here...": "在这里贴上 Markdown 或是网页内容...",
+ "Clear": "清除",
+ "This note is locked": "此份笔记已被锁定",
+ "Sorry, only owner can edit this note.": "抱歉,只有拥有者可以编辑此笔记",
+ "OK": "好的",
+ "Reach the limit": "到达上限",
+ "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份笔记可用的最大长度",
+ "Please reduce the content or divide it to more notes, thank you!": "请减少内容或是将内容切成更多笔记,谢谢!",
+ "Import from Gist": "从 Gist 导入",
+ "Paste your gist url here...": "在这里贴上 gist 网址...",
+ "Import from Snippet": "从 Snippet 导入",
+ "Select From Available Projects": "从可用的项目中选择",
+ "Select From Available Snippets": "从可用的 Snippets 中选择",
+ "OR": "或是",
+ "Export to Snippet": "导出到 Snippet",
+ "Select Visibility Level": "选择可见层级"
+} \ No newline at end of file
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
new file mode 100644
index 00000000..a3bb7774
--- /dev/null
+++ b/locales/zh-TW.json
@@ -0,0 +1,104 @@
+{
+ "Collaborative markdown notes": "Markdown 協作筆記",
+ "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即時協作筆記",
+ "Best way to write and share your knowledge in markdown.": "您使用 Markdown 寫作與分享知識的最佳方式",
+ "Intro": "簡介",
+ "History": "紀錄",
+ "New guest note": "建立訪客筆記",
+ "Collaborate with URL": "使用網址協作",
+ "Support charts and MathJax": "支援圖表與 MathJax",
+ "Support slide mode": "支援簡報模式",
+ "Sign In": "登入",
+ "Below is the history from browser": "以下為來自瀏覽器的紀錄",
+ "Welcome!": "歡迎!",
+ "New note": "建立筆記",
+ "or": "或",
+ "Sign Out": "登出",
+ "Explore all features": "探索所有功能",
+ "Select tags...": "選擇標籤...",
+ "Search keyword...": "搜尋關鍵字...",
+ "Sort by title": "用標題排序",
+ "Title": "標題",
+ "Sort by time": "用時間排序",
+ "Time": "時間",
+ "Export history": "匯出紀錄",
+ "Import history": "匯入紀錄",
+ "Clear history": "清空紀錄",
+ "Refresh history": "更新紀錄",
+ "No history": "沒有紀錄",
+ "Import from browser": "從瀏覽器匯入",
+ "Releases": "版本",
+ "Are you sure?": "你確定嗎?",
+ "Cancel": "取消",
+ "Yes, do it!": "沒錯,就這樣辦!",
+ "Choose method": "選擇方式",
+ "Sign in via %s": "透過 %s 登入",
+ "New": "新增",
+ "Publish": "發表",
+ "Extra": "增益",
+ "Revision": "修訂版本",
+ "Slide Mode": "簡報模式",
+ "Export": "匯出",
+ "Import": "匯入",
+ "Clipboard": "剪貼簿",
+ "Download": "下載",
+ "Raw HTML": "純 HTML",
+ "Edit": "編輯",
+ "View": "檢視",
+ "Both": "雙欄",
+ "Help": "協助",
+ "Upload Image": "上傳圖片",
+ "Menu": "選單",
+ "This page need refresh": "此頁面需要重新整理",
+ "You have an incompatible client version.": "您使用的是不相容的客戶端",
+ "Refresh to update.": "請重新整理來更新",
+ "New version available!": "新版本來了!",
+ "See releases notes here": "請由此查閱更新紀錄",
+ "Refresh to enjoy new features.": "請重新整理來享受最新功能",
+ "Your user state has changed.": "您的使用者狀態已變更",
+ "Refresh to load new user state.": "請重新整理來載入新的使用者狀態",
+ "Refresh": "重新整理",
+ "Contacts": "聯絡方式",
+ "Report an issue": "回報問題",
+ "Send us email": "寄信給我們",
+ "Documents": "文件",
+ "Features": "功能簡介",
+ "YAML Metadata": "YAML Metadata",
+ "Slide Example": "簡報範例",
+ "Cheatsheet": "快速簡表",
+ "Example": "範例",
+ "Syntax": "語法",
+ "Header": "標題",
+ "Unordered List": "無序清單",
+ "Ordered List": "有序清單",
+ "Todo List": "待辦事項",
+ "Blockquote": "引用",
+ "Bold font": "粗體",
+ "Italics font": "斜體",
+ "Strikethrough": "刪除線",
+ "Inserted text": "插入文字",
+ "Marked text": "標記文字",
+ "Link": "連結",
+ "Image": "圖片",
+ "Code": "程式碼",
+ "Externals": "外部",
+ "This is a alert area.": "這是警告區塊",
+ "Revert": "還原",
+ "Import from clipboard": "從剪貼簿匯入",
+ "Paste your markdown or webpage here...": "在這裡貼上 Markdown 或是網頁內容...",
+ "Clear": "清除",
+ "This note is locked": "此份筆記已被鎖定",
+ "Sorry, only owner can edit this note.": "抱歉,只有擁有者可以編輯此筆記",
+ "OK": "好的",
+ "Reach the limit": "到達上限",
+ "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份筆記可用的最大長度",
+ "Please reduce the content or divide it to more notes, thank you!": "請減少內容或是將內容切成更多筆記,謝謝!",
+ "Import from Gist": "從 Gist 匯入",
+ "Paste your gist url here...": "在這裡貼上 gist 網址...",
+ "Import from Snippet": "從 Snippet 匯入",
+ "Select From Available Projects": "從可用的專案中選擇",
+ "Select From Available Snippets": "從可用的 Snippets 中選擇",
+ "OR": "或是",
+ "Export to Snippet": "匯出到 Snippet",
+ "Select Visibility Level": "選擇可見層級"
+} \ No newline at end of file
diff --git a/locales/zh.json b/locales/zh.json
index a3bb7774..77c7eac5 100644..120000
--- a/locales/zh.json
+++ b/locales/zh.json
@@ -1,104 +1 @@
-{
- "Collaborative markdown notes": "Markdown 協作筆記",
- "Realtime collaborative markdown notes on all platforms.": "使用 Markdown 的跨平台即時協作筆記",
- "Best way to write and share your knowledge in markdown.": "您使用 Markdown 寫作與分享知識的最佳方式",
- "Intro": "簡介",
- "History": "紀錄",
- "New guest note": "建立訪客筆記",
- "Collaborate with URL": "使用網址協作",
- "Support charts and MathJax": "支援圖表與 MathJax",
- "Support slide mode": "支援簡報模式",
- "Sign In": "登入",
- "Below is the history from browser": "以下為來自瀏覽器的紀錄",
- "Welcome!": "歡迎!",
- "New note": "建立筆記",
- "or": "或",
- "Sign Out": "登出",
- "Explore all features": "探索所有功能",
- "Select tags...": "選擇標籤...",
- "Search keyword...": "搜尋關鍵字...",
- "Sort by title": "用標題排序",
- "Title": "標題",
- "Sort by time": "用時間排序",
- "Time": "時間",
- "Export history": "匯出紀錄",
- "Import history": "匯入紀錄",
- "Clear history": "清空紀錄",
- "Refresh history": "更新紀錄",
- "No history": "沒有紀錄",
- "Import from browser": "從瀏覽器匯入",
- "Releases": "版本",
- "Are you sure?": "你確定嗎?",
- "Cancel": "取消",
- "Yes, do it!": "沒錯,就這樣辦!",
- "Choose method": "選擇方式",
- "Sign in via %s": "透過 %s 登入",
- "New": "新增",
- "Publish": "發表",
- "Extra": "增益",
- "Revision": "修訂版本",
- "Slide Mode": "簡報模式",
- "Export": "匯出",
- "Import": "匯入",
- "Clipboard": "剪貼簿",
- "Download": "下載",
- "Raw HTML": "純 HTML",
- "Edit": "編輯",
- "View": "檢視",
- "Both": "雙欄",
- "Help": "協助",
- "Upload Image": "上傳圖片",
- "Menu": "選單",
- "This page need refresh": "此頁面需要重新整理",
- "You have an incompatible client version.": "您使用的是不相容的客戶端",
- "Refresh to update.": "請重新整理來更新",
- "New version available!": "新版本來了!",
- "See releases notes here": "請由此查閱更新紀錄",
- "Refresh to enjoy new features.": "請重新整理來享受最新功能",
- "Your user state has changed.": "您的使用者狀態已變更",
- "Refresh to load new user state.": "請重新整理來載入新的使用者狀態",
- "Refresh": "重新整理",
- "Contacts": "聯絡方式",
- "Report an issue": "回報問題",
- "Send us email": "寄信給我們",
- "Documents": "文件",
- "Features": "功能簡介",
- "YAML Metadata": "YAML Metadata",
- "Slide Example": "簡報範例",
- "Cheatsheet": "快速簡表",
- "Example": "範例",
- "Syntax": "語法",
- "Header": "標題",
- "Unordered List": "無序清單",
- "Ordered List": "有序清單",
- "Todo List": "待辦事項",
- "Blockquote": "引用",
- "Bold font": "粗體",
- "Italics font": "斜體",
- "Strikethrough": "刪除線",
- "Inserted text": "插入文字",
- "Marked text": "標記文字",
- "Link": "連結",
- "Image": "圖片",
- "Code": "程式碼",
- "Externals": "外部",
- "This is a alert area.": "這是警告區塊",
- "Revert": "還原",
- "Import from clipboard": "從剪貼簿匯入",
- "Paste your markdown or webpage here...": "在這裡貼上 Markdown 或是網頁內容...",
- "Clear": "清除",
- "This note is locked": "此份筆記已被鎖定",
- "Sorry, only owner can edit this note.": "抱歉,只有擁有者可以編輯此筆記",
- "OK": "好的",
- "Reach the limit": "到達上限",
- "Sorry, you've reached the max length this note can be.": "抱歉,您已使用到此份筆記可用的最大長度",
- "Please reduce the content or divide it to more notes, thank you!": "請減少內容或是將內容切成更多筆記,謝謝!",
- "Import from Gist": "從 Gist 匯入",
- "Paste your gist url here...": "在這裡貼上 gist 網址...",
- "Import from Snippet": "從 Snippet 匯入",
- "Select From Available Projects": "從可用的專案中選擇",
- "Select From Available Snippets": "從可用的 Snippets 中選擇",
- "OR": "或是",
- "Export to Snippet": "匯出到 Snippet",
- "Select Visibility Level": "選擇可見層級"
-} \ No newline at end of file
+locales/zh-TW.json \ No newline at end of file
diff --git a/package.json b/package.json
index e6273122..04b969e3 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"main": "app.js",
"license": "MIT",
"scripts": {
- "test": "npm run-script standard",
+ "test": "npm run-script standard && npm run-script jsonlint",
+ "jsonlint": "find . -not -path './node_modules/*' -type f -name '*.json' | while read json; do echo $json ; jsonlint -q $json; done",
"standard": "node ./node_modules/standard/bin/cmd.js",
"dev": "webpack --config webpack.config.js --progress --colors --watch",
"build": "webpack --config webpack.production.js --progress --colors --bail",
@@ -161,6 +162,7 @@
"html-webpack-plugin": "^2.25.0",
"imports-loader": "^0.7.0",
"json-loader": "^0.5.4",
+ "jsonlint": "^1.6.2",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"optimize-css-assets-webpack-plugin": "^1.3.0",
diff --git a/public/docs/features.md b/public/docs/features.md
index b64b988e..a894c087 100644
--- a/public/docs/features.md
+++ b/public/docs/features.md
@@ -47,12 +47,15 @@ or import content from your **clipboard** <i class="fa fa-clipboard"></i>, and t
It is possible to change the access permission to a note through the little button on the top right of the view.
There are four possible options:
-<i class="fa fa-leaf fa-fw"></i> **Freely**: Anyone can edit this note.
-<i class="fa fa-pencil fa-fw"></i> **Editable**: A signed-in user can edit this note.
-<i class="fa fa-id-card fa-fw"></i> **Limited**: People have to sign-in to view and edit this note.
-<i class="fa fa-lock fa-fw"></i> **Locked**: Anyone can view this note but only the owner can edit it.
-<i class="fa fa-umbrella fa-fw"></i> **Protected**: People have to sign-in to view this note but only owner can edit.
-<i class="fa fa-hand-stop-o fa-fw"></i> **Private**: Only the owner can view and edit this note.
+| |Owner read/write|Signed-in read|Signed-in write|Guest read|Guest write|
+|:-----------------------------|:--------------:|:------------:|:-------------:|:--------:|:---------:|
+|<span class="text-nowrap"><i class="fa fa-leaf fa-fw"></i> **Freely**</span> |✔|✔|✔|✔|✔|
+|<span class="text-nowrap"><i class="fa fa-pencil fa-fw"></i> **Editable**</span> |✔|✔|✔|✔|✖|
+|<span class="text-nowrap"><i class="fa fa-id-card fa-fw"></i> **Limited**</span> |✔|✔|✔|✖|✖|
+|<span class="text-nowrap"><i class="fa fa-lock fa-fw"></i> **Locked**</span> |✔|✔|✖|✔|✖|
+|<span class="text-nowrap"><i class="fa fa-umbrella fa-fw"></i> **Protected**</span> |✔|✔|✖|✖|✖|
+|<span class="text-nowrap"><i class="fa fa-hand-stop-o fa-fw"></i> **Private**</span> |✔|✖|✖|✖|✖|
+
**Only the owner of the note can change the note's permissions.**
diff --git a/public/js/extra.js b/public/js/extra.js
index a1a9dbb6..d36592d9 100644
--- a/public/js/extra.js
+++ b/public/js/extra.js
@@ -1092,7 +1092,7 @@ const gistPlugin = new Plugin(
(match, utils) => {
const gistid = match[1]
- const code = `<code data-gist-id="${gistid}"/>`
+ const code = `<code data-gist-id="${gistid}"></code>`
return code
}
)
diff --git a/public/js/locale.js b/public/js/locale.js
index 2a2c1814..71c0f99f 100644
--- a/public/js/locale.js
+++ b/public/js/locale.js
@@ -11,6 +11,9 @@ $('.ui-locale option').each(function () {
})
if (Cookies.get('locale')) {
lang = Cookies.get('locale')
+ if (lang === 'zh') {
+ lang = 'zh-TW'
+ }
} else if (supportLangs.indexOf(userLang) !== -1) {
lang = supportLangs[supportLangs.indexOf(userLang)]
} else if (supportLangs.indexOf(userLangCode) !== -1) {
diff --git a/public/views/hackmd/header.ejs b/public/views/hackmd/header.ejs
index 87d2b065..47b563ac 100644
--- a/public/views/hackmd/header.ejs
+++ b/public/views/hackmd/header.ejs
@@ -32,6 +32,7 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
</li>
+ <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Export') %></li>
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
@@ -46,6 +47,7 @@
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
</li>
<% } %>
+ <% } %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Import') %></li>
<li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
@@ -68,8 +70,10 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-download-raw-html" tabindex="-1" href="#" target="_self"><i class="fa fa-file-code-o fa-fw"></i> <%= __('Raw HTML') %></a>
</li>
- <li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
- </li>
+ <% if(allowpdfexport) {%>
+ <li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
+ </li>
+ <% } %>
<li class="divider"></li>
<li role="presentation"><a role="menuitem" class="ui-help" href="#" data-toggle="modal" data-target=".help-modal"><i class="fa fa-question-circle fa-fw"></i> Help</a>
</li>
@@ -129,6 +133,7 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-extra-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> <%= __('Slide Mode') %></a>
</li>
+ <% if((typeof github !== 'undefined' && github) || (typeof dropbox !== 'undefined' && dropbox) || (typeof google !== 'undefined' && google) || (typeof gitlab !== 'undefined' && gitlab && (!gitlab.scope || gitlab.scope === 'api'))) { %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Export') %></li>
<li role="presentation"><a role="menuitem" class="ui-save-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
@@ -143,6 +148,7 @@
<li role="presentation"><a role="menuitem" class="ui-save-snippet" href="#"><i class="fa fa-gitlab fa-fw"></i> Snippet</a>
</li>
<% } %>
+ <% } %>
<li class="divider"></li>
<li class="dropdown-header"><%= __('Import') %></li>
<li role="presentation"><a role="menuitem" class="ui-import-dropbox" tabindex="-1" href="#" target="_self"><i class="fa fa-dropbox fa-fw"></i> Dropbox</a>
@@ -165,8 +171,10 @@
</li>
<li role="presentation"><a role="menuitem" class="ui-download-raw-html" tabindex="-1" href="#" target="_self"><i class="fa fa-file-code-o fa-fw"></i> <%= __('Raw HTML') %></a>
</li>
- <li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
- </li>
+ <% if(allowpdfexport) {%>
+ <li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
+ </li>
+ <% } %>
</ul>
</li>
</ul>
diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs
index 911742ac..b9c5c426 100644
--- a/public/views/index/body.ejs
+++ b/public/views/index/body.ejs
@@ -130,7 +130,8 @@
</p>
<select class="ui-locale">
<option value="en">English</option>
- <option value="zh">中文</option>
+ <option value="zh-CN">简体中文</option>
+ <option value="zh-TW">繁體中文</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="ja">日本語</option>