mirror of
https://gitee.com/mafgwo/stackedit
synced 2024-11-16 11:42:23 +08:00
Merge branch 'dev'
This commit is contained in:
commit
9a6d0bddc8
2
.babelrc
2
.babelrc
|
@ -8,7 +8,7 @@
|
||||||
"env": {
|
"env": {
|
||||||
"test": {
|
"test": {
|
||||||
"presets": ["env", "stage-2"],
|
"presets": ["env", "stage-2"],
|
||||||
"plugins": [ "istanbul" ]
|
"plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,8 +3,7 @@ node_modules/
|
||||||
dist/
|
dist/
|
||||||
.history
|
.history
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.vscode
|
.vscode
|
||||||
stackedit_v4
|
stackedit_v4
|
||||||
chrome-app/*.zip
|
chrome-app/*.zip
|
||||||
|
/test/unit/coverage/
|
||||||
|
|
46
CHANGELOG.md
46
CHANGELOG.md
|
@ -1,46 +0,0 @@
|
||||||
### v5.11
|
|
||||||
|
|
||||||
- New file properties modal with extension presets
|
|
||||||
- Added new Markdown extensions: task lists, image size, mark
|
|
||||||
|
|
||||||
### v5.10
|
|
||||||
|
|
||||||
- Added temporary folder
|
|
||||||
- New iframe mode (see [here](https://benweet.github.io/stackedit.js/))
|
|
||||||
|
|
||||||
### v5.9
|
|
||||||
|
|
||||||
- Added explorer context menu
|
|
||||||
|
|
||||||
### v5.8
|
|
||||||
|
|
||||||
- New import menu with HTML to Markdown conversion
|
|
||||||
- HTML to Markdown conversion when pasting rich text in the editor
|
|
||||||
- Custom scrollbars on webkit
|
|
||||||
|
|
||||||
### v5.7
|
|
||||||
|
|
||||||
- Support for CouchDB workspaces
|
|
||||||
- Added FAQ
|
|
||||||
- Added welcome tour
|
|
||||||
|
|
||||||
### v5.6
|
|
||||||
|
|
||||||
- Themes support with new dark theme
|
|
||||||
|
|
||||||
### v5.5
|
|
||||||
|
|
||||||
- Integration with Google Drive
|
|
||||||
- New landing page
|
|
||||||
|
|
||||||
### v5.4
|
|
||||||
|
|
||||||
- Multi-workspaces capabilities
|
|
||||||
|
|
||||||
### v5.3
|
|
||||||
|
|
||||||
- Revision history
|
|
||||||
|
|
||||||
### v5.2
|
|
||||||
|
|
||||||
- Support for discussions/comments
|
|
|
@ -8,7 +8,9 @@ ENV V4_VERSION 4.3.22
|
||||||
RUN npm pack stackedit@$V4_VERSION \
|
RUN npm pack stackedit@$V4_VERSION \
|
||||||
&& tar xzf stackedit-*.tgz --strip 1 \
|
&& tar xzf stackedit-*.tgz --strip 1 \
|
||||||
&& yarn \
|
&& yarn \
|
||||||
&& yarn cache clean
|
&& yarn cache clean \
|
||||||
|
&& rm -rf ~/.cache/bower \
|
||||||
|
&& rm -rf ~/.local/share/bower
|
||||||
|
|
||||||
WORKDIR /opt/stackedit
|
WORKDIR /opt/stackedit
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
https://stackedit.io/
|
https://stackedit.io/
|
||||||
|
|
||||||
### NEW!!! Embed StackEdit in any website!
|
### Ecosystem
|
||||||
|
|
||||||
See https://github.com/benweet/stackedit.js
|
- [Chrome app](https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg)
|
||||||
|
- NEW! Embed StackEdit in any website with [stackedit.js](https://github.com/benweet/stackedit.js)
|
||||||
Chrome extension: https://chrome.google.com/webstore/detail/ajehldoplanpchfokmeempkekhnhmoha
|
- NEW! [Chrome extension](https://chrome.google.com/webstore/detail/ajehldoplanpchfokmeempkekhnhmoha) that uses stackedit.js
|
||||||
|
- [Community](https://community.stackedit.io/)
|
||||||
|
|
||||||
### Build Setup
|
### Build Setup
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ var path = require('path')
|
||||||
var webpack = require('webpack')
|
var webpack = require('webpack')
|
||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
|
var VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
var vueLoaderConfig = require('./vue-loader.conf')
|
var vueLoaderConfig = require('./vue-loader.conf')
|
||||||
var StylelintPlugin = require('stylelint-webpack-plugin')
|
var StylelintPlugin = require('stylelint-webpack-plugin')
|
||||||
var FaviconsWebpackPlugin = require('favicons-webpack-plugin')
|
var FaviconsWebpackPlugin = require('favicons-webpack-plugin')
|
||||||
|
@ -81,6 +82,7 @@ module.exports = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new VueLoaderPlugin(),
|
||||||
new StylelintPlugin({
|
new StylelintPlugin({
|
||||||
files: ['**/*.vue', '**/*.scss']
|
files: ['**/*.vue', '**/*.scss']
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -98,6 +98,7 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||||
ServiceWorker: {
|
ServiceWorker: {
|
||||||
events: true
|
events: true
|
||||||
},
|
},
|
||||||
|
AppCache: true,
|
||||||
excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html', '**/icons-*/*.png', '**/static/fonts/KaTeX_*'],
|
excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html', '**/icons-*/*.png', '**/static/fonts/KaTeX_*'],
|
||||||
externals: ['/', '/app', '/oauth2/callback']
|
externals: ['/', '/app', '/oauth2/callback']
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,7 +14,7 @@ function resolve (dir) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
style: './src/components/style.scss'
|
style: './src/styles/'
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [{
|
||||||
|
|
8958
package-lock.json
generated
8958
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
105
package.json
105
package.json
|
@ -14,7 +14,9 @@
|
||||||
"build": "node build/build.js && npm run build-style",
|
"build": "node build/build.js && npm run build-style",
|
||||||
"build-style": "webpack --config build/webpack.style.conf.js",
|
"build-style": "webpack --config build/webpack.style.conf.js",
|
||||||
"lint": "eslint --ext .js,.vue src server",
|
"lint": "eslint --ext .js,.vue src server",
|
||||||
"test": "npm run lint",
|
"unit": "jest --config test/unit/jest.conf.js --runInBand",
|
||||||
|
"unit-with-coverage": "jest --config test/unit/jest.conf.js --runInBand --coverage",
|
||||||
|
"test": "npm run lint && npm run unit",
|
||||||
"preversion": "npm run test",
|
"preversion": "npm run test",
|
||||||
"postversion": "git push origin master --tags && npm publish",
|
"postversion": "git push origin master --tags && npm publish",
|
||||||
"patch": "npm version patch -m \"Tag v%s\"",
|
"patch": "npm version patch -m \"Tag v%s\"",
|
||||||
|
@ -22,19 +24,21 @@
|
||||||
"major": "npm version major -m \"Tag v%s\""
|
"major": "npm version major -m \"Tag v%s\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vue/test-utils": "^1.0.0-beta.16",
|
||||||
"aws-sdk": "^2.133.0",
|
"aws-sdk": "^2.133.0",
|
||||||
|
"babel-runtime": "^6.26.0",
|
||||||
"bezier-easing": "^1.1.0",
|
"bezier-easing": "^1.1.0",
|
||||||
"body-parser": "^1.18.2",
|
"body-parser": "^1.18.2",
|
||||||
"clipboard": "^1.7.1",
|
"clipboard": "^1.7.1",
|
||||||
"compression": "^1.7.0",
|
"compression": "^1.7.0",
|
||||||
"diff-match-patch": "^1.0.0",
|
"diff-match-patch": "^1.0.0",
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.8",
|
||||||
"google-id-token-verifier": "^0.2.3",
|
"google-id-token-verifier": "^0.2.3",
|
||||||
"handlebars": "^4.0.10",
|
"handlebars": "^4.0.10",
|
||||||
"indexeddbshim": "^3.0.4",
|
"indexeddbshim": "^3.6.2",
|
||||||
"js-yaml": "^3.9.1",
|
"js-yaml": "^3.11.0",
|
||||||
"katex": "^0.9.0-alpha1",
|
"katex": "^0.9.0",
|
||||||
"markdown-it": "^8.3.1",
|
"markdown-it": "^8.4.1",
|
||||||
"markdown-it-abbr": "^1.0.4",
|
"markdown-it-abbr": "^1.0.4",
|
||||||
"markdown-it-deflist": "^2.0.2",
|
"markdown-it-deflist": "^2.0.2",
|
||||||
"markdown-it-emoji": "^1.3.0",
|
"markdown-it-emoji": "^1.3.0",
|
||||||
|
@ -46,21 +50,24 @@
|
||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"mermaid": "^7.1.0",
|
"mermaid": "^7.1.0",
|
||||||
"mousetrap": "^1.6.1",
|
"mousetrap": "^1.6.1",
|
||||||
"normalize-scss": "^7.0.0",
|
"normalize-scss": "^7.0.1",
|
||||||
"prismjs": "^1.6.0",
|
"prismjs": "^1.6.0",
|
||||||
"request": "^2.82.0",
|
"request": "^2.85.0",
|
||||||
"serve-static": "^1.12.6",
|
"serve-static": "^1.13.2",
|
||||||
"tmp": "^0.0.33",
|
"tmp": "^0.0.33",
|
||||||
"turndown": "^4.0.1",
|
"turndown": "^4.0.2",
|
||||||
"vue": "^2.3.3",
|
"vue": "^2.5.16",
|
||||||
"vuex": "^2.3.1"
|
"vuex": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.7.2",
|
"autoprefixer": "^6.7.2",
|
||||||
"babel-core": "^6.22.1",
|
"babel-core": "^6.26.3",
|
||||||
"babel-eslint": "^7.1.1",
|
"babel-eslint": "^8.2.3",
|
||||||
"babel-loader": "^6.2.10",
|
"babel-jest": "^21.0.2",
|
||||||
"babel-plugin-transform-runtime": "^6.22.0",
|
"babel-loader": "^7.1.4",
|
||||||
|
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||||
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||||
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-polyfill": "^6.23.0",
|
"babel-polyfill": "^6.23.0",
|
||||||
"babel-preset-env": "^1.3.2",
|
"babel-preset-env": "^1.3.2",
|
||||||
"babel-preset-stage-2": "^6.22.0",
|
"babel-preset-stage-2": "^6.22.0",
|
||||||
|
@ -68,57 +75,63 @@
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"connect-history-api-fallback": "^1.3.0",
|
"connect-history-api-fallback": "^1.3.0",
|
||||||
"copy-webpack-plugin": "^4.5.1",
|
"copy-webpack-plugin": "^4.5.1",
|
||||||
"css-loader": "^0.28.7",
|
"css-loader": "^0.28.11",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^4.19.1",
|
||||||
"eslint-config-airbnb-base": "^11.1.3",
|
"eslint-config-airbnb-base": "^12.1.0",
|
||||||
"eslint-friendly-formatter": "^2.0.7",
|
"eslint-friendly-formatter": "^4.0.1",
|
||||||
"eslint-import-resolver-webpack": "^0.8.1",
|
"eslint-import-resolver-webpack": "^0.9.0",
|
||||||
"eslint-loader": "^1.7.1",
|
"eslint-loader": "^2.0.0",
|
||||||
"eslint-plugin-html": "^2.0.0",
|
"eslint-plugin-html": "^4.0.3",
|
||||||
"eslint-plugin-import": "^2.2.0",
|
"eslint-plugin-import": "^2.11.0",
|
||||||
"eventsource-polyfill": "^0.9.6",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"express": "^4.15.5",
|
"express": "^4.16.3",
|
||||||
"extract-text-webpack-plugin": "^2.0.0",
|
"extract-text-webpack-plugin": "^2.0.0",
|
||||||
"favicons-webpack-plugin": "^0.0.7",
|
"favicons-webpack-plugin": "^0.0.9",
|
||||||
"file-loader": "^0.11.1",
|
"file-loader": "^1.1.11",
|
||||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
"friendly-errors-webpack-plugin": "^1.7.0",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"html-webpack-plugin": "^2.28.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"http-proxy-middleware": "^0.17.3",
|
"http-proxy-middleware": "^0.18.0",
|
||||||
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"ignore-loader": "^0.1.2",
|
"ignore-loader": "^0.1.2",
|
||||||
"node-sass": "^4.5.3",
|
"jest": "^23.0.0",
|
||||||
|
"jest-raw-loader": "^1.0.1",
|
||||||
|
"jest-serializer-vue": "^0.3.0",
|
||||||
|
"node-sass": "^4.9.0",
|
||||||
"npm-bump": "^0.0.23",
|
"npm-bump": "^0.0.23",
|
||||||
"offline-plugin": "^4.8.4",
|
"offline-plugin": "^5.0.3",
|
||||||
"opn": "^4.0.2",
|
"opn": "^4.0.2",
|
||||||
"optimize-css-assets-webpack-plugin": "^1.3.0",
|
"optimize-css-assets-webpack-plugin": "^1.3.2",
|
||||||
"ora": "^1.2.0",
|
"ora": "^1.2.0",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"rimraf": "^2.6.0",
|
"rimraf": "^2.6.0",
|
||||||
"sass-loader": "^6.0.5",
|
"sass-loader": "^7.0.1",
|
||||||
"semver": "^5.3.0",
|
"semver": "^5.5.0",
|
||||||
"shelljs": "^0.7.6",
|
"shelljs": "^0.8.1",
|
||||||
|
"stylelint": "^9.2.0",
|
||||||
"stylelint-config-standard": "^16.0.0",
|
"stylelint-config-standard": "^16.0.0",
|
||||||
"stylelint-processor-html": "^1.0.0",
|
"stylelint-processor-html": "^1.0.0",
|
||||||
"stylelint-webpack-plugin": "^0.7.0",
|
"stylelint-webpack-plugin": "^0.10.4",
|
||||||
"url-loader": "^0.5.8",
|
"url-loader": "^1.0.1",
|
||||||
"vue-loader": "^12.1.0",
|
"vue-jest": "^1.0.2",
|
||||||
"vue-style-loader": "^3.0.1",
|
"vue-loader": "^15.0.9",
|
||||||
"vue-template-compiler": "^2.3.3",
|
"vue-style-loader": "^4.1.0",
|
||||||
|
"vue-template-compiler": "^2.5.16",
|
||||||
"webpack": "^2.6.1",
|
"webpack": "^2.6.1",
|
||||||
"webpack-bundle-analyzer": "^2.2.1",
|
"webpack-bundle-analyzer": "^2.2.1",
|
||||||
"webpack-dev-middleware": "^1.10.0",
|
"webpack-dev-middleware": "^1.10.0",
|
||||||
"webpack-hot-middleware": "^2.18.0",
|
"webpack-hot-middleware": "^2.18.0",
|
||||||
"webpack-merge": "^4.1.0",
|
"webpack-merge": "^4.1.2",
|
||||||
"worker-loader": "^0.8.1"
|
"worker-loader": "^1.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4.0.0",
|
"node": ">= 8.0.0",
|
||||||
"npm": ">= 3.0.0"
|
"npm": ">= 5.0.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
"last 2 versions",
|
"last 2 versions",
|
||||||
"not ie <= 8"
|
"not ie <= 10"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,5 +29,8 @@ exports.githubToken = (req, res) => {
|
||||||
githubToken(req.query.clientId, req.query.code)
|
githubToken(req.query.clientId, req.query.code)
|
||||||
.then(
|
.then(
|
||||||
token => res.send(token),
|
token => res.send(token),
|
||||||
err => res.status(400).send(err ? err.message || err.toString() : 'bad_code'));
|
err => res
|
||||||
|
.status(400)
|
||||||
|
.send(err ? err.message || err.toString() : 'bad_code'),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* global window */
|
/* global window */
|
||||||
const spawn = require('child_process').spawn;
|
const { spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const tmp = require('tmp');
|
const tmp = require('tmp');
|
||||||
const user = require('./user');
|
const user = require('./user');
|
||||||
|
@ -76,7 +76,7 @@ exports.generate = (req, res) => {
|
||||||
params.push('--toc');
|
params.push('--toc');
|
||||||
}
|
}
|
||||||
options.tocDepth = parseInt(options.tocDepth, 10);
|
options.tocDepth = parseInt(options.tocDepth, 10);
|
||||||
if (!isNaN(options.tocDepth)) {
|
if (!Number.isNaN(options.tocDepth)) {
|
||||||
params.push('--toc-depth', options.tocDepth);
|
params.push('--toc-depth', options.tocDepth);
|
||||||
}
|
}
|
||||||
options.highlightStyle = highlightStyles.indexOf(options.highlightStyle) !== -1 ? options.highlightStyle : 'kate';
|
options.highlightStyle = highlightStyles.indexOf(options.highlightStyle) !== -1 ? options.highlightStyle : 'kate';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* global window,MathJax */
|
/* global window,MathJax */
|
||||||
const spawn = require('child_process').spawn;
|
const { spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const tmp = require('tmp');
|
const tmp = require('tmp');
|
||||||
const user = require('./user');
|
const user = require('./user');
|
||||||
|
@ -84,13 +84,13 @@ exports.generate = (req, res) => {
|
||||||
|
|
||||||
// Margins
|
// Margins
|
||||||
const marginTop = parseInt(`${options.marginTop}`, 10);
|
const marginTop = parseInt(`${options.marginTop}`, 10);
|
||||||
params.push('-T', isNaN(marginTop) ? 25 : marginTop);
|
params.push('-T', Number.isNaN(marginTop) ? 25 : marginTop);
|
||||||
const marginRight = parseInt(`${options.marginRight}`, 10);
|
const marginRight = parseInt(`${options.marginRight}`, 10);
|
||||||
params.push('-R', isNaN(marginRight) ? 25 : marginRight);
|
params.push('-R', Number.isNaN(marginRight) ? 25 : marginRight);
|
||||||
const marginBottom = parseInt(`${options.marginBottom}`, 10);
|
const marginBottom = parseInt(`${options.marginBottom}`, 10);
|
||||||
params.push('-B', isNaN(marginBottom) ? 25 : marginBottom);
|
params.push('-B', Number.isNaN(marginBottom) ? 25 : marginBottom);
|
||||||
const marginLeft = parseInt(`${options.marginLeft}`, 10);
|
const marginLeft = parseInt(`${options.marginLeft}`, 10);
|
||||||
params.push('-L', isNaN(marginLeft) ? 25 : marginLeft);
|
params.push('-L', Number.isNaN(marginLeft) ? 25 : marginLeft);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
if (options.headerCenter) {
|
if (options.headerCenter) {
|
||||||
|
|
|
@ -2,10 +2,12 @@ const request = require('request');
|
||||||
const AWS = require('aws-sdk');
|
const AWS = require('aws-sdk');
|
||||||
const verifier = require('google-id-token-verifier');
|
const verifier = require('google-id-token-verifier');
|
||||||
|
|
||||||
const BUCKET_NAME = process.env.USER_BUCKET_NAME || 'stackedit-users';
|
const {
|
||||||
const PAYPAL_URI = process.env.PAYPAL_URI || 'https://www.paypal.com/cgi-bin/webscr';
|
USER_BUCKET_NAME = 'stackedit-users',
|
||||||
const PAYPAL_RECEIVER_EMAIL = process.env.PAYPAL_RECEIVER_EMAIL || 'stackedit.project@gmail.com';
|
PAYPAL_URI = 'https://www.paypal.com/cgi-bin/webscr',
|
||||||
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
|
PAYPAL_RECEIVER_EMAIL = 'stackedit.project@gmail.com',
|
||||||
|
GOOGLE_CLIENT_ID,
|
||||||
|
} = process.env;
|
||||||
const s3Client = new AWS.S3();
|
const s3Client = new AWS.S3();
|
||||||
|
|
||||||
const cb = (resolve, reject) => (err, res) => {
|
const cb = (resolve, reject) => (err, res) => {
|
||||||
|
@ -18,21 +20,22 @@ const cb = (resolve, reject) => (err, res) => {
|
||||||
|
|
||||||
exports.getUser = id => new Promise((resolve, reject) => {
|
exports.getUser = id => new Promise((resolve, reject) => {
|
||||||
s3Client.getObject({
|
s3Client.getObject({
|
||||||
Bucket: BUCKET_NAME,
|
Bucket: USER_BUCKET_NAME,
|
||||||
Key: id,
|
Key: id,
|
||||||
}, cb(resolve, reject));
|
}, cb(resolve, reject));
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
res => JSON.parse(`${res.Body}`),
|
res => JSON.parse(`${res.Body}`),
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err.code !== 'NoSuchKey') {
|
if (err.code !== 'NoSuchKey') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
exports.putUser = (id, user) => new Promise((resolve, reject) => {
|
exports.putUser = (id, user) => new Promise((resolve, reject) => {
|
||||||
s3Client.putObject({
|
s3Client.putObject({
|
||||||
Bucket: BUCKET_NAME,
|
Bucket: USER_BUCKET_NAME,
|
||||||
Key: id,
|
Key: id,
|
||||||
Body: JSON.stringify(user),
|
Body: JSON.stringify(user),
|
||||||
}, cb(resolve, reject));
|
}, cb(resolve, reject));
|
||||||
|
@ -40,20 +43,24 @@ exports.putUser = (id, user) => new Promise((resolve, reject) => {
|
||||||
|
|
||||||
exports.removeUser = id => new Promise((resolve, reject) => {
|
exports.removeUser = id => new Promise((resolve, reject) => {
|
||||||
s3Client.deleteObject({
|
s3Client.deleteObject({
|
||||||
Bucket: BUCKET_NAME,
|
Bucket: USER_BUCKET_NAME,
|
||||||
Key: id,
|
Key: id,
|
||||||
}, cb(resolve, reject));
|
}, cb(resolve, reject));
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.getUserFromToken = idToken => new Promise(
|
exports.getUserFromToken = idToken => new Promise((resolve, reject) => verifier
|
||||||
(resolve, reject) => verifier.verify(idToken, GOOGLE_CLIENT_ID, cb(resolve, reject)))
|
.verify(idToken, GOOGLE_CLIENT_ID, cb(resolve, reject)))
|
||||||
.then(tokenInfo => exports.getUser(tokenInfo.sub));
|
.then(tokenInfo => exports.getUser(tokenInfo.sub));
|
||||||
|
|
||||||
exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken)
|
exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken)
|
||||||
.then(user => res.send(Object.assign({
|
.then(
|
||||||
sponsorUntil: 0,
|
user => res.send(Object.assign({
|
||||||
}, user)),
|
sponsorUntil: 0,
|
||||||
err => res.status(400).send(err ? err.message || err.toString() : 'invalid_token'));
|
}, user)),
|
||||||
|
err => res
|
||||||
|
.status(400)
|
||||||
|
.send(err ? err.message || err.toString() : 'invalid_token'),
|
||||||
|
);
|
||||||
|
|
||||||
exports.paypalIpn = (req, res, next) => Promise.resolve()
|
exports.paypalIpn = (req, res, next) => Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
<div class="app" :class="classes">
|
<div class="app" :class="classes">
|
||||||
<splash-screen v-if="!ready"></splash-screen>
|
<splash-screen v-if="!ready"></splash-screen>
|
||||||
<layout v-else></layout>
|
<layout v-else></layout>
|
||||||
<modal v-if="showModal"></modal>
|
<modal></modal>
|
||||||
<notification></notification>
|
<notification></notification>
|
||||||
<context-menu></context-menu>
|
<context-menu></context-menu>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue';
|
import '../styles';
|
||||||
|
import '../styles/markdownHighlighting.scss';
|
||||||
|
import '../styles/app.scss';
|
||||||
import Layout from './Layout';
|
import Layout from './Layout';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import Notification from './Notification';
|
import Notification from './Notification';
|
||||||
|
@ -19,50 +21,7 @@ import syncSvc from '../services/syncSvc';
|
||||||
import networkSvc from '../services/networkSvc';
|
import networkSvc from '../services/networkSvc';
|
||||||
import sponsorSvc from '../services/sponsorSvc';
|
import sponsorSvc from '../services/sponsorSvc';
|
||||||
import tempFileSvc from '../services/tempFileSvc';
|
import tempFileSvc from '../services/tempFileSvc';
|
||||||
import timeSvc from '../services/timeSvc';
|
import './common/vueGlobals';
|
||||||
import store from '../store';
|
|
||||||
|
|
||||||
// Global directives
|
|
||||||
Vue.directive('focus', {
|
|
||||||
inserted(el) {
|
|
||||||
el.focus();
|
|
||||||
const value = el.value;
|
|
||||||
if (value && el.setSelectionRange) {
|
|
||||||
el.setSelectionRange(0, value.length);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const setVisible = (el, value) => {
|
|
||||||
el.style.display = value ? '' : 'none';
|
|
||||||
if (value) {
|
|
||||||
el.removeAttribute('aria-hidden');
|
|
||||||
} else {
|
|
||||||
el.setAttribute('aria-hidden', 'true');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Vue.directive('show', {
|
|
||||||
bind(el, { value }) {
|
|
||||||
setVisible(el, value);
|
|
||||||
},
|
|
||||||
update(el, { value, oldValue }) {
|
|
||||||
if (value !== oldValue) {
|
|
||||||
setVisible(el, value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Vue.directive('title', {
|
|
||||||
bind(el, { value }) {
|
|
||||||
el.title = value;
|
|
||||||
el.setAttribute('aria-label', value);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Global filters
|
|
||||||
Vue.filter('formatTime', time =>
|
|
||||||
// Access the minute counter for reactive refresh
|
|
||||||
timeSvc.format(time, store.state.minuteCounter));
|
|
||||||
|
|
||||||
const themeClasses = {
|
const themeClasses = {
|
||||||
light: ['app--light'],
|
light: ['app--light'],
|
||||||
|
@ -85,28 +44,22 @@ export default {
|
||||||
const result = themeClasses[this.$store.getters['data/computedSettings'].colorTheme];
|
const result = themeClasses[this.$store.getters['data/computedSettings'].colorTheme];
|
||||||
return Array.isArray(result) ? result : themeClasses.light;
|
return Array.isArray(result) ? result : themeClasses.light;
|
||||||
},
|
},
|
||||||
showModal() {
|
|
||||||
return !!this.$store.getters['modal/config'];
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
syncSvc.init()
|
try {
|
||||||
.then(() => {
|
await syncSvc.init();
|
||||||
networkSvc.init();
|
await networkSvc.init();
|
||||||
sponsorSvc.init();
|
await sponsorSvc.init();
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
tempFileSvc.setReady();
|
tempFileSvc.setReady();
|
||||||
})
|
} catch (err) {
|
||||||
.catch((err) => {
|
if (err && err.message === 'RELOAD') {
|
||||||
if (err && err.message !== 'reload') {
|
window.location.reload();
|
||||||
console.error(err); // eslint-disable-line no-console
|
} else if (err && err.message !== 'RELOAD') {
|
||||||
this.$store.dispatch('notification/error', err);
|
console.error(err); // eslint-disable-line no-console
|
||||||
}
|
this.$store.dispatch('notification/error', err);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import 'common/app';
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
<div class="button-bar__inner button-bar__inner--top">
|
<div class="button-bar__inner button-bar__inner--top">
|
||||||
<button class="button-bar__button button" :class="{ 'button-bar__button--on': layoutSettings.showNavigationBar }" v-if="!light" @click="toggleNavigationBar()" v-title="'Toggle navigation bar'">
|
<button class="button-bar__button button-bar__button--navigation-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showNavigationBar }" v-if="!light" @click="toggleNavigationBar()" v-title="'Toggle navigation bar'">
|
||||||
<icon-navigation-bar></icon-navigation-bar>
|
<icon-navigation-bar></icon-navigation-bar>
|
||||||
</button>
|
</button>
|
||||||
<button class="button-bar__button button" :class="{ 'button-bar__button--on': layoutSettings.showSidePreview }" tour-step-anchor="editor" @click="toggleSidePreview()" v-title="'Toggle side preview'">
|
<button class="button-bar__button button-bar__button--side-preview-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showSidePreview }" tour-step-anchor="editor" @click="toggleSidePreview()" v-title="'Toggle side preview'">
|
||||||
<icon-side-preview></icon-side-preview>
|
<icon-side-preview></icon-side-preview>
|
||||||
</button>
|
</button>
|
||||||
<button class="button-bar__button button" @click="toggleEditor(false)" v-title="'Reader mode'">
|
<button class="button-bar__button button-bar__button--editor-toggler button" @click="toggleEditor(false)" v-title="'Reader mode'">
|
||||||
<icon-eye></icon-eye>
|
<icon-eye></icon-eye>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-bar__inner button-bar__inner--bottom">
|
<div class="button-bar__inner button-bar__inner--bottom">
|
||||||
<button class="button-bar__button button" :class="{ 'button-bar__button--on': layoutSettings.focusMode }" @click="toggleFocusMode()" v-title="'Toggle focus mode'">
|
<button class="button-bar__button button-bar__button--focus-mode-toggler button" :class="{ 'button-bar__button--on': layoutSettings.focusMode }" @click="toggleFocusMode()" v-title="'Toggle focus mode'">
|
||||||
<icon-target></icon-target>
|
<icon-target></icon-target>
|
||||||
</button>
|
</button>
|
||||||
<button class="button-bar__button button" :class="{ 'button-bar__button--on': layoutSettings.scrollSync }" @click="toggleScrollSync()" v-title="'Toggle scroll sync'">
|
<button class="button-bar__button button-bar__button--scroll-sync-toggler button" :class="{ 'button-bar__button--on': layoutSettings.scrollSync }" @click="toggleScrollSync()" v-title="'Toggle scroll sync'">
|
||||||
<icon-scroll-sync></icon-scroll-sync>
|
<icon-scroll-sync></icon-scroll-sync>
|
||||||
</button>
|
</button>
|
||||||
<button class="button-bar__button button" :class="{ 'button-bar__button--on': layoutSettings.showStatusBar }" @click="toggleStatusBar()" v-title="'Toggle status bar'">
|
<button class="button-bar__button button-bar__button--status-bar-toggler button" :class="{ 'button-bar__button--on': layoutSettings.showStatusBar }" @click="toggleStatusBar()" v-title="'Toggle status bar'">
|
||||||
<icon-status-bar></icon-status-bar>
|
<icon-status-bar></icon-status-bar>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.button-bar {
|
.button-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Prism from 'prismjs';
|
import Prism from 'prismjs';
|
||||||
import cledit from '../services/cledit';
|
import cledit from '../services/editor/cledit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['value', 'lang', 'disabled'],
|
props: ['value', 'lang', 'disabled'],
|
||||||
|
@ -28,7 +28,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.code-editor {
|
.code-editor {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div v-for="(item, idx) in items" :key="idx">
|
<div v-for="(item, idx) in items" :key="idx">
|
||||||
<div class="context-menu__separator" v-if="item.type === 'separator'"></div>
|
<div class="context-menu__separator" v-if="item.type === 'separator'"></div>
|
||||||
<div class="context-menu__item context-menu__item--disabled" v-else-if="item.disabled">{{item.name}}</div>
|
<div class="context-menu__item context-menu__item--disabled" v-else-if="item.disabled">{{item.name}}</div>
|
||||||
<a class="context-menu__item" href="javascript:void(0)" v-else @click.stop="close(item)">{{item.name}}</a>
|
<a class="context-menu__item" href="javascript:void(0)" v-else @click="close(item)">{{item.name}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,10 +22,8 @@ export default {
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close(item) {
|
close(item = null) {
|
||||||
if (item) {
|
this.resolve(item);
|
||||||
this.resolve(item);
|
|
||||||
}
|
|
||||||
this.$store.dispatch('contextMenu/close');
|
this.$store.dispatch('contextMenu/close');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -66,13 +66,14 @@ export default {
|
||||||
editorElt.querySelectorAll(`.discussion-editor-highlighting--${discussionId}`)
|
editorElt.querySelectorAll(`.discussion-editor-highlighting--${discussionId}`)
|
||||||
.cl_each(elt => elt.classList.add('discussion-editor-highlighting--selected'));
|
.cl_each(elt => elt.classList.add('discussion-editor-highlighting--selected'));
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -2,20 +2,20 @@
|
||||||
<div class="explorer flex flex--column">
|
<div class="explorer flex flex--column">
|
||||||
<div class="side-title flex flex--row flex--space-between">
|
<div class="side-title flex flex--row flex--space-between">
|
||||||
<div class="flex flex--row">
|
<div class="flex flex--row">
|
||||||
<button class="side-title__button button" @click="newItem()" v-title="'New file'">
|
<button class="side-title__button side-title__button--new-file button" @click="newItem()" v-title="'New file'">
|
||||||
<icon-file-plus></icon-file-plus>
|
<icon-file-plus></icon-file-plus>
|
||||||
</button>
|
</button>
|
||||||
<button class="side-title__button button" @click="newItem(true)" v-title="'New folder'">
|
<button class="side-title__button side-title__button--new-folder button" @click="newItem(true)" v-title="'New folder'">
|
||||||
<icon-folder-plus></icon-folder-plus>
|
<icon-folder-plus></icon-folder-plus>
|
||||||
</button>
|
</button>
|
||||||
<button class="side-title__button button" @click="deleteItem()" v-title="'Delete'">
|
<button class="side-title__button side-title__button--delete button" @click="deleteItem()" v-title="'Delete'">
|
||||||
<icon-delete></icon-delete>
|
<icon-delete></icon-delete>
|
||||||
</button>
|
</button>
|
||||||
<button class="side-title__button button" @click="editItem()" v-title="'Rename'">
|
<button class="side-title__button side-title__button--rename button" @click="editItem()" v-title="'Rename'">
|
||||||
<icon-pen></icon-pen>
|
<icon-pen></icon-pen>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="side-title__button button" @click="toggleExplorer(false)" v-title="'Close explorer'">
|
<button class="side-title__button side-title__button--close button" @click="toggleExplorer(false)" v-title="'Close explorer'">
|
||||||
<icon-close></icon-close>
|
<icon-close></icon-close>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||||
import ExplorerNode from './ExplorerNode';
|
import ExplorerNode from './ExplorerNode';
|
||||||
|
import explorerSvc from '../services/explorerSvc';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -49,10 +50,8 @@ export default {
|
||||||
...mapActions('data', [
|
...mapActions('data', [
|
||||||
'toggleExplorer',
|
'toggleExplorer',
|
||||||
]),
|
]),
|
||||||
...mapActions('explorer', [
|
newItem: isFolder => explorerSvc.newItem(isFolder),
|
||||||
'newItem',
|
deleteItem: () => explorerSvc.deleteItem(),
|
||||||
'deleteItem',
|
|
||||||
]),
|
|
||||||
editItem() {
|
editItem() {
|
||||||
const node = this.selectedNode;
|
const node = this.selectedNode;
|
||||||
if (!node.isTrash && !node.isTemp) {
|
if (!node.isTrash && !node.isTemp) {
|
||||||
|
@ -68,7 +67,8 @@ export default {
|
||||||
this.$store.dispatch('explorer/openNode', currentFileId);
|
this.$store.dispatch('explorer/openNode', currentFileId);
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--open': isOpen, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node.item.id)" @dragleave.stop="isDragTarget && setDragTargetId()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
|
<div class="explorer-node" :class="{'explorer-node--selected': isSelected, 'explorer-node--folder': node.isFolder, 'explorer-node--open': isOpen, 'explorer-node--trash': node.isTrash, 'explorer-node--temp': node.isTemp, 'explorer-node--drag-target': isDragTargetFolder}" @dragover.prevent @dragenter.stop="node.noDrop || setDragTarget(node)" @dragleave.stop="isDragTarget && setDragTarget()" @drop.prevent.stop="onDrop" @contextmenu="onContextMenu">
|
||||||
<div class="explorer-node__item-editor" v-if="isEditing" :class="['explorer-node__item-editor--' + node.item.type]" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
<div class="explorer-node__item-editor" v-if="isEditing" :style="{paddingLeft: leftPadding}" draggable="true" @dragstart.stop.prevent>
|
||||||
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc="submitEdit(true)" v-model="editingNodeName">
|
<input type="text" class="text-input" v-focus @blur="submitEdit()" @keydown.stop @keydown.enter="submitEdit()" @keydown.esc="submitEdit(true)" v-model="editingNodeName">
|
||||||
</div>
|
</div>
|
||||||
<div class="explorer-node__item" v-else :class="['explorer-node__item--' + node.item.type]" :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTargetId()">
|
<div class="explorer-node__item" v-else :style="{paddingLeft: leftPadding}" @click="select()" draggable="true" @dragstart.stop="setDragSourceId" @dragend.stop="setDragTarget()">
|
||||||
{{node.item.name}}
|
{{node.item.name}}
|
||||||
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
<icon-provider class="explorer-node__location" v-for="location in node.locations" :key="location.id" :provider-id="location.providerId"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
|
<div class="explorer-node__children" v-if="node.isFolder && isOpen">
|
||||||
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
<explorer-node v-for="node in node.folders" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||||
<div v-if="newChild" class="explorer-node__new-child" :class="['explorer-node__new-child--' + newChild.item.type]" :style="{paddingLeft: childLeftPadding}">
|
<div v-if="newChild" class="explorer-node__new-child" :class="{'explorer-node__new-child--folder': newChild.isFolder}" :style="{paddingLeft: childLeftPadding}">
|
||||||
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
|
<input type="text" class="text-input" v-focus @blur="submitNewChild()" @keydown.stop @keydown.enter="submitNewChild()" @keydown.esc="submitNewChild(true)" v-model.trim="newChildName">
|
||||||
</div>
|
</div>
|
||||||
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
<explorer-node v-for="node in node.files" :key="node.item.id" :node="node" :depth="depth + 1"></explorer-node>
|
||||||
|
@ -19,7 +19,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations, mapActions } from 'vuex';
|
import { mapMutations, mapActions } from 'vuex';
|
||||||
import utils from '../services/utils';
|
import workspaceSvc from '../services/workspaceSvc';
|
||||||
|
import explorerSvc from '../services/explorerSvc';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'explorer-node', // Required for recursivity
|
name: 'explorer-node', // Required for recursivity
|
||||||
|
@ -72,13 +73,10 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations('explorer', [
|
...mapMutations('explorer', [
|
||||||
'setDragTargetId',
|
|
||||||
'setEditingId',
|
'setEditingId',
|
||||||
]),
|
]),
|
||||||
...mapActions('explorer', [
|
...mapActions('explorer', [
|
||||||
'setDragTarget',
|
'setDragTarget',
|
||||||
'newItem',
|
|
||||||
'deleteItem',
|
|
||||||
]),
|
]),
|
||||||
select(id = this.node.item.id, doOpen = true) {
|
select(id = this.node.item.id, doOpen = true) {
|
||||||
const node = this.$store.getters['explorer/nodeMap'][id];
|
const node = this.$store.getters['explorer/nodeMap'][id];
|
||||||
|
@ -98,35 +96,37 @@ export default {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
submitNewChild(cancel) {
|
async submitNewChild(cancel) {
|
||||||
const newChildNode = this.$store.state.explorer.newChildNode;
|
const { newChildNode } = this.$store.state.explorer;
|
||||||
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
if (!cancel && !newChildNode.isNil && newChildNode.item.name) {
|
||||||
if (newChildNode.isFolder) {
|
try {
|
||||||
const id = utils.uid();
|
if (newChildNode.isFolder) {
|
||||||
this.$store.commit('folder/setItem', {
|
const item = await workspaceSvc.storeItem(newChildNode.item);
|
||||||
...newChildNode.item,
|
this.select(item.id);
|
||||||
id,
|
} else {
|
||||||
name: utils.sanitizeName(newChildNode.item.name),
|
const item = await workspaceSvc.createFile(newChildNode.item);
|
||||||
});
|
this.select(item.id);
|
||||||
this.select(id);
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
this.$store.dispatch('createFile', newChildNode.item)
|
// Cancel
|
||||||
.then(file => this.select(file.id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.$store.commit('explorer/setNewItem', null);
|
this.$store.commit('explorer/setNewItem', null);
|
||||||
},
|
},
|
||||||
submitEdit(cancel) {
|
async submitEdit(cancel) {
|
||||||
const editingNode = this.$store.getters['explorer/editingNode'];
|
const { item } = this.$store.getters['explorer/editingNode'];
|
||||||
const id = editingNode.item.id;
|
|
||||||
const value = this.editingValue;
|
const value = this.editingValue;
|
||||||
if (!cancel && id && value) {
|
|
||||||
this.$store.commit(editingNode.isFolder ? 'folder/patchItem' : 'file/patchItem', {
|
|
||||||
id,
|
|
||||||
name: utils.sanitizeName(value),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setEditingId(null);
|
this.setEditingId(null);
|
||||||
|
if (!cancel && item.id && value) {
|
||||||
|
try {
|
||||||
|
await workspaceSvc.storeItem({
|
||||||
|
...item,
|
||||||
|
name: value,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setDragSourceId(evt) {
|
setDragSourceId(evt) {
|
||||||
if (this.node.noDrag) {
|
if (this.node.noDrag) {
|
||||||
|
@ -141,27 +141,22 @@ export default {
|
||||||
onDrop() {
|
onDrop() {
|
||||||
const sourceNode = this.$store.getters['explorer/dragSourceNode'];
|
const sourceNode = this.$store.getters['explorer/dragSourceNode'];
|
||||||
const targetNode = this.$store.getters['explorer/dragTargetNodeFolder'];
|
const targetNode = this.$store.getters['explorer/dragTargetNodeFolder'];
|
||||||
this.setDragTargetId();
|
this.setDragTarget();
|
||||||
if (!sourceNode.isNil
|
if (!sourceNode.isNil
|
||||||
&& !targetNode.isNil
|
&& !targetNode.isNil
|
||||||
&& sourceNode.item.id !== targetNode.item.id
|
&& sourceNode.item.id !== targetNode.item.id
|
||||||
) {
|
) {
|
||||||
const patch = {
|
workspaceSvc.storeItem({
|
||||||
id: sourceNode.item.id,
|
...sourceNode.item,
|
||||||
parentId: targetNode.item.id,
|
parentId: targetNode.item.id,
|
||||||
};
|
});
|
||||||
if (sourceNode.isFolder) {
|
|
||||||
this.$store.commit('folder/patchItem', patch);
|
|
||||||
} else {
|
|
||||||
this.$store.commit('file/patchItem', patch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onContextMenu(evt) {
|
async onContextMenu(evt) {
|
||||||
if (this.select(undefined, false)) {
|
if (this.select(undefined, false)) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
this.$store.dispatch('contextMenu/open', {
|
const item = await this.$store.dispatch('contextMenu/open', {
|
||||||
coordinates: {
|
coordinates: {
|
||||||
left: evt.clientX,
|
left: evt.clientX,
|
||||||
top: evt.clientY,
|
top: evt.clientY,
|
||||||
|
@ -169,11 +164,11 @@ export default {
|
||||||
items: [{
|
items: [{
|
||||||
name: 'New file',
|
name: 'New file',
|
||||||
disabled: !this.node.isFolder || this.node.isTrash,
|
disabled: !this.node.isFolder || this.node.isTrash,
|
||||||
perform: () => this.newItem(false),
|
perform: () => explorerSvc.newItem(false),
|
||||||
}, {
|
}, {
|
||||||
name: 'New folder',
|
name: 'New folder',
|
||||||
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
|
disabled: !this.node.isFolder || this.node.isTrash || this.node.isTemp,
|
||||||
perform: () => this.newItem(true),
|
perform: () => explorerSvc.newItem(true),
|
||||||
}, {
|
}, {
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
}, {
|
}, {
|
||||||
|
@ -182,10 +177,12 @@ export default {
|
||||||
perform: () => this.setEditingId(this.node.item.id),
|
perform: () => this.setEditingId(this.node.item.id),
|
||||||
}, {
|
}, {
|
||||||
name: 'Delete',
|
name: 'Delete',
|
||||||
perform: () => this.deleteItem(),
|
perform: () => explorerSvc.deleteItem(),
|
||||||
}],
|
}],
|
||||||
})
|
});
|
||||||
.then(item => item.perform());
|
if (item) {
|
||||||
|
item.perform();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -229,17 +226,25 @@ $item-font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.explorer-node__item--folder,
|
.explorer-node--trash,
|
||||||
.explorer-node__item-editor--folder,
|
.explorer-node--temp {
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.explorer-node--folder > .explorer-node__item,
|
||||||
|
.explorer-node--folder > .explorer-node__item-editor,
|
||||||
.explorer-node__new-child--folder {
|
.explorer-node__new-child--folder {
|
||||||
&::before {
|
&::before {
|
||||||
content: '▹';
|
content: '▹';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: -13px;
|
margin-left: -13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.explorer-node--open > & {
|
.explorer-node--folder.explorer-node--open > .explorer-node__item,
|
||||||
content: '▾';
|
.explorer-node--folder.explorer-node--open > .explorer-node__item-editor {
|
||||||
}
|
&::before {
|
||||||
|
content: '▾';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import { mapState } from 'vuex';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import cledit from '../services/cledit';
|
import cledit from '../services/editor/cledit';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
import EditorClassApplier from './common/EditorClassApplier';
|
import EditorClassApplier from './common/EditorClassApplier';
|
||||||
|
|
||||||
|
@ -70,7 +70,8 @@ class DynamicClassApplier {
|
||||||
() => ({
|
() => ({
|
||||||
start: this.startMarker.offset,
|
start: this.startMarker.offset,
|
||||||
end: this.endMarker.offset,
|
end: this.endMarker.offset,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +127,10 @@ export default {
|
||||||
offsetList.forEach((offset, i) => {
|
offsetList.forEach((offset, i) => {
|
||||||
const key = `${offset.start}:${offset.end}`;
|
const key = `${offset.start}:${offset.end}`;
|
||||||
this.classAppliers[key] = oldClassAppliers[key] || new DynamicClassApplier(
|
this.classAppliers[key] = oldClassAppliers[key] || new DynamicClassApplier(
|
||||||
'find-replace-highlighting', offset, i > 200);
|
'find-replace-highlighting',
|
||||||
|
offset,
|
||||||
|
i > 200,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore
|
// Ignore
|
||||||
|
@ -156,9 +160,9 @@ export default {
|
||||||
this.findPosition = 0;
|
this.findPosition = 0;
|
||||||
},
|
},
|
||||||
find(mode = 'forward') {
|
find(mode = 'forward') {
|
||||||
const selectedClassApplier = this.selectedClassApplier;
|
const { selectedClassApplier } = this;
|
||||||
this.unselectClassApplier();
|
this.unselectClassApplier();
|
||||||
const selectionMgr = editorSvc.clEditor.selectionMgr;
|
const { selectionMgr } = editorSvc.clEditor;
|
||||||
const startOffset = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
|
const startOffset = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
|
||||||
const endOffset = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
|
const endOffset = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
|
||||||
const keys = Object.keys(this.classAppliers);
|
const keys = Object.keys(this.classAppliers);
|
||||||
|
@ -206,7 +210,10 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editorSvc.clEditor.replaceAll(
|
editorSvc.clEditor.replaceAll(
|
||||||
this.replaceRegex, this.replaceText, this.selectedClassApplier.startMarker.offset);
|
this.replaceRegex,
|
||||||
|
this.replaceText,
|
||||||
|
this.selectedClassApplier.startMarker.offset,
|
||||||
|
);
|
||||||
this.$nextTick(() => this.find());
|
this.$nextTick(() => this.find());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -227,7 +234,9 @@ export default {
|
||||||
|
|
||||||
// Highlight occurences
|
// Highlight occurences
|
||||||
this.debouncedHighlightOccurrences = cledit.Utils.debounce(
|
this.debouncedHighlightOccurrences = cledit.Utils.debounce(
|
||||||
() => this.highlightOccurrences(), 25);
|
() => this.highlightOccurrences(),
|
||||||
|
25,
|
||||||
|
);
|
||||||
// Refresh highlighting when find text changes or changing options
|
// Refresh highlighting when find text changes or changing options
|
||||||
this.$watch(() => this.findText, this.debouncedHighlightOccurrences);
|
this.$watch(() => this.findText, this.debouncedHighlightOccurrences);
|
||||||
this.$watch(() => this.findCaseSensitive, this.debouncedHighlightOccurrences);
|
this.$watch(() => this.findCaseSensitive, this.debouncedHighlightOccurrences);
|
||||||
|
@ -273,7 +282,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.find-replace {
|
.find-replace {
|
||||||
padding: 0 35px 0 25px;
|
padding: 0 35px 0 25px;
|
||||||
|
@ -344,7 +353,7 @@ export default {
|
||||||
.find-replace__find-stats {
|
.find-replace__find-stats {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
opacity: 0.5;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.find-replace-highlighting {
|
.find-replace-highlighting {
|
||||||
|
|
|
@ -140,7 +140,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.layout {
|
.layout {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="modal" @keydown.esc="onEscape" @keydown.tab="onTab">
|
<div class="modal" v-if="config" @keydown.esc="onEscape" @keydown.tab="onTab" @focusin="onFocusInOut" @focusout="onFocusInOut">
|
||||||
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
<component v-if="currentModalComponent" :is="currentModalComponent"></component>
|
||||||
<modal-inner v-else aria-label="Dialog">
|
<modal-inner v-else aria-label="Dialog">
|
||||||
<div class="modal__content" v-html="config.content"></div>
|
<div class="modal__content" v-html="simpleModal.contentHtml(config)"></div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" v-if="config.rejectText" @click="config.reject()">{{config.rejectText}}</button>
|
<button class="button" v-if="simpleModal.rejectText" @click="config.reject()">{{simpleModal.rejectText}}</button>
|
||||||
<button class="button" v-if="config.resolveText" @click="config.resolve()">{{config.resolveText}}</button>
|
<button class="button button--resolve" v-if="simpleModal.resolveText" @click="config.resolve()">{{simpleModal.resolveText}}</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import simpleModals from '../data/simpleModals';
|
||||||
import editorSvc from '../services/editorSvc';
|
import editorSvc from '../services/editorSvc';
|
||||||
import ModalInner from './modals/common/ModalInner';
|
import ModalInner from './modals/common/ModalInner';
|
||||||
import FilePropertiesModal from './modals/FilePropertiesModal';
|
import FilePropertiesModal from './modals/FilePropertiesModal';
|
||||||
|
@ -41,6 +42,7 @@ import DropboxPublishModal from './modals/providers/DropboxPublishModal';
|
||||||
import GithubAccountModal from './modals/providers/GithubAccountModal';
|
import GithubAccountModal from './modals/providers/GithubAccountModal';
|
||||||
import GithubOpenModal from './modals/providers/GithubOpenModal';
|
import GithubOpenModal from './modals/providers/GithubOpenModal';
|
||||||
import GithubSaveModal from './modals/providers/GithubSaveModal';
|
import GithubSaveModal from './modals/providers/GithubSaveModal';
|
||||||
|
import GithubWorkspaceModal from './modals/providers/GithubWorkspaceModal';
|
||||||
import GithubPublishModal from './modals/providers/GithubPublishModal';
|
import GithubPublishModal from './modals/providers/GithubPublishModal';
|
||||||
import GistSyncModal from './modals/providers/GistSyncModal';
|
import GistSyncModal from './modals/providers/GistSyncModal';
|
||||||
import GistPublishModal from './modals/providers/GistPublishModal';
|
import GistPublishModal from './modals/providers/GistPublishModal';
|
||||||
|
@ -84,6 +86,7 @@ export default {
|
||||||
GithubAccountModal,
|
GithubAccountModal,
|
||||||
GithubOpenModal,
|
GithubOpenModal,
|
||||||
GithubSaveModal,
|
GithubSaveModal,
|
||||||
|
GithubWorkspaceModal,
|
||||||
GithubPublishModal,
|
GithubPublishModal,
|
||||||
GistSyncModal,
|
GistSyncModal,
|
||||||
GistPublishModal,
|
GistPublishModal,
|
||||||
|
@ -110,6 +113,9 @@ export default {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
simpleModal() {
|
||||||
|
return simpleModals[this.config.type] || {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onEscape() {
|
onEscape() {
|
||||||
|
@ -132,14 +138,14 @@ export default {
|
||||||
const isFocusIn = evt.type === 'focusin';
|
const isFocusIn = evt.type === 'focusin';
|
||||||
if (evt.target.parentNode && evt.target.parentNode.parentNode) {
|
if (evt.target.parentNode && evt.target.parentNode.parentNode) {
|
||||||
// Focus effect
|
// Focus effect
|
||||||
if (evt.target.parentNode.classList.contains('form-entry__field') &&
|
if (evt.target.parentNode.classList.contains('form-entry__field')
|
||||||
evt.target.parentNode.parentNode.classList.contains('form-entry')) {
|
&& evt.target.parentNode.parentNode.classList.contains('form-entry')) {
|
||||||
evt.target.parentNode.parentNode.classList.toggle('form-entry--focused', isFocusIn);
|
evt.target.parentNode.parentNode.classList.toggle('form-entry--focused', isFocusIn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isFocusIn && this.config) {
|
if (isFocusIn && this.config) {
|
||||||
const modalInner = this.$el.querySelector('.modal__inner-2');
|
const modalInner = this.$el.querySelector('.modal__inner-2');
|
||||||
let target = evt.target;
|
let { target } = evt;
|
||||||
while (target) {
|
while (target) {
|
||||||
if (target === modalInner) {
|
if (target === modalInner) {
|
||||||
return;
|
return;
|
||||||
|
@ -151,20 +157,24 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('focusin', this.onFocusInOut);
|
this.$watch(
|
||||||
window.addEventListener('focusout', this.onFocusInOut);
|
() => this.config,
|
||||||
const tabbables = getTabbables(this.$el);
|
(isOpen) => {
|
||||||
tabbables[0].focus();
|
if (isOpen) {
|
||||||
},
|
const tabbables = getTabbables(this.$el);
|
||||||
destroyed() {
|
if (tabbables[0]) {
|
||||||
window.removeEventListener('focusin', this.onFocusInOut);
|
tabbables[0].focus();
|
||||||
window.removeEventListener('focusout', this.onFocusInOut);
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -173,8 +183,8 @@ export default {
|
||||||
background-color: rgba(160, 160, 160, 0.5);
|
background-color: rgba(160, 160, 160, 0.5);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
hr {
|
p {
|
||||||
margin: 0.5em 0;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +198,7 @@ export default {
|
||||||
.modal__inner-2 {
|
.modal__inner-2 {
|
||||||
margin: 40px 10px 100px;
|
margin: 40px 10px 100px;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
padding: 40px 50px 30px;
|
padding: 50px 50px 40px;
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -221,9 +231,9 @@ export default {
|
||||||
|
|
||||||
.modal__image {
|
.modal__image {
|
||||||
float: left;
|
float: left;
|
||||||
width: 64px;
|
width: 60px;
|
||||||
height: 64px;
|
height: 60px;
|
||||||
margin: 1.5em 1.5em 0.5em 0;
|
margin: 1.5em 1.2em 0.5em 0;
|
||||||
|
|
||||||
& + *::after {
|
& + *::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -240,7 +250,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__sub-title {
|
.modal__sub-title {
|
||||||
opacity: 0.5;
|
opacity: 0.6;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
@ -262,9 +272,16 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal__info--multiline {
|
||||||
|
padding-top: 0.1em;
|
||||||
|
padding-bottom: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
.modal__button-bar {
|
.modal__button-bar {
|
||||||
margin-top: 1.75rem;
|
margin-top: 2rem;
|
||||||
text-align: right;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-entry {
|
.form-entry {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<!-- Explorer -->
|
<!-- Explorer -->
|
||||||
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
|
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
|
||||||
<button class="navigation-bar__button button" v-if="light" @click="close()" v-title="'Close StackEdit'"><icon-close-circle></icon-close-circle></button>
|
<button class="navigation-bar__button button" v-if="light" @click="close()" v-title="'Close StackEdit'"><icon-close-circle></icon-close-circle></button>
|
||||||
<button class="navigation-bar__button button" v-else tour-step-anchor="explorer" @click="toggleExplorer()" v-title="'Toggle explorer'"><icon-folder></icon-folder></button>
|
<button class="navigation-bar__button navigation-bar__button--explorer-toggler button" v-else tour-step-anchor="explorer" @click="toggleExplorer()" v-title="'Toggle explorer'"><icon-folder></icon-folder></button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Side bar -->
|
<!-- Side bar -->
|
||||||
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
|
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
|
<div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
|
||||||
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div>
|
<div class="navigation-bar__title navigation-bar__title--text text-input" :style="{width: titleWidth + 'px'}">{{title}}</div>
|
||||||
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle()" @keydown.esc="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
|
<input class="navigation-bar__title navigation-bar__title--input text-input" :class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" :style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keydown.enter="submitTitle(false)" @keydown.esc="submitTitle(true)" @mouseenter="titleHover = true" @mouseleave="titleHover = false" v-model="title">
|
||||||
<!-- Sync/Publish -->
|
<!-- Sync/Publish -->
|
||||||
<div class="flex flex--row" :class="{'navigation-bar__hidden': styles.hideLocations}">
|
<div class="flex flex--row" :class="{'navigation-bar__hidden': styles.hideLocations}">
|
||||||
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Synchronized location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
|
<a class="navigation-bar__button navigation-bar__button--location button" :class="{'navigation-bar__button--blink': location.id === currentLocation.id}" v-for="location in syncLocations" :key="location.id" :href="location.url" target="_blank" v-title="'Synchronized location'"><icon-provider :provider-id="location.providerId"></icon-provider></a>
|
||||||
|
@ -56,6 +56,7 @@ import tempFileSvc from '../services/tempFileSvc';
|
||||||
import utils from '../services/utils';
|
import utils from '../services/utils';
|
||||||
import pagedownButtons from '../data/pagedownButtons';
|
import pagedownButtons from '../data/pagedownButtons';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
|
import workspaceSvc from '../services/workspaceSvc';
|
||||||
|
|
||||||
// According to mousetrap
|
// According to mousetrap
|
||||||
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
|
const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
|
||||||
|
@ -63,17 +64,16 @@ const mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'Meta' : 'Ctrl';
|
||||||
const getShortcut = (method) => {
|
const getShortcut = (method) => {
|
||||||
let result = '';
|
let result = '';
|
||||||
Object.entries(store.getters['data/computedSettings'].shortcuts).some(([keys, shortcut]) => {
|
Object.entries(store.getters['data/computedSettings'].shortcuts).some(([keys, shortcut]) => {
|
||||||
if (`${shortcut.method || shortcut}` !== method) {
|
if (`${shortcut.method || shortcut}` === method) {
|
||||||
return false;
|
result = keys.split('+').map(key => key.toLowerCase()).map((key) => {
|
||||||
|
if (key === 'mod') {
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
// Capitalize
|
||||||
|
return key && `${key[0].toUpperCase()}${key.slice(1)}`;
|
||||||
|
}).join('+');
|
||||||
}
|
}
|
||||||
result = keys.split('+').map(key => key.toLowerCase()).map((key) => {
|
return result;
|
||||||
if (key === 'mod') {
|
|
||||||
return mod;
|
|
||||||
}
|
|
||||||
// Capitalize
|
|
||||||
return key && `${key[0].toUpperCase()}${key.slice(1)}`;
|
|
||||||
}).join('+');
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
return result && ` – ${result}`;
|
return result && ` – ${result}`;
|
||||||
};
|
};
|
||||||
|
@ -151,6 +151,13 @@ export default {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
editCancelTrigger() {
|
||||||
|
const current = this.$store.getters['file/current'];
|
||||||
|
return utils.serializeObject([
|
||||||
|
current.id,
|
||||||
|
current.name,
|
||||||
|
]);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations('content', [
|
...mapMutations('content', [
|
||||||
|
@ -184,16 +191,22 @@ export default {
|
||||||
editorSvc.pagedownEditor.uiManager.doClick(name);
|
editorSvc.pagedownEditor.uiManager.doClick(name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editTitle(toggle) {
|
async editTitle(toggle) {
|
||||||
this.titleFocus = toggle;
|
this.titleFocus = toggle;
|
||||||
if (toggle) {
|
if (toggle) {
|
||||||
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
|
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
|
||||||
} else {
|
} else {
|
||||||
const title = this.title.trim();
|
const title = this.title.trim();
|
||||||
|
this.title = this.$store.getters['file/current'].name;
|
||||||
if (title) {
|
if (title) {
|
||||||
this.$store.dispatch('file/patchCurrent', { name: utils.sanitizeName(title) });
|
try {
|
||||||
} else {
|
await workspaceSvc.storeItem({
|
||||||
this.title = this.$store.getters['file/current'].name;
|
...this.$store.getters['file/current'],
|
||||||
|
name: title,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -209,10 +222,13 @@ export default {
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['file/current'].name,
|
() => this.editCancelTrigger,
|
||||||
(name) => {
|
() => {
|
||||||
this.title = name;
|
this.title = '';
|
||||||
}, { immediate: true });
|
this.editTitle(false);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.titleFakeElt = this.$el.querySelector('.navigation-bar__title--fake');
|
this.titleFakeElt = this.$el.querySelector('.navigation-bar__title--fake');
|
||||||
|
@ -223,7 +239,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.navigation-bar {
|
.navigation-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { mapGetters, mapActions } from 'vuex';
|
||||||
import CommentList from './gutters/CommentList';
|
import CommentList from './gutters/CommentList';
|
||||||
import PreviewNewDiscussionButton from './gutters/PreviewNewDiscussionButton';
|
import PreviewNewDiscussionButton from './gutters/PreviewNewDiscussionButton';
|
||||||
|
|
||||||
const appUri = `${location.protocol}//${location.host}`;
|
const appUri = `${window.location.protocol}//${window.location.host}`;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -98,13 +98,14 @@ export default {
|
||||||
previewElt.querySelectorAll(`.discussion-preview-highlighting--${discussionId}`)
|
previewElt.querySelectorAll(`.discussion-preview-highlighting--${discussionId}`)
|
||||||
.cl_each(elt => elt.classList.add('discussion-preview-highlighting--selected'));
|
.cl_each(elt => elt.classList.add('discussion-preview-highlighting--selected'));
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.preview,
|
.preview,
|
||||||
.preview__inner-1 {
|
.preview__inner-1 {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<template>
|
|
||||||
<span class="provider-name">{{name}}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import userSvc from '../services/userSvc';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['providerId'],
|
|
||||||
computed: {
|
|
||||||
name() {
|
|
||||||
switch (this.userId) {
|
|
||||||
default:
|
|
||||||
return 'Google Drive';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
userSvc.getInfo(this.userId);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="side-bar__inner">
|
<div class="side-bar__inner">
|
||||||
<main-menu v-if="panel === 'menu'"></main-menu>
|
<main-menu v-if="panel === 'menu'"></main-menu>
|
||||||
<workspaces-menu v-if="panel === 'workspaces'"></workspaces-menu>
|
<workspaces-menu v-else-if="panel === 'workspaces'"></workspaces-menu>
|
||||||
<sync-menu v-else-if="panel === 'sync'"></sync-menu>
|
<sync-menu v-else-if="panel === 'sync'"></sync-menu>
|
||||||
<publish-menu v-else-if="panel === 'publish'"></publish-menu>
|
<publish-menu v-else-if="panel === 'publish'"></publish-menu>
|
||||||
<history-menu v-else-if="panel === 'history'"></history-menu>
|
<history-menu v-else-if="panel === 'history'"></history-menu>
|
||||||
|
@ -75,7 +75,11 @@ export default {
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
panel() {
|
panel() {
|
||||||
return !this.$store.state.light && this.$store.getters['data/layoutSettings'].sideBarPanel;
|
if (this.$store.state.light) {
|
||||||
|
return null; // No menu in light mode
|
||||||
|
}
|
||||||
|
const result = this.$store.getters['data/layoutSettings'].sideBarPanel;
|
||||||
|
return panelNames[result] ? result : 'menu';
|
||||||
},
|
},
|
||||||
panelName() {
|
panelName() {
|
||||||
return panelNames[this.panel];
|
return panelNames[this.panel];
|
||||||
|
@ -93,7 +97,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.side-bar {
|
.side-bar {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -112,6 +116,11 @@ export default {
|
||||||
hr + hr {
|
hr + hr {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textfield {
|
||||||
|
font-size: 14px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-bar__inner {
|
.side-bar__inner {
|
||||||
|
@ -164,7 +173,7 @@ export default {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: -10px -10px 10px;
|
margin: -10px -10px 10px;
|
||||||
background-color: $info-bg;
|
background-color: $info-bg;
|
||||||
font-size: 0.95em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
|
|
@ -92,7 +92,7 @@ export default {
|
||||||
this.htmlSelection = true;
|
this.htmlSelection = true;
|
||||||
if (!text) {
|
if (!text) {
|
||||||
this.htmlSelection = false;
|
this.htmlSelection = false;
|
||||||
text = editorSvc.previewCtx.text;
|
({ text } = editorSvc.previewCtx);
|
||||||
}
|
}
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
this.htmlStats.forEach((stat) => {
|
this.htmlStats.forEach((stat) => {
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default {
|
||||||
const updateMaskY = () => {
|
const updateMaskY = () => {
|
||||||
const scrollPosition = editorSvc.getScrollPosition();
|
const scrollPosition = editorSvc.getScrollPosition();
|
||||||
if (scrollPosition) {
|
if (scrollPosition) {
|
||||||
const sectionDesc = editorSvc.previewCtx.sectionDescList[scrollPosition.sectionIdx];
|
const sectionDesc = editorSvc.previewCtxMeasured.sectionDescList[scrollPosition.sectionIdx];
|
||||||
this.maskY = sectionDesc.tocDimension.startOffset +
|
this.maskY = sectionDesc.tocDimension.startOffset +
|
||||||
(scrollPosition.posInSection * sectionDesc.tocDimension.height);
|
(scrollPosition.posInSection * sectionDesc.tocDimension.height);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,21 @@
|
||||||
<div class="tour" @keydown.esc="skip">
|
<div class="tour" @keydown.esc="skip">
|
||||||
<div class="tour-step" :class="'tour-step--' + step" :style="stepStyle">
|
<div class="tour-step" :class="'tour-step--' + step" :style="stepStyle">
|
||||||
<div class="tour-step__inner" v-if="step === 'welcome'">
|
<div class="tour-step__inner" v-if="step === 'welcome'">
|
||||||
<h2>Welcome to StackEdit!</h2>
|
<h2>Welcome back!</h2>
|
||||||
<p>Greater, lighter, faster... <b>StackEdit 5</b> is here!</p>
|
<p>The new <b>StackEdit 5</b> is here!</p>
|
||||||
<p>Please click <b>Next</b> to take a quick tour.</p>
|
<p>Please click <b>Next</b> to take a quick tour.</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">Skip</button>
|
<button class="button" @click="finish">Skip</button>
|
||||||
<button class="button" @click="next">Next</button>
|
<button class="button button--resolve" @click="next">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tour-step__inner" v-else-if="step === 'editor'">
|
<div class="tour-step__inner" v-else-if="step === 'editor'">
|
||||||
<h2>Your Markdown editor</h2>
|
<h2>Your Markdown editor</h2>
|
||||||
<p>StackEdit renders your Markdown into HTML in real-time.</p>
|
<p>StackEdit converts your Markdown to HTML in real-time.</p>
|
||||||
<p>Click <icon-side-preview></icon-side-preview> to toggle the side preview.</p>
|
<p>Click <icon-side-preview></icon-side-preview> to toggle the side preview.</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">Skip</button>
|
<button class="button" @click="finish">Skip</button>
|
||||||
<button class="button" @click="next">Next</button>
|
<button class="button button--resolve" @click="next">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tour-step__inner" v-else-if="step === 'explorer'">
|
<div class="tour-step__inner" v-else-if="step === 'explorer'">
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<p>Click <icon-folder></icon-folder> to open the file explorer.</p>
|
<p>Click <icon-folder></icon-folder> to open the file explorer.</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">Skip</button>
|
<button class="button" @click="finish">Skip</button>
|
||||||
<button class="button" @click="next">Next</button>
|
<button class="button button--resolve" @click="next">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tour-step__inner" v-else-if="step === 'menu'">
|
<div class="tour-step__inner" v-else-if="step === 'menu'">
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<p>Click <icon-provider provider-id="stackedit"></icon-provider> to explore the menu.</p>
|
<p>Click <icon-provider provider-id="stackedit"></icon-provider> to explore the menu.</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">Skip</button>
|
<button class="button" @click="finish">Skip</button>
|
||||||
<button class="button" @click="next">Next</button>
|
<button class="button button--resolve" @click="next">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tour-step__inner" v-else-if="step === 'end'">
|
<div class="tour-step__inner" v-else-if="step === 'end'">
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
<p>If you like StackEdit, please rate 5 stars on the <a target="_blank" href="https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg/reviews">Chrome Web Store</a>.</p>
|
<p>If you like StackEdit, please rate 5 stars on the <a target="_blank" href="https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg/reviews">Chrome Web Store</a>.</p>
|
||||||
<p>You can also star the project on <a target="_blank" href="https://github.com/benweet/stackedit">GitHub</a> and join the <a target="_blank" href="https://community.stackedit.io/">community</a>.</p>
|
<p>You can also star the project on <a target="_blank" href="https://github.com/benweet/stackedit">GitHub</a> and join the <a target="_blank" href="https://community.stackedit.io/">community</a>.</p>
|
||||||
<div class="tour-step__button-bar">
|
<div class="tour-step__button-bar">
|
||||||
<button class="button" @click="finish">Ok</button>
|
<button class="button button--resolve" @click="finish">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,7 +126,7 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'common/variables.scss';
|
@import '../styles/variables.scss';
|
||||||
|
|
||||||
.tour {
|
.tour {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -139,12 +139,12 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
$tour-step-background: mix(#f3f3f3, $selection-highlighting-color, 75%);
|
$tour-step-background: mix(#f3f3f3, $selection-highlighting-color, 75%);
|
||||||
$tour-step-width: 220px;
|
$tour-step-width: 240px;
|
||||||
|
|
||||||
.tour-step__inner {
|
.tour-step__inner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: $tour-step-background;
|
background-color: $tour-step-background;
|
||||||
padding: 1.5em 1em 1em;
|
padding: 1.5em;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: 1.33;
|
line-height: 1.33;
|
||||||
width: $tour-step-width;
|
width: $tour-step-width;
|
||||||
|
@ -213,6 +213,13 @@ $tour-step-width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tour-step__button-bar {
|
.tour-step__button-bar {
|
||||||
text-align: right;
|
margin-top: 1.5em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,13 +10,11 @@ export default {
|
||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
computed: {
|
computed: {
|
||||||
url() {
|
url() {
|
||||||
const userInfo = this.$store.state.userInfo.itemMap[this.userId];
|
userSvc.getInfo(this.userId);
|
||||||
|
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
||||||
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
return userInfo && userInfo.imageUrl && `url('${userInfo.imageUrl}')`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
userSvc.getInfo(this.userId);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,10 @@ export default {
|
||||||
props: ['userId'],
|
props: ['userId'],
|
||||||
computed: {
|
computed: {
|
||||||
name() {
|
name() {
|
||||||
const userInfo = this.$store.state.userInfo.itemMap[this.userId];
|
userSvc.getInfo(this.userId);
|
||||||
|
const userInfo = this.$store.state.userInfo.itemsById[this.userId];
|
||||||
return userInfo ? userInfo.name : 'Someone';
|
return userInfo ? userInfo.name : 'Someone';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
userSvc.getInfo(this.userId);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import cledit from '../../services/cledit';
|
import cledit from '../../services/editor/cledit';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
|
||||||
|
@ -10,7 +10,9 @@ const nextTickExecCbs = cledit.Utils.debounce(() => {
|
||||||
}
|
}
|
||||||
if (savedSelection) {
|
if (savedSelection) {
|
||||||
editorSvc.clEditor.selectionMgr.setSelectionStartEnd(
|
editorSvc.clEditor.selectionMgr.setSelectionStartEnd(
|
||||||
savedSelection.start, savedSelection.end);
|
savedSelection.start,
|
||||||
|
savedSelection.end,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
savedSelection = null;
|
savedSelection = null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import cledit from '../../services/cledit';
|
import cledit from '../../services/editor/cledit';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
|
||||||
|
@ -40,14 +40,22 @@ export default class PreviewClassApplier {
|
||||||
const offset = this.offsetGetter();
|
const offset = this.offsetGetter();
|
||||||
if (offset) {
|
if (offset) {
|
||||||
const offsetStart = editorSvc.getPreviewOffset(
|
const offsetStart = editorSvc.getPreviewOffset(
|
||||||
offset.start, editorSvc.previewCtx.sectionDescList);
|
offset.start,
|
||||||
|
editorSvc.previewCtx.sectionDescList,
|
||||||
|
);
|
||||||
const offsetEnd = editorSvc.getPreviewOffset(
|
const offsetEnd = editorSvc.getPreviewOffset(
|
||||||
offset.end, editorSvc.previewCtx.sectionDescList);
|
offset.end,
|
||||||
|
editorSvc.previewCtx.sectionDescList,
|
||||||
|
);
|
||||||
if (offsetStart != null && offsetEnd != null && offsetStart !== offsetEnd) {
|
if (offsetStart != null && offsetEnd != null && offsetStart !== offsetEnd) {
|
||||||
const start = cledit.Utils.findContainer(
|
const start = cledit.Utils.findContainer(
|
||||||
editorSvc.previewElt, Math.min(offsetStart, offsetEnd));
|
editorSvc.previewElt,
|
||||||
|
Math.min(offsetStart, offsetEnd),
|
||||||
|
);
|
||||||
const end = cledit.Utils.findContainer(
|
const end = cledit.Utils.findContainer(
|
||||||
editorSvc.previewElt, Math.max(offsetStart, offsetEnd));
|
editorSvc.previewElt,
|
||||||
|
Math.max(offsetStart, offsetEnd),
|
||||||
|
);
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.setStart(start.container, start.offsetInContainer);
|
range.setStart(start.container, start.offsetInContainer);
|
||||||
range.setEnd(end.container, end.offsetInContainer);
|
range.setEnd(end.container, end.offsetInContainer);
|
||||||
|
|
80
src/components/common/vueGlobals.js
Normal file
80
src/components/common/vueGlobals.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Clipboard from 'clipboard';
|
||||||
|
import timeSvc from '../../services/timeSvc';
|
||||||
|
import store from '../../store';
|
||||||
|
|
||||||
|
// Global directives
|
||||||
|
Vue.directive('focus', {
|
||||||
|
inserted(el) {
|
||||||
|
el.focus();
|
||||||
|
const { value } = el;
|
||||||
|
if (value && el.setSelectionRange) {
|
||||||
|
el.setSelectionRange(0, value.length);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const setVisible = (el, value) => {
|
||||||
|
el.style.display = value ? '' : 'none';
|
||||||
|
if (value) {
|
||||||
|
el.removeAttribute('aria-hidden');
|
||||||
|
} else {
|
||||||
|
el.setAttribute('aria-hidden', 'true');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Vue.directive('show', {
|
||||||
|
bind(el, { value }) {
|
||||||
|
setVisible(el, value);
|
||||||
|
},
|
||||||
|
update(el, { value, oldValue }) {
|
||||||
|
if (value !== oldValue) {
|
||||||
|
setVisible(el, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const setElTitle = (el, title) => {
|
||||||
|
el.title = title;
|
||||||
|
el.setAttribute('aria-label', title);
|
||||||
|
};
|
||||||
|
Vue.directive('title', {
|
||||||
|
bind(el, { value }) {
|
||||||
|
setElTitle(el, value);
|
||||||
|
},
|
||||||
|
update(el, { value, oldValue }) {
|
||||||
|
if (value !== oldValue) {
|
||||||
|
setElTitle(el, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clipboard directive
|
||||||
|
const createClipboard = (el, value) => {
|
||||||
|
el.seClipboard = new Clipboard(el, { text: () => value });
|
||||||
|
};
|
||||||
|
const destroyClipboard = (el) => {
|
||||||
|
if (el.seClipboard) {
|
||||||
|
el.seClipboard.destroy();
|
||||||
|
el.seClipboard = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Vue.directive('clipboard', {
|
||||||
|
bind(el, { value }) {
|
||||||
|
createClipboard(el, value);
|
||||||
|
},
|
||||||
|
update(el, { value, oldValue }) {
|
||||||
|
if (value !== oldValue) {
|
||||||
|
destroyClipboard(el);
|
||||||
|
createClipboard(el, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
destroyClipboard(el);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global filters
|
||||||
|
Vue.filter('formatTime', time =>
|
||||||
|
// Access the minute counter for reactive refresh
|
||||||
|
timeSvc.format(time, store.state.minuteCounter));
|
||||||
|
|
|
@ -47,11 +47,13 @@ export default {
|
||||||
...mapMutations('discussion', [
|
...mapMutations('discussion', [
|
||||||
'setIsCommenting',
|
'setIsCommenting',
|
||||||
]),
|
]),
|
||||||
removeComment() {
|
async removeComment() {
|
||||||
this.$store.dispatch('modal/commentDeletion')
|
try {
|
||||||
.then(
|
await this.$store.dispatch('modal/open', 'commentDeletion');
|
||||||
() => this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment }),
|
this.$store.dispatch('discussion/cleanCurrentFile', { filterComment: this.comment });
|
||||||
() => {}); // Cancel
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -63,8 +65,7 @@ export default {
|
||||||
let scrollerMirrorElt;
|
let scrollerMirrorElt;
|
||||||
const getScrollerMirrorElt = () => {
|
const getScrollerMirrorElt = () => {
|
||||||
if (!scrollerMirrorElt) {
|
if (!scrollerMirrorElt) {
|
||||||
scrollerMirrorElt = document.querySelector(
|
scrollerMirrorElt = document.querySelector(`.comment-list .comment--${commentId} .comment__text-inner`);
|
||||||
`.comment-list .comment--${commentId} .comment__text-inner`);
|
|
||||||
}
|
}
|
||||||
return scrollerMirrorElt || { scrollTop: 0 };
|
return scrollerMirrorElt || { scrollTop: 0 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -107,10 +107,13 @@ export default {
|
||||||
this.currentDiscussionLastCommentId
|
this.currentDiscussionLastCommentId
|
||||||
&& this.$el.querySelector(`.comment--${this.currentDiscussionLastCommentId}`),
|
&& this.$el.querySelector(`.comment--${this.currentDiscussionLastCommentId}`),
|
||||||
this.$el.querySelector('.comment--new'),
|
this.$el.querySelector('.comment--new'),
|
||||||
true);
|
true,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
tops[discussionId] = getTop(discussion,
|
tops[discussionId] = getTop(
|
||||||
this.$el.querySelector(`.comment--discussion-${discussionId}`));
|
discussion,
|
||||||
|
this.$el.querySelector(`.comment--discussion-${discussionId}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.tops = tops;
|
this.tops = tops;
|
||||||
|
@ -120,7 +123,8 @@ export default {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.updateTopsTrigger,
|
() => this.updateTopsTrigger,
|
||||||
() => this.updateTops(),
|
() => this.updateTops(),
|
||||||
{ immediate: true });
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
||||||
this.scrollerElt = layoutSettings.showEditor
|
this.scrollerElt = layoutSettings.showEditor
|
||||||
|
@ -161,7 +165,8 @@ export default {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.updateStickyTrigger,
|
() => this.updateStickyTrigger,
|
||||||
() => this.updateSticky(),
|
() => this.updateSticky(),
|
||||||
{ immediate: true });
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
// Move preview discussions once previewCtxWithDiffs has been calculated
|
// Move preview discussions once previewCtxWithDiffs has been calculated
|
||||||
if (!editorSvc.previewCtxWithDiffs) {
|
if (!editorSvc.previewCtxWithDiffs) {
|
||||||
|
@ -178,7 +183,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.comment-list {
|
.comment-list {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapGetters, mapMutations } from 'vuex';
|
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import animationSvc from '../../services/animationSvc';
|
import animationSvc from '../../services/animationSvc';
|
||||||
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
||||||
|
@ -67,6 +67,9 @@ export default {
|
||||||
...mapMutations('discussion', [
|
...mapMutations('discussion', [
|
||||||
'setCurrentDiscussionId',
|
'setCurrentDiscussionId',
|
||||||
]),
|
]),
|
||||||
|
...mapActions('notification', [
|
||||||
|
'info',
|
||||||
|
]),
|
||||||
goToDiscussion(discussionId = this.currentDiscussionId) {
|
goToDiscussion(discussionId = this.currentDiscussionId) {
|
||||||
this.setCurrentDiscussionId(discussionId);
|
this.setCurrentDiscussionId(discussionId);
|
||||||
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
const layoutSettings = this.$store.getters['data/layoutSettings'];
|
||||||
|
@ -75,7 +78,7 @@ export default {
|
||||||
? editorSvc.clEditor.selectionMgr.getCoordinates(discussion.end)
|
? editorSvc.clEditor.selectionMgr.getCoordinates(discussion.end)
|
||||||
: editorSvc.getPreviewOffsetCoordinates(editorSvc.getPreviewOffset(discussion.end));
|
: editorSvc.getPreviewOffsetCoordinates(editorSvc.getPreviewOffset(discussion.end));
|
||||||
if (!coordinates) {
|
if (!coordinates) {
|
||||||
this.$store.dispatch('notification/info', "Discussion can't be located in the file.");
|
this.info("Discussion can't be located in the file.");
|
||||||
} else {
|
} else {
|
||||||
const scrollerElt = layoutSettings.showEditor
|
const scrollerElt = layoutSettings.showEditor
|
||||||
? editorSvc.editorElt.parentNode
|
? editorSvc.editorElt.parentNode
|
||||||
|
@ -93,20 +96,22 @@ export default {
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeDiscussion() {
|
async removeDiscussion() {
|
||||||
this.$store.dispatch('modal/discussionDeletion')
|
try {
|
||||||
.then(
|
await this.$store.dispatch('modal/open', 'discussionDeletion');
|
||||||
() => this.$store.dispatch('discussion/cleanCurrentFile', {
|
this.$store.dispatch('discussion/cleanCurrentFile', {
|
||||||
filterDiscussion: this.currentDiscussion,
|
filterDiscussion: this.currentDiscussion,
|
||||||
}),
|
});
|
||||||
() => {}); // Cancel
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.current-discussion {
|
.current-discussion {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="comment__header flex flex--row flex--space-between flex--align-center">
|
<div class="comment__header flex flex--row flex--space-between flex--align-center">
|
||||||
<div class="comment__user flex flex--row flex--align-center">
|
<div class="comment__user flex flex--row flex--align-center">
|
||||||
<div class="comment__user-image">
|
<div class="comment__user-image">
|
||||||
<user-image :user-id="loginToken.sub"></user-image>
|
<user-image :user-id="userId"></user-image>
|
||||||
</div>
|
</div>
|
||||||
<span class="user-name">{{loginToken.name}}</span>
|
<span class="user-name">{{loginToken.name}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
import { mapGetters, mapMutations, mapActions } from 'vuex';
|
import { mapGetters, mapMutations, mapActions } from 'vuex';
|
||||||
import Prism from 'prismjs';
|
import Prism from 'prismjs';
|
||||||
import UserImage from '../UserImage';
|
import UserImage from '../UserImage';
|
||||||
import cledit from '../../services/cledit';
|
import cledit from '../../services/editor/cledit';
|
||||||
import editorSvc from '../../services/editorSvc';
|
import editorSvc from '../../services/editorSvc';
|
||||||
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
import markdownConversionSvc from '../../services/markdownConversionSvc';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
|
@ -33,9 +33,12 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
UserImage,
|
UserImage,
|
||||||
},
|
},
|
||||||
computed: mapGetters('workspace', [
|
computed: {
|
||||||
'loginToken',
|
...mapGetters('workspace', [
|
||||||
]),
|
'loginToken',
|
||||||
|
'userId',
|
||||||
|
]),
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations('discussion', [
|
...mapMutations('discussion', [
|
||||||
'setNewCommentFocus',
|
'setNewCommentFocus',
|
||||||
|
@ -53,7 +56,7 @@ export default {
|
||||||
const discussionId = this.$store.state.discussion.currentDiscussionId;
|
const discussionId = this.$store.state.discussion.currentDiscussionId;
|
||||||
const comment = {
|
const comment = {
|
||||||
discussionId,
|
discussionId,
|
||||||
sub: this.loginToken.sub,
|
sub: this.userId,
|
||||||
text,
|
text,
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
};
|
};
|
||||||
|
@ -83,9 +86,11 @@ export default {
|
||||||
const clEditor = cledit(preElt, scrollerElt, true);
|
const clEditor = cledit(preElt, scrollerElt, true);
|
||||||
clEditor.init({
|
clEditor.init({
|
||||||
sectionHighlighter: section => Prism.highlight(
|
sectionHighlighter: section => Prism.highlight(
|
||||||
section.text, editorSvc.prismGrammars[section.data]),
|
section.text,
|
||||||
sectionParser: text => markdownConversionSvc.parseSections(
|
editorSvc.prismGrammars[section.data],
|
||||||
editorSvc.converter, text).sections,
|
),
|
||||||
|
sectionParser: text => markdownConversionSvc
|
||||||
|
.parseSections(editorSvc.converter, text).sections,
|
||||||
content: this.$store.state.discussion.newCommentText,
|
content: this.$store.state.discussion.newCommentText,
|
||||||
selectionStart: this.$store.state.discussion.newCommentSelection.start,
|
selectionStart: this.$store.state.discussion.newCommentSelection.start,
|
||||||
selectionEnd: this.$store.state.discussion.newCommentSelection.end,
|
selectionEnd: this.$store.state.discussion.newCommentSelection.end,
|
||||||
|
@ -111,14 +116,14 @@ export default {
|
||||||
clEditor.focus();
|
clEditor.focus();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{ immediate: true });
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
if (isSticky) {
|
if (isSticky) {
|
||||||
let scrollerMirrorElt;
|
let scrollerMirrorElt;
|
||||||
const getScrollerMirrorElt = () => {
|
const getScrollerMirrorElt = () => {
|
||||||
if (!scrollerMirrorElt) {
|
if (!scrollerMirrorElt) {
|
||||||
scrollerMirrorElt = document.querySelector(
|
scrollerMirrorElt = document.querySelector('.comment-list .comment--new .comment__text-inner');
|
||||||
'.comment-list .comment--new .comment__text-inner');
|
|
||||||
}
|
}
|
||||||
return scrollerMirrorElt || { scrollTop: 0 };
|
return scrollerMirrorElt || { scrollTop: 0 };
|
||||||
};
|
};
|
||||||
|
@ -147,7 +152,7 @@ export default {
|
||||||
);
|
);
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.state.discussion.newCommentText,
|
() => this.$store.state.discussion.newCommentText,
|
||||||
newCommentText => clEditor.setContent(newCommentText),
|
newCommentText => clEditor.setContent(newCommentText),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default {
|
||||||
) {
|
) {
|
||||||
this.selection = editorSvc.getTrimmedSelection();
|
this.selection = editorSvc.getTrimmedSelection();
|
||||||
if (this.selection) {
|
if (this.selection) {
|
||||||
const text = editorSvc.previewCtxWithDiffs.text;
|
const { text } = editorSvc.previewCtxWithDiffs;
|
||||||
offset = editorSvc.getPreviewOffset(this.selection.end);
|
offset = editorSvc.getPreviewOffset(this.selection.end);
|
||||||
while (offset && text[offset - 1] === '\n') {
|
while (offset && text[offset - 1] === '\n') {
|
||||||
offset -= 1;
|
offset -= 1;
|
||||||
|
@ -46,7 +46,8 @@ export default {
|
||||||
editorSvc.$on('previewSelectionRange', () => this.checkSelection());
|
editorSvc.$on('previewSelectionRange', () => this.checkSelection());
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['layout/styles'].previewWidth,
|
() => this.$store.getters['layout/styles'].previewWidth,
|
||||||
() => this.checkSelection());
|
() => this.checkSelection(),
|
||||||
|
);
|
||||||
this.checkSelection();
|
this.checkSelection();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.sticky-comment {
|
.sticky-comment {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -12,18 +12,19 @@
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="exportPdf">
|
<menu-entry @click.native="exportPdf">
|
||||||
<icon-download slot="icon"></icon-download>
|
<icon-download slot="icon"></icon-download>
|
||||||
<div><div class="menu-entry__label">sponsor</div> Export as PDF</div>
|
<div><div class="menu-entry__label" :class="{'menu-entry__label--warning': !isSponsor}">sponsor</div> Export as PDF</div>
|
||||||
<span>Produce a PDF from an HTML template.</span>
|
<span>Produce a PDF from an HTML template.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="exportPandoc">
|
<menu-entry @click.native="exportPandoc">
|
||||||
<icon-download slot="icon"></icon-download>
|
<icon-download slot="icon"></icon-download>
|
||||||
<div><div class="menu-entry__label">sponsor</div> Export with Pandoc</div>
|
<div><div class="menu-entry__label" :class="{'menu-entry__label--warning': !isSponsor}">sponsor</div> Export with Pandoc</div>
|
||||||
<span>Convert to PDF, Word, EPUB...</span>
|
<span>Convert to PDF, Word, EPUB...</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import exportSvc from '../../services/exportSvc';
|
import exportSvc from '../../services/exportSvc';
|
||||||
|
|
||||||
|
@ -31,23 +32,24 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
MenuEntry,
|
MenuEntry,
|
||||||
},
|
},
|
||||||
|
computed: mapGetters(['isSponsor']),
|
||||||
methods: {
|
methods: {
|
||||||
exportMarkdown() {
|
exportMarkdown() {
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
return exportSvc.exportToDisk(currentFile.id, 'md')
|
return exportSvc.exportToDisk(currentFile.id, 'md')
|
||||||
.catch(() => {}); // Cancel
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
exportHtml() {
|
exportHtml() {
|
||||||
return this.$store.dispatch('modal/open', 'htmlExport')
|
return this.$store.dispatch('modal/open', 'htmlExport')
|
||||||
.catch(() => {}); // Cancel
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
exportPdf() {
|
exportPdf() {
|
||||||
return this.$store.dispatch('modal/open', 'pdfExport')
|
return this.$store.dispatch('modal/open', 'pdfExport')
|
||||||
.catch(() => {}); // Cancel
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
exportPandoc() {
|
exportPandoc() {
|
||||||
return this.$store.dispatch('modal/open', 'pandocExport')
|
return this.$store.dispatch('modal/open', 'pandocExport')
|
||||||
.catch(() => {}); // Cancel
|
.catch(() => { /* Cancel */ });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,26 +1,41 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="history side-bar__panel">
|
<div class="history side-bar__panel side-bar__panel--menu">
|
||||||
<div class="side-bar__info" v-if="!syncToken">
|
<div class="side-bar__info">
|
||||||
<p>You have to <a href="javascript:void(0)" @click="signin">sign in with Google</a> to enable revision history.</p>
|
<p v-if="syncLocations.length > 1">
|
||||||
<p><b>Note:</b> This will sync your main workspace.</p>
|
<select slot="field" class="textfield" v-model="syncLocationId" @keydown.enter="resolve()">
|
||||||
</div>
|
<option v-for="location in syncLocations" :key="location.id" :value="location.id">
|
||||||
<div class="side-bar__info" v-if="loading">
|
{{ location.description }}
|
||||||
<p>Loading history…</p>
|
</option>
|
||||||
</div>
|
</select>
|
||||||
<div class="side-bar__info" v-else-if="!revisionsWithSpacer.length">
|
</p>
|
||||||
<p><b>{{currentFileName}}</b> has no history.</p>
|
<p v-if="!historyContext">Synchronize <b>{{currentFileName}}</b> to enable revision history or <a href="javascript:void(0)" @click="signin">sign in with Google</a> to synchronize your main workspace.</p>
|
||||||
</div>
|
<p v-else-if="loading">Loading history…</p>
|
||||||
<div class="revision" v-for="revision in revisionsWithSpacer" :key="revision.id">
|
<p v-else-if="!revisionsWithSpacer.length"><b>{{currentFileName}}</b> has no history.</p>
|
||||||
<div class="history__spacer" v-if="revision.spacer"></div>
|
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
||||||
<a class="revision__button button flex flex--row" href="javascript:void(0)" @click="open(revision)">
|
<div class="menu-entry__icon menu-entry__icon--image">
|
||||||
<div class="revision__icon">
|
<icon-provider :provider-id="syncLocation.providerId"></icon-provider>
|
||||||
<user-image :user-id="revision.sub"></user-image>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="revision__header flex flex--column">
|
<span v-if="syncLocation.url">
|
||||||
<user-name :user-id="revision.sub"></user-name>
|
The following revisions are stored in <a :href="syncLocation.url" target="_blank">{{ syncLocationProviderName }}</a>.
|
||||||
<div class="revision__created">{{revision.created | formatTime}}</div>
|
</span>
|
||||||
</div>
|
<span v-else>
|
||||||
</a>
|
The following revisions are stored in {{ syncLocationProviderName }}.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="revision" v-for="revision in revisionsWithSpacer" :key="revision.id">
|
||||||
|
<div class="history__spacer" v-if="revision.spacer"></div>
|
||||||
|
<a class="revision__button button flex flex--row" href="javascript:void(0)" @click="open(revision)">
|
||||||
|
<div class="revision__icon">
|
||||||
|
<user-image :user-id="revision.sub"></user-image>
|
||||||
|
</div>
|
||||||
|
<div class="revision__header flex flex--column">
|
||||||
|
<user-name :user-id="revision.sub"></user-name>
|
||||||
|
<div class="revision__created">{{revision.created | formatTime}}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="history__spacer history__spacer--last" v-if="revisions.length"></div>
|
<div class="history__spacer history__spacer--last" v-if="revisions.length"></div>
|
||||||
<div class="flex flex--row flex--end" v-if="showMoreButton">
|
<div class="flex flex--row flex--end" v-if="showMoreButton">
|
||||||
|
@ -30,8 +45,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapMutations, mapGetters } from 'vuex';
|
import { mapState, mapMutations, mapGetters } from 'vuex';
|
||||||
import providerRegistry from '../../services/providers/providerRegistry';
|
import providerRegistry from '../../services/providers/common/providerRegistry';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import UserImage from '../UserImage';
|
import UserImage from '../UserImage';
|
||||||
import UserName from '../UserName';
|
import UserName from '../UserName';
|
||||||
|
@ -44,11 +59,11 @@ import syncSvc from '../../services/syncSvc';
|
||||||
let editorClassAppliers = [];
|
let editorClassAppliers = [];
|
||||||
let previewClassAppliers = [];
|
let previewClassAppliers = [];
|
||||||
|
|
||||||
let cachedFileId;
|
let cachedHistoryContextHash;
|
||||||
let revisionsPromise;
|
let revisionsPromise;
|
||||||
let revisionContentPromises;
|
let revisionContentPromises;
|
||||||
const pageSize = 30;
|
const pageSize = 30;
|
||||||
const spacerThreshold = 12 * 60 * 60 * 1000; // 12h
|
const spacerThreshold = 60 * 60 * 1000; // 1h
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -60,16 +75,73 @@ export default {
|
||||||
allRevisions: [],
|
allRevisions: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
showCount: pageSize,
|
showCount: pageSize,
|
||||||
|
syncLocationId: null,
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('workspace', [
|
...mapGetters('data', [
|
||||||
'syncToken',
|
'syncDataByItemId',
|
||||||
]),
|
]),
|
||||||
|
...mapGetters('syncLocation', {
|
||||||
|
syncLocations: 'currentWithWorkspaceSyncLocation',
|
||||||
|
}),
|
||||||
|
...mapState('content', [
|
||||||
|
'revisionContent',
|
||||||
|
]),
|
||||||
|
syncLocation() {
|
||||||
|
return utils.someResult(this.syncLocations, (syncLocation) => {
|
||||||
|
if (syncLocation.id === this.syncLocationId) {
|
||||||
|
return syncLocation;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
syncLocationProviderName() {
|
||||||
|
if (!this.syncLocation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return providerRegistry.providersById[this.syncLocation.providerId].name;
|
||||||
|
},
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return this.$store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
|
historyContext() {
|
||||||
|
const { syncLocation } = this;
|
||||||
|
if (syncLocation) {
|
||||||
|
const provider = providerRegistry.providersById[syncLocation.providerId];
|
||||||
|
const token = provider.getToken(syncLocation);
|
||||||
|
const fileId = this.$store.getters['file/current'].id;
|
||||||
|
const contentId = `${fileId}/content`;
|
||||||
|
const historyContext = {
|
||||||
|
token,
|
||||||
|
fileId,
|
||||||
|
contentId,
|
||||||
|
syncLocation: this.syncLocation,
|
||||||
|
};
|
||||||
|
if (syncLocation.id !== 'main') {
|
||||||
|
return historyContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add syncData for workspace sync location
|
||||||
|
const { syncDataByItemId } = this;
|
||||||
|
const fileSyncData = syncDataByItemId[fileId];
|
||||||
|
const contentSyncData = syncDataByItemId[contentId];
|
||||||
|
if (fileSyncData && contentSyncData) {
|
||||||
|
return {
|
||||||
|
...historyContext,
|
||||||
|
fileSyncData,
|
||||||
|
contentSyncData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
historyContextHash() {
|
||||||
|
return utils.serializeObject(this.historyContext);
|
||||||
|
},
|
||||||
revisions() {
|
revisions() {
|
||||||
return this.allRevisions.slice(0, this.showCount);
|
return this.allRevisions.slice()
|
||||||
|
.sort((revision1, revision2) => revision2.created - revision1.created)
|
||||||
|
.slice(0, this.showCount);
|
||||||
},
|
},
|
||||||
revisionsWithSpacer() {
|
revisionsWithSpacer() {
|
||||||
let previousCreated = 0;
|
let previousCreated = 0;
|
||||||
|
@ -85,23 +157,18 @@ export default {
|
||||||
showMoreButton() {
|
showMoreButton() {
|
||||||
return this.showCount < this.allRevisions.length;
|
return this.showCount < this.allRevisions.length;
|
||||||
},
|
},
|
||||||
refreshTrigger() {
|
|
||||||
return utils.serializeObject([
|
|
||||||
this.$store.getters['file/current'].id,
|
|
||||||
this.syncToken,
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations('content', [
|
...mapMutations('content', [
|
||||||
'setRevisionContent',
|
'setRevisionContent',
|
||||||
]),
|
]),
|
||||||
signin() {
|
async signin() {
|
||||||
return googleHelper.signin()
|
try {
|
||||||
.then(
|
await googleHelper.signin();
|
||||||
() => syncSvc.requestSync(),
|
syncSvc.requestSync();
|
||||||
() => {}, // Cancel
|
} catch (e) {
|
||||||
);
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
this.$store.dispatch('data/setSideBarPanel', 'menu');
|
this.$store.dispatch('data/setSideBarPanel', 'menu');
|
||||||
|
@ -112,25 +179,31 @@ export default {
|
||||||
open(revision) {
|
open(revision) {
|
||||||
let revisionContentPromise = revisionContentPromises[revision.id];
|
let revisionContentPromise = revisionContentPromises[revision.id];
|
||||||
if (!revisionContentPromise) {
|
if (!revisionContentPromise) {
|
||||||
revisionContentPromise = new Promise((resolve, reject) => {
|
const historyContext = utils.deepCopy(this.historyContext);
|
||||||
const syncToken = this.syncToken;
|
if (historyContext) {
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||||
this.$store.dispatch('queue/enqueue',
|
revisionContentPromise = new Promise((resolve, reject) => this.$store.dispatch(
|
||||||
() => Promise.resolve()
|
'queue/enqueue',
|
||||||
.then(() => this.workspaceProvider.getRevisionContent(
|
() => provider.getFileRevisionContent({
|
||||||
syncToken, currentFile.id, revision.id))
|
...historyContext,
|
||||||
.then(resolve, reject));
|
revisionId: revision.id,
|
||||||
});
|
})
|
||||||
revisionContentPromises[revision.id] = revisionContentPromise;
|
.then(resolve, reject),
|
||||||
revisionContentPromise.catch(() => {
|
));
|
||||||
revisionContentPromises[revision.id] = null;
|
revisionContentPromises[revision.id] = revisionContentPromise;
|
||||||
});
|
revisionContentPromise.catch((err) => {
|
||||||
|
this.$store.dispatch('notification/error', err);
|
||||||
|
revisionContentPromises[revision.id] = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (revisionContentPromise) {
|
||||||
|
revisionContentPromise.then(revisionContent =>
|
||||||
|
this.$store.dispatch('content/setRevisionContent', revisionContent));
|
||||||
}
|
}
|
||||||
revisionContentPromise.then(revisionContent =>
|
|
||||||
this.$store.dispatch('content/setRevisionContent', revisionContent));
|
|
||||||
},
|
},
|
||||||
refreshHighlighters() {
|
refreshHighlighters() {
|
||||||
const revisionContent = this.$store.state.content.revisionContent;
|
const { revisionContent } = this;
|
||||||
editorClassAppliers.forEach(editorClassApplier => editorClassApplier.stop());
|
editorClassAppliers.forEach(editorClassApplier => editorClassApplier.stop());
|
||||||
editorClassAppliers = [];
|
editorClassAppliers = [];
|
||||||
previewClassAppliers.forEach(previewClassApplier => previewClassApplier.stop());
|
previewClassAppliers.forEach(previewClassApplier => previewClassApplier.stop());
|
||||||
|
@ -145,41 +218,53 @@ export default {
|
||||||
end: offset + text.length,
|
end: offset + text.length,
|
||||||
};
|
};
|
||||||
editorClassAppliers.push(new EditorClassApplier(
|
editorClassAppliers.push(new EditorClassApplier(
|
||||||
[`revision-diff--${utils.uid()}`, ...classes], offsets));
|
[`revision-diff--${utils.uid()}`, ...classes],
|
||||||
|
offsets,
|
||||||
|
));
|
||||||
previewClassAppliers.push(new PreviewClassApplier(
|
previewClassAppliers.push(new PreviewClassApplier(
|
||||||
[`revision-diff--${utils.uid()}`, ...classes], offsets));
|
[`revision-diff--${utils.uid()}`, ...classes],
|
||||||
|
offsets,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
offset += text.length;
|
offset += text.length;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
watch: {
|
||||||
// Find the workspace provider
|
// Fix syncLocationId
|
||||||
const workspace = this.$store.getters['workspace/currentWorkspace'];
|
syncLocation: {
|
||||||
this.workspaceProvider = providerRegistry.providers[workspace.providerId];
|
immediate: true,
|
||||||
|
handler(value) {
|
||||||
// Watch file changes
|
if (!value) {
|
||||||
this.$watch(
|
const firstSyncLocation = this.syncLocations[0];
|
||||||
() => this.refreshTrigger,
|
if (firstSyncLocation) {
|
||||||
() => {
|
this.syncLocationId = firstSyncLocation.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Load revision list on context changes
|
||||||
|
historyContextHash: {
|
||||||
|
immediate: true,
|
||||||
|
handler() {
|
||||||
this.allRevisions = [];
|
this.allRevisions = [];
|
||||||
const id = this.$store.getters['file/current'].id;
|
const historyContext = utils.deepCopy(this.historyContext);
|
||||||
const syncToken = this.syncToken;
|
if (historyContext) {
|
||||||
if (id && syncToken) {
|
if (this.historyContextHash !== cachedHistoryContextHash) {
|
||||||
if (id !== cachedFileId) {
|
|
||||||
this.setRevisionContent();
|
this.setRevisionContent();
|
||||||
cachedFileId = id;
|
cachedHistoryContextHash = this.historyContextHash;
|
||||||
revisionContentPromises = {};
|
revisionContentPromises = {};
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||||
revisionsPromise = new Promise((resolve, reject) => {
|
revisionsPromise = new Promise((resolve, reject) => this.$store.dispatch(
|
||||||
this.$store.dispatch('queue/enqueue',
|
'queue/enqueue',
|
||||||
() => Promise.resolve()
|
() => provider
|
||||||
.then(() => this.workspaceProvider.listRevisions(syncToken, currentFile.id))
|
.listFileRevisions(historyContext)
|
||||||
.then(resolve, reject));
|
.then(resolve, reject),
|
||||||
})
|
))
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
cachedFileId = null;
|
this.$store.dispatch('notification/error', err);
|
||||||
|
cachedHistoryContextHash = null;
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -191,39 +276,40 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, { immediate: true });
|
},
|
||||||
|
},
|
||||||
const loadOne = () => {
|
// Load each revision on revision list changes
|
||||||
if (!this.destroyed) {
|
revisions(revisions) {
|
||||||
this.$store.dispatch('queue/enqueue',
|
const { historyContext } = this;
|
||||||
() => {
|
if (historyContext) {
|
||||||
let loadPromise;
|
this.$store.dispatch(
|
||||||
this.revisions.some((revision) => {
|
'queue/enqueue',
|
||||||
if (!revision.created) {
|
() => utils.awaitSequence(revisions, async (revision) => {
|
||||||
const syncToken = this.syncToken;
|
// Make sure revisions and historyContext haven't changed
|
||||||
const currentFile = this.$store.getters['file/current'];
|
if (!this.destroyed
|
||||||
loadPromise = this.workspaceProvider
|
&& this.revisions === revisions
|
||||||
.loadRevision(syncToken, currentFile.id, revision)
|
&& this.historyContext === historyContext
|
||||||
.then(() => loadOne());
|
) {
|
||||||
}
|
const provider = providerRegistry.providersById[this.syncLocation.providerId];
|
||||||
return loadPromise;
|
await provider.loadFileRevision({
|
||||||
});
|
...historyContext,
|
||||||
return loadPromise;
|
revision,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
// Refresh highlighters on open/close revision
|
||||||
this.$watch(
|
revisionContent: {
|
||||||
() => this.revisions,
|
immediate: true,
|
||||||
() => loadOne(),
|
handler() {
|
||||||
{ immediate: true });
|
this.refreshHighlighters();
|
||||||
|
},
|
||||||
// Watch diffs changes
|
},
|
||||||
this.$watch(
|
},
|
||||||
() => this.$store.state.content.revisionContent,
|
created() {
|
||||||
() => this.refreshHighlighters());
|
// Close revision on escape
|
||||||
|
|
||||||
// Close revision
|
|
||||||
this.onKeyup = (evt) => {
|
this.onKeyup = (evt) => {
|
||||||
if (evt.which === 27) {
|
if (evt.which === 27) {
|
||||||
// Esc key
|
// Esc key
|
||||||
|
@ -246,11 +332,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.history {
|
|
||||||
padding: 5px 5px 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history__button {
|
.history__button {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -266,7 +348,7 @@ export default {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 24px;
|
left: 19px;
|
||||||
border-left: 2px dotted $hr-color;
|
border-left: 2px dotted $hr-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +359,7 @@ export default {
|
||||||
|
|
||||||
.revision__button {
|
.revision__button {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 15px;
|
padding: 10px;
|
||||||
height: auto;
|
height: auto;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -287,7 +369,7 @@ export default {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 24px;
|
left: 19px;
|
||||||
border-left: 2px solid $hr-color;
|
border-left: 2px solid $hr-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,20 +400,21 @@ export default {
|
||||||
.revision__header {
|
.revision__header {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
line-height: 1.33;
|
||||||
}
|
}
|
||||||
|
|
||||||
.revision__created {
|
.revision__created {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
opacity: 0.5;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout--revision {
|
.layout--revision {
|
||||||
.cledit-section *,
|
.cledit-section *,
|
||||||
.cl-preview-section * {
|
.cl-preview-section * {
|
||||||
color: transparentize($editor-color-light, 0.67) !important;
|
color: transparentize($editor-color-light, 0.5) !important;
|
||||||
|
|
||||||
.app--dark & {
|
.app--dark & {
|
||||||
color: transparentize($editor-color-dark, 0.67) !important;
|
color: transparentize($editor-color-dark, 0.5) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex--column">
|
<div class="flex flex--column">
|
||||||
<div>Import Markdown</div>
|
<div>Import Markdown</div>
|
||||||
<span>Open a plain text file.</span>
|
<span>Import a plain text file.</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<input class="hidden-file" id="import-html-file-input" type="file" @change="onImportHtml">
|
<input class="hidden-file" id="import-html-file-input" type="file" @change="onImportHtml">
|
||||||
|
@ -27,8 +27,9 @@
|
||||||
import TurndownService from 'turndown/lib/turndown.browser.umd';
|
import TurndownService from 'turndown/lib/turndown.browser.umd';
|
||||||
import htmlSanitizer from '../../libs/htmlSanitizer';
|
import htmlSanitizer from '../../libs/htmlSanitizer';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
import providerUtils from '../../services/providers/providerUtils';
|
import Provider from '../../services/providers/common/Provider';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
|
import workspaceSvc from '../../services/workspaceSvc';
|
||||||
|
|
||||||
const turndownService = new TurndownService(store.getters['data/computedSettings'].turndown);
|
const turndownService = new TurndownService(store.getters['data/computedSettings'].turndown);
|
||||||
|
|
||||||
|
@ -52,27 +53,25 @@ export default {
|
||||||
MenuEntry,
|
MenuEntry,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onImportMarkdown(evt) {
|
async onImportMarkdown(evt) {
|
||||||
const file = evt.target.files[0];
|
const file = evt.target.files[0];
|
||||||
readFile(file)
|
const content = await readFile(file);
|
||||||
.then(content => this.$store.dispatch('createFile', {
|
const item = await workspaceSvc.createFile({
|
||||||
...providerUtils.parseContent(content),
|
...Provider.parseContent(content),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})
|
});
|
||||||
.then(item => this.$store.commit('file/setCurrentId', item.id)));
|
this.$store.commit('file/setCurrentId', item.id);
|
||||||
},
|
},
|
||||||
onImportHtml(evt) {
|
async onImportHtml(evt) {
|
||||||
const file = evt.target.files[0];
|
const file = evt.target.files[0];
|
||||||
readFile(file)
|
const content = await readFile(file);
|
||||||
.then(content => this.$store.dispatch('createFile', {
|
const sanitizedContent = htmlSanitizer.sanitizeHtml(content)
|
||||||
...providerUtils.parseContent(
|
.replace(/ /g, ' '); // Replace non-breaking spaces with classic spaces
|
||||||
turndownService.turndown(
|
const item = await workspaceSvc.createFile({
|
||||||
htmlSanitizer.sanitizeHtml(content)
|
...Provider.parseContent(turndownService.turndown(sanitizedContent)),
|
||||||
.replace(/ /g, ' '), // Replace non-breaking spaces with classic spaces
|
name: file.name,
|
||||||
)),
|
});
|
||||||
name: file.name,
|
this.$store.commit('file/setCurrentId', item.id);
|
||||||
}))
|
|
||||||
.then(item => this.$store.commit('file/setCurrentId', item.id));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="side-bar__panel side-bar__panel--menu">
|
<div class="side-bar__panel side-bar__panel--menu">
|
||||||
<div class="menu-info-entries">
|
<div class="side-bar__info">
|
||||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-if="loginToken">
|
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-if="loginToken">
|
||||||
<div class="menu-entry__icon menu-entry__icon--image">
|
<div class="menu-entry__icon menu-entry__icon--image">
|
||||||
<user-image :user-id="loginToken.sub"></user-image>
|
<user-image :user-id="userId"></user-image>
|
||||||
</div>
|
</div>
|
||||||
<span>Signed in as <b>{{loginToken.name}}</b>.</span>
|
<span>Signed in as <b>{{loginToken.name}}</b>.</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,18 @@
|
||||||
<div class="menu-entry__icon menu-entry__icon--image">
|
<div class="menu-entry__icon menu-entry__icon--image">
|
||||||
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
<icon-provider :provider-id="currentWorkspace.providerId"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<span><b>{{currentWorkspace.name}}</b> synced.</span>
|
<span v-if="currentWorkspace.providerId === 'googleDriveAppData'">
|
||||||
|
<b>{{currentWorkspace.name}}</b> synced with your Google Drive app data folder.
|
||||||
|
</span>
|
||||||
|
<span v-else-if="currentWorkspace.providerId === 'googleDriveWorkspace'">
|
||||||
|
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">Google Drive folder</a>.
|
||||||
|
</span>
|
||||||
|
<span v-else-if="currentWorkspace.providerId === 'couchdbWorkspace'">
|
||||||
|
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">CouchDB database</a>.
|
||||||
|
</span>
|
||||||
|
<span v-else-if="currentWorkspace.providerId === 'githubWorkspace'">
|
||||||
|
<b>{{currentWorkspace.name}}</b> synced with a <a :href="workspaceLocationUrl" target="_blank">GitHub repo</a>.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
<div class="menu-entry menu-entry--info flex flex--row flex--align-center" v-else>
|
||||||
<div class="menu-entry__icon menu-entry__icon--disabled">
|
<div class="menu-entry__icon menu-entry__icon--disabled">
|
||||||
|
@ -27,7 +38,7 @@
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="setPanel('workspaces')">
|
<menu-entry @click.native="setPanel('workspaces')">
|
||||||
<icon-database slot="icon"></icon-database>
|
<icon-database slot="icon"></icon-database>
|
||||||
<div>Workspaces</div>
|
<div><div class="menu-entry__label menu-entry__label--warning">new</div> Workspaces</div>
|
||||||
<span>Switch to another workspace.</span>
|
<span>Switch to another workspace.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -83,6 +94,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import MenuEntry from './common/MenuEntry';
|
import MenuEntry from './common/MenuEntry';
|
||||||
|
import providerRegistry from '../../services/providers/common/providerRegistry';
|
||||||
import UserImage from '../UserImage';
|
import UserImage from '../UserImage';
|
||||||
import googleHelper from '../../services/providers/helpers/googleHelper';
|
import googleHelper from '../../services/providers/helpers/googleHelper';
|
||||||
import syncSvc from '../../services/syncSvc';
|
import syncSvc from '../../services/syncSvc';
|
||||||
|
@ -97,25 +109,34 @@ export default {
|
||||||
'currentWorkspace',
|
'currentWorkspace',
|
||||||
'syncToken',
|
'syncToken',
|
||||||
'loginToken',
|
'loginToken',
|
||||||
|
'userId',
|
||||||
]),
|
]),
|
||||||
|
workspaceLocationUrl() {
|
||||||
|
const provider = providerRegistry.providersById[this.currentWorkspace.providerId];
|
||||||
|
return provider.getWorkspaceLocationUrl(this.currentWorkspace);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('data', {
|
...mapActions('data', {
|
||||||
setPanel: 'setSideBarPanel',
|
setPanel: 'setSideBarPanel',
|
||||||
}),
|
}),
|
||||||
signin() {
|
async signin() {
|
||||||
return googleHelper.signin()
|
try {
|
||||||
.then(
|
await googleHelper.signin();
|
||||||
() => syncSvc.requestSync(),
|
syncSvc.requestSync();
|
||||||
() => {}, // Cancel
|
} catch (e) {
|
||||||
);
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fileProperties() {
|
async fileProperties() {
|
||||||
return this.$store.dispatch('modal/open', 'fileProperties')
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await this.$store.dispatch('modal/open', 'fileProperties');
|
||||||
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
print() {
|
print() {
|
||||||
print();
|
window.print();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="templates">
|
<menu-entry @click.native="templates">
|
||||||
<icon-code-braces slot="icon"></icon-code-braces>
|
<icon-code-braces slot="icon"></icon-code-braces>
|
||||||
<div>Templates</div>
|
<div><div class="menu-entry__label menu-entry__label--count">{{templateCount}}</div> Templates</div>
|
||||||
<span>Configure Handlebars templates for your exports.</span>
|
<span>Configure Handlebars templates for your exports.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="reset">
|
<menu-entry @click.native="reset">
|
||||||
|
@ -50,6 +50,11 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
MenuEntry,
|
MenuEntry,
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
templateCount() {
|
||||||
|
return Object.keys(this.$store.getters['data/allTemplatesById']).length;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onImportBackup(evt) {
|
onImportBackup(evt) {
|
||||||
const file = evt.target.files[0];
|
const file = evt.target.files[0];
|
||||||
|
@ -72,35 +77,36 @@ export default {
|
||||||
...utils.queryParams,
|
...utils.queryParams,
|
||||||
exportWorkspace: true,
|
exportWorkspace: true,
|
||||||
}, true);
|
}, true);
|
||||||
const iframeElt = utils.createHiddenIframe(url);
|
window.location.href = url;
|
||||||
document.body.appendChild(iframeElt);
|
window.location.reload(true);
|
||||||
setTimeout(() => {
|
|
||||||
document.body.removeChild(iframeElt);
|
|
||||||
}, 60000);
|
|
||||||
},
|
},
|
||||||
settings() {
|
async settings() {
|
||||||
return this.$store.dispatch('modal/open', 'settings')
|
try {
|
||||||
.then(
|
const settings = await this.$store.dispatch('modal/open', 'settings');
|
||||||
settings => this.$store.dispatch('data/setSettings', settings),
|
this.$store.dispatch('data/setSettings', settings);
|
||||||
() => {}, // Cancel
|
} catch (e) {
|
||||||
);
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
templates() {
|
async templates() {
|
||||||
return this.$store.dispatch('modal/open', 'templates')
|
try {
|
||||||
.then(
|
const { templates } = await this.$store.dispatch('modal/open', 'templates');
|
||||||
({ templates }) => this.$store.dispatch('data/setTemplates', templates),
|
this.$store.dispatch('data/setTemplatesById', templates);
|
||||||
() => {}, // Cancel
|
} catch (e) {
|
||||||
);
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
reset() {
|
async reset() {
|
||||||
return this.$store.dispatch('modal/reset')
|
try {
|
||||||
.then(() => {
|
await this.$store.dispatch('modal/open', 'reset');
|
||||||
location.href = '#reset=true';
|
window.location.href = '#reset=true';
|
||||||
location.reload();
|
window.location.reload();
|
||||||
});
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
},
|
},
|
||||||
about() {
|
about() {
|
||||||
return this.$store.dispatch('modal/open', 'about');
|
this.$store.dispatch('modal/open', 'about');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="managePublish">
|
<menu-entry @click.native="managePublish">
|
||||||
<icon-view-list slot="icon"></icon-view-list>
|
<icon-view-list slot="icon"></icon-view-list>
|
||||||
<div>File publication</div>
|
<div><div class="menu-entry__label menu-entry__label--count">{{locationCount}}</div> File publication</div>
|
||||||
<span>Manage current file publication locations.</span>
|
<span>Manage current file publication locations.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,15 +113,19 @@ import zendeskHelper from '../../services/providers/helpers/zendeskHelper';
|
||||||
import publishSvc from '../../services/publishSvc';
|
import publishSvc from '../../services/publishSvc';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
|
|
||||||
const tokensToArray = (tokens, filter = () => true) => Object.keys(tokens)
|
const tokensToArray = (tokens, filter = () => true) => Object.values(tokens)
|
||||||
.map(sub => tokens[sub])
|
|
||||||
.filter(token => filter(token))
|
.filter(token => filter(token))
|
||||||
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
||||||
|
|
||||||
const openPublishModal = (token, type) => store.dispatch('modal/open', {
|
const publishModalOpener = type => async (token) => {
|
||||||
type,
|
try {
|
||||||
token,
|
const publishLocation = await store.dispatch('modal/open', {
|
||||||
}).then(publishLocation => publishSvc.createPublishLocation(publishLocation));
|
type,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
publishSvc.createPublishLocation(publishLocation);
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -137,26 +141,29 @@ export default {
|
||||||
...mapGetters('publishLocation', {
|
...mapGetters('publishLocation', {
|
||||||
publishLocations: 'current',
|
publishLocations: 'current',
|
||||||
}),
|
}),
|
||||||
|
locationCount() {
|
||||||
|
return Object.keys(this.publishLocations).length;
|
||||||
|
},
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return this.$store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
googleDriveTokens() {
|
googleDriveTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isDrive);
|
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||||
},
|
},
|
||||||
dropboxTokens() {
|
dropboxTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/dropboxTokens']);
|
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
||||||
},
|
},
|
||||||
githubTokens() {
|
githubTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/githubTokens']);
|
return tokensToArray(this.$store.getters['data/githubTokensBySub']);
|
||||||
},
|
},
|
||||||
wordpressTokens() {
|
wordpressTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/wordpressTokens']);
|
return tokensToArray(this.$store.getters['data/wordpressTokensBySub']);
|
||||||
},
|
},
|
||||||
bloggerTokens() {
|
bloggerTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isBlogger);
|
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isBlogger);
|
||||||
},
|
},
|
||||||
zendeskTokens() {
|
zendeskTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/zendeskTokens']);
|
return tokensToArray(this.$store.getters['data/zendeskTokensBySub']);
|
||||||
},
|
},
|
||||||
noToken() {
|
noToken() {
|
||||||
return !this.googleDriveTokens.length
|
return !this.googleDriveTokens.length
|
||||||
|
@ -173,77 +180,53 @@ export default {
|
||||||
publishSvc.requestPublish();
|
publishSvc.requestPublish();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
managePublish() {
|
async managePublish() {
|
||||||
return this.$store.dispatch('modal/open', 'publishManagement');
|
try {
|
||||||
|
await this.$store.dispatch('modal/open', 'publishManagement');
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
addGoogleDriveAccount() {
|
async addGoogleDriveAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'googleDriveAccount',
|
await this.$store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||||
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
|
await googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
addDropboxAccount() {
|
async addDropboxAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'dropboxAccount',
|
await this.$store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||||
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
|
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
addGithubAccount() {
|
async addGithubAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'githubAccount',
|
await this.$store.dispatch('modal/open', { type: 'githubAccount' });
|
||||||
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
|
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
addWordpressAccount() {
|
async addWordpressAccount() {
|
||||||
return wordpressHelper.addAccount()
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await wordpressHelper.addAccount();
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
addBloggerAccount() {
|
async addBloggerAccount() {
|
||||||
return googleHelper.addBloggerAccount()
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await googleHelper.addBloggerAccount();
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
addZendeskAccount() {
|
async addZendeskAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'zendeskAccount',
|
const { subdomain, clientId } = await this.$store.dispatch('modal/open', { type: 'zendeskAccount' });
|
||||||
onResolve: ({ subdomain, clientId }) => zendeskHelper.addAccount(subdomain, clientId),
|
await zendeskHelper.addAccount(subdomain, clientId);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishGoogleDrive(token) {
|
|
||||||
return openPublishModal(token, 'googleDrivePublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishDropbox(token) {
|
|
||||||
return openPublishModal(token, 'dropboxPublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishGithub(token) {
|
|
||||||
return openPublishModal(token, 'githubPublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishGist(token) {
|
|
||||||
return openPublishModal(token, 'gistPublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishWordpress(token) {
|
|
||||||
return openPublishModal(token, 'wordpressPublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishBlogger(token) {
|
|
||||||
return openPublishModal(token, 'bloggerPublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishBloggerPage(token) {
|
|
||||||
return openPublishModal(token, 'bloggerPagePublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
|
||||||
publishZendesk(token) {
|
|
||||||
return openPublishModal(token, 'zendeskPublish')
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
|
publishGoogleDrive: publishModalOpener('googleDrivePublish'),
|
||||||
|
publishDropbox: publishModalOpener('dropboxPublish'),
|
||||||
|
publishGithub: publishModalOpener('githubPublish'),
|
||||||
|
publishGist: publishModalOpener('gistPublish'),
|
||||||
|
publishWordpress: publishModalOpener('wordpressPublish'),
|
||||||
|
publishBlogger: publishModalOpener('bloggerPublish'),
|
||||||
|
publishBloggerPage: publishModalOpener('bloggerPagePublish'),
|
||||||
|
publishZendesk: publishModalOpener('zendeskPublish'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="side-bar__panel side-bar__panel--menu">
|
<div class="side-bar__panel side-bar__panel--menu">
|
||||||
<div class="side-bar__info" v-if="isCurrentTemp">
|
<div class="side-bar__info" v-if="isCurrentTemp">
|
||||||
<p><b>{{currentFileName}}</b> can not be synchronized as it's a temporary file.</p>
|
<p><b>{{currentFileName}}</b> can not be synced as it's a temporary file.</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="side-bar__info" v-if="noToken">
|
<div class="side-bar__info" v-if="noToken">
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="manageSync">
|
<menu-entry @click.native="manageSync">
|
||||||
<icon-view-list slot="icon"></icon-view-list>
|
<icon-view-list slot="icon"></icon-view-list>
|
||||||
<div>File synchronization</div>
|
<div><div class="menu-entry__label menu-entry__label--count">{{locationCount}}</div> File synchronization</div>
|
||||||
<span>Manage current file synchronized locations.</span>
|
<span>Manage current file synchronized locations.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
|
@ -91,8 +91,7 @@ import githubProvider from '../../services/providers/githubProvider';
|
||||||
import syncSvc from '../../services/syncSvc';
|
import syncSvc from '../../services/syncSvc';
|
||||||
import store from '../../store';
|
import store from '../../store';
|
||||||
|
|
||||||
const tokensToArray = (tokens, filter = () => true) => Object.keys(tokens)
|
const tokensToArray = (tokens, filter = () => true) => Object.values(tokens)
|
||||||
.map(sub => tokens[sub])
|
|
||||||
.filter(token => filter(token))
|
.filter(token => filter(token))
|
||||||
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
||||||
|
|
||||||
|
@ -116,19 +115,22 @@ export default {
|
||||||
'isCurrentTemp',
|
'isCurrentTemp',
|
||||||
]),
|
]),
|
||||||
...mapGetters('syncLocation', {
|
...mapGetters('syncLocation', {
|
||||||
syncLocations: 'current',
|
syncLocations: 'currentWithWorkspaceSyncLocation',
|
||||||
}),
|
}),
|
||||||
|
locationCount() {
|
||||||
|
return Object.keys(this.syncLocations).length;
|
||||||
|
},
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return this.$store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
googleDriveTokens() {
|
googleDriveTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/googleTokens'], token => token.isDrive);
|
return tokensToArray(this.$store.getters['data/googleTokensBySub'], token => token.isDrive);
|
||||||
},
|
},
|
||||||
dropboxTokens() {
|
dropboxTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/dropboxTokens']);
|
return tokensToArray(this.$store.getters['data/dropboxTokensBySub']);
|
||||||
},
|
},
|
||||||
githubTokens() {
|
githubTokens() {
|
||||||
return tokensToArray(this.$store.getters['data/githubTokens']);
|
return tokensToArray(this.$store.getters['data/githubTokensBySub']);
|
||||||
},
|
},
|
||||||
noToken() {
|
noToken() {
|
||||||
return !this.googleDriveTokens.length
|
return !this.googleDriveTokens.length
|
||||||
|
@ -142,63 +144,74 @@ export default {
|
||||||
syncSvc.requestSync();
|
syncSvc.requestSync();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
manageSync() {
|
async manageSync() {
|
||||||
return this.$store.dispatch('modal/open', 'syncManagement');
|
try {
|
||||||
|
await this.$store.dispatch('modal/open', 'syncManagement');
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
addGoogleDriveAccount() {
|
async addGoogleDriveAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'googleDriveAccount',
|
await this.$store.dispatch('modal/open', { type: 'googleDriveAccount' });
|
||||||
onResolve: () => googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess),
|
await googleHelper.addDriveAccount(!store.getters['data/localSettings'].googleDriveRestrictedAccess);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
addDropboxAccount() {
|
async addDropboxAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'dropboxAccount',
|
await this.$store.dispatch('modal/open', { type: 'dropboxAccount' });
|
||||||
onResolve: () => dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess),
|
await dropboxHelper.addAccount(!store.getters['data/localSettings'].dropboxRestrictedAccess);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
addGithubAccount() {
|
async addGithubAccount() {
|
||||||
return this.$store.dispatch('modal/open', {
|
try {
|
||||||
type: 'githubAccount',
|
await this.$store.dispatch('modal/open', { type: 'githubAccount' });
|
||||||
onResolve: () => githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess),
|
await githubHelper.addAccount(store.getters['data/localSettings'].githubRepoFullAccess);
|
||||||
})
|
} catch (e) { /* cancel */ }
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
openGoogleDrive(token) {
|
async openGoogleDrive(token) {
|
||||||
return googleHelper.openPicker(token, 'doc')
|
const files = await googleHelper.openPicker(token, 'doc');
|
||||||
.then(files => this.$store.dispatch('queue/enqueue',
|
this.$store.dispatch(
|
||||||
() => googleDriveProvider.openFiles(token, files)));
|
'queue/enqueue',
|
||||||
|
() => googleDriveProvider.openFiles(token, files),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
openDropbox(token) {
|
async openDropbox(token) {
|
||||||
return dropboxHelper.openChooser(token)
|
const paths = await dropboxHelper.openChooser(token);
|
||||||
.then(paths => this.$store.dispatch('queue/enqueue',
|
this.$store.dispatch(
|
||||||
() => dropboxProvider.openFiles(token, paths)));
|
'queue/enqueue',
|
||||||
|
() => dropboxProvider.openFiles(token, paths),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
saveGoogleDrive(token) {
|
async saveGoogleDrive(token) {
|
||||||
return openSyncModal(token, 'googleDriveSave')
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await openSyncModal(token, 'googleDriveSave');
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
saveDropbox(token) {
|
async saveDropbox(token) {
|
||||||
return openSyncModal(token, 'dropboxSave')
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await openSyncModal(token, 'dropboxSave');
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
openGithub(token) {
|
async openGithub(token) {
|
||||||
return store.dispatch('modal/open', {
|
try {
|
||||||
type: 'githubOpen',
|
const syncLocation = await store.dispatch('modal/open', {
|
||||||
token,
|
type: 'githubOpen',
|
||||||
})
|
token,
|
||||||
.then(syncLocation => this.$store.dispatch('queue/enqueue',
|
});
|
||||||
() => githubProvider.openFile(token, syncLocation)));
|
this.$store.dispatch(
|
||||||
|
'queue/enqueue',
|
||||||
|
() => githubProvider.openFile(token, syncLocation),
|
||||||
|
);
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
saveGithub(token) {
|
async saveGithub(token) {
|
||||||
return openSyncModal(token, 'githubSave')
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await openSyncModal(token, 'githubSave');
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
saveGist(token) {
|
async saveGist(token) {
|
||||||
return openSyncModal(token, 'gistSync')
|
try {
|
||||||
.catch(() => {}); // Cancel
|
await openSyncModal(token, 'gistSync');
|
||||||
|
} catch (e) { /* cancel */ }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="side-bar__panel side-bar__panel--menu">
|
<div class="side-bar__panel side-bar__panel--menu">
|
||||||
<div class="workspace" v-for="(workspace, id) in sanitizedWorkspaces" :key="id">
|
<div class="workspace" v-for="(workspace, id) in workspacesById" :key="id">
|
||||||
<menu-entry :href="workspace.url" target="_blank">
|
<menu-entry :href="workspace.url" target="_blank">
|
||||||
<icon-provider slot="icon" :provider-id="workspace.providerId"></icon-provider>
|
<icon-provider slot="icon" :provider-id="workspace.providerId"></icon-provider>
|
||||||
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">current</div>{{workspace.name}}</div>
|
<div class="workspace__name"><div class="menu-entry__label" v-if="currentWorkspace === workspace">current</div>{{workspace.name}}</div>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<menu-entry @click.native="addGoogleDriveWorkspace">
|
|
||||||
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
|
||||||
<span>Add Google Drive workspace</span>
|
|
||||||
</menu-entry>
|
|
||||||
<menu-entry @click.native="addCouchdbWorkspace">
|
<menu-entry @click.native="addCouchdbWorkspace">
|
||||||
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
|
<icon-provider slot="icon" provider-id="couchdbWorkspace"></icon-provider>
|
||||||
<span>Add CouchDB workspace</span>
|
<div>CouchDB workspace</div>
|
||||||
|
<span>Add a workspace synced with a CouchDB database.</span>
|
||||||
|
</menu-entry>
|
||||||
|
<menu-entry @click.native="addGithubWorkspace">
|
||||||
|
<icon-provider slot="icon" provider-id="githubWorkspace"></icon-provider>
|
||||||
|
<div>GitHub workspace</div>
|
||||||
|
<span>Add a workspace synced with a GitHub repository.</span>
|
||||||
|
</menu-entry>
|
||||||
|
<menu-entry @click.native="addGoogleDriveWorkspace">
|
||||||
|
<icon-provider slot="icon" provider-id="googleDriveWorkspace"></icon-provider>
|
||||||
|
<div>Google Drive workspace</div>
|
||||||
|
<span>Add a workspace synced with a Google Drive folder.</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
<menu-entry @click.native="manageWorkspaces">
|
<menu-entry @click.native="manageWorkspaces">
|
||||||
<icon-database slot="icon"></icon-database>
|
<icon-database slot="icon"></icon-database>
|
||||||
<span>Manage workspaces</span>
|
<span><div class="menu-entry__label menu-entry__label--count">{{workspaceCount}}</div> Manage workspaces</span>
|
||||||
</menu-entry>
|
</menu-entry>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -32,37 +39,53 @@ export default {
|
||||||
MenuEntry,
|
MenuEntry,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('data', [
|
|
||||||
'sanitizedWorkspaces',
|
|
||||||
]),
|
|
||||||
...mapGetters('workspace', [
|
...mapGetters('workspace', [
|
||||||
|
'workspacesById',
|
||||||
'currentWorkspace',
|
'currentWorkspace',
|
||||||
]),
|
]),
|
||||||
|
workspaceCount() {
|
||||||
|
return Object.keys(this.workspacesById).length;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addGoogleDriveWorkspace() {
|
async addCouchdbWorkspace() {
|
||||||
return googleHelper.addDriveAccount(true)
|
try {
|
||||||
.then(token => this.$store.dispatch('modal/open', {
|
this.$store.dispatch('modal/open', {
|
||||||
|
type: 'couchdbWorkspace',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async addGithubWorkspace() {
|
||||||
|
try {
|
||||||
|
this.$store.dispatch('modal/open', {
|
||||||
|
type: 'githubWorkspace',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Cancel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async addGoogleDriveWorkspace() {
|
||||||
|
try {
|
||||||
|
const token = await googleHelper.addDriveAccount(true);
|
||||||
|
this.$store.dispatch('modal/open', {
|
||||||
type: 'googleDriveWorkspace',
|
type: 'googleDriveWorkspace',
|
||||||
token,
|
token,
|
||||||
}))
|
});
|
||||||
.catch(() => {}); // Cancel
|
} catch (e) {
|
||||||
},
|
// Cancel
|
||||||
addCouchdbWorkspace() {
|
}
|
||||||
return this.$store.dispatch('modal/open', {
|
|
||||||
type: 'couchdbWorkspace',
|
|
||||||
})
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
manageWorkspaces() {
|
manageWorkspaces() {
|
||||||
return this.$store.dispatch('modal/open', 'workspaceManagement');
|
this.$store.dispatch('modal/open', 'workspaceManagement');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.workspace .menu-entry {
|
.workspace .menu-entry {
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../common/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
|
||||||
.menu-entry {
|
.menu-entry {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -24,9 +24,13 @@
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
opacity: 0.5;
|
opacity: 0.67;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
|
||||||
|
.menu-entry__label {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline;
|
display: inline;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
@ -34,12 +38,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-info-entries {
|
|
||||||
padding: 10px;
|
|
||||||
margin: -10px -10px 10px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-entry--info {
|
.menu-entry--info {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
@ -70,10 +68,22 @@
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0.05em 0.25em;
|
line-height: 1;
|
||||||
background-color: darken($error-color, 10);
|
padding: 0.15em 0.25em;
|
||||||
|
background-color: #fff;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-entry__label--warning {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
background-color: darken($error-color, 10);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-entry__label--count {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-entry__text {
|
.menu-entry__text {
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
<modal-inner class="modal__inner-1--about-modal" aria-label="About">
|
<modal-inner class="modal__inner-1--about-modal" aria-label="About">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<div class="logo-background"></div>
|
<div class="logo-background"></div>
|
||||||
<small>v{{version}} — © 2018 Dock5 Software</small>
|
<small>© 2013-2018 Dock5 Software Ltd.<br>v{{version}}</small>
|
||||||
<hr>
|
<hr>
|
||||||
StackEdit on <a target="_blank" href="https://github.com/benweet/stackedit/">GitHub</a>
|
StackEdit on <a target="_blank" href="https://github.com/benweet/stackedit/">GitHub</a>
|
||||||
<br>
|
<br>
|
||||||
<a target="_blank" href="https://github.com/benweet/stackedit/issues">Issue tracker</a> — <a target="_blank" href="https://github.com/benweet/stackedit/blob/master/CHANGELOG.md">Changelog</a>
|
<a target="_blank" href="https://github.com/benweet/stackedit/issues">Issue tracker</a> — <a target="_blank" href="https://github.com/benweet/stackedit/releases">Changelog</a>
|
||||||
<br>
|
<br>
|
||||||
<a target="_blank" href="https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg">Chrome app</a> — <a target="_blank" href="https://chrome.google.com/webstore/detail/ajehldoplanpchfokmeempkekhnhmoha">Chrome extension</a>
|
<a target="_blank" href="https://chrome.google.com/webstore/detail/iiooodelglhkcpgbajoejffhijaclcdg">Chrome app</a> — <a target="_blank" href="https://chrome.google.com/webstore/detail/ajehldoplanpchfokmeempkekhnhmoha">Chrome extension</a>
|
||||||
<br>
|
<br>
|
||||||
StackEdit on <a target="_blank" href="https://twitter.com/stackedit/">Twitter</a>
|
<a target="_blank" href="https://community.stackedit.io/">Community</a> — <a target="_blank" href="https://community.stackedit.io/c/how-to">Tutos and How To</a>
|
||||||
<br>
|
<br>
|
||||||
<a target="_blank" href="https://community.stackedit.io/">Community</a>
|
StackEdit on <a target="_blank" href="https://twitter.com/stackedit/">Twitter</a>
|
||||||
<div class="modal__info">
|
<div class="modal__info">
|
||||||
For commercial support or custom development, please <a href="mailto:stackedit.project@gmail.com">send us an email</a>.
|
For commercial support or custom development, please <a href="mailto:stackedit.project@gmail.com">send us an email</a>.
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<a target="_blank" href="privacy_policy.html">Privacy Policy</a>
|
<a target="_blank" href="privacy_policy.html">Privacy Policy</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.resolve()">Close</button>
|
<button class="button button--resolve" @click="config.resolve()">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -59,7 +59,7 @@ export default {
|
||||||
|
|
||||||
.logo-background {
|
.logo-background {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
margin: 0.5rem 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
|
|
|
@ -41,6 +41,9 @@
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Status">
|
<form-entry label="Status">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="status" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="status" @keydown.enter="resolve()">
|
||||||
|
<div class="form-entry__info">
|
||||||
|
<b>Example:</b> draft
|
||||||
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Date" info="YYYY-MM-DD">
|
<form-entry label="Date" info="YYYY-MM-DD">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="date" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="date" @keydown.enter="resolve()">
|
||||||
|
@ -54,7 +57,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__error modal__error--file-properties">{{error}}</div>
|
<div class="modal__error modal__error--file-properties">{{error}}</div>
|
||||||
<div class="modal__info">
|
<div class="modal__info modal__info--multiline">
|
||||||
<p><strong>ProTip:</strong> You can manually toggle extensions:</p>
|
<p><strong>ProTip:</strong> You can manually toggle extensions:</p>
|
||||||
<pre class=" language-yaml"><code class="prism language-yaml"><span class="token key atrule">extensions</span><span class="token punctuation">:</span>
|
<pre class=" language-yaml"><code class="prism language-yaml"><span class="token key atrule">extensions</span><span class="token punctuation">:</span>
|
||||||
<span class="token key atrule">emoji</span><span class="token punctuation">:</span>
|
<span class="token key atrule">emoji</span><span class="token punctuation">:</span>
|
||||||
|
@ -75,7 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -223,10 +226,10 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__inner-1--file-properties {
|
.modal__inner-1.modal__inner-1--file-properties {
|
||||||
max-width: 540px;
|
max-width: 520px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__error--file-properties {
|
.modal__error--file-properties {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p>Please choose a template for your <b>HTML export</b>.</p>
|
<p>Please choose a template for your <b>HTML export</b>.</p>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -14,15 +14,15 @@
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button button--copy">Copy to clipboard</button>
|
<button class="button button--copy" v-clipboard="result" @click="info('HTML copied to clipboard!')">Copy</button>
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Clipboard from 'clipboard';
|
import { mapActions } from 'vuex';
|
||||||
import exportSvc from '../../services/exportSvc';
|
import exportSvc from '../../services/exportSvc';
|
||||||
import modalTemplate from './common/modalTemplate';
|
import modalTemplate from './common/modalTemplate';
|
||||||
|
|
||||||
|
@ -37,29 +37,27 @@ export default modalTemplate({
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
this.$watch('selectedTemplate', (selectedTemplate) => {
|
this.$watch('selectedTemplate', (selectedTemplate) => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(async () => {
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
exportSvc.applyTemplate(currentFile.id, this.allTemplates[selectedTemplate])
|
const html = await exportSvc.applyTemplate(
|
||||||
.then((html) => {
|
currentFile.id,
|
||||||
this.result = html;
|
this.allTemplatesById[selectedTemplate],
|
||||||
});
|
);
|
||||||
|
this.result = html;
|
||||||
}, 10);
|
}, 10);
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
this.clipboard = new Clipboard(this.$el.querySelector('.button--copy'), {
|
|
||||||
text: () => this.result,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
this.clipboard.destroy();
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions('notification', [
|
||||||
|
'info',
|
||||||
|
]),
|
||||||
resolve() {
|
resolve() {
|
||||||
const config = this.config;
|
const { config } = this;
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
config.resolve();
|
config.resolve();
|
||||||
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplates[this.selectedTemplate]);
|
exportSvc.exportToDisk(currentFile.id, 'html', this.allTemplatesById[this.selectedTemplate]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="reject()">Cancel</button>
|
<button class="button" @click="reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -36,9 +36,8 @@ export default modalTemplate({
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
googlePhotosTokens() {
|
googlePhotosTokens() {
|
||||||
const googleTokens = this.$store.getters['data/googleTokens'];
|
const googleTokensBySub = this.$store.getters['data/googleTokensBySub'];
|
||||||
return Object.entries(googleTokens)
|
return Object.values(googleTokensBySub)
|
||||||
.map(([, token]) => token)
|
|
||||||
.filter(token => token.isPhotos)
|
.filter(token => token.isPhotos)
|
||||||
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
.sort((token1, token2) => token1.name.localeCompare(token2.name));
|
||||||
},
|
},
|
||||||
|
@ -48,28 +47,30 @@ export default modalTemplate({
|
||||||
if (!this.url) {
|
if (!this.url) {
|
||||||
this.setError('url');
|
this.setError('url');
|
||||||
} else {
|
} else {
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
callback(this.url);
|
callback(this.url);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reject() {
|
reject() {
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.reject();
|
this.config.reject();
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
addGooglePhotosAccount() {
|
addGooglePhotosAccount() {
|
||||||
return googleHelper.addPhotosAccount();
|
return googleHelper.addPhotosAccount();
|
||||||
},
|
},
|
||||||
openGooglePhotos(token) {
|
async openGooglePhotos(token) {
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.reject();
|
this.config.reject();
|
||||||
googleHelper.openPicker(token, 'img')
|
const res = await googleHelper.openPicker(token, 'img');
|
||||||
.then(res => res[0] && this.$store.dispatch('modal/open', {
|
if (res[0]) {
|
||||||
|
this.$store.dispatch('modal/open', {
|
||||||
type: 'googlePhoto',
|
type: 'googlePhoto',
|
||||||
url: res[0].url,
|
url: res[0].url,
|
||||||
callback,
|
callback,
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="reject()">Cancel</button>
|
<button class="button" @click="reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -25,13 +25,13 @@ export default modalTemplate({
|
||||||
if (!this.url) {
|
if (!this.url) {
|
||||||
this.setError('url');
|
this.setError('url');
|
||||||
} else {
|
} else {
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
callback(this.url);
|
callback(this.url);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reject() {
|
reject() {
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.reject();
|
this.config.reject();
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -38,19 +38,20 @@ export default modalTemplate({
|
||||||
selectedFormat: 'pandocExportFormat',
|
selectedFormat: 'pandocExportFormat',
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve() {
|
async resolve() {
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
const currentContent = this.$store.getters['content/current'];
|
const currentContent = this.$store.getters['content/current'];
|
||||||
const selectedFormat = this.selectedFormat;
|
const { selectedFormat } = this;
|
||||||
this.$store.dispatch('queue/enqueue', () => Promise.all([
|
const [sponsorToken, token] = await this.$store.dispatch('queue/enqueue', () => Promise.all([
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
const sponsorToken = this.$store.getters['workspace/sponsorToken'];
|
const tokenToRefresh = this.$store.getters['workspace/sponsorToken'];
|
||||||
return sponsorToken && googleHelper.refreshToken(sponsorToken);
|
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||||
}),
|
}),
|
||||||
sponsorSvc.getToken(),
|
sponsorSvc.getToken(),
|
||||||
])
|
]));
|
||||||
.then(([sponsorToken, token]) => networkSvc.request({
|
try {
|
||||||
|
const { body } = await networkSvc.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'pandocExport',
|
url: 'pandocExport',
|
||||||
params: {
|
params: {
|
||||||
|
@ -63,19 +64,16 @@ export default modalTemplate({
|
||||||
body: JSON.stringify(editorSvc.getPandocAst()),
|
body: JSON.stringify(editorSvc.getPandocAst()),
|
||||||
blob: true,
|
blob: true,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
})
|
});
|
||||||
.then((res) => {
|
FileSaver.saveAs(body, `${currentFile.name}.${selectedFormat}`);
|
||||||
FileSaver.saveAs(res.body, `${currentFile.name}.${selectedFormat}`);
|
} catch (err) {
|
||||||
}, (err) => {
|
if (err.status === 401) {
|
||||||
if (err.status !== 401) {
|
this.$store.dispatch('modal/open', 'sponsorOnly');
|
||||||
throw err;
|
} else {
|
||||||
}
|
|
||||||
this.$store.dispatch('modal/sponsorOnly');
|
|
||||||
}))
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err); // eslint-disable-line no-console
|
console.error(err); // eslint-disable-line no-console
|
||||||
this.$store.dispatch('notification/error', err);
|
this.$store.dispatch('notification/error', err);
|
||||||
}));
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p>Please choose a template for your <b>PDF export</b>.</p>
|
<p>Please choose a template for your <b>PDF export</b>.</p>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select class="textfield" slot="field" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -33,19 +33,24 @@ export default modalTemplate({
|
||||||
selectedTemplate: 'pdfExportTemplate',
|
selectedTemplate: 'pdfExportTemplate',
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve() {
|
async resolve() {
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
const currentFile = this.$store.getters['file/current'];
|
const currentFile = this.$store.getters['file/current'];
|
||||||
this.$store.dispatch('queue/enqueue', () => Promise.all([
|
const [sponsorToken, token, html] = await this.$store
|
||||||
Promise.resolve().then(() => {
|
.dispatch('queue/enqueue', () => Promise.all([
|
||||||
const sponsorToken = this.$store.getters['workspace/sponsorToken'];
|
Promise.resolve().then(() => {
|
||||||
return sponsorToken && googleHelper.refreshToken(sponsorToken);
|
const tokenToRefresh = this.$store.getters['workspace/sponsorToken'];
|
||||||
}),
|
return tokenToRefresh && googleHelper.refreshToken(tokenToRefresh);
|
||||||
sponsorSvc.getToken(),
|
}),
|
||||||
exportSvc.applyTemplate(
|
sponsorSvc.getToken(),
|
||||||
currentFile.id, this.allTemplates[this.selectedTemplate], true),
|
exportSvc.applyTemplate(
|
||||||
])
|
currentFile.id,
|
||||||
.then(([sponsorToken, token, html]) => networkSvc.request({
|
this.allTemplatesById[this.selectedTemplate],
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
try {
|
||||||
|
const { body } = await networkSvc.request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: 'pdfExport',
|
url: 'pdfExport',
|
||||||
params: {
|
params: {
|
||||||
|
@ -56,19 +61,16 @@ export default modalTemplate({
|
||||||
body: html,
|
body: html,
|
||||||
blob: true,
|
blob: true,
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
})
|
});
|
||||||
.then((res) => {
|
FileSaver.saveAs(body, `${currentFile.name}.pdf`);
|
||||||
FileSaver.saveAs(res.body, `${currentFile.name}.pdf`);
|
} catch (err) {
|
||||||
}, (err) => {
|
if (err.status === 401) {
|
||||||
if (err.status !== 401) {
|
this.$store.dispatch('modal/open', 'sponsorOnly');
|
||||||
throw err;
|
} else {
|
||||||
}
|
|
||||||
this.$store.dispatch('modal/sponsorOnly');
|
|
||||||
}))
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err); // eslint-disable-line no-console
|
console.error(err); // eslint-disable-line no-console
|
||||||
this.$store.dispatch('notification/error', err);
|
this.$store.dispatch('notification/error', err);
|
||||||
}));
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,38 +1,53 @@
|
||||||
<template>
|
<template>
|
||||||
<modal-inner class="modal__inner-1--publish-management" aria-label="Manage publication locations">
|
<modal-inner class="modal__inner-1--publish-management" aria-label="Manage publication locations">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
|
<div class="modal__image">
|
||||||
|
<icon-upload></icon-upload>
|
||||||
|
</div>
|
||||||
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p>
|
<p v-if="publishLocations.length"><b>{{currentFileName}}</b> is published to the following location(s):</p>
|
||||||
<p v-else><b>{{currentFileName}}</b> is not published yet.</p>
|
<p v-else><b>{{currentFileName}}</b> is not published yet.</p>
|
||||||
<div>
|
<div>
|
||||||
<div class="publish-entry flex flex--row flex--align-center" v-for="location in publishLocations" :key="location.id">
|
<div class="publish-entry flex flex--column" v-for="location in publishLocations" :key="location.id">
|
||||||
<div class="publish-entry__icon flex flex--column flex--center">
|
<div class="publish-entry__header flex flex--row flex--align-center">
|
||||||
<icon-provider :provider-id="location.providerId"></icon-provider>
|
<div class="publish-entry__icon flex flex--column flex--center">
|
||||||
|
<icon-provider :provider-id="location.providerId"></icon-provider>
|
||||||
|
</div>
|
||||||
|
<div class="publish-entry__description">
|
||||||
|
{{location.description}}
|
||||||
|
</div>
|
||||||
|
<div class="publish-entry__buttons flex flex--row flex--center">
|
||||||
|
<button class="publish-entry__button button" @click="remove(location)" v-title="'Remove location'">
|
||||||
|
<icon-delete></icon-delete>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="publish-entry__description">
|
<div class="publish-entry__row flex flex--row flex--align-center">
|
||||||
{{location.description}}
|
<div class="publish-entry__url">
|
||||||
</div>
|
{{location.url}}
|
||||||
<div class="publish-entry__buttons flex flex--row flex--center">
|
</div>
|
||||||
<a class="publish-entry__button button" :href="location.url" target="_blank">
|
<div class="publish-entry__buttons flex flex--row flex--center" v-if="location.url">
|
||||||
<icon-open-in-new></icon-open-in-new>
|
<button class="publish-entry__button button" v-clipboard="location.url" @click="info('Location URL copied to clipboard!')" v-title="'Copy URL'">
|
||||||
</a>
|
<icon-content-copy></icon-content-copy>
|
||||||
<button class="publish-entry__button button" @click="remove(location)">
|
</button>
|
||||||
<icon-delete></icon-delete>
|
<a class="publish-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'Open location'">
|
||||||
</button>
|
<icon-open-in-new></icon-open-in-new>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__info" v-if="publishLocations.length">
|
<div class="modal__info" v-if="publishLocations.length">
|
||||||
<b>Note:</b> Removing a synchronized location won't delete any file.
|
<b>Tip:</b> Removing a location won't delete any file.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.resolve()">Close</button>
|
<button class="button button--resolve" @click="config.resolve()">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -51,6 +66,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions('notification', [
|
||||||
|
'info',
|
||||||
|
]),
|
||||||
remove(location) {
|
remove(location) {
|
||||||
this.$store.commit('publishLocation/deleteItem', location.id);
|
this.$store.commit('publishLocation/deleteItem', location.id);
|
||||||
},
|
},
|
||||||
|
@ -59,47 +77,73 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__inner-1--publish-management {
|
|
||||||
max-width: 560px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.publish-entry {
|
.publish-entry {
|
||||||
padding: 0.5rem 0.25rem;
|
margin: 1.5em 0;
|
||||||
border-bottom: 1px solid $hr-color;
|
height: auto;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
$button-size: 30px;
|
||||||
border-bottom: none;
|
$small-button-size: 22px;
|
||||||
}
|
|
||||||
|
.publish-entry__header {
|
||||||
|
line-height: $button-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-entry__row {
|
||||||
|
margin-top: 1px;
|
||||||
|
padding-top: 1px;
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, 0.15);
|
||||||
|
line-height: $small-button-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-entry__icon {
|
.publish-entry__icon {
|
||||||
height: 30px;
|
height: 22px;
|
||||||
width: 30px;
|
width: 22px;
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-entry__description {
|
.publish-entry__description {
|
||||||
opacity: 0.5;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 0.9em;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-entry__url {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 0.67em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-entry__buttons {
|
.publish-entry__buttons {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
|
|
||||||
|
.publish-entry__row & {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.publish-entry__button {
|
.publish-entry__button {
|
||||||
width: 38px;
|
width: $button-size;
|
||||||
height: 38px;
|
height: $button-size;
|
||||||
padding: 6px;
|
padding: 4px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
||||||
|
.publish-entry__row & {
|
||||||
|
width: $small-button-size;
|
||||||
|
height: $small-button-size;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="!error && config.resolve(strippedCustomSettings)">Ok</button>
|
<button class="button button--resolve" @click="!error && config.resolve(strippedCustomSettings)">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -36,7 +36,7 @@ import { mapGetters } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
import Tab from './common/Tab';
|
import Tab from './common/Tab';
|
||||||
import CodeEditor from '../CodeEditor';
|
import CodeEditor from '../CodeEditor';
|
||||||
import defaultSettings from '../../data/defaultSettings.yml';
|
import defaultSettings from '../../data/defaults/defaultSettings.yml';
|
||||||
|
|
||||||
const emptySettings = `# Add your custom settings here to override the
|
const emptySettings = `# Add your custom settings here to override the
|
||||||
# default settings.
|
# default settings.
|
||||||
|
@ -81,10 +81,10 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__inner-1--settings {
|
.modal__inner-1.modal__inner-1--settings {
|
||||||
max-width: 600px;
|
max-width: 560px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__error--settings {
|
.modal__error--settings {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<modal-inner class="modal__inner-1--sponsor" aria-label="Sponsor">
|
<modal-inner class="modal__inner-1--sponsor" aria-label="Sponsor">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<p>Please choose a <b>PayPal</b> option.</p>
|
<p>Please choose a <b>PayPal</b> option:</p>
|
||||||
<a class="paypal-option button flex flex--row flex--center" v-for="button in buttons" :key="button.id" :href="button.link">
|
<a class="paypal-option button flex flex--row flex--center" v-for="button in buttons" :key="button.id" :href="button.link">
|
||||||
<div class="flex flex--column">
|
<div class="flex flex--column">
|
||||||
<div>{{button.price}}<div class="paypal-option__offer" v-if="button.offer">{{button.offer}}</div></div>
|
<div>{{button.price}}<div class="paypal-option__offer" v-if="button.offer">{{button.offer}}</div></div>
|
||||||
|
@ -63,10 +63,10 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__inner-1--sponsor {
|
.modal__inner-1.modal__inner-1--sponsor {
|
||||||
max-width: 380px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paypal-option {
|
.paypal-option {
|
||||||
|
@ -81,7 +81,7 @@ export default {
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
opacity: 0.5;
|
opacity: 0.6;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,53 @@
|
||||||
<template>
|
<template>
|
||||||
<modal-inner class="modal__inner-1--sync-management" aria-label="Manage synchronized locations">
|
<modal-inner class="modal__inner-1--sync-management" aria-label="Manage synchronized locations">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
|
<div class="modal__image">
|
||||||
|
<icon-sync></icon-sync>
|
||||||
|
</div>
|
||||||
<p v-if="syncLocations.length"><b>{{currentFileName}}</b> is synchronized with the following location(s):</p>
|
<p v-if="syncLocations.length"><b>{{currentFileName}}</b> is synchronized with the following location(s):</p>
|
||||||
<p v-else><b>{{currentFileName}}</b> is not synchronized yet.</p>
|
<p v-else><b>{{currentFileName}}</b> is not synchronized yet.</p>
|
||||||
<div>
|
<div>
|
||||||
<div class="sync-entry flex flex--row flex--align-center" v-for="location in syncLocations" :key="location.id">
|
<div class="sync-entry flex flex--column" v-for="location in syncLocations" :key="location.id">
|
||||||
<div class="sync-entry__icon flex flex--column flex--center">
|
<div class="sync-entry__header flex flex--row flex--align-center">
|
||||||
<icon-provider :provider-id="location.providerId"></icon-provider>
|
<div class="sync-entry__icon flex flex--column flex--center">
|
||||||
|
<icon-provider :provider-id="location.providerId"></icon-provider>
|
||||||
|
</div>
|
||||||
|
<div class="sync-entry__description">
|
||||||
|
{{location.description}}
|
||||||
|
</div>
|
||||||
|
<div class="sync-entry__buttons flex flex--row flex--center">
|
||||||
|
<button class="sync-entry__button button" @click="remove(location)" v-title="'Remove location'">
|
||||||
|
<icon-delete></icon-delete>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sync-entry__description">
|
<div class="sync-entry__row flex flex--row flex--align-center">
|
||||||
{{location.description}}
|
<div class="sync-entry__url">
|
||||||
</div>
|
{{location.url || 'Google Drive app data'}}
|
||||||
<div class="sync-entry__buttons flex flex--row flex--center">
|
</div>
|
||||||
<a class="sync-entry__button button" :href="location.url" target="_blank">
|
<div class="sync-entry__buttons flex flex--row flex--center" v-if="location.url">
|
||||||
<icon-open-in-new></icon-open-in-new>
|
<button class="sync-entry__button button" v-clipboard="location.url" @click="info('Location URL copied to clipboard!')" v-title="'Copy URL'">
|
||||||
</a>
|
<icon-content-copy></icon-content-copy>
|
||||||
<button class="sync-entry__button button" @click="remove(location)">
|
</button>
|
||||||
<icon-delete></icon-delete>
|
<a class="sync-entry__button button" v-if="location.url" :href="location.url" target="_blank" v-title="'Open location'">
|
||||||
</button>
|
<icon-open-in-new></icon-open-in-new>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__info" v-if="syncLocations.length">
|
<div class="modal__info" v-if="syncLocations.length">
|
||||||
<b>Note:</b> Removing a synchronized location won't delete any file.
|
<b>Tip:</b> Removing a location won't delete any file.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.resolve()">Close</button>
|
<button class="button button--resolve" @click="config.resolve()">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -44,66 +59,100 @@ export default {
|
||||||
'config',
|
'config',
|
||||||
]),
|
]),
|
||||||
...mapGetters('syncLocation', {
|
...mapGetters('syncLocation', {
|
||||||
syncLocations: 'current',
|
syncLocations: 'currentWithWorkspaceSyncLocation',
|
||||||
}),
|
}),
|
||||||
currentFileName() {
|
currentFileName() {
|
||||||
return this.$store.getters['file/current'].name;
|
return this.$store.getters['file/current'].name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions('notification', [
|
||||||
|
'info',
|
||||||
|
]),
|
||||||
remove(location) {
|
remove(location) {
|
||||||
this.$store.commit('syncLocation/deleteItem', location.id);
|
if (location.id === 'main') {
|
||||||
|
this.info('This location can not be removed.');
|
||||||
|
} else {
|
||||||
|
this.$store.commit('syncLocation/deleteItem', location.id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__inner-1--sync-management {
|
|
||||||
max-width: 560px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sync-entry {
|
.sync-entry {
|
||||||
padding: 0.5rem 0.25rem;
|
margin: 1.5em 0;
|
||||||
border-bottom: 1px solid $hr-color;
|
height: auto;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
$button-size: 30px;
|
||||||
border-bottom: none;
|
$small-button-size: 22px;
|
||||||
}
|
|
||||||
|
.sync-entry__header {
|
||||||
|
line-height: $button-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-entry__row {
|
||||||
|
margin-top: 1px;
|
||||||
|
padding-top: 1px;
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, 0.15);
|
||||||
|
line-height: $small-button-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-entry__icon {
|
.sync-entry__icon {
|
||||||
height: 30px;
|
height: 22px;
|
||||||
width: 30px;
|
width: 22px;
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-entry__description {
|
.sync-entry__description {
|
||||||
opacity: 0.5;
|
|
||||||
line-height: 1.4;
|
|
||||||
font-size: 0.9em;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-entry__url {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 0.67em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-entry__buttons {
|
.sync-entry__buttons {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
|
|
||||||
|
.sync-entry__row & {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-entry__button {
|
.sync-entry__button {
|
||||||
width: 38px;
|
width: $button-size;
|
||||||
height: 38px;
|
height: $button-size;
|
||||||
padding: 6px;
|
padding: 4px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
||||||
|
.sync-entry__row & {
|
||||||
|
width: $small-button-size;
|
||||||
|
height: $small-button-size;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -55,8 +55,8 @@ import { mapGetters } from 'vuex';
|
||||||
import utils from '../../services/utils';
|
import utils from '../../services/utils';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
import CodeEditor from '../CodeEditor';
|
import CodeEditor from '../CodeEditor';
|
||||||
import emptyTemplateValue from '../../data/emptyTemplateValue.html';
|
import emptyTemplateValue from '../../data/empties/emptyTemplateValue.html';
|
||||||
import emptyTemplateHelpers from '!raw-loader!../../data/emptyTemplateHelpers.js'; // eslint-disable-line
|
import emptyTemplateHelpers from '!raw-loader!../../data/empties/emptyTemplateHelpers.js'; // eslint-disable-line
|
||||||
|
|
||||||
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
|
||||||
|
|
||||||
|
@ -91,11 +91,11 @@ export default {
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.$store.getters['data/allTemplates'],
|
() => this.$store.getters['data/allTemplatesById'],
|
||||||
(allTemplates) => {
|
(allTemplatesById) => {
|
||||||
const templates = {};
|
const templates = {};
|
||||||
// Sort templates by name
|
// Sort templates by name
|
||||||
Object.entries(allTemplates)
|
Object.entries(allTemplatesById)
|
||||||
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
||||||
.forEach(([id, template]) => {
|
.forEach(([id, template]) => {
|
||||||
const templateClone = utils.deepCopy(template);
|
const templateClone = utils.deepCopy(template);
|
||||||
|
@ -105,10 +105,12 @@ export default {
|
||||||
this.templates = templates;
|
this.templates = templates;
|
||||||
this.selectedId = this.config.selectedId;
|
this.selectedId = this.config.selectedId;
|
||||||
if (!templates[this.selectedId]) {
|
if (!templates[this.selectedId]) {
|
||||||
this.selectedId = Object.keys(templates)[0];
|
[this.selectedId] = Object.keys(templates);
|
||||||
}
|
}
|
||||||
this.isEditing = false;
|
this.isEditing = false;
|
||||||
}, { immediate: true });
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
this.$watch('selectedId', (selectedId) => {
|
this.$watch('selectedId', (selectedId) => {
|
||||||
const template = this.templates[selectedId];
|
const template = this.templates[selectedId];
|
||||||
this.showHelpers = template.helpers !== emptyTemplateHelpers;
|
this.showHelpers = template.helpers !== emptyTemplateHelpers;
|
||||||
|
@ -137,7 +139,7 @@ export default {
|
||||||
},
|
},
|
||||||
remove() {
|
remove() {
|
||||||
delete this.templates[this.selectedId];
|
delete this.templates[this.selectedId];
|
||||||
this.selectedId = Object.keys(this.templates)[0];
|
[this.selectedId] = Object.keys(this.templates);
|
||||||
},
|
},
|
||||||
submitEdit(cancel) {
|
submitEdit(cancel) {
|
||||||
const template = this.templates[this.selectedId];
|
const template = this.templates[this.selectedId];
|
||||||
|
@ -161,7 +163,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.modal__inner-1--templates {
|
.modal__inner-1.modal__inner-1--templates {
|
||||||
max-width: 680px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,39 +1,69 @@
|
||||||
<template>
|
<template>
|
||||||
<modal-inner class="modal__inner-1--workspace-management" aria-label="Manage workspaces">
|
<modal-inner class="modal__inner-1--workspace-management" aria-label="Manage workspaces">
|
||||||
<div class="modal__content">
|
<div class="modal__content">
|
||||||
<div class="workspace-entry flex flex--row flex--align-center" v-for="(workspace, id) in sanitizedWorkspaces" :key="id">
|
<div class="modal__image">
|
||||||
<div class="workspace-entry__icon flex flex--column flex--center">
|
<icon-database></icon-database>
|
||||||
<icon-provider :provider-id="workspace.providerId"></icon-provider>
|
</div>
|
||||||
</div>
|
<p>The following workspaces are locally available:</p>
|
||||||
<div class="workspace-entry__description flex flex--column">
|
<div class="workspace-entry flex flex--column" v-for="(workspace, id) in workspacesById" :key="id">
|
||||||
<input class="text-input" type="text" v-if="editedId === id" v-focus @blur="submitEdit()" @keydown.enter="submitEdit()" @keydown.esc.stop="submitEdit(true)" v-model="editingName">
|
<div class="flex flex--column">
|
||||||
<div class="workspace-entry__name" v-else>
|
<div class="workspace-entry__header flex flex--row flex--align-center">
|
||||||
{{workspace.name}}
|
<div class="workspace-entry__icon">
|
||||||
|
<icon-provider :provider-id="workspace.providerId"></icon-provider>
|
||||||
|
</div>
|
||||||
|
<input class="text-input" type="text" v-if="editedId === id" v-focus @blur="submitEdit()" @keydown.enter="submitEdit()" @keydown.esc.stop="submitEdit(true)" v-model="editingName">
|
||||||
|
<div class="workspace-entry__name" v-else>{{workspace.name}}</div>
|
||||||
|
<div class="workspace-entry__buttons flex flex--row">
|
||||||
|
<button class="workspace-entry__button button" @click="edit(id)" v-title="'Edit name'">
|
||||||
|
<icon-pen></icon-pen>
|
||||||
|
</button>
|
||||||
|
<button class="workspace-entry__button button" @click="remove(id)" v-title="'Remove'">
|
||||||
|
<icon-delete></icon-delete>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-entry__url">
|
<div class="workspace-entry__row flex flex--row flex--align-center">
|
||||||
{{workspace.url}}
|
<div class="workspace-entry__url">
|
||||||
|
{{workspace.url}}
|
||||||
|
</div>
|
||||||
|
<div class="workspace-entry__buttons flex flex--row">
|
||||||
|
<button class="workspace-entry__button button" v-clipboard="workspace.url" @click="info('Workspace URL copied to clipboard!')" v-title="'Copy URL'">
|
||||||
|
<icon-content-copy></icon-content-copy>
|
||||||
|
</button>
|
||||||
|
<a class="workspace-entry__button button" :href="workspace.url" target="_blank" v-title="'Open workspace'">
|
||||||
|
<icon-open-in-new></icon-open-in-new>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="workspace-entry__row flex flex--row flex--align-center" v-if="workspace.locationUrl">
|
||||||
|
<div class="workspace-entry__url">
|
||||||
|
{{workspace.locationUrl}}
|
||||||
|
</div>
|
||||||
|
<div class="workspace-entry__buttons flex flex--row">
|
||||||
|
<button class="workspace-entry__button button" v-clipboard="workspace.locationUrl" @click="info('Workspace URL copied to clipboard!')" v-title="'Copy URL'">
|
||||||
|
<icon-content-copy></icon-content-copy>
|
||||||
|
</button>
|
||||||
|
<a class="workspace-entry__button button" :href="workspace.locationUrl" target="_blank" v-title="'Open workspace location'">
|
||||||
|
<icon-open-in-new></icon-open-in-new>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-entry__buttons flex flex--row flex--center">
|
</div>
|
||||||
<button class="workspace-entry__button button" @click="edit(id)">
|
<div class="modal__info">
|
||||||
<icon-pen></icon-pen>
|
<b>ProTip:</b> A workspace is accessible <b>offline</b> once it has been opened for the first time.
|
||||||
</button>
|
|
||||||
<button class="workspace-entry__button button" v-if="id !== currentWorkspace.id && id !== mainWorkspace.id" @click="remove(id)">
|
|
||||||
<icon-delete></icon-delete>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.resolve()">Close</button>
|
<button class="button button--resolve" @click="config.resolve()">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import ModalInner from './common/ModalInner';
|
import ModalInner from './common/ModalInner';
|
||||||
import localDbSvc from '../../services/localDbSvc';
|
import workspaceSvc from '../../services/workspaceSvc';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -47,105 +77,128 @@ export default {
|
||||||
...mapGetters('modal', [
|
...mapGetters('modal', [
|
||||||
'config',
|
'config',
|
||||||
]),
|
]),
|
||||||
...mapGetters('data', [
|
|
||||||
'workspaces',
|
|
||||||
'sanitizedWorkspaces',
|
|
||||||
]),
|
|
||||||
...mapGetters('workspace', [
|
...mapGetters('workspace', [
|
||||||
|
'workspacesById',
|
||||||
'mainWorkspace',
|
'mainWorkspace',
|
||||||
'currentWorkspace',
|
'currentWorkspace',
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions('notification', [
|
||||||
|
'info',
|
||||||
|
]),
|
||||||
edit(id) {
|
edit(id) {
|
||||||
this.editedId = id;
|
this.editedId = id;
|
||||||
this.editingName = this.workspaces[id].name;
|
this.editingName = this.workspacesById[id].name;
|
||||||
},
|
},
|
||||||
submitEdit(cancel) {
|
submitEdit(cancel) {
|
||||||
const workspace = this.workspaces[this.editedId];
|
const workspace = this.workspacesById[this.editedId];
|
||||||
if (workspace && !cancel && this.editingName) {
|
if (workspace) {
|
||||||
this.$store.dispatch('data/patchWorkspaces', {
|
if (!cancel && this.editingName) {
|
||||||
[this.editedId]: {
|
this.$store.dispatch('workspace/patchWorkspacesById', {
|
||||||
...workspace,
|
[this.editedId]: {
|
||||||
name: this.editingName,
|
...workspace,
|
||||||
},
|
name: this.editingName,
|
||||||
});
|
},
|
||||||
} else {
|
});
|
||||||
this.editingName = workspace.name;
|
} else {
|
||||||
|
this.editingName = workspace.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.editedId = null;
|
this.editedId = null;
|
||||||
},
|
},
|
||||||
remove(id) {
|
async remove(id) {
|
||||||
return this.$store.dispatch('modal/removeWorkspace')
|
if (id === this.mainWorkspace.id) {
|
||||||
.then(
|
this.info('Your main workspace can not be removed.');
|
||||||
() => localDbSvc.removeWorkspace(id),
|
} else if (id === this.currentWorkspace.id) {
|
||||||
() => {}, // Cancel
|
this.info('Please close the workspace before removing it.');
|
||||||
);
|
} else {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('modal/open', 'removeWorkspace');
|
||||||
|
workspaceSvc.removeWorkspace(id);
|
||||||
|
} catch (e) { /* Cancel */ }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../common/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__inner-1--workspace-management {
|
|
||||||
max-width: 560px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-entry {
|
.workspace-entry {
|
||||||
text-align: left;
|
margin: 1.75em 0;
|
||||||
padding-left: 10px;
|
|
||||||
margin: 15px 0;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
text-transform: none;
|
}
|
||||||
|
|
||||||
&:last-child {
|
$button-size: 30px;
|
||||||
border-bottom: none;
|
$small-button-size: 22px;
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
.workspace-entry__header {
|
||||||
text-overflow: ellipsis;
|
line-height: $button-size;
|
||||||
overflow: hidden;
|
|
||||||
|
.text-input {
|
||||||
|
border: 1px solid $link-color;
|
||||||
|
padding: 0 5px;
|
||||||
|
line-height: $button-size;
|
||||||
|
height: $button-size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-entry__row {
|
||||||
|
margin-top: 1px;
|
||||||
|
padding-top: 1px;
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, 0.15);
|
||||||
|
line-height: $small-button-size;
|
||||||
|
}
|
||||||
|
|
||||||
.workspace-entry__icon {
|
.workspace-entry__icon {
|
||||||
height: 20px;
|
height: 22px;
|
||||||
width: 20px;
|
width: 22px;
|
||||||
margin-right: 12px;
|
margin-right: 0.75rem;
|
||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-entry__description {
|
|
||||||
width: 100%;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-entry__name {
|
.workspace-entry__name {
|
||||||
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-entry__url {
|
.workspace-entry__url {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
font-size: 0.75em;
|
font-size: 0.67em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-entry__buttons {
|
.workspace-entry__buttons {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
|
|
||||||
|
.workspace-entry__row & {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-entry__button {
|
.workspace-entry__button {
|
||||||
width: 36px;
|
width: $button-size;
|
||||||
height: 36px;
|
height: $button-size;
|
||||||
padding: 6px;
|
padding: 4px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
||||||
|
.workspace-entry__row & {
|
||||||
|
width: $small-button-size;
|
||||||
|
height: $small-button-size;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<icon-close></icon-close>
|
<icon-close></icon-close>
|
||||||
</button>
|
</button>
|
||||||
<div class="modal__sponsor-button" v-if="showSponsorButton">
|
<div class="modal__sponsor-button" v-if="showSponsorButton">
|
||||||
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>. Please consider
|
StackEdit is <a class="not-tabbable" target="_blank" href="https://github.com/benweet/stackedit/">open source</a>, please consider
|
||||||
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
<a class="not-tabbable" href="javascript:void(0)" @click="sponsor">sponsoring</a> for just $5.
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -24,40 +24,38 @@ export default {
|
||||||
'config',
|
'config',
|
||||||
]),
|
]),
|
||||||
showSponsorButton() {
|
showSponsorButton() {
|
||||||
const type = this.$store.getters['modal/config'].type;
|
const { type } = this.$store.getters['modal/config'];
|
||||||
return !this.$store.getters.isSponsor && type !== 'sponsor' && type !== 'signInForSponsorship';
|
return !this.$store.getters.isSponsor && type !== 'sponsor' && type !== 'signInForSponsorship';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sponsor() {
|
async sponsor() {
|
||||||
Promise.resolve()
|
try {
|
||||||
.then(() => !this.$store.getters['workspace/sponsorToken'] &&
|
if (!this.$store.getters['workspace/sponsorToken']) {
|
||||||
// If user has to sign in
|
// User has to sign in
|
||||||
this.$store.dispatch('modal/signInForSponsorship', {
|
await this.$store.dispatch('modal/open', 'signInForSponsorship');
|
||||||
onResolve: () => googleHelper.signin()
|
await googleHelper.signin();
|
||||||
.then(() => syncSvc.requestSync()),
|
syncSvc.requestSync();
|
||||||
}))
|
}
|
||||||
.then(() => {
|
if (!this.$store.getters.isSponsor) {
|
||||||
if (!this.$store.getters.isSponsor) {
|
await this.$store.dispatch('modal/open', 'sponsor');
|
||||||
this.$store.dispatch('modal/open', 'sponsor');
|
}
|
||||||
}
|
} catch (e) { /* cancel */ }
|
||||||
})
|
|
||||||
.catch(() => {}); // Cancel
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../common/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
|
||||||
.modal__close-button {
|
.modal__close-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: rgba(0, 0, 0, 0.5);
|
||||||
width: 30px;
|
width: 32px;
|
||||||
height: 30px;
|
height: 32px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
|
|
|
@ -52,28 +52,26 @@ export default (desc) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (key === 'selectedTemplate') {
|
if (key === 'selectedTemplate') {
|
||||||
component.computed.allTemplates = () => {
|
component.computed.allTemplatesById = () => {
|
||||||
const allTemplates = store.getters['data/allTemplates'];
|
const allTemplatesById = store.getters['data/allTemplatesById'];
|
||||||
const sortedTemplates = {};
|
const sortedTemplatesById = {};
|
||||||
Object.entries(allTemplates)
|
Object.entries(allTemplatesById)
|
||||||
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
.sort(([, template1], [, template2]) => collator.compare(template1.name, template2.name))
|
||||||
.forEach(([templateId, template]) => {
|
.forEach(([templateId, template]) => {
|
||||||
sortedTemplates[templateId] = template;
|
sortedTemplatesById[templateId] = template;
|
||||||
});
|
});
|
||||||
return sortedTemplates;
|
return sortedTemplatesById;
|
||||||
};
|
};
|
||||||
// Make use of `function` to have `this` bound to the component
|
// Make use of `function` to have `this` bound to the component
|
||||||
component.methods.configureTemplates = function () { // eslint-disable-line func-names
|
component.methods.configureTemplates = async function () { // eslint-disable-line func-names
|
||||||
store.dispatch('modal/open', {
|
const { templates, selectedId } = await store.dispatch('modal/open', {
|
||||||
type: 'templates',
|
type: 'templates',
|
||||||
selectedId: this.selectedTemplate,
|
selectedId: this.selectedTemplate,
|
||||||
})
|
});
|
||||||
.then(({ templates, selectedId }) => {
|
store.dispatch('data/setTemplatesById', templates);
|
||||||
store.dispatch('data/setTemplates', templates);
|
store.dispatch('data/patchLocalSettings', {
|
||||||
store.dispatch('data/patchLocalSettings', {
|
[id]: selectedId,
|
||||||
[id]: selectedId,
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="bloggerPage"></icon-provider>
|
<icon-provider provider-id="bloggerPage"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Blogger Page</b>.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>Blogger Page</b>.</p>
|
||||||
<form-entry label="Blog URL" error="blogUrl">
|
<form-entry label="Blog URL" error="blogUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="blogUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="blogUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -54,7 +54,10 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = bloggerPageProvider.makeLocation(
|
const location = bloggerPageProvider.makeLocation(
|
||||||
this.config.token, this.blogUrl, this.pageId);
|
this.config.token,
|
||||||
|
this.blogUrl,
|
||||||
|
this.pageId,
|
||||||
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="blogger"></icon-provider>
|
<icon-provider provider-id="blogger"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Blogger</b> site.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>Blogger</b> site.</p>
|
||||||
<form-entry label="Blog URL" error="blogUrl">
|
<form-entry label="Blog URL" error="blogUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="blogUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="blogUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -55,7 +55,10 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = bloggerProvider.makeLocation(
|
const location = bloggerProvider.makeLocation(
|
||||||
this.config.token, this.blogUrl, this.postId);
|
this.config.token,
|
||||||
|
this.blogUrl,
|
||||||
|
this.postId,
|
||||||
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,7 +45,7 @@ export default modalTemplate({
|
||||||
name: this.name,
|
name: this.name,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
};
|
};
|
||||||
this.$store.dispatch('data/setCouchdbToken', token);
|
this.$store.dispatch('data/addCouchdbToken', token);
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,20 +4,20 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="couchdb"></icon-provider>
|
<icon-provider provider-id="couchdb"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will create a workspace synchronized with a <b>CouchDB</b> database.</p>
|
<p>Create a workspace synced with a <b>CouchDB</b> database.</p>
|
||||||
<form-entry label="Database URL" error="dbUrl">
|
<form-entry label="Database URL" error="dbUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="dbUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="dbUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> https://instance.smileupps.com/stackedit-workspace
|
<b>Example:</b> https://instance.smileupps.com/stackedit-workspace
|
||||||
</div>
|
</div>
|
||||||
<div class="form-entry__actions">
|
<div class="form-entry__actions">
|
||||||
<a href="https://community.stackedit.io/t/couchdb-workspace-setup/" target="_blank">More info</a>
|
<a href="https://community.stackedit.io/t/couchdb-workspace-setup/" target="_blank">How to setup?</a>
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="dropbox"></icon-provider>
|
<icon-provider provider-id="dropbox"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>Dropbox</b> account to <b>StackEdit</b>.</p>
|
<p>Link your <b>Dropbox</b> account to <b>StackEdit</b>.</p>
|
||||||
<div class="form-entry">
|
<div class="form-entry">
|
||||||
<div class="form-entry__checkbox">
|
<div class="form-entry__checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="config.resolve()">Ok</button>
|
<button class="button button--resolve" @click="config.resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,17 +4,17 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="dropbox"></icon-provider>
|
<icon-provider provider-id="dropbox"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Dropbox</b>.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>Dropbox</b>.</p>
|
||||||
<form-entry label="File path" error="path">
|
<form-entry label="File path" error="path">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> {{config.token.fullAccess ? '' : '/Applications/StackEdit (restricted)'}}/path/to/My Document.html<br>
|
<b>Example:</b> {{config.token.fullAccess ? '' : '/Applications/StackEdit (restricted)'}}/path/to/My Document.html<br>
|
||||||
If the file exists, it will be replaced.
|
If the file exists, it will be overwritten.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,18 +4,18 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="dropbox"></icon-provider>
|
<icon-provider provider-id="dropbox"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will save <b>{{currentFileName}}</b> to your <b>Dropbox</b> and keep it synchronized.</p>
|
<p>Save <b>{{currentFileName}}</b> to your <b>Dropbox</b> and keep it synced.</p>
|
||||||
<form-entry label="File path" error="path">
|
<form-entry label="File path" error="path">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> {{config.token.fullAccess ? '' : '/Applications/StackEdit (restricted)'}}/path/to/My Document.md<br>
|
<b>Example:</b> {{config.token.fullAccess ? '' : '/Applications/StackEdit (restricted)'}}/path/to/My Document.md<br>
|
||||||
If the file exists, it will be replaced.
|
If the file exists, it will be overwritten.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="gist"></icon-provider>
|
<icon-provider provider-id="gist"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to a <b>Gist</b>.</p>
|
<p>Publish <b>{{currentFileName}}</b> to a <b>Gist</b>.</p>
|
||||||
<form-entry label="Filename" error="filename">
|
<form-entry label="Filename" error="filename">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
||||||
</form-entry>
|
</form-entry>
|
||||||
|
@ -18,12 +18,12 @@
|
||||||
<form-entry label="Existing Gist ID" info="optional">
|
<form-entry label="Existing Gist ID" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
If the file exists in the Gist, it will be replaced.
|
If the file exists in the Gist, it will be overwritten.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -65,7 +65,11 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = gistProvider.makeLocation(
|
const location = gistProvider.makeLocation(
|
||||||
this.config.token, this.filename, this.isPublic, this.gistId);
|
this.config.token,
|
||||||
|
this.filename,
|
||||||
|
this.isPublic,
|
||||||
|
this.gistId,
|
||||||
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="gist"></icon-provider>
|
<icon-provider provider-id="gist"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will save <b>{{currentFileName}}</b> to a <b>Gist</b> and keep it synchronized.</p>
|
<p>Save <b>{{currentFileName}}</b> to a <b>Gist</b> and keep it synced.</p>
|
||||||
<form-entry label="Filename" error="filename">
|
<form-entry label="Filename" error="filename">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="filename" @keydown.enter="resolve()">
|
||||||
</form-entry>
|
</form-entry>
|
||||||
|
@ -18,13 +18,13 @@
|
||||||
<form-entry label="Existing Gist ID" info="optional">
|
<form-entry label="Existing Gist ID" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="gistId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
If the file exists in the Gist, it will be replaced.
|
If the file exists in the Gist, it will be overwritten.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -51,7 +51,11 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = gistProvider.makeLocation(
|
const location = gistProvider.makeLocation(
|
||||||
this.config.token, this.filename, this.isPublic, this.gistId);
|
this.config.token,
|
||||||
|
this.filename,
|
||||||
|
this.isPublic,
|
||||||
|
this.gistId,
|
||||||
|
);
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,18 +4,18 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="github"></icon-provider>
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>GitHub</b> account to <b>StackEdit</b>.</p>
|
<p>Link your <b>GitHub</b> account to <b>StackEdit</b>.</p>
|
||||||
<div class="form-entry">
|
<div class="form-entry">
|
||||||
<div class="form-entry__checkbox">
|
<div class="form-entry__checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" v-model="repoFullAccess"> Grant access to my <b>private repositories</b>
|
<input type="checkbox" v-model="repoFullAccess"> Grant access to your private repositories
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="config.resolve()">Ok</button>
|
<button class="button button--resolve" @click="config.resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,29 +4,29 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="github"></icon-provider>
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will open a file from your <b>GitHub</b> repository and keep it synchronized.</p>
|
<p>Open a file from your <b>GitHub</b> repository and keep it synced.</p>
|
||||||
<form-entry label="Repository URL" error="repoUrl">
|
<form-entry label="Repository URL" error="repoUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> https://github.com/benweet/stackedit
|
<b>Example:</b> https://github.com/benweet/stackedit
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Branch" info="optional">
|
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
|
||||||
<div class="form-entry__info">
|
|
||||||
If not provided, the <code>master</code> branch will be used.
|
|
||||||
</div>
|
|
||||||
</form-entry>
|
|
||||||
<form-entry label="File path" error="path">
|
<form-entry label="File path" error="path">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> docs/README.md
|
<b>Example:</b> path/to/README.md
|
||||||
|
</div>
|
||||||
|
</form-entry>
|
||||||
|
<form-entry label="Branch" info="optional">
|
||||||
|
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||||
|
<div class="form-entry__info">
|
||||||
|
If not supplied, the <code>master</code> branch will be used.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
<script>
|
<script>
|
||||||
import githubProvider from '../../../services/providers/githubProvider';
|
import githubProvider from '../../../services/providers/githubProvider';
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import utils from '../../../services/utils';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
@ -52,13 +53,18 @@ export default modalTemplate({
|
||||||
this.setError('path');
|
this.setError('path');
|
||||||
}
|
}
|
||||||
if (this.repoUrl && this.path) {
|
if (this.repoUrl && this.path) {
|
||||||
const parsedRepo = githubProvider.parseRepoUrl(this.repoUrl);
|
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||||
if (!parsedRepo) {
|
if (!parsedRepo) {
|
||||||
this.setError('repoUrl');
|
this.setError('repoUrl');
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = githubProvider.makeLocation(
|
const location = githubProvider.makeLocation(
|
||||||
this.config.token, parsedRepo.owner, parsedRepo.repo, this.branch || 'master', this.path);
|
this.config.token,
|
||||||
|
parsedRepo.owner,
|
||||||
|
parsedRepo.repo,
|
||||||
|
this.branch || 'master',
|
||||||
|
this.path,
|
||||||
|
);
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,29 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="github"></icon-provider>
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>GitHub</b> repository.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>GitHub</b> repository.</p>
|
||||||
<form-entry label="Repository URL" error="repoUrl">
|
<form-entry label="Repository URL" error="repoUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> https://github.com/benweet/stackedit
|
<b>Example:</b> https://github.com/benweet/stackedit
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Branch" info="optional">
|
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
|
||||||
<div class="form-entry__info">
|
|
||||||
If not provided, the master branch will be used.
|
|
||||||
</div>
|
|
||||||
</form-entry>
|
|
||||||
<form-entry label="File path" error="path">
|
<form-entry label="File path" error="path">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> docs/README.md<br>
|
<b>Example:</b> path/to/README.md<br>
|
||||||
If the file exists, it will be replaced.
|
If the file exists, it will be overwritten.
|
||||||
|
</div>
|
||||||
|
</form-entry>
|
||||||
|
<form-entry label="Branch" info="optional">
|
||||||
|
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||||
|
<div class="form-entry__info">
|
||||||
|
If not supplied, the <code>master</code> branch will be used.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -73,7 +73,12 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = githubProvider.makeLocation(
|
const location = githubProvider.makeLocation(
|
||||||
this.config.token, parsedRepo[1], parsedRepo[2], this.branch || 'master', this.path);
|
this.config.token,
|
||||||
|
parsedRepo[1],
|
||||||
|
parsedRepo[2],
|
||||||
|
this.branch || 'master',
|
||||||
|
this.path,
|
||||||
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="github"></icon-provider>
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will save <b>{{currentFileName}}</b> to your <b>GitHub</b> repository and keep it synchronized.</p>
|
<p>Save <b>{{currentFileName}}</b> to your <b>GitHub</b> repository and keep it synced.</p>
|
||||||
<form-entry label="Repository URL" error="repoUrl">
|
<form-entry label="Repository URL" error="repoUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -14,20 +14,20 @@
|
||||||
<form-entry label="Branch" info="optional">
|
<form-entry label="Branch" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
If not provided, the <code>master</code> branch will be used.
|
If not supplied, the <code>master</code> branch will be used.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="File path" error="path">
|
<form-entry label="File path" error="path">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> docs/README.md<br>
|
<b>Example:</b> path/to/README.md<br>
|
||||||
If the file exists, it will be replaced.
|
If the file exists, it will be overwritten.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
<script>
|
<script>
|
||||||
import githubProvider from '../../../services/providers/githubProvider';
|
import githubProvider from '../../../services/providers/githubProvider';
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import utils from '../../../services/utils';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
@ -49,22 +50,22 @@ export default modalTemplate({
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve() {
|
resolve() {
|
||||||
if (!this.repoUrl) {
|
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||||
|
if (!parsedRepo) {
|
||||||
this.setError('repoUrl');
|
this.setError('repoUrl');
|
||||||
}
|
}
|
||||||
if (!this.path) {
|
if (!this.path) {
|
||||||
this.setError('path');
|
this.setError('path');
|
||||||
}
|
}
|
||||||
if (this.repoUrl && this.path) {
|
if (parsedRepo && this.path) {
|
||||||
const parsedRepo = githubProvider.parseRepoUrl(this.repoUrl);
|
const location = githubProvider.makeLocation(
|
||||||
if (!parsedRepo) {
|
this.config.token,
|
||||||
this.setError('repoUrl');
|
parsedRepo.owner,
|
||||||
} else {
|
parsedRepo.repo,
|
||||||
// Return new location
|
this.branch || 'master',
|
||||||
const location = githubProvider.makeLocation(
|
this.path,
|
||||||
this.config.token, parsedRepo.owner, parsedRepo.repo, this.branch || 'master', this.path);
|
);
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
65
src/components/modals/providers/GithubWorkspaceModal.vue
Normal file
65
src/components/modals/providers/GithubWorkspaceModal.vue
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<modal-inner aria-label="Synchronize with GitHub">
|
||||||
|
<div class="modal__content">
|
||||||
|
<div class="modal__image">
|
||||||
|
<icon-provider provider-id="github"></icon-provider>
|
||||||
|
</div>
|
||||||
|
<p>Create a workspace synced with a <b>GitHub</b> repository folder.</p>
|
||||||
|
<form-entry label="Repository URL" error="repoUrl">
|
||||||
|
<input slot="field" class="textfield" type="text" v-model.trim="repoUrl" @keydown.enter="resolve()">
|
||||||
|
<div class="form-entry__info">
|
||||||
|
<b>Example:</b> https://github.com/benweet/stackedit
|
||||||
|
</div>
|
||||||
|
</form-entry>
|
||||||
|
<form-entry label="Branch" info="optional">
|
||||||
|
<input slot="field" class="textfield" type="text" v-model.trim="branch" @keydown.enter="resolve()">
|
||||||
|
<div class="form-entry__info">
|
||||||
|
If not supplied, the <code>master</code> branch will be used.
|
||||||
|
</div>
|
||||||
|
</form-entry>
|
||||||
|
<form-entry label="Folder path" info="optional">
|
||||||
|
<input slot="field" class="textfield" type="text" v-model.trim="path" @keydown.enter="resolve()">
|
||||||
|
<div class="form-entry__info">
|
||||||
|
If not supplied, the root folder will be used.
|
||||||
|
</div>
|
||||||
|
</form-entry>
|
||||||
|
</div>
|
||||||
|
<div class="modal__button-bar">
|
||||||
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
|
</div>
|
||||||
|
</modal-inner>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import utils from '../../../services/utils';
|
||||||
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
|
||||||
|
export default modalTemplate({
|
||||||
|
data: () => ({
|
||||||
|
branch: '',
|
||||||
|
path: '',
|
||||||
|
}),
|
||||||
|
computedLocalSettings: {
|
||||||
|
repoUrl: 'githubWorkspaceRepoUrl',
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resolve() {
|
||||||
|
const parsedRepo = utils.parseGithubRepoUrl(this.repoUrl);
|
||||||
|
if (!parsedRepo) {
|
||||||
|
this.setError('repoUrl');
|
||||||
|
} else {
|
||||||
|
const path = this.path && this.path.replace(/^\//, '');
|
||||||
|
const url = utils.addQueryParams('app', {
|
||||||
|
...parsedRepo,
|
||||||
|
providerId: 'githubWorkspace',
|
||||||
|
branch: this.branch || 'master',
|
||||||
|
path: path || undefined,
|
||||||
|
}, true);
|
||||||
|
this.config.resolve();
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>Google Drive</b> account to <b>StackEdit</b>.</p>
|
<p>Link your <b>Google Drive</b> account to <b>StackEdit</b>.</p>
|
||||||
<div class="form-entry">
|
<div class="form-entry">
|
||||||
<div class="form-entry__checkbox">
|
<div class="form-entry__checkbox">
|
||||||
<label>
|
<label>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="config.resolve()">Ok</button>
|
<button class="button button--resolve" @click="config.resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Google Drive</b> account.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>Google Drive</b> account.</p>
|
||||||
<form-entry label="Folder ID" info="optional">
|
<form-entry label="Folder ID" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -32,9 +32,9 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template" v-if="format === 'html'">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -73,16 +73,18 @@ export default modalTemplate({
|
||||||
'modal/hideUntil',
|
'modal/hideUntil',
|
||||||
googleHelper.openPicker(this.config.token, 'folder')
|
googleHelper.openPicker(this.config.token, 'folder')
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
if (folders[0]) {
|
||||||
googleDriveFolderId: folders[0].id,
|
this.$store.dispatch('data/patchLocalSettings', {
|
||||||
});
|
googleDriveFolderId: folders[0].id,
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
resolve() {
|
resolve() {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = googleDriveProvider.makeLocation(
|
const location = googleDriveProvider.makeLocation(this.config.token, this.fileId);
|
||||||
this.config.token, this.fileId);
|
if (this.format === 'html') {
|
||||||
if (this.format) {
|
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
}
|
}
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will save <b>{{currentFileName}}</b> to your <b>Google Drive</b> account and keep it synchronized.</p>
|
<p>Save <b>{{currentFileName}}</b> to your <b>Google Drive</b> account and keep it synced.</p>
|
||||||
<form-entry label="Folder ID" info="optional">
|
<form-entry label="Folder ID" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -46,15 +46,21 @@ export default modalTemplate({
|
||||||
'modal/hideUntil',
|
'modal/hideUntil',
|
||||||
googleHelper.openPicker(this.config.token, 'folder')
|
googleHelper.openPicker(this.config.token, 'folder')
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
if (folders[0]) {
|
||||||
googleDriveFolderId: folders[0].id,
|
this.$store.dispatch('data/patchLocalSettings', {
|
||||||
});
|
googleDriveFolderId: folders[0].id,
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
resolve() {
|
resolve() {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = googleDriveProvider.makeLocation(
|
const location = googleDriveProvider.makeLocation(
|
||||||
this.config.token, this.fileId, this.folderId);
|
this.config.token,
|
||||||
|
this.fileId,
|
||||||
|
this.folderId,
|
||||||
|
);
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="googleDrive"></icon-provider>
|
<icon-provider provider-id="googleDrive"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will create a workspace synchronized with a <b>Google Drive</b> folder.</p>
|
<p>Create a workspace synced with a <b>Google Drive</b> folder.</p>
|
||||||
<form-entry label="Folder ID" info="optional">
|
<form-entry label="Folder ID" info="optional">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="folderId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -37,10 +37,13 @@ export default modalTemplate({
|
||||||
'modal/hideUntil',
|
'modal/hideUntil',
|
||||||
googleHelper.openPicker(this.config.token, 'folder')
|
googleHelper.openPicker(this.config.token, 'folder')
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
this.$store.dispatch('data/patchLocalSettings', {
|
if (folders[0]) {
|
||||||
googleDriveWorkspaceFolderId: folders[0].id,
|
this.$store.dispatch('data/patchLocalSettings', {
|
||||||
});
|
googleDriveWorkspaceFolderId: folders[0].id,
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
resolve() {
|
resolve() {
|
||||||
const url = utils.addQueryParams('app', {
|
const url = utils.addQueryParams('app', {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="reject()">Cancel</button>
|
<button class="button" @click="reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,20 +42,20 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve() {
|
resolve() {
|
||||||
let url = this.config.url;
|
let { url } = this.config;
|
||||||
const size = parseInt(this.size, 10);
|
const size = parseInt(this.size, 10);
|
||||||
if (!isNaN(size)) {
|
if (!Number.isNaN(size)) {
|
||||||
url = makeThumbnail(url, size);
|
url = makeThumbnail(url, size);
|
||||||
}
|
}
|
||||||
if (this.title) {
|
if (this.title) {
|
||||||
url += ` "${this.title}"`;
|
url += ` "${this.title}"`;
|
||||||
}
|
}
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.resolve();
|
this.config.resolve();
|
||||||
callback(url);
|
callback(url);
|
||||||
},
|
},
|
||||||
reject() {
|
reject() {
|
||||||
const callback = this.config.callback;
|
const { callback } = this.config;
|
||||||
this.config.reject();
|
this.config.reject();
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="wordpress"></icon-provider>
|
<icon-provider provider-id="wordpress"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>WordPress</b> site.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>WordPress</b> site.</p>
|
||||||
<form-entry label="Site domain" error="domain">
|
<form-entry label="Site domain" error="domain">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="domain" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="domain" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
<b>Example:</b> example.wordpress.com<br>
|
<b>Example:</b> example.wordpress.com<br>
|
||||||
<b>Jetpack plugin</b> is required for self-hosted sites.
|
<b>Note:</b> Jetpack is required for self-hosted sites.
|
||||||
</div>
|
</div>
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Existing post ID" info="optional">
|
<form-entry label="Existing post ID" info="optional">
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -57,7 +57,10 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = wordpressProvider.makeLocation(
|
const location = wordpressProvider.makeLocation(
|
||||||
this.config.token, this.domain, this.postId);
|
this.config.token,
|
||||||
|
this.domain,
|
||||||
|
this.postId,
|
||||||
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="zendesk"></icon-provider>
|
<icon-provider provider-id="zendesk"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will link your <b>Zendesk</b> account to <b>StackEdit</b>.</p>
|
<p>Link your <b>Zendesk</b> account to <b>StackEdit</b>.</p>
|
||||||
<form-entry label="Site URL" error="siteUrl">
|
<form-entry label="Site URL" error="siteUrl">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="siteUrl" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="siteUrl" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -21,18 +21,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import utils from '../../../services/utils';
|
|
||||||
import modalTemplate from '../common/modalTemplate';
|
import modalTemplate from '../common/modalTemplate';
|
||||||
|
import constants from '../../../data/constants';
|
||||||
|
|
||||||
export default modalTemplate({
|
export default modalTemplate({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
redirectUrl: utils.oauth2RedirectUri,
|
redirectUrl: constants.oauth2RedirectUri,
|
||||||
}),
|
}),
|
||||||
computedLocalSettings: {
|
computedLocalSettings: {
|
||||||
siteUrl: 'zendeskSiteUrl',
|
siteUrl: 'zendeskSiteUrl',
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="modal__image">
|
<div class="modal__image">
|
||||||
<icon-provider provider-id="zendesk"></icon-provider>
|
<icon-provider provider-id="zendesk"></icon-provider>
|
||||||
</div>
|
</div>
|
||||||
<p>This will publish <b>{{currentFileName}}</b> to your <b>Zendesk Help Center</b>.</p>
|
<p>Publish <b>{{currentFileName}}</b> to your <b>Zendesk Help Center</b>.</p>
|
||||||
<form-entry label="Section ID" error="sectionId">
|
<form-entry label="Section ID" error="sectionId">
|
||||||
<input slot="field" class="textfield" type="text" v-model.trim="sectionId" @keydown.enter="resolve()">
|
<input slot="field" class="textfield" type="text" v-model.trim="sectionId" @keydown.enter="resolve()">
|
||||||
<div class="form-entry__info">
|
<div class="form-entry__info">
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
</form-entry>
|
</form-entry>
|
||||||
<form-entry label="Template">
|
<form-entry label="Template">
|
||||||
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
<select slot="field" class="textfield" v-model="selectedTemplate" @keydown.enter="resolve()">
|
||||||
<option v-for="(template, id) in allTemplates" :key="id" :value="id">
|
<option v-for="(template, id) in allTemplatesById" :key="id" :value="id">
|
||||||
{{ template.name }}
|
{{ template.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal__button-bar">
|
<div class="modal__button-bar">
|
||||||
<button class="button" @click="config.reject()">Cancel</button>
|
<button class="button" @click="config.reject()">Cancel</button>
|
||||||
<button class="button" @click="resolve()">Ok</button>
|
<button class="button button--resolve" @click="resolve()">Ok</button>
|
||||||
</div>
|
</div>
|
||||||
</modal-inner>
|
</modal-inner>
|
||||||
</template>
|
</template>
|
||||||
|
@ -62,7 +62,11 @@ export default modalTemplate({
|
||||||
} else {
|
} else {
|
||||||
// Return new location
|
// Return new location
|
||||||
const location = zendeskProvider.makeLocation(
|
const location = zendeskProvider.makeLocation(
|
||||||
this.config.token, this.sectionId, this.locale || 'en-us', this.articleId);
|
this.config.token,
|
||||||
|
this.sectionId,
|
||||||
|
this.locale || 'en-us',
|
||||||
|
this.articleId,
|
||||||
|
);
|
||||||
location.templateId = this.selectedTemplate;
|
location.templateId = this.selectedTemplate;
|
||||||
this.config.resolve(location);
|
this.config.resolve(location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
@import './common/base';
|
|
30
src/data/constants.js
Normal file
30
src/data/constants.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const origin = `${window.location.protocol}//${window.location.host}`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
cleanTrashAfter: 0 * 24 * 60 * 60 * 1000, // 7 days
|
||||||
|
origin,
|
||||||
|
oauth2RedirectUri: `${origin}/oauth2/callback`,
|
||||||
|
types: [
|
||||||
|
'contentState',
|
||||||
|
'syncedContent',
|
||||||
|
'content',
|
||||||
|
'file',
|
||||||
|
'folder',
|
||||||
|
'syncLocation',
|
||||||
|
'publishLocation',
|
||||||
|
'data',
|
||||||
|
],
|
||||||
|
localStorageDataIds: [
|
||||||
|
'workspaces',
|
||||||
|
'settings',
|
||||||
|
'layoutSettings',
|
||||||
|
'tokens',
|
||||||
|
],
|
||||||
|
userIdPrefixes: {
|
||||||
|
db: 'dropbox',
|
||||||
|
gh: 'github',
|
||||||
|
go: 'google',
|
||||||
|
},
|
||||||
|
textMaxLength: 250000,
|
||||||
|
defaultName: 'Untitled',
|
||||||
|
};
|
|
@ -15,6 +15,7 @@ export default () => ({
|
||||||
dropboxPublishTemplate: 'styledHtml',
|
dropboxPublishTemplate: 'styledHtml',
|
||||||
githubRepoFullAccess: false,
|
githubRepoFullAccess: false,
|
||||||
githubRepoUrl: '',
|
githubRepoUrl: '',
|
||||||
|
githubWorkspaceRepoUrl: '',
|
||||||
githubPublishTemplate: 'jekyllSite',
|
githubPublishTemplate: 'jekyllSite',
|
||||||
gistIsPublic: false,
|
gistIsPublic: false,
|
||||||
gistPublishTemplate: 'plainText',
|
gistPublishTemplate: 'plainText',
|
|
@ -1,11 +1,11 @@
|
||||||
# light or dark
|
# light or dark
|
||||||
colorTheme: light
|
colorTheme: light
|
||||||
# Auto-sync frequency (in ms). Minimum is 60000.
|
|
||||||
autoSyncEvery: 60000
|
|
||||||
# Adjust font size in editor and preview
|
# Adjust font size in editor and preview
|
||||||
fontSizeFactor: 1
|
fontSizeFactor: 1
|
||||||
# Adjust maximum text width in editor and preview
|
# Adjust maximum text width in editor and preview
|
||||||
maxWidthFactor: 1
|
maxWidthFactor: 1
|
||||||
|
# Auto-sync frequency (in ms). Minimum is 60000.
|
||||||
|
autoSyncEvery: 90000
|
||||||
|
|
||||||
# Editor settings
|
# Editor settings
|
||||||
editor:
|
editor:
|
||||||
|
@ -54,7 +54,7 @@ wkhtmltopdf:
|
||||||
marginRight: 25
|
marginRight: 25
|
||||||
marginBottom: 25
|
marginBottom: 25
|
||||||
marginLeft: 25
|
marginLeft: 25
|
||||||
# `A3`, `A4`, `Legal` or `Letter`
|
# A3, A4, Legal or Letter
|
||||||
pageSize: A4
|
pageSize: A4
|
||||||
|
|
||||||
# Options passed to pandoc
|
# Options passed to pandoc
|
||||||
|
@ -77,6 +77,12 @@ turndown:
|
||||||
linkStyle: inlined
|
linkStyle: inlined
|
||||||
linkReferenceStyle: full
|
linkReferenceStyle: full
|
||||||
|
|
||||||
|
# GitHub commit messages
|
||||||
|
github:
|
||||||
|
createFileMessage: '{{path}} created from https://stackedit.io/'
|
||||||
|
updateFileMessage: '{{path}} updated from https://stackedit.io/'
|
||||||
|
deleteFileMessage: '{{path}} deleted from https://stackedit.io/'
|
||||||
|
|
||||||
# Default content for new files
|
# Default content for new files
|
||||||
newFileContent: |
|
newFileContent: |
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export default () => ({
|
export default () => ({
|
||||||
main: {
|
main: {
|
||||||
name: 'Main workspace',
|
name: 'Main workspace',
|
||||||
// The rest will be filled by the data/sanitizedWorkspaces getter
|
// The rest will be filled by the workspace/workspacesById getter
|
||||||
},
|
},
|
||||||
});
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user