From 07d824faca0f823b9d573980144f02be3708cbeb Mon Sep 17 00:00:00 2001 From: Benoit Schweblin Date: Sat, 29 Jun 2019 17:33:21 +0100 Subject: [PATCH] Added server conf endpoint. New localDbSvc.getWorkspaceItems method used to export workspaces. Added offline availability in the workspace management modal. New accordion in the badge management modal. Add badge creation checks in unit tests. --- .eslintrc.js | 4 +- build/webpack.dev.conf.js | 4 +- config/dev.env.js | 4 +- config/index.js | 2 +- config/prod.env.js | 4 +- server/conf.js | 37 +++++++ server/github.js | 3 +- server/index.js | 2 + server/pandoc.js | 4 +- server/pdf.js | 4 +- server/user.js | 19 ++-- src/components/ExplorerNode.vue | 2 +- src/components/Notification.vue | 2 +- src/components/SideBar.vue | 5 +- src/components/menus/MainMenu.vue | 32 +++--- src/components/menus/PublishMenu.vue | 15 ++- src/components/menus/SyncMenu.vue | 6 +- src/components/menus/WorkspaceBackupMenu.vue | 21 ++-- src/components/menus/WorkspacesMenu.vue | 10 +- .../modals/AccountManagementModal.vue | 12 ++- .../modals/BadgeManagementModal.vue | 57 +++++++---- .../modals/WorkspaceManagementModal.vue | 30 +++++- src/data/constants.js | 3 +- src/data/features.js | 98 ++++++++++++++----- src/data/welcomeFile.md | 2 +- src/services/badgeSvc.js | 4 +- src/services/explorerSvc.js | 3 +- src/services/localDbSvc.js | 47 +++++---- src/services/networkSvc.js | 73 +++++++++----- src/services/providers/dropboxProvider.js | 6 +- .../providers/helpers/dropboxHelper.js | 16 ++- .../providers/helpers/githubHelper.js | 4 +- .../providers/helpers/gitlabHelper.js | 1 + .../providers/helpers/googleHelper.js | 17 +--- .../providers/helpers/wordpressHelper.js | 4 +- .../providers/helpers/zendeskHelper.js | 1 + src/services/syncSvc.js | 2 +- src/store/data.js | 33 ++++--- test/unit/jest.conf.js | 2 - test/unit/specs/components/ButtonBar.spec.js | 18 ++-- test/unit/specs/components/Explorer.spec.js | 27 +++-- .../specs/components/ExplorerNode.spec.js | 92 +++++++++++------ .../specs/components/NavigationBar.spec.js | 6 +- test/unit/specs/specUtils.js | 11 ++- 44 files changed, 497 insertions(+), 252 deletions(-) create mode 100644 server/conf.js diff --git a/.eslintrc.js b/.eslintrc.js index 2eb86f64..3c6c4baf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,9 +16,7 @@ module.exports = { ], globals: { "NODE_ENV": false, - "VERSION": false, - "GOOGLE_CLIENT_ID": false, - "GITHUB_CLIENT_ID": false + "VERSION": false }, // check if imports actually resolve 'settings': { diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index fbebcecd..6ec2eef5 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -19,9 +19,7 @@ module.exports = merge(baseWebpackConfig, { devtool: 'source-map', plugins: [ new webpack.DefinePlugin({ - NODE_ENV: config.dev.env.NODE_ENV, - GOOGLE_CLIENT_ID: config.dev.env.GOOGLE_CLIENT_ID, - GITHUB_CLIENT_ID: config.dev.env.GITHUB_CLIENT_ID + NODE_ENV: config.dev.env.NODE_ENV }), // https://github.com/glenjamin/webpack-hot-middleware#installation--usage new webpack.HotModuleReplacementPlugin(), diff --git a/config/dev.env.js b/config/dev.env.js index 6b882aa6..efead7c8 100644 --- a/config/dev.env.js +++ b/config/dev.env.js @@ -2,7 +2,5 @@ var merge = require('webpack-merge') var prodEnv = require('./prod.env') module.exports = merge(prodEnv, { - NODE_ENV: '"development"', - GOOGLE_CLIENT_ID: '"241271498917-c3loeet001r90q6u79q484bsh5clg4fr.apps.googleusercontent.com"', - GITHUB_CLIENT_ID: '"cbf0cf25cfd026be23e1"' + NODE_ENV: '"development"' }) diff --git a/config/index.js b/config/index.js index b5211e47..9fecd843 100644 --- a/config/index.js +++ b/config/index.js @@ -24,7 +24,7 @@ module.exports = { dev: { env: require('./dev.env'), port: 8080, - autoOpenBrowser: true, + autoOpenBrowser: false, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: {}, diff --git a/config/prod.env.js b/config/prod.env.js index ee5d146f..773d263d 100644 --- a/config/prod.env.js +++ b/config/prod.env.js @@ -1,5 +1,3 @@ module.exports = { - NODE_ENV: '"production"', - GOOGLE_CLIENT_ID: '"241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com"', - GITHUB_CLIENT_ID: '"30c1491057c9ad4dbd56"' + NODE_ENV: '"production"' } diff --git a/server/conf.js b/server/conf.js new file mode 100644 index 00000000..0394803f --- /dev/null +++ b/server/conf.js @@ -0,0 +1,37 @@ +const pandocPath = process.env.PANDOC_PATH || 'pandoc'; +const wkhtmltopdfPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf'; +const userBucketName = process.env.USER_BUCKET_NAME || 'stackedit-users'; +const paypalUri = process.env.PAYPAL_URI || 'https://www.paypal.com/cgi-bin/webscr'; +const paypalReceiverEmail = process.env.PAYPAL_RECEIVER_EMAIL; + +const dropboxAppKey = process.env.DROPBOX_APP_KEY; +const dropboxAppKeyFull = process.env.DROPBOX_APP_KEY_FULL; +const githubClientId = process.env.GITHUB_CLIENT_ID; +const githubClientSecret = process.env.GITHUB_CLIENT_SECRET; +const googleClientId = process.env.GOOGLE_CLIENT_ID; +const googleApiKey = process.env.GOOGLE_API_KEY; +const wordpressClientId = process.env.WORDPRESS_CLIENT_ID; + +exports.values = { + pandocPath, + wkhtmltopdfPath, + userBucketName, + paypalUri, + paypalReceiverEmail, + dropboxAppKey, + dropboxAppKeyFull, + githubClientId, + githubClientSecret, + googleClientId, + googleApiKey, + wordpressClientId, +}; + +exports.publicValues = { + dropboxAppKey, + dropboxAppKeyFull, + githubClientId, + googleClientId, + googleApiKey, + wordpressClientId, +}; diff --git a/server/github.js b/server/github.js index eadb340c..83aff9e3 100644 --- a/server/github.js +++ b/server/github.js @@ -1,5 +1,6 @@ const qs = require('qs'); // eslint-disable-line import/no-extraneous-dependencies const request = require('request'); +const conf = require('./conf'); function githubToken(clientId, code) { return new Promise((resolve, reject) => { @@ -8,7 +9,7 @@ function githubToken(clientId, code) { url: 'https://github.com/login/oauth/access_token', qs: { client_id: clientId, - client_secret: process.env.GITHUB_SECRET, + client_secret: conf.values.githubClientSecret, code, }, }, (err, res, body) => { diff --git a/server/index.js b/server/index.js index 289d8519..0f116eee 100644 --- a/server/index.js +++ b/server/index.js @@ -6,6 +6,7 @@ const user = require('./user'); const github = require('./github'); const pdf = require('./pdf'); const pandoc = require('./pandoc'); +const conf = require('./conf'); const resolvePath = pathToResolve => path.join(__dirname, '..', pathToResolve); @@ -24,6 +25,7 @@ module.exports = (app, serveV4) => { } app.get('/oauth2/githubToken', github.githubToken); + app.get('/conf', (req, res) => res.send(conf.publicValues)); app.get('/userInfo', user.userInfo); app.post('/pdfExport', pdf.generate); app.post('/pandocExport', pandoc.generate); diff --git a/server/pandoc.js b/server/pandoc.js index 79b1505f..8ac0f3d3 100644 --- a/server/pandoc.js +++ b/server/pandoc.js @@ -3,6 +3,7 @@ const { spawn } = require('child_process'); const fs = require('fs'); const tmp = require('tmp'); const user = require('./user'); +const conf = require('./conf'); const outputFormats = { asciidoc: 'text/plain', @@ -90,10 +91,9 @@ exports.generate = (req, res) => { reject(error); } - const binPath = process.env.PANDOC_PATH || 'pandoc'; const format = outputFormat === 'pdf' ? 'latex' : outputFormat; params.push('-f', 'json', '-t', format, '-o', filePath); - const pandoc = spawn(binPath, params, { + const pandoc = spawn(conf.values.pandocPath, params, { stdio: [ 'pipe', 'ignore', diff --git a/server/pdf.js b/server/pdf.js index 7b849732..0fe4337a 100644 --- a/server/pdf.js +++ b/server/pdf.js @@ -3,6 +3,7 @@ const { spawn } = require('child_process'); const fs = require('fs'); const tmp = require('tmp'); const user = require('./user'); +const conf = require('./conf'); /* eslint-disable no-var, prefer-arrow-callback, func-names */ function waitForJavaScript() { @@ -127,10 +128,9 @@ exports.generate = (req, res) => { params.push('--page-size', !authorizedPageSizes.includes(options.pageSize) ? 'A4' : options.pageSize); // Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason - const binPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf'; params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`); params.push('--window-status', 'done'); - const wkhtmltopdf = spawn(binPath, params.concat('-', filePath), { + const wkhtmltopdf = spawn(conf.values.wkhtmltopdfPath, params.concat('-', filePath), { stdio: [ 'pipe', 'ignore', diff --git a/server/user.js b/server/user.js index 9ff126d7..6f94411e 100644 --- a/server/user.js +++ b/server/user.js @@ -1,13 +1,8 @@ const request = require('request'); const AWS = require('aws-sdk'); const verifier = require('google-id-token-verifier'); +const conf = require('./conf'); -const { - USER_BUCKET_NAME = 'stackedit-users', - PAYPAL_URI = 'https://www.paypal.com/cgi-bin/webscr', - PAYPAL_RECEIVER_EMAIL = 'stackedit.project@gmail.com', - GOOGLE_CLIENT_ID, -} = process.env; const s3Client = new AWS.S3(); const cb = (resolve, reject) => (err, res) => { @@ -20,7 +15,7 @@ const cb = (resolve, reject) => (err, res) => { exports.getUser = id => new Promise((resolve, reject) => { s3Client.getObject({ - Bucket: USER_BUCKET_NAME, + Bucket: conf.values.userBucketName, Key: id, }, cb(resolve, reject)); }) @@ -35,7 +30,7 @@ exports.getUser = id => new Promise((resolve, reject) => { exports.putUser = (id, user) => new Promise((resolve, reject) => { s3Client.putObject({ - Bucket: USER_BUCKET_NAME, + Bucket: conf.values.userBucketName, Key: id, Body: JSON.stringify(user), }, cb(resolve, reject)); @@ -43,13 +38,13 @@ exports.putUser = (id, user) => new Promise((resolve, reject) => { exports.removeUser = id => new Promise((resolve, reject) => { s3Client.deleteObject({ - Bucket: USER_BUCKET_NAME, + Bucket: conf.values.userBucketName, Key: id, }, cb(resolve, reject)); }); exports.getUserFromToken = idToken => new Promise((resolve, reject) => verifier - .verify(idToken, GOOGLE_CLIENT_ID, cb(resolve, reject))) + .verify(idToken, conf.values.googleClientId, cb(resolve, reject))) .then(tokenInfo => exports.getUser(tokenInfo.sub)); exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken) @@ -78,7 +73,7 @@ exports.paypalIpn = (req, res, next) => Promise.resolve() sponsorUntil = Date.now() + (5 * 366 * 24 * 60 * 60 * 1000); // 5 years } if ( - req.body.receiver_email !== PAYPAL_RECEIVER_EMAIL || + req.body.receiver_email !== conf.values.paypalReceiverEmail || req.body.payment_status !== 'Completed' || req.body.mc_currency !== 'USD' || (req.body.txn_type !== 'web_accept' && req.body.txn_type !== 'subscr_payment') || @@ -90,7 +85,7 @@ exports.paypalIpn = (req, res, next) => Promise.resolve() // Processing PayPal IPN req.body.cmd = '_notify-validate'; return new Promise((resolve, reject) => request.post({ - uri: PAYPAL_URI, + uri: conf.values.paypalUri, form: req.body, }, (err, response, body) => { if (err) { diff --git a/src/components/ExplorerNode.vue b/src/components/ExplorerNode.vue index 092979f1..e3771a3f 100644 --- a/src/components/ExplorerNode.vue +++ b/src/components/ExplorerNode.vue @@ -156,7 +156,7 @@ export default { ...sourceNode.item, parentId: targetNode.item.id, }); - badgeSvc.addBadge('moveFiles'); + badgeSvc.addBadge(sourceNode.isFolder ? 'moveFolder' : 'moveFile'); } }, async onContextMenu(evt) { diff --git a/src/components/Notification.vue b/src/components/Notification.vue index 2ed2ac4c..d60a5ce6 100644 --- a/src/components/Notification.vue +++ b/src/components/Notification.vue @@ -3,7 +3,7 @@
- +
diff --git a/src/components/SideBar.vue b/src/components/SideBar.vue index 99819cb2..bf4019f9 100644 --- a/src/components/SideBar.vue +++ b/src/components/SideBar.vue @@ -175,8 +175,9 @@ export default { p { margin: 10px 15px; - line-height: 1.5; - font-style: italic; + font-size: 0.9rem; + opacity: 0.67; + line-height: 1.3; } } diff --git a/src/components/menus/MainMenu.vue b/src/components/menus/MainMenu.vue index a5a8ef84..85f2260a 100644 --- a/src/components/menus/MainMenu.vue +++ b/src/components/menus/MainMenu.vue @@ -84,25 +84,25 @@ Print
- - -
Settings
- Tweak application and keyboard shortcuts. + + +
Badges
+ List application features and earned badges. +
+ + +
Accounts
+ Manage access to your external accounts.
Templates
Configure Handlebars templates for your exports.
- - -
User accounts
- Manage access to your external accounts. -
- - -
Badges
- List application features and earned badges. + + +
Settings
+ Tweak application and keyboard shortcuts.

@@ -111,10 +111,8 @@ -
Reset application
- Sign out and clean all workspace data. + Reset application
-
About StackEdit @@ -218,7 +216,7 @@ export default { async reset() { try { await store.dispatch('modal/open', 'reset'); - window.location.href = '#reset=true'; + localStorage.setItem('resetStackEdit', '1'); window.location.reload(); } catch (e) { /* Cancel */ } }, diff --git a/src/components/menus/PublishMenu.vue b/src/components/menus/PublishMenu.vue index 4c959045..8655a107 100644 --- a/src/components/menus/PublishMenu.vue +++ b/src/components/menus/PublishMenu.vue @@ -4,9 +4,6 @@

{{currentFileName}} can't be published as it's a temporary file.

- +
@@ -181,8 +181,13 @@ export default { return tokensToArray(store.getters['data/zendeskTokensBySub']); }, noToken() { - return Object.values(store.getters['data/tokensByType']) - .every(tokens => !Object.keys(tokens).length); + return !this.bloggerTokens.length + && !this.dropboxTokens.length + && !this.githubTokens.length + && !this.gitlabTokens.length + && !this.googleDriveTokens.length + && !this.wordpressTokens.length + && !this.zendeskTokens.length; }, }, methods: { diff --git a/src/components/menus/SyncMenu.vue b/src/components/menus/SyncMenu.vue index 6c5451e4..e76e9889 100644 --- a/src/components/menus/SyncMenu.vue +++ b/src/components/menus/SyncMenu.vue @@ -4,9 +4,6 @@

{{currentFileName}} can't be synced as it's a temporary file.

- +
diff --git a/src/components/menus/WorkspaceBackupMenu.vue b/src/components/menus/WorkspaceBackupMenu.vue index 309f107c..d5281fc0 100644 --- a/src/components/menus/WorkspaceBackupMenu.vue +++ b/src/components/menus/WorkspaceBackupMenu.vue @@ -17,15 +17,19 @@ @@ -76,19 +86,24 @@ export default { .badge-entry { font-size: 0.8em; - margin: 0.75rem 0 0; - } - - svg { - width: 1.67em; - height: 1.67em; - margin-right: 0.25em; - opacity: 0.33; - flex: none; + margin: 0.75rem 0; } } -.badge-entry--earned svg { +.badge-entry__icon { + width: 1.67em; + height: 1.67em; + margin-right: 0.25em; + opacity: 0.3; + flex: none; +} + +.badge-entry__icon--some-earned { + opacity: 0.5; + color: goldenrod; +} + +.badge-entry__icon--earned { opacity: 1; color: goldenrod; } @@ -100,10 +115,10 @@ export default { .badge-entry__name { font-size: 1.2em; font-weight: bold; - opacity: 0.5; + opacity: 0.4; +} - .badge-entry--earned & { - opacity: 1; - } +.badge-entry__name--earned { + opacity: 1; } diff --git a/src/components/modals/WorkspaceManagementModal.vue b/src/components/modals/WorkspaceManagementModal.vue index 52120d58..3e9b49ac 100644 --- a/src/components/modals/WorkspaceManagementModal.vue +++ b/src/components/modals/WorkspaceManagementModal.vue @@ -4,7 +4,7 @@ -

The following workspaces are locally available:

+

The following workspaces are accessible:

@@ -48,11 +48,13 @@
+
+ + available offline + +
-