diff options
-rw-r--r-- | README.md | 12 | ||||
-rw-r--r-- | app.js | 16 | ||||
-rw-r--r-- | config.json.example | 3 | ||||
-rw-r--r-- | lib/config/default.js | 1 | ||||
-rw-r--r-- | lib/config/environment.js | 2 | ||||
-rw-r--r-- | lib/config/index.js | 6 | ||||
-rw-r--r-- | lib/history.js | 15 | ||||
-rw-r--r-- | lib/migrations/20150702001020-update-to-0_3_1.js | 6 | ||||
-rw-r--r-- | lib/migrations/20160112220142-note-add-lastchange.js | 6 | ||||
-rw-r--r-- | lib/migrations/20160420180355-note-add-alias.js | 6 | ||||
-rw-r--r-- | lib/migrations/20160515114000-user-add-tokens.js | 6 | ||||
-rw-r--r-- | lib/migrations/20160607060246-support-revision.js | 6 | ||||
-rw-r--r-- | lib/migrations/20160703062241-support-authorship.js | 6 | ||||
-rw-r--r-- | lib/migrations/20161009040430-support-delete-note.js | 8 | ||||
-rw-r--r-- | lib/migrations/20161201050312-support-email-signin.js | 14 | ||||
-rw-r--r-- | lib/models/note.js | 6 | ||||
-rw-r--r-- | lib/response.js | 4 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | public/js/index.js | 17 | ||||
-rw-r--r-- | public/views/codimd/body.ejs | 2 | ||||
-rw-r--r-- | public/views/index/body.ejs | 2 | ||||
-rw-r--r-- | yarn.lock | 6 |
22 files changed, 131 insertions, 21 deletions
@@ -29,6 +29,7 @@ Thanks for using! :smile: - [Heroku Deployment](#heroku-deployment) - [Kubernetes](#kubernetes) - [CodiMD by docker container](#codimd-by-docker-container) + - [Cloudron](#cloudron) - [Upgrade](#upgrade) - [Native setup](#native-setup) - [Configuration](#configuration) @@ -121,6 +122,12 @@ docker-compose up ``` Read more about it in the [docker repository…](https://github.com/hackmdio/docker-hackmd) +## Cloudron + +Install CodiMD on [Cloudron](https://cloudron.io): + +[![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=io.hackmd.cloudronapp) + # Upgrade ## Native setup @@ -170,7 +177,9 @@ There are some config settings you need to change in the files below. | `DEBUG` | `true` or `false` | set debug mode; show more logs | | `CMD_DOMAIN` | `codimd.org` | domain name | | `CMD_URL_PATH` | `codimd` | sub URL path, like `www.example.com/<URL_PATH>` | +| `CMD_HOST` | `localhost` | host to listen on | | `CMD_PORT` | `80` | web app port | +| `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) | | `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) | | `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) | | `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) | @@ -192,6 +201,7 @@ There are some config settings you need to change in the files below. | `CMD_GITLAB_BASEURL` | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) | | `CMD_GITLAB_CLIENTID` | no example | GitLab API client id | | `CMD_GITLAB_CLIENTSECRET` | no example | GitLab API client secret | +| `CMD_GITLAB_VERSION` | no example | GitLab API version (v3 or v4) | | `CMD_MATTERMOST_BASEURL` | no example | Mattermost authentication endpoint | | `CMD_MATTERMOST_CLIENTID` | no example | Mattermost API client id | | `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret | @@ -252,7 +262,9 @@ There are some config settings you need to change in the files below. | `debug` | `true` or `false` | set debug mode, show more logs | | `domain` | `localhost` | domain name | | `urlPath` | `codimd` | sub URL path, like `www.example.com/<urlpath>` | +| `host` | `localhost` | host to listen on | | `port` | `80` | web app port | +| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) | | `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) | @@ -205,11 +205,21 @@ io.sockets.on('connection', realtime.connection) // listen function startListen () { - server.listen(config.port, function () { + var address + var listenCallback = function () { var schema = config.useSSL ? 'HTTPS' : 'HTTP' - logger.info('%s Server listening at port %d', schema, config.port) + logger.info('%s Server listening at %s', schema, address) realtime.maintenance = false - }) + } + + // use unix domain socket if 'path' is specified + if (config.path) { + address = config.path + server.listen(config.path, listenCallback) + } else { + address = config.host + ':' + config.port + server.listen(config.port, config.host, listenCallback) + } } // sync db then start listen diff --git a/config.json.example b/config.json.example index 1f2ec3d5..16c95509 100644 --- a/config.json.example +++ b/config.json.example @@ -55,7 +55,8 @@ "baseURL": "change this", "clientID": "change this", "clientSecret": "change this", - "scope": "use 'read_user' scope for auth user only or remove this property if you need gitlab snippet import/export support (will result to be default scope 'api')" + "scope": "use 'read_user' scope for auth user only or remove this property if you need gitlab snippet import/export support (will result to be default scope 'api')", + "version": "use 'v4' if gitlab version > 11, 'v3' otherwise. Default to 'v4'" }, "mattermost": { "baseURL": "change this", diff --git a/lib/config/default.js b/lib/config/default.js index 5c39a4da..6096bce4 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -3,6 +3,7 @@ module.exports = { domain: '', urlPath: '', + host: '0.0.0.0', port: 3000, urlAddPort: false, allowOrigin: ['localhost'], diff --git a/lib/config/environment.js b/lib/config/environment.js index d850ac9d..6c4ce92f 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -5,7 +5,9 @@ const {toBooleanConfig, toArrayConfig, toIntegerConfig} = require('./utils') module.exports = { domain: process.env.CMD_DOMAIN, urlPath: process.env.CMD_URL_PATH, + host: process.env.CMD_HOST, port: toIntegerConfig(process.env.CMD_PORT), + path: process.env.CMD_PATH, urlAddPort: toBooleanConfig(process.env.CMD_URL_ADDPORT), useSSL: toBooleanConfig(process.env.CMD_USESSL), hsts: { diff --git a/lib/config/index.js b/lib/config/index.js index ac03fcd4..f96684ea 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -103,6 +103,12 @@ config.isSAMLEnable = config.saml.idpSsoUrl config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret config.isPDFExportEnable = config.allowPDFExport +// Check gitlab api version +if (config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') { + logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4') + config.gitlab.version = 'v4' +} + // Only update i18n files in development setups config.updateI18nFiles = (env === Environment.development) diff --git a/lib/history.js b/lib/history.js index c7d2472c..9c389bfa 100644 --- a/lib/history.js +++ b/lib/history.js @@ -31,6 +31,15 @@ function getHistory (userid, callback) { history = JSON.parse(user.history) // migrate LZString encoded note id to base64url encoded note id for (let i = 0, l = history.length; i < l; i++) { + // Calculate minimal string length for an UUID that is encoded + // base64 encoded and optimize comparsion by using -1 + // this should make a lot of LZ-String parsing errors obsolete + // as we can assume that a nodeId that is 48 chars or longer is a + // noteID. + const base64UuidLength = ((4 * 36) / 3) - 1 + if (!(history[i].id.length > base64UuidLength)) { + continue + } try { let id = LZString.decompressFromBase64(history[i].id) if (id && models.Note.checkNoteIdValid(id)) { @@ -38,7 +47,11 @@ function getHistory (userid, callback) { } } catch (err) { // most error here comes from LZString, ignore - logger.error(err) + if (err.message === 'Cannot read property \'charAt\' of undefined') { + logger.warning('Looks like we can not decode "' + history[i].id + '" with LZString. Can be ignored.') + } else { + logger.error(err) + } } } history = parseHistoryToObject(history) diff --git a/lib/migrations/20150702001020-update-to-0_3_1.js b/lib/migrations/20150702001020-update-to-0_3_1.js index 40d9c97a..e661a343 100644 --- a/lib/migrations/20150702001020-update-to-0_3_1.js +++ b/lib/migrations/20150702001020-update-to-0_3_1.js @@ -20,6 +20,12 @@ module.exports = { type: Sequelize.INTEGER, defaultValue: 0 }) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'shortid'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/migrations/20160112220142-note-add-lastchange.js b/lib/migrations/20160112220142-note-add-lastchange.js index b4e111b3..d0030d6b 100644 --- a/lib/migrations/20160112220142-note-add-lastchange.js +++ b/lib/migrations/20160112220142-note-add-lastchange.js @@ -7,6 +7,12 @@ module.exports = { return queryInterface.addColumn('Notes', 'lastchangeAt', { type: Sequelize.DATE }) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'lastchangeuserId'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/migrations/20160420180355-note-add-alias.js b/lib/migrations/20160420180355-note-add-alias.js index a043cd5c..4bad29ca 100644 --- a/lib/migrations/20160420180355-note-add-alias.js +++ b/lib/migrations/20160420180355-note-add-alias.js @@ -7,6 +7,12 @@ module.exports = { return queryInterface.addIndex('Notes', ['alias'], { indicesType: 'UNIQUE' }) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'alias'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/migrations/20160515114000-user-add-tokens.js b/lib/migrations/20160515114000-user-add-tokens.js index 4d5818be..4245f1ad 100644 --- a/lib/migrations/20160515114000-user-add-tokens.js +++ b/lib/migrations/20160515114000-user-add-tokens.js @@ -3,6 +3,12 @@ module.exports = { up: function (queryInterface, Sequelize) { return queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING).then(function () { return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'accessToken'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/migrations/20160607060246-support-revision.js b/lib/migrations/20160607060246-support-revision.js index bcab97e3..10f288b0 100644 --- a/lib/migrations/20160607060246-support-revision.js +++ b/lib/migrations/20160607060246-support-revision.js @@ -15,6 +15,12 @@ module.exports = { createdAt: Sequelize.DATE, updatedAt: Sequelize.DATE }) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'savedAt'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/migrations/20160703062241-support-authorship.js b/lib/migrations/20160703062241-support-authorship.js index d73923b0..b3ced8c4 100644 --- a/lib/migrations/20160703062241-support-authorship.js +++ b/lib/migrations/20160703062241-support-authorship.js @@ -16,6 +16,12 @@ module.exports = { createdAt: Sequelize.DATE, updatedAt: Sequelize.DATE }) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'authorship'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/migrations/20161009040430-support-delete-note.js b/lib/migrations/20161009040430-support-delete-note.js index a39d1086..4df7a81c 100644 --- a/lib/migrations/20161009040430-support-delete-note.js +++ b/lib/migrations/20161009040430-support-delete-note.js @@ -1,7 +1,13 @@ 'use strict' module.exports = { up: function (queryInterface, Sequelize) { - return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE) + return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'deletedAt'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } + }) }, down: function (queryInterface, Sequelize) { diff --git a/lib/migrations/20161201050312-support-email-signin.js b/lib/migrations/20161201050312-support-email-signin.js index d33225f1..4653e67a 100644 --- a/lib/migrations/20161201050312-support-email-signin.js +++ b/lib/migrations/20161201050312-support-email-signin.js @@ -2,7 +2,19 @@ module.exports = { up: function (queryInterface, Sequelize) { return queryInterface.addColumn('Users', 'email', Sequelize.TEXT).then(function () { - return queryInterface.addColumn('Users', 'password', Sequelize.TEXT) + return queryInterface.addColumn('Users', 'password', Sequelize.TEXT).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'password'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } + }) + }).catch(function (error) { + if (error.message === "ER_DUP_FIELDNAME: Duplicate column name 'email'") { + console.log('Migration has already run… ignoring.') + } else { + throw error + } }) }, diff --git a/lib/models/note.js b/lib/models/note.js index ec7e2b13..0e8dd4dd 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -227,7 +227,11 @@ module.exports = function (sequelize, DataTypes) { var id = LZString.decompressFromBase64(noteId) if (id && Note.checkNoteIdValid(id)) { return callback(null, id) } else { return _callback(null, null) } } catch (err) { - logger.error(err) + if (err.message === 'Cannot read property \'charAt\' of undefined') { + logger.warning('Looks like we can not decode "' + noteId + '" with LZString. Can be ignored.') + } else { + logger.error(err) + } return _callback(null, null) } }, diff --git a/lib/response.js b/lib/response.js index 3a31c511..37211998 100644 --- a/lib/response.js +++ b/lib/response.js @@ -573,11 +573,11 @@ function gitlabActionProjects (req, res, note) { } }).then(function (user) { if (!user) { return response.errorNotFound(res) } - var ret = { baseURL: config.gitlab.baseURL } + var ret = { baseURL: config.gitlab.baseURL, version: config.gitlab.version } ret.accesstoken = user.accessToken ret.profileid = user.profileid request( - config.gitlab.baseURL + '/api/v3/projects?access_token=' + user.accessToken, + config.gitlab.baseURL + '/api/' + config.gitlab.version + '/projects?access_token=' + user.accessToken, function (error, httpResponse, body) { if (!error && httpResponse.statusCode === 200) { ret.projects = JSON.parse(body) diff --git a/package.json b/package.json index 88bef3e0..1740500c 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "markdown-it-regexp": "^0.4.0", "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", - "markdown-pdf": "^8.0.0", + "markdown-pdf": "^9.0.0", "mathjax": "~2.7.0", "mermaid": "~7.1.0", "mattermost": "^3.4.0", diff --git a/public/js/index.js b/public/js/index.js index 6e13fe9c..1330deac 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -970,6 +970,7 @@ ui.toolbar.export.snippet.click(function () { .done(function (data) { $('#snippetExportModalAccessToken').val(data.accesstoken) $('#snippetExportModalBaseURL').val(data.baseURL) + $('#snippetExportModalVersion').val(data.version) $('#snippetExportModalLoading').hide() $('#snippetExportModal').modal('toggle') $('#snippetExportModalProjects').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Projects</option>') @@ -1021,6 +1022,7 @@ ui.toolbar.import.snippet.click(function () { .done(function (data) { $('#snippetImportModalAccessToken').val(data.accesstoken) $('#snippetImportModalBaseURL').val(data.baseURL) + $('#snippetImportModalVersion').val(data.version) $('#snippetImportModalContent').prop('disabled', false) $('#snippetImportModalConfirm').prop('disabled', false) $('#snippetImportModalLoading').hide() @@ -1243,10 +1245,10 @@ ui.modal.snippetImportProjects.change(function () { var accesstoken = $('#snippetImportModalAccessToken').val() var baseURL = $('#snippetImportModalBaseURL').val() var project = $('#snippetImportModalProjects').val() - + var version = $('#snippetImportModalVersion').val() $('#snippetImportModalLoading').show() $('#snippetImportModalContent').val('/projects/' + project) - $.get(baseURL + '/api/v3/projects/' + project + '/snippets?access_token=' + accesstoken) + $.get(baseURL + '/api/' + version + '/projects/' + project + '/snippets?access_token=' + accesstoken) .done(function (data) { $('#snippetImportModalSnippets').find('option').remove().end().append('<option value="init" selected="selected" disabled="disabled">Select From Available Snippets</option>') data.forEach(function (snippet) { @@ -1433,7 +1435,7 @@ $('#snippetImportModalConfirm').click(function () { } else { ui.spinner.show() var accessToken = '?access_token=' + $('#snippetImportModalAccessToken').val() - var fullURL = $('#snippetImportModalBaseURL').val() + '/api/v3' + snippeturl + var fullURL = $('#snippetImportModalBaseURL').val() + '/api/' + $('#snippetImportModalVersion').val() + snippeturl $.get(fullURL + accessToken) .done(function (data) { var content = '# ' + (data.title || 'Snippet Import') @@ -1470,15 +1472,19 @@ $('#snippetImportModalConfirm').click(function () { $('#snippetExportModalConfirm').click(function () { var accesstoken = $('#snippetExportModalAccessToken').val() var baseURL = $('#snippetExportModalBaseURL').val() + var version = $('#snippetExportModalVersion').val() + var data = { title: $('#snippetExportModalTitle').val(), file_name: $('#snippetExportModalFileName').val(), code: editor.getValue(), - visibility_level: $('#snippetExportModalVisibility').val() + visibility_level: $('#snippetExportModalVisibility').val(), + visibility: $('#snippetExportModalVisibility').val() === 0 ? 'private' : ($('#snippetExportModalVisibility').val() === 10 ? 'internal' : '') } + if (!data.title || !data.file_name || !data.code || !data.visibility_level || !$('#snippetExportModalProjects').val()) return $('#snippetExportModalLoading').show() - var fullURL = baseURL + '/api/v3/projects/' + $('#snippetExportModalProjects').val() + '/snippets?access_token=' + accesstoken + var fullURL = baseURL + '/api/' + version + '/projects/' + $('#snippetExportModalProjects').val() + '/snippets?access_token=' + accesstoken $.post(fullURL , data , function (ret) { @@ -1487,7 +1493,6 @@ $('#snippetExportModalConfirm').click(function () { var redirect = baseURL + '/' + $("#snippetExportModalProjects option[value='" + $('#snippetExportModalProjects').val() + "']").text() + '/snippets/' + ret.id showMessageModal('<i class="fa fa-gitlab"></i> Export to Snippet', 'Export Successful!', redirect, 'View Snippet Here', true) } - , 'json' ) }) diff --git a/public/views/codimd/body.ejs b/public/views/codimd/body.ejs index b5932a61..d4f27a93 100644 --- a/public/views/codimd/body.ejs +++ b/public/views/codimd/body.ejs @@ -153,6 +153,7 @@ <div class="modal-body"> <input type="hidden" id="snippetImportModalAccessToken" /> <input type="hidden" id="snippetImportModalBaseURL" /> + <input type="hidden" id="snippetImportModalVersion" /> <div class="ui-field-contain" style="display:table;margin-bottom:10px;width:100%;"> <div style="display:table-row;margin-bottom:5px;"> <label style="display:table-cell;">Project:</label> @@ -191,6 +192,7 @@ <div class="modal-body"> <input type="hidden" id="snippetExportModalAccessToken" /> <input type="hidden" id="snippetExportModalBaseURL" /> + <input type="hidden" id="snippetExportModalVersion" /> <div class="ui-field-contain" style="display:table;margin-bottom:10px;width:100%;"> <div style="display:table-row;margin-bottom:5px;"> <label style="display:table-cell;">Title:</label> diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs index 29fa3181..53dbf2a2 100644 --- a/public/views/index/body.ejs +++ b/public/views/index/body.ejs @@ -152,7 +152,7 @@ © 2018 <a href="https://hackmd.io">CodiMD</a> | <a href="<%- url %>/s/release-notes" target="_blank"><%= __('Releases') %></a><% if(privacyStatement) { %> | <a href="<%- url %>/s/privacy" target="_blank"><%= __('Privacy') %></a><% } %><% if(termsOfUse) { %> | <a href="<%- url %>/s/terms-of-use" target="_blank"><%= __('Terms of Use') %></a><% } %> </p> <h6 class="social-foot"> - <%- __('Follow us on %s and %s.', '<a href="https://github.com/hackmdio/CodiMD" target="_blank"><i class="fa fa-github"></i> GitHub</a>, <a href="https://twitter.com/hackmdio" target="_blank"><i class="fa fa-twitter"></i> Twitter</a>', '<a href="https://www.facebook.com/hackmdio" target="_blank"><i class="fa fa-facebook-square"></i> Facebook</a>') %> + <%- __('Follow us on %s and %s.', '<a href="https://github.com/hackmdio/CodiMD" target="_blank"><i class="fa fa-github"></i> GitHub</a>, <a href="https://riot.im/app/#/room/#codimd:matrix.org" target="_blank"><i class="fa fa-comments"></i> Riot</a>') %> </h6> </div> </div> @@ -4698,9 +4698,9 @@ markdown-it@^8.2.2: mdurl "^1.0.1" uc.micro "^1.0.3" -markdown-pdf@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/markdown-pdf/-/markdown-pdf-8.1.1.tgz#25c025d4f4f91869ac0f3f6fd7f7d32237669438" +markdown-pdf@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/markdown-pdf/-/markdown-pdf-9.0.0.tgz#d699f29c3b6c41da4b9a2ec7d09ea8895daef146" dependencies: commander "^2.2.0" duplexer "^0.1.1" |