diff --git a/app.js b/app.js index 4980d45d02..4b8499d78e 100644 --- a/app.js +++ b/app.js @@ -196,6 +196,7 @@ app.locals.serverURL = config.serverURL app.locals.sourceURL = config.sourceURL app.locals.allowAnonymous = config.allowAnonymous app.locals.allowAnonymousEdits = config.allowAnonymousEdits +app.locals.allowVisibleSource = config.allowVisibleSource app.locals.permission = config.permission app.locals.allowPDFExport = config.allowPDFExport app.locals.authProviders = { diff --git a/lib/config/default.js b/lib/config/default.js index 95ee1940fe..473adaa681 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -33,6 +33,7 @@ module.exports = { allowAnonymousEdits: true, allowAnonymousViews: true, allowFreeURL: false, + allowVisibleSource: false, forbiddenNoteIDs: ['robots.txt', 'favicon.ico', 'api'], defaultPermission: 'editable', dbURL: '', diff --git a/lib/config/environment.js b/lib/config/environment.js index 0867aecf54..0b9a821aab 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -29,6 +29,7 @@ module.exports = { allowAnonymousEdits: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS_EDITS), allowAnonymousViews: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS_VIEWS), allowFreeURL: toBooleanConfig(process.env.CMD_ALLOW_FREEURL), + allowVisibleSource: toBooleanConfig(process.env.CMD_ALLOW_VISIBLE_SOURCE), forbiddenNoteIDs: toArrayConfig(process.env.CMD_FORBIDDEN_NOTE_IDS), defaultPermission: process.env.CMD_DEFAULT_PERMISSION, dbURL: process.env.CMD_DB_URL, diff --git a/public/css/index.css b/public/css/index.css index eeb1f8b98a..3a2698d5e8 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -11,6 +11,15 @@ body { /*overflow: hidden;*/ } +a[disabled='disabled'] { + color: #939090 !important; + pointer-events: none; +} + +a[disabled='disabled']:hover { + color: #939090 !important; +} + .night a, .night .open-files-container li.selected a { color: #5EB7E0; diff --git a/public/js/index.js b/public/js/index.js index c736cb7f2c..2cf7e392ea 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -256,6 +256,7 @@ const statusType = { // global vars window.loaded = false +let blockSourceView = false let needRefresh = false let isDirty = false let editShown = false @@ -290,6 +291,7 @@ const lastInfo = { } let personalInfo = {} let onlineUsers = [] +let currentPermission = '' const fileTypes = { pl: 'perl', cgi: 'perl', @@ -319,6 +321,21 @@ defaultTextHeight = parseInt($('.CodeMirror').css('line-height')) // initalize ui reference const ui = getUIElements() +// controls to need disabled when allowVisibleSource run +// this prevent user to access source code +const modeChangeControls = { + edit: ui.toolbar.edit, + both: ui.toolbar.both +} +const exportImportControls = { + revision: ui.toolbar.extra.revision, + markdown: ui.toolbar.download.markdown, + rawhtml: ui.toolbar.download.rawhtml, + gist: ui.toolbar.import.gist, + clipboard: ui.toolbar.import.clipboard, + pandoc: ui.toolbar.download.pandoc +} + // page actions var opts = { lines: 11, // The number of lines to draw @@ -423,6 +440,8 @@ Visibility.change(function (e, state) { // when page ready $(document).ready(function () { + if (ui.toolbar.edit.data('blockSource')) { replaceUrlToViewMode(window.location.href) } + idle.checkAway() checkResponsive() // if in smaller screen, we don't need advanced scrollbar @@ -470,12 +489,14 @@ $(document).ready(function () { // allow on all tags key.filter = function (e) { return true } key('ctrl+alt+e', function (e) { + if (blockSourceView) return changeMode(modeType.edit) }) key('ctrl+alt+v', function (e) { changeMode(modeType.view) }) key('ctrl+alt+b', function (e) { + if (blockSourceView) return changeMode(modeType.both) }) // toggle-dropdown @@ -499,6 +520,81 @@ $(window).on('error', function () { // setNeedRefresh(); }) +function checkParameter (isLogin, permission) { + if (typeof isLogin !== 'boolean' || !permission) { + throw new Error('one or more parameter is incorrect') + } + return allowVisibleSource(isLogin, permission) +} + +function replaceUrlToViewMode (url) { + const urlHasEditOrBoth = /\?edit|\?both/ + if (urlHasEditOrBoth.test(url)) { + const newUrl = url.toString().replace(urlHasEditOrBoth, '?view') + window.location.replace(newUrl) + } +} + +function allowVisibleSource (isLogin, permission) { + switch (permission) { + case 'freely': + blockSourceView = false + break + case 'editable': + case 'limited': + if (!isLogin) { + blockSourceView = true + disableControls() + } else { + blockSourceView = false + enableControls() + } + break + case 'locked': + case 'protected': + case 'private': + if (personalInfo.userid && window.owner && personalInfo.userid === window.owner) { + blockSourceView = false + } else { + blockSourceView = true + disableControls() + } + break + } +} + +function disableControls () { + for (const key of Object.keys(modeChangeControls)) { + modeChangeControls[key].attr({ + disabled: 'disabled' + }) + } + for (const key of Object.keys(exportImportControls)) { + exportImportControls[key].attr({ + disabled: 'disabled' + }) + exportImportControls[key].parent().css('cursor', 'not-allowed') + } +} + +function enableControls () { + for (const key of Object.keys(modeChangeControls)) { + modeChangeControls[key].removeAttr('disable') + } + for (const key of Object.keys(exportImportControls)) { + exportImportControls[key].removeAttr('disable') + } +} + +function userIsLogin (userPersonalInfo) { + return !!userPersonalInfo && !!userPersonalInfo.login +} + +function isAllowUserChangeMode () { + const isOwner = personalInfo.userid && window.owner && personalInfo.userid === window.owner + return isOwner || !blockSourceView +} + setupSyncAreas(ui.area.codemirrorScroll, ui.area.view, ui.area.markdown, editor) function autoSyncscroll () { @@ -1567,11 +1663,11 @@ function importFromUrl (url) { // mode ui.toolbar.mode.click(function () { - toggleMode() + if (isAllowUserChangeMode()) { return toggleMode() } }) // edit ui.toolbar.edit.click(function () { - changeMode(modeType.edit) + if (isAllowUserChangeMode()) { return changeMode(modeType.edit) } }) // view ui.toolbar.view.click(function () { @@ -1579,7 +1675,7 @@ ui.toolbar.view.click(function () { }) // both ui.toolbar.both.click(function () { - changeMode(modeType.both) + if (isAllowUserChangeMode()) { return changeMode(modeType.both) } }) ui.toolbar.night.click(function () { @@ -2047,6 +2143,16 @@ socket.on('refresh', function (data) { editor.setOption('maxLength', editorInstance.config.docmaxlength) updateInfo(data) updatePermission(data.permission) + currentPermission = data.permission + // run allowVisibleSource functionality + if (ui.toolbar.edit.data('blockSource')) { + try { + checkParameter(userIsLogin(personalInfo), currentPermission) + } catch (error) { + console.log(error) + } + } + if (ui.toolbar.edit.data('blockSource') && blockSourceView) { replaceUrlToViewMode(window.location.href) } if (!window.loaded) { // auto change mode if no content detected var nocontent = editor.getValue().length <= 0 diff --git a/public/js/lib/editor/ui-elements.js b/public/js/lib/editor/ui-elements.js index d084607fb1..f7b37fe319 100644 --- a/public/js/lib/editor/ui-elements.js +++ b/public/js/lib/editor/ui-elements.js @@ -19,7 +19,8 @@ export const getUIElements = () => ({ markdown: $('.ui-download-markdown'), html: $('.ui-download-html'), rawhtml: $('.ui-download-raw-html'), - pdf: $('.ui-download-pdf-beta') + pdf: $('.ui-download-pdf-beta'), + pandoc: $('.ui-download-pandoc') }, export: { dropbox: $('.ui-save-dropbox'), diff --git a/public/views/codimd/header.ejs b/public/views/codimd/header.ejs index 86c006f783..3ecbf98334 100644 --- a/public/views/codimd/header.ejs +++ b/public/views/codimd/header.ejs @@ -76,20 +76,35 @@
  • Help
  • - + <% if(!allowVisibleSource) { %> + + <% } else { %> + + + + <% } %>