From abd089051249dc83a9d2dd187cbdec48eb4ffcde Mon Sep 17 00:00:00 2001 From: benweet Date: Sat, 4 Nov 2017 16:59:48 +0000 Subject: [PATCH] Added favicons. Added sponsorship options. Added pdf and pandoc export. Added emoji and mermaid extensions. Added find/replace support. Added HTML template with TOC. Updated welcome file. --- .dockerignore | 4 + Dockerfile | 16 + build/webpack.base.conf.js | 26 +- build/webpack.prod.conf.js | 2 +- build/webpack.style.conf.js | 57 ++ index.html | 6 +- package.json | 13 +- server/index.js | 44 +- server/pandoc.js | 152 +++ server/pdf.js | 188 ++++ server/user.js | 129 +++ src/assets/favicon.png | Bin 0 -> 56767 bytes src/assets/logo.svg | 2 +- src/components/ButtonBar.vue | 36 +- src/components/CodeEditor.vue | 1 - src/components/ExplorerNode.vue | 2 +- src/components/FindReplace.vue | 359 +++++++ src/components/Layout.vue | 27 +- src/components/Modal.vue | 91 +- src/components/NavigationBar.vue | 4 +- src/components/SideBar.vue | 2 - src/components/StatusBar.vue | 8 +- src/components/Toc.vue | 18 +- src/components/common/EditorClassApplier.js | 85 ++ src/components/common/app.scss | 17 + src/components/common/base.scss | 140 ++- src/components/common/mermaid.scss | 359 +++++++ src/components/menus/ExportMenu.vue | 16 +- src/components/menus/MainMenu.vue | 4 +- src/components/menus/MoreMenu.vue | 26 +- src/components/menus/PublishMenu.vue | 2 +- src/components/menus/SyncMenu.vue | 22 +- .../menus/{ => common}/MenuEntry.vue | 30 +- src/components/modals/AboutModal.vue | 18 +- src/components/modals/FilePropertiesModal.vue | 22 +- src/components/modals/HtmlExportModal.vue | 38 +- src/components/modals/ImageModal.vue | 20 +- src/components/modals/LinkModal.vue | 18 +- src/components/modals/PandocExportModal.vue | 82 ++ src/components/modals/PdfExportModal.vue | 75 ++ .../modals/PublishManagementModal.vue | 18 +- src/components/modals/SettingsModal.vue | 22 +- src/components/modals/SponsorModal.vue | 98 ++ src/components/modals/SyncManagementModal.vue | 18 +- src/components/modals/TemplatesModal.vue | 18 +- .../modals/{ => common}/FormEntry.vue | 2 +- src/components/modals/common/ModalInner.vue | 71 ++ src/components/modals/{ => common}/Tab.vue | 0 .../modals/{ => common}/modalTemplate.js | 20 +- .../BloggerPagePublishModal.vue | 18 +- .../{ => providers}/BloggerPublishModal.vue | 18 +- .../{ => providers}/DropboxAccountModal.vue | 16 +- .../{ => providers}/DropboxPublishModal.vue | 18 +- .../DropboxSaveModal.vue} | 18 +- .../{ => providers}/GistPublishModal.vue | 18 +- .../modals/{ => providers}/GistSyncModal.vue | 18 +- .../{ => providers}/GithubAccountModal.vue | 18 +- .../modals/providers/GithubOpenModal.vue | 68 ++ .../{ => providers}/GithubPublishModal.vue | 18 +- .../GithubSaveModal.vue} | 24 +- .../GoogleDrivePublishModal.vue | 20 +- .../GoogleDriveSaveModal.vue} | 20 +- .../{ => providers}/GooglePhotoModal.vue | 16 +- .../{ => providers}/WordpressPublishModal.vue | 18 +- .../{ => providers}/ZendeskAccountModal.vue | 18 +- .../{ => providers}/ZendeskPublishModal.vue | 18 +- src/components/style.scss | 1 + src/data/defaultFileProperties.yml | 24 +- src/data/defaultLocalSettings.js | 4 + src/data/defaultSettings.yml | 23 +- src/data/emptyTemplateValue.html | 10 + src/data/styledHtmlTemplate.html | 11 +- src/data/styledHtmlWithTocTemplate.html | 28 + src/data/welcomeFile.md | 263 ++--- src/extensions/emojiExtension.js | 13 + src/extensions/index.js | 6 +- .../{katexExt.js => katexExtension.js} | 0 .../{markdownExt.js => markdownExtension.js} | 4 +- src/extensions/mermaidExtension.js | 122 +++ src/icons/File.vue | 5 + src/icons/index.js | 2 + src/index.js | 6 + src/libs/cleditCore.js | 23 +- src/libs/cleditSelectionMgr.js | 96 +- src/services/diffUtils.js | 2 +- src/services/editorEngineSvc.js | 19 +- src/services/editorSvc.js | 16 +- src/services/exportSvc.js | 24 +- src/services/localDbSvc.js | 33 +- src/services/networkSvc.js | 21 +- src/services/optional/shortcuts.js | 13 +- src/services/providers/githubProvider.js | 50 + .../providers/helpers/googleHelper.js | 72 +- src/services/sponsorSvc.js | 53 + src/services/syncSvc.js | 95 +- src/services/utils.js | 54 + src/store/index.js | 13 + src/store/modules/data.js | 42 +- src/store/modules/findReplace.js | 32 + src/store/modules/layout.js | 13 +- src/store/modules/modal.js | 18 +- yarn.lock | 949 +++++++++++++++--- 102 files changed, 4076 insertions(+), 874 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 build/webpack.style.conf.js create mode 100644 server/pandoc.js create mode 100644 server/pdf.js create mode 100644 server/user.js create mode 100644 src/assets/favicon.png create mode 100644 src/components/FindReplace.vue create mode 100644 src/components/common/EditorClassApplier.js create mode 100644 src/components/common/mermaid.scss rename src/components/menus/{ => common}/MenuEntry.vue (57%) create mode 100644 src/components/modals/PandocExportModal.vue create mode 100644 src/components/modals/PdfExportModal.vue create mode 100644 src/components/modals/SponsorModal.vue rename src/components/modals/{ => common}/FormEntry.vue (90%) create mode 100644 src/components/modals/common/ModalInner.vue rename src/components/modals/{ => common}/Tab.vue (100%) rename src/components/modals/{ => common}/modalTemplate.js (70%) rename src/components/modals/{ => providers}/BloggerPagePublishModal.vue (81%) rename src/components/modals/{ => providers}/BloggerPublishModal.vue (82%) rename src/components/modals/{ => providers}/DropboxAccountModal.vue (67%) rename src/components/modals/{ => providers}/DropboxPublishModal.vue (79%) rename src/components/modals/{DropboxSyncModal.vue => providers/DropboxSaveModal.vue} (72%) rename src/components/modals/{ => providers}/GistPublishModal.vue (84%) rename src/components/modals/{ => providers}/GistSyncModal.vue (78%) rename src/components/modals/{ => providers}/GithubAccountModal.vue (52%) create mode 100644 src/components/modals/providers/GithubOpenModal.vue rename src/components/modals/{ => providers}/GithubPublishModal.vue (85%) rename src/components/modals/{GithubSyncModal.vue => providers/GithubSaveModal.vue} (72%) rename src/components/modals/{ => providers}/GoogleDrivePublishModal.vue (85%) rename src/components/modals/{GoogleDriveSyncModal.vue => providers/GoogleDriveSaveModal.vue} (76%) rename src/components/modals/{ => providers}/GooglePhotoModal.vue (80%) rename src/components/modals/{ => providers}/WordpressPublishModal.vue (83%) rename src/components/modals/{ => providers}/ZendeskAccountModal.vue (81%) rename src/components/modals/{ => providers}/ZendeskPublishModal.vue (84%) create mode 100644 src/components/style.scss create mode 100644 src/data/styledHtmlWithTocTemplate.html create mode 100644 src/extensions/emojiExtension.js rename src/extensions/{katexExt.js => katexExtension.js} (100%) rename src/extensions/{markdownExt.js => markdownExtension.js} (98%) create mode 100644 src/extensions/mermaidExtension.js create mode 100644 src/icons/File.vue create mode 100644 src/services/sponsorSvc.js create mode 100644 src/store/modules/findReplace.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..dec83b25 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +.git +dist +.history diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..297ccc1f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM benweet/stackedit-base + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY package.json /usr/src/app/ +COPY yarn.lock /usr/src/app/ +COPY gulpfile.js /usr/src/app/ +RUN yarn && yarn cache clean +COPY . /usr/src/app +ENV NODE_ENV production +RUN yarn run build + +EXPOSE 8080 + +CMD [ "node", "." ] diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index 91c09bc1..9ba3c353 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -4,6 +4,7 @@ var utils = require('./utils') var config = require('../config') var vueLoaderConfig = require('./vue-loader.conf') var StylelintPlugin = require('stylelint-webpack-plugin') +var FaviconsWebpackPlugin = require('favicons-webpack-plugin') function resolve (dir) { return path.join(__dirname, '..', dir) @@ -13,6 +14,10 @@ module.exports = { entry: { app: './src/' }, + node: { + // For mermaid + fs: 'empty' // jison generated code requires 'fs' + }, output: { path: config.build.assetsRoot, filename: '[name].js', @@ -45,7 +50,14 @@ module.exports = { { test: /\.js$/, loader: 'babel-loader', - include: [resolve('src'), resolve('test')] + include: [resolve('src'), resolve('test'), resolve('node_modules/mermaid/src')], + exclude: [ + resolve('node_modules/mermaid/src/diagrams/classDiagram/parser'), + resolve('node_modules/mermaid/src/diagrams/flowchart/parser'), + resolve('node_modules/mermaid/src/diagrams/gantt/parser'), + resolve('node_modules/mermaid/src/diagrams/gitGraph/parser'), + resolve('node_modules/mermaid/src/diagrams/sequenceDiagram/parser'), + ], }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, @@ -56,13 +68,9 @@ module.exports = { } }, { - test: /\.(ttf|eot|otf|woff2)$/, loader: 'ignore-loader' - }, - { - test: /\.woff(\?.*)?$/, - loader: 'url-loader', + test: /\.(ttf|eot|otf|woff2?)(\?.*)?$/, + loader: 'file-loader', options: { - limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } }, @@ -76,6 +84,10 @@ module.exports = { new StylelintPlugin({ files: ['**/*.vue', '**/*.scss'] }), + new FaviconsWebpackPlugin({ + logo: resolve('src/assets/favicon.png'), + title: 'StackEdit', + }), new webpack.DefinePlugin({ VERSION: JSON.stringify(require('../package.json').version) }) diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 1ce999de..7764829a 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -96,7 +96,7 @@ var webpackConfig = merge(baseWebpackConfig, { ServiceWorker: { events: true }, - excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html'], + excludes: ['**/.*', '**/*.map', '**/index.html', '**/static/oauth2/callback.html', '**/icons-*/*.png', '**/static/fonts/KaTeX_*'], externals: ['/app', '/oauth2/callback'] }) ] diff --git a/build/webpack.style.conf.js b/build/webpack.style.conf.js new file mode 100644 index 00000000..06745ea2 --- /dev/null +++ b/build/webpack.style.conf.js @@ -0,0 +1,57 @@ +var path = require('path') +var utils = require('./utils') +var webpack = require('webpack') +var utils = require('./utils') +var config = require('../config') +var vueLoaderConfig = require('./vue-loader.conf') +var StylelintPlugin = require('stylelint-webpack-plugin') +var FaviconsWebpackPlugin = require('favicons-webpack-plugin') +var ExtractTextPlugin = require('extract-text-webpack-plugin') +var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') + +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + +module.exports = { + entry: { + style: './src/components/style.scss' + }, + module: { + rules: [{ + test: /\.(ttf|eot|otf|woff2?)(\?.*)?$/, + loader: 'file-loader', + options: { + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + }] + .concat(utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true + })), + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: config.build.assetsPublicPath + }, + plugins: [ + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + }, + sourceMap: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: '[name].css', + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: { + safe: true + } + }), + ] +} diff --git a/index.html b/index.html index 4ad2de16..d87fb168 100644 --- a/index.html +++ b/index.html @@ -4,11 +4,6 @@ StackEdit - - - - - @@ -18,5 +13,6 @@
+ diff --git a/package.json b/package.json index 34e93c5e..31f7cb44 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "scripts": { "postinstall": "gulp build-prism", "start": "node build/dev-server.js", - "build": "node build/build.js", + "build": "node build/build.js && npm run build-style", + "build-style": "webpack --config build/webpack.style.conf.js", "lint": "eslint --ext .js,.vue src server", "preversion": "npm run lint", "postversion": "git push origin master --tags && npm publish", @@ -20,11 +21,14 @@ "major": "npm version major -m \"Tag v%s\"" }, "dependencies": { + "aws-sdk": "^2.133.0", "bezier-easing": "^1.1.0", + "body-parser": "^1.18.2", "clipboard": "^1.7.1", "compression": "^1.7.0", "diff-match-patch": "^1.0.0", "file-saver": "^1.3.3", + "google-id-token-verifier": "^0.2.3", "handlebars": "^4.0.10", "indexeddbshim": "^3.0.4", "js-yaml": "^3.9.1", @@ -37,12 +41,13 @@ "markdown-it-pandoc-renderer": "1.1.3", "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", + "mermaid": "^7.1.0", "mousetrap": "^1.6.1", "normalize-scss": "^7.0.0", "prismjs": "^1.6.0", - "raw-loader": "^0.5.1", "request": "^2.82.0", "serve-static": "^1.12.6", + "tmp": "^0.0.33", "vue": "^2.3.3", "vuex": "^2.3.1" }, @@ -59,7 +64,7 @@ "chalk": "^1.1.3", "connect-history-api-fallback": "^1.3.0", "copy-webpack-plugin": "^4.0.1", - "css-loader": "^0.28.4", + "css-loader": "^0.28.7", "eslint": "^3.19.0", "eslint-config-airbnb-base": "^11.1.3", "eslint-friendly-formatter": "^2.0.7", @@ -70,6 +75,7 @@ "eventsource-polyfill": "^0.9.6", "express": "^4.15.5", "extract-text-webpack-plugin": "^2.0.0", + "favicons-webpack-plugin": "^0.0.7", "file-loader": "^0.11.1", "friendly-errors-webpack-plugin": "^1.1.3", "gulp": "^3.9.1", @@ -83,6 +89,7 @@ "opn": "^4.0.2", "optimize-css-assets-webpack-plugin": "^1.3.0", "ora": "^1.2.0", + "raw-loader": "^0.5.1", "rimraf": "^2.6.0", "sass-loader": "^6.0.5", "semver": "^5.3.0", diff --git a/server/index.js b/server/index.js index cefc618f..30eec007 100644 --- a/server/index.js +++ b/server/index.js @@ -1,7 +1,13 @@ const compression = require('compression'); const serveStatic = require('serve-static'); +const bodyParser = require('body-parser'); const path = require('path'); +const user = require('./user'); const github = require('./github'); +const pdf = require('./pdf'); +const pandoc = require('./pandoc'); + +const resolvePath = pathToResolve => path.join(__dirname, '..', pathToResolve); module.exports = (app, serveV4) => { // Use gzip compression @@ -22,10 +28,20 @@ module.exports = (app, serveV4) => { app.use(compression()); } + // Parse body mostly for PayPal IPN + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ + extended: false, + })); + app.get('/oauth2/githubToken', github.githubToken); + app.get('/userInfo', user.userInfo); + app.post('/paypalIpn', user.paypalIpn); + app.post('/pdfExport', pdf.generate); + app.post('/pandocExport', pandoc.generate); + if (serveV4) { /* eslint-disable global-require, import/no-unresolved */ - app.post('/pdfExport', require('../stackedit_v4/app/pdf').export); app.post('/sshPublish', require('../stackedit_v4/app/ssh').publish); app.post('/picasaImportImg', require('../stackedit_v4/app/picasa').importImg); app.get('/downloadImport', require('../stackedit_v4/app/download').importPublic); @@ -33,29 +49,39 @@ module.exports = (app, serveV4) => { } // Serve callback.html in /app - app.get('/oauth2/callback', (req, res) => res.sendFile(path.join(__dirname, '../static/oauth2/callback.html'))); + app.get('/oauth2/callback', (req, res) => res.sendFile(resolvePath('static/oauth2/callback.html'))); // Serve static resources if (process.env.NODE_ENV === 'production') { if (serveV4) { // Serve landing.html in / - app.get('/', (req, res) => res.sendFile(require.resolve('../stackedit_v4/views/landing.html'))); + app.get('/', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/landing.html'))); // Serve editor.html in /viewer - app.get('/editor', (req, res) => res.sendFile(require.resolve('../stackedit_v4/views/editor.html'))); + app.get('/editor', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/editor.html'))); // Serve viewer.html in /viewer - app.get('/viewer', (req, res) => res.sendFile(require.resolve('../stackedit_v4/views/viewer.html'))); + app.get('/viewer', (req, res) => res.sendFile(resolvePath('stackedit_v4/views/viewer.html'))); } // Serve index.html in /app - app.get('/app', (req, res) => res.sendFile(path.join(__dirname, '../dist/index.html'))); + app.get('/app', (req, res) => res.sendFile(resolvePath('dist/index.html'))); - app.use(serveStatic(path.join(__dirname, '../dist'))); + // Serve style.css with 1 day max-age + app.get('/style.css', (req, res) => res.sendFile(resolvePath('dist/style.css'), { + maxAge: '1d', + })); + + // Serve the static folder with 1 year max-age + app.use('/static', serveStatic(resolvePath('dist/static'), { + maxAge: '1y', + })); + + app.use(serveStatic(resolvePath('dist'))); if (serveV4) { - app.use(serveStatic(path.dirname(require.resolve('../stackedit_v4/public/cache.manifest')))); + app.use(serveStatic(path.dirname(resolvePath('stackedit_v4/public/cache.manifest')))); // Error 404 - app.use((req, res) => res.status(404).sendFile(require.resolve('../stackedit_v4/views/error_404.html'))); + app.use((req, res) => res.status(404).sendFile(resolvePath('stackedit_v4/views/error_404.html'))); } } }; diff --git a/server/pandoc.js b/server/pandoc.js new file mode 100644 index 00000000..a1285e05 --- /dev/null +++ b/server/pandoc.js @@ -0,0 +1,152 @@ +/* global window */ +const spawn = require('child_process').spawn; +const fs = require('fs'); +const tmp = require('tmp'); +const user = require('./user'); + +const outputFormats = { + asciidoc: 'text/plain', + context: 'application/x-latex', + epub: 'application/epub+zip', + epub3: 'application/epub+zip', + latex: 'application/x-latex', + odt: 'application/vnd.oasis.opendocument.text', + pdf: 'application/pdf', + rst: 'text/plain', + rtf: 'application/rtf', + textile: 'text/plain', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', +}; + +const highlightStyles = [ + 'pygments', + 'kate', + 'monochrome', + 'espresso', + 'zenburn', + 'haddock', + 'tango', +]; + +const readJson = (str) => { + try { + return JSON.parse(str); + } catch (e) { + return {}; + } +}; + +exports.generate = (req, res) => { + let pandocError = ''; + const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format) + ? req.query.format + : 'pdf'; + Promise.all([ + user.checkSponsor(req.query.idToken), + user.checkMonetize(req.query.token), + ]) + .then(([isSponsor, isMonetize]) => { + if (!isSponsor && !isMonetize) { + throw new Error('unauthorized'); + } + + return new Promise((resolve, reject) => { + tmp.file({ + postfix: `.${outputFormat}`, + }, (err, filePath, fd, cleanupCallback) => { + if (err) { + reject(err); + } else { + resolve({ + filePath, + cleanupCallback, + }); + } + }); + }); + }) + .then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => { + const options = readJson(req.query.options); + const metadata = readJson(req.query.metadata); + const params = []; + + params.push('--latex-engine=xelatex'); + params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl='); + if (options.toc) { + params.push('--toc'); + } + options.tocDepth = parseInt(options.tocDepth, 10); + if (!isNaN(options.tocDepth)) { + params.push('--toc-depth', options.tocDepth); + } + options.highlightStyle = highlightStyles.indexOf(options.highlightStyle) !== -1 ? options.highlightStyle : 'kate'; + params.push('--highlight-style', options.highlightStyle); + Object.keys(metadata).forEach((key) => { + params.push('-M', `${key}=${metadata[key]}`); + }); + + let finished = false; + + function onError(error) { + finished = true; + cleanupCallback(); + reject(error); + } + + const binPath = process.env.PANDOC_PATH || 'pandoc'; + const format = outputFormat === 'pdf' ? 'latex' : outputFormat; + params.push('-f', 'json', '-t', format, '-o', filePath); + const pandoc = spawn(binPath, params, { + stdio: [ + 'pipe', + 'ignore', + 'pipe', + ], + }); + let timeoutId = setTimeout(() => { + timeoutId = null; + pandoc.kill(); + }, 50000); + pandoc.on('error', onError); + pandoc.stdin.on('error', onError); + pandoc.stderr.on('data', (data) => { + pandocError += `${data}`; + }); + pandoc.on('close', (code) => { + if (!finished) { + clearTimeout(timeoutId); + if (!timeoutId) { + res.statusCode = 408; + cleanupCallback(); + reject(new Error('timeout')); + } else if (code) { + cleanupCallback(); + reject(); + } else { + res.set('Content-Type', outputFormats[outputFormat]); + const readStream = fs.createReadStream(filePath); + readStream.on('open', () => readStream.pipe(res)); + readStream.on('close', () => cleanupCallback()); + readStream.on('error', () => { + cleanupCallback(); + reject(); + }); + } + } + }); + req.pipe(pandoc.stdin); + })) + .catch((err) => { + const message = err && err.message; + if (message === 'unauthorized') { + res.statusCode = 401; + res.end('Unauthorized.'); + } else if (message === 'timeout') { + res.statusCode = 408; + res.end('Request timeout.'); + } else { + res.statusCode = 400; + res.end(pandocError || 'Unknown error.'); + } + }); +}; diff --git a/server/pdf.js b/server/pdf.js new file mode 100644 index 00000000..6912e63d --- /dev/null +++ b/server/pdf.js @@ -0,0 +1,188 @@ +/* global window,MathJax */ +const spawn = require('child_process').spawn; +const fs = require('fs'); +const tmp = require('tmp'); +const user = require('./user'); + +/* eslint-disable no-var, prefer-arrow-callback, func-names */ +function waitForJavaScript() { + if (window.MathJax) { + // Amazon EC2: fix TeX font detection + MathJax.Hub.Register.StartupHook('HTML-CSS Jax Startup', function () { + var htmlCss = MathJax.OutputJax['HTML-CSS']; + htmlCss.Font.checkWebFont = function (check, font, callback) { + if (check.time(callback)) { + return; + } + if (check.total === 0) { + htmlCss.Font.testFont(font); + setTimeout(check, 200); + } else { + callback(check.STATUS.OK); + } + }; + }); + MathJax.Hub.Queue(function () { + window.status = 'done'; + }); + } else { + setTimeout(function () { + window.status = 'done'; + }, 2000); + } +} +/* eslint-disable no-var, prefer-arrow-callback, func-names */ + +const authorizedPageSizes = [ + 'A3', + 'A4', + 'Legal', + 'Letter', +]; + +const readJson = (str) => { + try { + return JSON.parse(str); + } catch (e) { + return {}; + } +}; + +exports.generate = (req, res) => { + let wkhtmltopdfError = ''; + Promise.all([ + user.checkSponsor(req.query.idToken), + user.checkMonetize(req.query.token), + ]) + .then(([isSponsor, isMonetize]) => { + if (!isSponsor && !isMonetize) { + throw new Error('unauthorized'); + } + return new Promise((resolve, reject) => { + tmp.file((err, filePath, fd, cleanupCallback) => { + if (err) { + reject(err); + } else { + resolve({ + filePath, + cleanupCallback, + }); + } + }); + }); + }) + .then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => { + let finished = false; + + function onError(err) { + finished = true; + cleanupCallback(); + reject(err); + } + const options = readJson(req.query.options); + const params = []; + + // Margins + const marginTop = parseInt(`${options.marginTop}`, 10); + params.push('-T', isNaN(marginTop) ? 25 : marginTop); + const marginRight = parseInt(`${options.marginRight}`, 10); + params.push('-R', isNaN(marginRight) ? 25 : marginRight); + const marginBottom = parseInt(`${options.marginBottom}`, 10); + params.push('-B', isNaN(marginBottom) ? 25 : marginBottom); + const marginLeft = parseInt(`${options.marginLeft}`, 10); + params.push('-L', isNaN(marginLeft) ? 25 : marginLeft); + + // Header + if (options.headerCenter) { + params.push('--header-center', `${options.headerCenter}`); + } + if (options.headerLeft) { + params.push('--header-left', `${options.headerLeft}`); + } + if (options.headerRight) { + params.push('--header-right', `${options.headerRight}`); + } + if (options.headerFontName) { + params.push('--header-font-name', `${options.headerFontName}`); + } + if (options.headerFontSize) { + params.push('--header-font-size', `${options.headerFontSize}`); + } + + // Footer + if (options.footerCenter) { + params.push('--footer-center', `${options.footerCenter}`); + } + if (options.footerLeft) { + params.push('--footer-left', `${options.footerLeft}`); + } + if (options.footerRight) { + params.push('--footer-right', `${options.footerRight}`); + } + if (options.footerFontName) { + params.push('--footer-font-name', `${options.footerFontName}`); + } + if (options.footerFontSize) { + params.push('--footer-font-size', `${options.footerFontSize}`); + } + + // Page size + params.push('--page-size', authorizedPageSizes.indexOf(options.pageSize) === -1 ? 'A4' : options.pageSize); + + // Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason + const binPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf'; + params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`); + params.push('--window-status', 'done'); + const wkhtmltopdf = spawn(binPath, params.concat('-', filePath), { + stdio: [ + 'pipe', + 'ignore', + 'pipe', + ], + }); + let timeoutId = setTimeout(function () { + timeoutId = null; + wkhtmltopdf.kill(); + }, 50000); + wkhtmltopdf.on('error', onError); + wkhtmltopdf.stdin.on('error', onError); + wkhtmltopdf.stderr.on('data', (data) => { + wkhtmltopdfError += `${data}`; + }); + wkhtmltopdf.on('close', (code) => { + if (!finished) { + clearTimeout(timeoutId); + if (!timeoutId) { + cleanupCallback(); + reject(new Error('timeout')); + } else if (code) { + cleanupCallback(); + reject(); + } else { + res.set('Content-Type', 'application/pdf'); + const readStream = fs.createReadStream(filePath); + readStream.on('open', () => readStream.pipe(res)); + readStream.on('close', () => cleanupCallback()); + readStream.on('error', () => { + cleanupCallback(); + reject(); + }); + } + } + }); + req.pipe(wkhtmltopdf.stdin); + })) + .catch((err) => { + const message = err && err.message; + if (message === 'unauthorized') { + res.statusCode = 401; + res.end('Unauthorized.'); + } else if (message === 'timeout') { + res.statusCode = 408; + res.end('Request timeout.'); + } else { + res.statusCode = 400; + res.end(wkhtmltopdfError || 'Unknown error.'); + } + }); +}; diff --git a/server/user.js b/server/user.js new file mode 100644 index 00000000..018e9af9 --- /dev/null +++ b/server/user.js @@ -0,0 +1,129 @@ +const request = require('request'); +const AWS = require('aws-sdk'); +const verifier = require('google-id-token-verifier'); + +const BUCKET_NAME = process.env.USER_BUCKET_NAME || 'stackedit-users'; +const PAYPAL_URI = process.env.PAYPAL_URI || 'https://www.paypal.com/cgi-bin/webscr'; +const PAYPAL_RECEIVER_EMAIL = process.env.PAYPAL_RECEIVER_EMAIL || 'stackedit.project@gmail.com'; +const GOOGLE_CLIENT_ID = '241271498917-t4t7d07qis7oc0ahaskbif3ft6tk63cd.apps.googleusercontent.com'; +const s3Client = new AWS.S3(); + +const cb = (resolve, reject) => (err, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } +}; + +exports.getUser = id => new Promise((resolve, reject) => { + s3Client.getObject({ + Bucket: BUCKET_NAME, + Key: id, + }, cb(resolve, reject)); +}) + .then( + res => JSON.parse(`${res.Body}`), + (err) => { + if (err.code !== 'NoSuchKey') { + throw err; + } + }); + +exports.putUser = (id, user) => new Promise((resolve, reject) => { + s3Client.putObject({ + Bucket: BUCKET_NAME, + Key: id, + Body: JSON.stringify(user), + }, cb(resolve, reject)); +}); + +exports.removeUser = id => new Promise((resolve, reject) => { + s3Client.deleteObject({ + Bucket: BUCKET_NAME, + Key: id, + }, cb(resolve, reject)); +}); + +exports.getUserFromToken = idToken => new Promise( + (resolve, reject) => verifier.verify(idToken, GOOGLE_CLIENT_ID, cb(resolve, reject))) + .then(tokenInfo => exports.getUser(tokenInfo.sub)); + +exports.userInfo = (req, res) => exports.getUserFromToken(req.query.idToken) + .then(user => res.send(Object.assign({ + sponsorUntil: 0, + }, user)), + err => res.status(400).send(err ? err.message || err.toString() : 'invalid_token')); + +exports.paypalIpn = (req, res, next) => Promise.resolve() + .then(() => { + const userId = req.body.custom; + const paypalEmail = req.body.payer_email; + const gross = parseFloat(req.body.mc_gross); + let sponsorUntil; + if (gross === 5) { + sponsorUntil = Date.now() + (3 * 31 * 24 * 60 * 60 * 1000); // 3 months + } else if (gross === 15) { + sponsorUntil = Date.now() + (366 * 24 * 60 * 60 * 1000); // 1 year + } else if (gross === 25) { + sponsorUntil = Date.now() + (2 * 366 * 24 * 60 * 60 * 1000); // 2 years + } else if (gross === 50) { + sponsorUntil = Date.now() + (5 * 366 * 24 * 60 * 60 * 1000); // 5 years + } + if ( + req.body.receiver_email !== PAYPAL_RECEIVER_EMAIL || + req.body.payment_status !== 'Completed' || + req.body.mc_currency !== 'USD' || + (req.body.txn_type !== 'web_accept' && req.body.txn_type !== 'subscr_payment') || + !userId || !sponsorUntil + ) { + // Ignoring PayPal IPN + return res.end(); + } + // Processing PayPal IPN + req.body.cmd = '_notify-validate'; + return new Promise((resolve, reject) => request.post({ + uri: PAYPAL_URI, + form: req.body, + }, (err, response, body) => { + if (err) { + reject(err); + } else if (body !== 'VERIFIED') { + reject(new Error('PayPal IPN unverified')); + } else { + resolve(); + } + })) + .then(() => exports.putUser(userId, { + paypalEmail, + sponsorUntil, + })) + .then(() => res.end()); + }) + .catch(next); + +exports.checkSponsor = (idToken) => { + if (!idToken) { + return Promise.resolve(false); + } + return exports.getUserFromToken(idToken) + .then(userInfo => userInfo && userInfo.sponsorUntil > Date.now(), () => false); +}; + +exports.checkMonetize = (token) => { + if (!token) { + return Promise.resolve(false); + } + return new Promise(resolve => request({ + uri: 'https://monetizejs.com/api/payments', + qs: { + access_token: token, + }, + json: true, + }, (err, paymentsRes, payments) => { + const authorized = payments && payments.app === 'ESTHdCYOi18iLhhO' && ( + (payments.chargeOption && payments.chargeOption.alias === 'once') || + (payments.subscriptionOption && payments.subscriptionOption.alias === 'yearly')); + resolve(!err && paymentsRes.statusCode === 200 && authorized); + })); +}; diff --git a/src/assets/favicon.png b/src/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1f5bc2aa21f60088b4df15bf4b8df9912559900d GIT binary patch literal 56767 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%0D5itm^hdCF`fg6E!*eBf`48 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 V4;3X2U$9PsqZ9tw{BhQi{{iwwG8+H@ literal 0 HcmV?d00001 diff --git a/src/assets/logo.svg b/src/assets/logo.svg index 44db5f09..db985353 100644 --- a/src/assets/logo.svg +++ b/src/assets/logo.svg @@ -1 +1 @@ - + diff --git a/src/components/ButtonBar.vue b/src/components/ButtonBar.vue index 24a62744..84dcc233 100644 --- a/src/components/ButtonBar.vue +++ b/src/components/ButtonBar.vue @@ -1,26 +1,26 @@ @@ -60,19 +60,27 @@ export default { } .button-bar__button { - cursor: pointer; + display: block; color: rgba(0, 0, 0, 0.2); width: 26px; height: 26px; padding: 2px; margin: 3px 0; + &:active, + &:focus, &:hover { - color: rgba(0, 0, 0, 0.4); + color: rgba(0, 0, 0, 0.2); } } .button-bar__button--on { color: rgba(0, 0, 0, 0.4); + + &:active, + &:focus, + &:hover { + color: rgba(0, 0, 0, 0.4); + } } diff --git a/src/components/CodeEditor.vue b/src/components/CodeEditor.vue index dc1d0143..34658aa5 100644 --- a/src/components/CodeEditor.vue +++ b/src/components/CodeEditor.vue @@ -35,7 +35,6 @@ export default { font-family: $font-family-monospace; font-size: $font-size-monospace; font-variant-ligatures: no-common-ligatures; - white-space: pre-wrap; word-break: break-word; word-wrap: normal; height: auto; diff --git a/src/components/ExplorerNode.vue b/src/components/ExplorerNode.vue index 6f0abc45..01f20da7 100644 --- a/src/components/ExplorerNode.vue +++ b/src/components/ExplorerNode.vue @@ -22,7 +22,7 @@ import { mapMutations, mapActions } from 'vuex'; import utils from '../services/utils'; export default { - name: 'explorer-node', + name: 'explorer-node', // Required for recursivity props: ['node', 'depth'], data: () => ({ editingValue: '', diff --git a/src/components/FindReplace.vue b/src/components/FindReplace.vue new file mode 100644 index 00000000..756d1ca7 --- /dev/null +++ b/src/components/FindReplace.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/src/components/Layout.vue b/src/components/Layout.vue index 4da85037..f98419a9 100644 --- a/src/components/Layout.vue +++ b/src/components/Layout.vue @@ -1,7 +1,7 @@ diff --git a/src/components/modals/PdfExportModal.vue b/src/components/modals/PdfExportModal.vue new file mode 100644 index 00000000..8628cdf5 --- /dev/null +++ b/src/components/modals/PdfExportModal.vue @@ -0,0 +1,75 @@ + + + diff --git a/src/components/modals/PublishManagementModal.vue b/src/components/modals/PublishManagementModal.vue index 96cf02bb..cf491c4f 100644 --- a/src/components/modals/PublishManagementModal.vue +++ b/src/components/modals/PublishManagementModal.vue @@ -1,6 +1,6 @@ + + diff --git a/src/components/modals/SyncManagementModal.vue b/src/components/modals/SyncManagementModal.vue index c2181e39..28e63b8e 100644 --- a/src/components/modals/SyncManagementModal.vue +++ b/src/components/modals/SyncManagementModal.vue @@ -1,6 +1,6 @@ + + diff --git a/src/components/modals/Tab.vue b/src/components/modals/common/Tab.vue similarity index 100% rename from src/components/modals/Tab.vue rename to src/components/modals/common/Tab.vue diff --git a/src/components/modals/modalTemplate.js b/src/components/modals/common/modalTemplate.js similarity index 70% rename from src/components/modals/modalTemplate.js rename to src/components/modals/common/modalTemplate.js index a2ba5017..6e2abd52 100644 --- a/src/components/modals/modalTemplate.js +++ b/src/components/modals/common/modalTemplate.js @@ -1,5 +1,8 @@ +import ModalInner from './ModalInner'; import FormEntry from './FormEntry'; -import store from '../../store'; +import store from '../../../store'; + +const collator = new Intl.Collator(undefined, { sensitivity: 'base' }); export default (desc) => { const component = { @@ -10,6 +13,7 @@ export default (desc) => { }), components: { ...desc.components || {}, + ModalInner, FormEntry, }, computed: { @@ -49,8 +53,18 @@ export default (desc) => { }, }; if (key === 'selectedTemplate') { - component.computed.allTemplates = () => store.getters['data/allTemplates']; - component.methods.configureTemplates = () => { + component.computed.allTemplates = () => { + const allTemplates = store.getters['data/allTemplates']; + const sortedTemplates = {}; + Object.keys(allTemplates) + .sort((id1, id2) => collator.compare(allTemplates[id1].name, allTemplates[id2].name)) + .forEach((templateId) => { + sortedTemplates[templateId] = allTemplates[templateId]; + }); + return sortedTemplates; + }; + // Make use of `function` to have `this` bound to the component + component.methods.configureTemplates = function () { // eslint-disable-line func-names store.dispatch('modal/open', { type: 'templates', selectedId: this.selectedTemplate, diff --git a/src/components/modals/BloggerPagePublishModal.vue b/src/components/modals/providers/BloggerPagePublishModal.vue similarity index 81% rename from src/components/modals/BloggerPagePublishModal.vue rename to src/components/modals/providers/BloggerPagePublishModal.vue index 71173e38..575bfaa6 100644 --- a/src/components/modals/BloggerPagePublishModal.vue +++ b/src/components/modals/providers/BloggerPagePublishModal.vue @@ -1,6 +1,6 @@ diff --git a/src/components/modals/GithubPublishModal.vue b/src/components/modals/providers/GithubPublishModal.vue similarity index 85% rename from src/components/modals/GithubPublishModal.vue rename to src/components/modals/providers/GithubPublishModal.vue index 84fcd2ee..8d988d2c 100644 --- a/src/components/modals/GithubPublishModal.vue +++ b/src/components/modals/providers/GithubPublishModal.vue @@ -1,6 +1,6 @@ + - -
{{{files.0.content.html}}}
+{{#if pdf}} + +{{else}} + +{{/if}} +
{{{files.0.content.html}}}
diff --git a/src/data/styledHtmlWithTocTemplate.html b/src/data/styledHtmlWithTocTemplate.html new file mode 100644 index 00000000..dd812431 --- /dev/null +++ b/src/data/styledHtmlWithTocTemplate.html @@ -0,0 +1,28 @@ + + + + + + + {{files.0.name}} + + + +{{#if pdf}} + +{{else}} + +{{/if}} +
+
+ {{#tocToHtml files.0.content.toc 2}}{{/tocToHtml}} +
+
+
+
+ {{{files.0.content.html}}} +
+
+ + + diff --git a/src/data/welcomeFile.md b/src/data/welcomeFile.md index 3e9b7b4d..2817fa8f 100644 --- a/src/data/welcomeFile.md +++ b/src/data/welcomeFile.md @@ -1,196 +1,117 @@ # Welcome to StackEdit! -Hey! I'm your first Markdown document in **StackEdit**[^stackedit]. Don't delete me, I'm here to help! I can be recovered anyway in the **Utils** tab of the **Settings** dialog. +Hi! I'm your first Markdown file in **StackEdit**. If you want to learn about StackEdit, you can read me. If you want to play with Markdown, you can edit me. If you have finished with me, you can just create new files by opening the **file explorer** on left corner of the navigation bar. -## Documents +# Files -StackEdit stores your documents in your browser, which means all your documents are automatically saved locally and are accessible **offline!** +StackEdit stores your files in your browser, which means all your files are automatically saved locally and are accessible **offline!** > **Note:** +> +> - StackEdit can be used offline thanks to the application cache. +> - Your local files are not shared between different browsers or computers unless you use the [synchronization mechanism](#synchronization). +> - Clearing your browser's data may **delete all your files!** Make sure you sign in with Google to have all your files and settings backed up and synced. -> - StackEdit is accessible offline after the application has been loaded for the first time. -> - Your local documents are not shared between different browsers or computers. -> - Clearing your browser's data may **delete all your local documents!** Make sure your documents are synchronized with **Google Drive** or **Dropbox** (check out the [ Synchronization](#synchronization) section). +## Create files and folders -#### Create a document +The file explorer is accessible using the button in left corner of the navigation bar. You can create a new file by clicking the **New file** button in the file explorer. You can also create folders by clicking the **New folder** button. -The document panel is accessible using the button in the navigation bar. You can create a new document by clicking **New document** in the document panel. +## Switch to another file -#### Switch to another document +All your files are listed in the file explorer. You can switch from one to another by clicking a file in the list. -All your local documents are listed in the document panel. You can switch from one to another by clicking a document in the list or you can toggle documents using Ctrl+[ and Ctrl+]. +## Rename a file -#### Rename a document +You can rename the current file by clicking the file name in the navigation bar or by clicking the **Rename** button in the file explorer. -You can rename the current document by clicking the document title in the navigation bar. +## Delete a file -#### Delete a document +You can delete the current file by clicking the **Remove** button in the file explorer. The file will be moved into the **Trash** folder and automatically deleted after 7 days of inactivity. -You can delete the current document by clicking **Delete document** in the document panel. +## Export a file -#### Export a document +You can export the current file by clicking **Export to disk** in the menu. You can choose to export the file as plain Markdown, as HTML using a Handlebars template or as a PDF. -You can save the current document to a file by clicking **Export to disk** from the menu panel. -> **Tip:** Check out the [ Publish a document](#publish-a-document) section for a description of the different output formats. +# Synchronization +Synchronization is one of the biggest features of StackEdit. It enables you to synchronize any file in your workspace with other files stored in your **Google Drive**, your **Dropbox** and your **GitHub** accounts. This allows you to keep writing on other devices, collaborate with people you share the file with, integrate easily into your workflow... The synchronization mechanism takes place every minute in the background, downloading, merging, and uploading file modifications. -## Synchronization +There are two types of synchronization and they can complement each other: -StackEdit can be combined with **Google Drive** and **Dropbox** to have your documents saved in the *Cloud*. The synchronization mechanism takes care of uploading your modifications or downloading the latest version of your documents. +- The workspace synchronization will sync all your files, folders and settings automatically. This will allow you to fetch your workspace on any other device. + > To start syncing your workspace, just sign in with Google in the menu. -> **Note:** +- The file synchronization will keep one file of the workspace synced with one or multiple files in **Google Drive**, **Dropbox** or **GitHub**. + > Before starting to sync files, you must link an account in the **Synchronize** sub-menu. -> - Full access to **Google Drive** or **Dropbox** is required to be able to import any document in StackEdit. Permission restrictions can be configured in the settings. -> - Imported documents are downloaded in your browser and are not transmitted to a server. -> - If you experience problems saving your documents on Google Drive, check and optionally disable browser extensions, such as Disconnect. +## Open a file -#### Open a document +You can open a file from **Google Drive**, **Dropbox** or **GitHub** by opening the **Synchronize** sub-menu and clicking **Open from**. Once opened in the workspace, any modification in the file will be automatically synced. -You can open a document from **Google Drive** or the **Dropbox** by opening the **Synchronize** sub-menu and by clicking **Open from...**. Once opened, any modification in your document will be automatically synchronized with the file in your **Google Drive** / **Dropbox** account. +## Save a file -#### Save a document +You can save any file of the workspace to **Google Drive**, **Dropbox** or **GitHub** by opening the **Synchronize** sub-menu and clicking **Save on**. Even if a file in the workspace is already synced, you can save it to another location. StackEdit can sync one file with multiple locations and accounts. -You can save any document by opening the **Synchronize** sub-menu and by clicking **Save on...**. Even if your document is already synchronized with **Google Drive** or **Dropbox**, you can export it to a another location. StackEdit can synchronize one document with multiple locations and accounts. +## Synchronize a file -#### Synchronize a document +Once your file is linked to a synchronized location, StackEdit will periodically synchronize it by downloading/uploading any modification. A merge will be performed if necessary and conflicts will be resolved. -Once your document is linked to a **Google Drive** or a **Dropbox** file, StackEdit will periodically (every 3 minutes) synchronize it by downloading/uploading any modification. A merge will be performed if necessary and conflicts will be detected. +If you just have modified your file and you want to force syncing, click the **Synchronize now** button in the navigation bar. -If you just have modified your document and you want to force the synchronization, click the button in the navigation bar. +> **Note:** The **Synchronize now** button is disabled if you have no file to synchronize. -> **Note:** The button is disabled when you have no document to synchronize. +## Manage file synchronization -#### Manage document synchronization +Since one file can be synced with multiple locations, you can list and manage synchronized locations by clicking **File synchronization** in the **Synchronize** sub-menu. This allows you to list and remove synchronized locations that are linked to your file. -Since one document can be synchronized with multiple locations, you can list and manage synchronized locations by clicking **Manage synchronization** in the **Synchronize** sub-menu. This will let you remove synchronization locations that are associated to your document. -> **Note:** If you delete the file from **Google Drive** or from **Dropbox**, the document will no longer be synchronized with that location. +# Publication +Publishing in StackEdit makes it simple for you to publish online your files. Once you're happy with a file, you can publish it to different hosting platforms like **Blogger**, **Dropbox**, **Gist**, **GitHub**, **Google Drive**, **WordPress** and **Zendesk**. With [Handlebars templates](http://handlebarsjs.com/), you have full control over what you export. -## Publication +> Before starting to publish, you must link an account in the **Publish** sub-menu. -Once you are happy with your document, you can publish it on different websites directly from StackEdit. As for now, StackEdit can publish on **Blogger**, **Dropbox**, **Gist**, **GitHub**, **Google Drive**, **Tumblr**, **WordPress** and on any SSH server. +## Publish a File -#### Publish a document +You can publish your file by opening the **Publish** sub-menu and by clicking **Publish to**. For some locations, you can choose between the following formats: -You can publish your document by opening the **Publish** sub-menu and by choosing a website. In the dialog box, you can choose the publication format: +- Markdown: publish the Markdown text on a website that can interpret it (**GitHub** for instance), +- HTML: publish the file converted to HTML via a Handlebars template (on a blog for example). -- Markdown, to publish the Markdown text on a website that can interpret it (**GitHub** for instance), -- HTML, to publish the document converted into HTML (on a blog for example), -- Template, to have a full control of the output. +## Update a publication -> **Note:** The default template is a simple webpage wrapping your document in HTML format. You can customize it in the **Advanced** tab of the **Settings** dialog. +After publishing, StackEdit keeps your file linked to that publication which makes it easy for you to re-publish it. Once you have modified your file and you want to update your publication, click on the **Publish now** button in the navigation bar. -#### Update a publication +> **Note:** The **Publish now** button is disabled if your file has not been published yet. -After publishing, StackEdit will keep your document linked to that publication which makes it easy for you to update it. Once you have modified your document and you want to update your publication, click on the button in the navigation bar. +## Manage file publication -> **Note:** The button is disabled when your document has not been published yet. +Since one file can be published to multiple locations, you can list and manage publish locations by clicking **File publication** in the **Publish** sub-menu. This allows you to list and remove publication locations that are linked to your file. -#### Manage document publication -Since one document can be published on multiple locations, you can list and manage publish locations by clicking **Manage publication** in the menu panel. This will let you remove publication locations that are associated to your document. +# Markdown extensions -> **Note:** If the file has been removed from the website or the blog, the document will no longer be published on that location. +StackEdit extends the standard Markdown syntax by adding extra **Markdown extensions**, providing you with some nice features. +> **ProTip:** You can disable any **Markdown extension** in the **File properties** dialog. -## Markdown Extra -StackEdit supports **Markdown Extra**, which extends **Markdown** syntax with some nice features. - -> **Tip:** You can disable any **Markdown Extra** feature in the **Extensions** tab of the **Settings** dialog. - -> **Note:** You can find more information about **Markdown** syntax [here][2] and **Markdown Extra** extension [here][3]. - - -### Tables - -**Markdown Extra** has a special syntax for tables: - -Item | Value --------- | --- -Computer | $1600 -Phone | $12 -Pipe | $1 - -You can specify column alignment with one or two colons: - -| Item | Value | Qty | -| :------- | ----: | :---: | -| Computer | $1600 | 5 | -| Phone | $12 | 12 | -| Pipe | $1 | 234 | - - -### Definition Lists - -**Markdown Extra** has a special syntax for definition lists too: - -Term 1 -Term 2 -: Definition A -: Definition B - -Term 3 - -: Definition C - -: Definition D - - > part of definition D - - -### Fenced code blocks - -GitHub's fenced code blocks are also supported with **Highlight.js** syntax highlighting: - -``` -// Foo -var bar = 0; -``` - -```js -var foo = 'bar'; // baz -``` - -> **Tip:** To use **Prettify** instead of **Highlight.js**, just configure the **Markdown Extra** extension in the **Settings** dialog. - -> **Note:** You can find more information: - -> - about **Prettify** syntax highlighting [here][5], -> - about **Highlight.js** syntax highlighting [here][6]. - - -### Footnotes - -You can create footnotes like this[^footnote]. - - [^footnote]: Here is the *text* of the **footnote**. - - -### SmartyPants +## SmartyPants SmartyPants converts ASCII punctuation characters into "smart" typographic punctuation HTML entities. For example: -| | ASCII | HTML | - ----------------- | ---------------------------- | ------------------ -| Single backticks | `'Isn't this fun?'` | 'Isn't this fun?' | -| Quotes | `"Isn't this fun?"` | "Isn't this fun?" | -| Dashes | `-- is en-dash, --- is em-dash` | -- is en-dash, --- is em-dash | +| |ASCII |HTML | +|----------------|-------------------------------|-----------------------------| +|Single backticks|`'Isn't this fun?'` |'Isn't this fun?' | +|Quotes |`"Isn't this fun?"` |"Isn't this fun?" | +|Dashes |`-- is en-dash, --- is em-dash`|-- is en-dash, --- is em-dash| -### Table of contents +## KaTeX -You can insert a table of contents using the marker `[TOC]`: - -[TOC] - - -### MathJax - -You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com][1]: +You can render LaTeX mathematical expressions using [KaTeX](https://khan.github.io/KaTeX/): The *Gamma function* satisfying $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$ is via the Euler integral @@ -198,55 +119,31 @@ $$ \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. $$ -> **Tip:** To make sure mathematical expressions are rendered properly on your website, include **MathJax** into your template: +> You can find more information about **LaTeX** mathematical expressions [here](http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference). -``` - + +## UML diagrams + +You can render UML diagrams using [Mermaid](https://mermaidjs.github.io/). For example, this will produce a sequence diagram: + +```mermaid +sequenceDiagram +Alice ->> Bob: Hello Bob, how are you? +Bob-->>John: How about you John? +Bob--x Alice: I am good thanks! +Bob-x John: I am good thanks! +Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + +Bob-->Alice: Checking with John... +Alice->John: Yes... John, how are you? ``` -> **Note:** You can find more information about **LaTeX** mathematical expressions [here][4]. +And this will produce a flow chart: - -### UML diagrams - -You can also render sequence diagrams like this: - -```sequence -Alice->Bob: Hello Bob, how are you? -Note right of Bob: Bob thinks -Bob-->Alice: I am good thanks! +```mermaid +graph LR +A[Square Rect] -- Link text --> B((Circle)) +A --> C(Round Rect) +B --> D{Rhombus} +C --> D ``` - -And flow charts like this: - -```flow -st=>start: Start -e=>end -op=>operation: My Operation -cond=>condition: Yes or No? - -st->op->cond -cond(yes)->e -cond(no)->op -``` - -> **Note:** You can find more information: - -> - about **Sequence diagrams** syntax [here][7], -> - about **Flow charts** syntax [here][8]. - -### Support StackEdit - -[![](https://cdn.monetizejs.com/resources/button-32.png)](https://monetizejs.com/authorize?client_id=ESTHdCYOi18iLhhO&summary=true) - - [^stackedit]: [StackEdit](https://stackedit.io/) is a full-featured, open-source Markdown editor based on PageDown, the Markdown library used by Stack Overflow and the other Stack Exchange sites. - - - [1]: http://math.stackexchange.com/ - [2]: http://daringfireball.net/projects/markdown/syntax "Markdown" - [3]: https://github.com/jmcmanus/pagedown-extra "Pagedown Extra" - [4]: http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference - [5]: https://code.google.com/p/google-code-prettify/ - [6]: http://highlightjs.org/ - [7]: http://bramp.github.io/js-sequence-diagrams/ - [8]: http://adrai.github.io/flowchart.js/ diff --git a/src/extensions/emojiExtension.js b/src/extensions/emojiExtension.js new file mode 100644 index 00000000..7146ced4 --- /dev/null +++ b/src/extensions/emojiExtension.js @@ -0,0 +1,13 @@ +import markdownItEmoji from 'markdown-it-emoji'; +import extensionSvc from '../services/extensionSvc'; + +extensionSvc.onGetOptions((options, properties) => { + options.emoji = properties.extensions.emoji.enabled; + options.emojiShortcuts = properties.extensions.emoji.shortcuts; +}); + +extensionSvc.onInitConverter(1, (markdown, options) => { + if (options.emoji) { + markdown.use(markdownItEmoji, options.emojiShortcuts ? {} : { shortcuts: {} }); + } +}); diff --git a/src/extensions/index.js b/src/extensions/index.js index acb95064..8cf9553f 100644 --- a/src/extensions/index.js +++ b/src/extensions/index.js @@ -1,2 +1,4 @@ -import './katexExt'; -import './markdownExt'; +import './emojiExtension'; +import './katexExtension'; +import './markdownExtension'; +import './mermaidExtension'; diff --git a/src/extensions/katexExt.js b/src/extensions/katexExtension.js similarity index 100% rename from src/extensions/katexExt.js rename to src/extensions/katexExtension.js diff --git a/src/extensions/markdownExt.js b/src/extensions/markdownExtension.js similarity index 98% rename from src/extensions/markdownExt.js rename to src/extensions/markdownExtension.js index 73b0c023..4fb2b9ed 100644 --- a/src/extensions/markdownExt.js +++ b/src/extensions/markdownExtension.js @@ -178,9 +178,9 @@ extensionSvc.onInitConverter(0, (markdown, options) => { extensionSvc.onSectionPreview((elt) => { elt.querySelectorAll('.prism').cl_each((prismElt) => { - if (!prismElt.highlighted) { + if (!prismElt.highlightedWithPrism) { Prism.highlightElement(prismElt); + prismElt.highlightedWithPrism = true; } - prismElt.highlighted = true; }); }); diff --git a/src/extensions/mermaidExtension.js b/src/extensions/mermaidExtension.js new file mode 100644 index 00000000..5a06bd02 --- /dev/null +++ b/src/extensions/mermaidExtension.js @@ -0,0 +1,122 @@ +import mermaidUtils from 'mermaid/src/utils'; +import flowRenderer from 'mermaid/src/diagrams/flowchart/flowRenderer'; +import seq from 'mermaid/src/diagrams/sequenceDiagram/sequenceRenderer'; +import info from 'mermaid/src/diagrams/example/exampleRenderer'; +import gantt from 'mermaid/src/diagrams/gantt/ganttRenderer'; +import classRenderer from 'mermaid/src/diagrams/classDiagram/classRenderer'; +import gitGraphRenderer from 'mermaid/src/diagrams/gitGraph/gitGraphRenderer'; +import extensionSvc from '../services/extensionSvc'; +import utils from '../services/utils'; + +const config = { + logLevel: 5, + startOnLoad: false, + arrowMarkerAbsolute: false, + flowchart: { + htmlLabels: true, + useMaxWidth: true, + }, + sequenceDiagram: { + diagramMarginX: 50, + diagramMarginY: 10, + actorMargin: 50, + width: 150, + height: 65, + boxMargin: 10, + boxTextMargin: 5, + noteMargin: 10, + messageMargin: 35, + mirrorActors: true, + bottomMarginAdj: 1, + useMaxWidth: true, + }, + gantt: { + titleTopMargin: 25, + barHeight: 20, + barGap: 4, + topPadding: 50, + leftPadding: 75, + gridLineStartPadding: 35, + fontSize: 11, + fontFamily: '"Open-Sans", "sans-serif"', + numberSectionStyles: 3, + axisFormatter: [ + // Within a day + ['%I:%M', d => d.getHours()], + // Monday a week + ['w. %U', d => d.getDay() === 1], + // Day within a week (not monday) + ['%a %d', d => d.getDay() && d.getDate() !== 1], + // within a month + ['%b %d', d => d.getDate() !== 1], + // Month + ['%m-%y', d => d.getMonth()], + ], + }, + classDiagram: {}, + gitGraph: {}, + info: {}, +}; + +const containerElt = document.createElement('div'); +containerElt.className = 'hidden-rendering-container'; +document.body.appendChild(containerElt); + +const render = (elt) => { + const svgId = `mermaid-svg-${utils.uid()}`; + const txt = elt.textContent; + containerElt.innerHTML = `
`; + + try { + const graphType = mermaidUtils.detectType(txt); + switch (graphType) { + case 'gitGraph': + config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + gitGraphRenderer.setConf(config.gitGraph); + gitGraphRenderer.draw(txt, svgId, false); + break; + case 'graph': + config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + flowRenderer.setConf(config.flowchart); + flowRenderer.draw(txt, svgId, false); + break; + case 'dotGraph': + config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + flowRenderer.setConf(config.flowchart); + flowRenderer.draw(txt, svgId, true); + break; + case 'sequenceDiagram': + config.sequenceDiagram.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + seq.setConf(config.sequenceDiagram); + seq.draw(txt, svgId); + break; + case 'gantt': + config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + gantt.setConf(config.gantt); + gantt.draw(txt, svgId); + break; + case 'classDiagram': + config.classDiagram.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + classRenderer.setConf(config.classDiagram); + classRenderer.draw(txt, svgId); + break; + case 'info': + default: + config.info.arrowMarkerAbsolute = config.arrowMarkerAbsolute; + info.draw(txt, svgId, 'Unknown'); + break; + } + elt.parentNode.replaceChild(containerElt.firstChild, elt); + } catch (e) { + console.error(e); // eslint-disable-line no-console + } +}; + +extensionSvc.onGetOptions((options, properties) => { + options.mermaid = properties.extensions.mermaid.enabled; +}); + +extensionSvc.onSectionPreview((elt) => { + elt.querySelectorAll('.prism.language-mermaid').cl_each( + diagramElt => render(diagramElt.parentNode)); +}); diff --git a/src/icons/File.vue b/src/icons/File.vue new file mode 100644 index 00000000..5ed84566 --- /dev/null +++ b/src/icons/File.vue @@ -0,0 +1,5 @@ + diff --git a/src/icons/index.js b/src/icons/index.js index 86240515..76ef6ccf 100644 --- a/src/icons/index.js +++ b/src/icons/index.js @@ -40,6 +40,7 @@ import Information from './Information'; import Alert from './Alert'; import SignalOff from './SignalOff'; import Folder from './Folder'; +import File from './File'; import ScrollSync from './ScrollSync'; import Printer from './Printer'; import Undo from './Undo'; @@ -87,6 +88,7 @@ Vue.component('iconInformation', Information); Vue.component('iconAlert', Alert); Vue.component('iconSignalOff', SignalOff); Vue.component('iconFolder', Folder); +Vue.component('iconFile', File); Vue.component('iconScrollSync', ScrollSync); Vue.component('iconPrinter', Printer); Vue.component('iconUndo', Undo); diff --git a/src/index.js b/src/index.js index c3822e38..5a0c4088 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,6 @@ import Vue from 'vue'; +import 'babel-polyfill'; +import 'indexeddbshim/dist/indexeddbshim'; import * as OfflinePluginRuntime from 'offline-plugin/runtime'; import './extensions/'; import './services/optional'; @@ -7,6 +9,10 @@ import App from './components/App'; import store from './store'; import localDbSvc from './services/localDbSvc'; +if (!indexedDB) { + throw new Error('Your browser is not supported. Please upgrade to the latest version.'); +} + if (NODE_ENV === 'production') { OfflinePluginRuntime.install({ onUpdateReady: () => { diff --git a/src/libs/cleditCore.js b/src/libs/cleditCore.js index bbf9f8cd..48dba65f 100644 --- a/src/libs/cleditCore.js +++ b/src/libs/cleditCore.js @@ -103,12 +103,13 @@ function cledit(contentElt, scrollElt, windowParam) { selectionMgr.updateCursorCoordinates(true) } - function replaceAll(search, replacement) { + function replaceAll(search, replacement, startOffset = 0) { undoMgr.setDefaultMode('single') - var textContent = getTextContent() - var value = textContent.replace(search, replacement) - if (value !== textContent) { - var offset = editor.setContent(value) + var text = getTextContent() + var subtext = getTextContent().slice(startOffset); + var value = subtext.replace(search, replacement) + if (value !== subtext) { + var offset = editor.setContent(text.slice(0, startOffset) + value); selectionMgr.setSelectionStartEnd(offset.end, offset.end) selectionMgr.updateCursorCoordinates(true) } @@ -130,7 +131,7 @@ function cledit(contentElt, scrollElt, windowParam) { var triggerSpellCheck = debounce(function () { var selection = editor.$window.getSelection() - if (!selectionMgr.hasFocus || highlighter.isComposing || selectionMgr.selectionStart !== selectionMgr.selectionEnd || !selection.modify) { + if (!selectionMgr.hasFocus() || highlighter.isComposing || selectionMgr.selectionStart !== selectionMgr.selectionEnd || !selection.modify) { return } // Hack for Chrome to trigger the spell checker @@ -209,7 +210,8 @@ function cledit(contentElt, scrollElt, windowParam) { if (!editor.$window.document.contains(contentElt)) { watcher.stopWatching() editor.$window.removeEventListener('keydown', windowKeydownListener) - editor.$window.removeEventListener('mouseup', windowMouseupListener) + editor.$window.removeEventListener('mousedown', windowMouseListener) + editor.$window.removeEventListener('mouseup', windowMouseListener) editor.$trigger('destroy') return true } @@ -226,12 +228,13 @@ function cledit(contentElt, scrollElt, windowParam) { editor.$window.addEventListener('keydown', windowKeydownListener, false) // Mouseup can happen outside the editor element - function windowMouseupListener() { + function windowMouseListener() { if (!tryDestroy()) { selectionMgr.saveSelectionState(true, false) } } - editor.$window.addEventListener('mouseup', windowMouseupListener) + editor.$window.addEventListener('mousedown', windowMouseListener) + editor.$window.addEventListener('mouseup', windowMouseListener) // This can also provoke selection changes and does not fire mouseup event on Chrome/OSX contentElt.addEventListener('contextmenu', selectionMgr.saveSelectionState.cl_bind(selectionMgr, true, false)) @@ -297,12 +300,10 @@ function cledit(contentElt, scrollElt, windowParam) { }, false) contentElt.addEventListener('focus', function () { - selectionMgr.hasFocus = true editor.$trigger('focus') }, false) contentElt.addEventListener('blur', function () { - selectionMgr.hasFocus = false editor.$trigger('blur') }, false) diff --git a/src/libs/cleditSelectionMgr.js b/src/libs/cleditSelectionMgr.js index 93dd1d9d..e5acbe9a 100644 --- a/src/libs/cleditSelectionMgr.js +++ b/src/libs/cleditSelectionMgr.js @@ -49,12 +49,16 @@ function SelectionMgr(editor) { this.$trigger('cursorCoordinatesChanged', coordinates) } if (adjustScroll) { - var adjustment = scrollElt.clientHeight / 2 * editor.options.getCursorFocusRatio() + var scrollEltHeight = scrollElt.clientHeight; + if (typeof adjustScroll === 'number') { + scrollEltHeight -= adjustScroll + } + var adjustment = scrollEltHeight / 2 * editor.options.getCursorFocusRatio() var cursorTop = this.cursorCoordinates.top + this.cursorCoordinates.height / 2 // Adjust cursorTop with contentElt position relative to scrollElt cursorTop += contentElt.getBoundingClientRect().top - scrollElt.getBoundingClientRect().top + scrollElt.scrollTop; var minScrollTop = cursorTop - adjustment - var maxScrollTop = cursorTop + adjustment - scrollElt.clientHeight + var maxScrollTop = cursorTop + adjustment - scrollEltHeight if (scrollElt.scrollTop > minScrollTop) { scrollElt.scrollTop = minScrollTop } else if (scrollElt.scrollTop < maxScrollTop) { @@ -84,6 +88,10 @@ function SelectionMgr(editor) { } } + this.hasFocus = function() { + return contentElt === editor.$document.activeElement; + } + this.restoreSelection = function () { var min = Math.min(this.selectionStart, this.selectionEnd) var max = Math.max(this.selectionStart, this.selectionEnd) @@ -132,9 +140,9 @@ function SelectionMgr(editor) { saveLastSelection() } - this.setSelectionStartEnd = function (start, end, focus) { + this.setSelectionStartEnd = function (start, end) { setSelection(start, end) - return focus !== false && this.restoreSelection() + return this.hasFocus() && this.restoreSelection() } this.saveSelectionState = (function () { @@ -230,48 +238,50 @@ function SelectionMgr(editor) { } function save() { - var selectionStart = self.selectionStart - var selectionEnd = self.selectionEnd - var selection = editor.$window.getSelection() var result - if (selection.rangeCount > 0) { - var selectionRange = selection.getRangeAt(0) - var node = selectionRange.startContainer - if ((contentElt.compareDocumentPosition(node) & window.Node.DOCUMENT_POSITION_CONTAINED_BY) || contentElt === node) { - var offset = selectionRange.startOffset - if (node.firstChild && offset > 0) { - node = node.childNodes[offset - 1] - offset = node.textContent.length - } - var container = node - while (node !== contentElt) { - while ((node = node.previousSibling)) { - offset += (node.textContent || '').length + if (self.hasFocus()) { + var selectionStart = self.selectionStart + var selectionEnd = self.selectionEnd + var selection = editor.$window.getSelection() + if (selection.rangeCount > 0) { + var selectionRange = selection.getRangeAt(0) + var node = selectionRange.startContainer + if ((contentElt.compareDocumentPosition(node) & window.Node.DOCUMENT_POSITION_CONTAINED_BY) || contentElt === node) { + var offset = selectionRange.startOffset + if (node.firstChild && offset > 0) { + node = node.childNodes[offset - 1] + offset = node.textContent.length + } + var container = node + while (node !== contentElt) { + while ((node = node.previousSibling)) { + offset += (node.textContent || '').length + } + node = container = container.parentNode + } + var selectionText = selectionRange + '' + // Fix end of line when only br is selected + var brElt = selectionRange.endContainer.firstChild + if (brElt && brElt.tagName === 'BR' && selectionRange.endOffset === 1) { + selectionText += '\n' + } + if (comparePoints(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset) === 1) { + selectionStart = offset + selectionText.length + selectionEnd = offset + } else { + selectionStart = offset + selectionEnd = offset + selectionText.length } - node = container = container.parentNode - } - var selectionText = selectionRange + '' - // Fix end of line when only br is selected - var brElt = selectionRange.endContainer.firstChild - if (brElt && brElt.tagName === 'BR' && selectionRange.endOffset === 1) { - selectionText += '\n' - } - if (comparePoints(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset) === 1) { - selectionStart = offset + selectionText.length - selectionEnd = offset - } else { - selectionStart = offset - selectionEnd = offset + selectionText.length - } - if (selectionStart === selectionEnd && selectionStart === editor.getContent().length) { - // If cursor is after the trailingNode - selectionStart = --selectionEnd - result = self.setSelectionStartEnd(selectionStart, selectionEnd) - } else { - setSelection(selectionStart, selectionEnd) - result = checkSelection(selectionRange) - result = result || lastSelectionStart !== self.selectionStart // selectionRange doesn't change when selection is at the start of a section + if (selectionStart === selectionEnd && selectionStart === editor.getContent().length) { + // If cursor is after the trailingNode + selectionStart = --selectionEnd + result = self.setSelectionStartEnd(selectionStart, selectionEnd) + } else { + setSelection(selectionStart, selectionEnd) + result = checkSelection(selectionRange) + result = result || lastSelectionStart !== self.selectionStart // selectionRange doesn't change when selection is at the start of a section + } } } } diff --git a/src/services/diffUtils.js b/src/services/diffUtils.js index f78f8da1..2d69fd4e 100644 --- a/src/services/diffUtils.js +++ b/src/services/diffUtils.js @@ -103,7 +103,7 @@ function mergeText(serverText, clientText, lastMergedText) { const lastMergedTextDiffs = diffMatchPatch.diff_main(lastMergedText, intersectionText) // Keep only equalities and deletions .filter(diff => diff[0] !== DiffMatchPatch.DIFF_INSERT); - diffMatchPatch.diff_cleanupSemantic(serverClientDiffs); + diffMatchPatch.diff_cleanupSemantic(lastMergedTextDiffs); // Make a patch with deletions only const patches = diffMatchPatch.patch_make(lastMergedText, lastMergedTextDiffs); // Apply patch to fusion text diff --git a/src/services/editorEngineSvc.js b/src/services/editorEngineSvc.js index a177d08a..800b8af6 100644 --- a/src/services/editorEngineSvc.js +++ b/src/services/editorEngineSvc.js @@ -131,24 +131,25 @@ export default { initClEditor(opts) { const content = store.getters['content/current']; if (content) { - const options = Object.assign({}, opts); + const contentState = store.getters['contentState/current']; + const options = Object.assign({ + selectionStart: contentState.selectionStart, + selectionEnd: contentState.selectionEnd, + patchHandler: { + makePatches, + applyPatches, + reversePatches, + }, + }, opts); if (contentId !== content.id) { contentId = content.id; currentPatchableText = diffUtils.makePatchableText(content, markerKeys, markerIdxMap); previousPatchableText = currentPatchableText; syncDiscussionMarkers(content, false); - const contentState = store.getters['contentState/current']; options.content = content.text; - options.selectionStart = contentState.selectionStart; - options.selectionEnd = contentState.selectionEnd; } - options.patchHandler = { - makePatches, - applyPatches, - reversePatches, - }; clEditor.init(options); } }, diff --git a/src/services/editorSvc.js b/src/services/editorSvc.js index 43cd3f84..892f6d75 100644 --- a/src/services/editorSvc.js +++ b/src/services/editorSvc.js @@ -208,6 +208,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b let insertBeforePreviewElt = this.previewElt.firstChild; let insertBeforeTocElt = this.tocElt.firstChild; let previewHtml = ''; + let loadingImages = []; this.conversionCtx.htmlSectionDiff.forEach((item) => { for (let i = 0; i < item[1].length; i += 1) { const section = this.conversionCtx.sectionList[sectionIdx]; @@ -218,9 +219,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b newSectionDescList.push(sectionDesc); previewHtml += sectionDesc.html; sectionIdx += 1; - insertBeforePreviewElt.classList.remove('modified'); insertBeforePreviewElt = insertBeforePreviewElt.nextSibling; - insertBeforeTocElt.classList.remove('modified'); insertBeforeTocElt = insertBeforeTocElt.nextSibling; } else if (item[0] === -1) { sectionDescIdx += 1; @@ -236,7 +235,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b // Create preview section element sectionPreviewElt = document.createElement('div'); - sectionPreviewElt.className = 'cl-preview-section modified'; + sectionPreviewElt.className = 'cl-preview-section'; sectionPreviewElt.innerHTML = html; if (insertBeforePreviewElt) { this.previewElt.insertBefore(sectionPreviewElt, insertBeforePreviewElt); @@ -244,10 +243,14 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b this.previewElt.appendChild(sectionPreviewElt); } extensionSvc.sectionPreview(sectionPreviewElt, this.options); + loadingImages = [ + ...loadingImages, + ...Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('img')), + ]; // Create TOC section element sectionTocElt = document.createElement('div'); - sectionTocElt.className = 'cl-toc-section modified'; + sectionTocElt.className = 'cl-toc-section'; const headingElt = sectionPreviewElt.querySelector('h1, h2, h3, h4, h5, h6'); if (headingElt) { const clonedElt = headingElt.cloneNode(true); @@ -278,8 +281,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b ]('toc-tab--empty'); // Run preview async operations (image loading, mathjax...) - const loadingImages = this.previewElt.querySelectorAll('.cl-preview-section.modified img'); - const loadedPromises = loadingImages.cl_map(imgElt => new Promise((resolve) => { + const loadedPromises = loadingImages.map(imgElt => new Promise((resolve) => { if (!imgElt.src) { resolve(); return; @@ -392,7 +394,7 @@ const editorSvc = Object.assign(new Vue(), { // Use a vue instance as an event b const editorEndOffset = editorSvc.getEditorOffset(previewSelectionEndOffset); if (editorStartOffset !== undefined && editorEndOffset !== undefined) { editorEngineSvc.clEditor.selectionMgr.setSelectionStartEnd( - editorStartOffset, editorEndOffset, false); + editorStartOffset, editorEndOffset); } } editorSvc.previewSelectionRange = range; diff --git a/src/services/exportSvc.js b/src/services/exportSvc.js index 1a085516..ba5e2d01 100644 --- a/src/services/exportSvc.js +++ b/src/services/exportSvc.js @@ -34,6 +34,10 @@ function groupHeadings(headings, level = 1) { return result; } +const containerElt = document.createElement('div'); +containerElt.className = 'hidden-rendering-container'; +document.body.appendChild(containerElt); + export default { /** * Apply the template to the file content @@ -41,7 +45,7 @@ export default { applyTemplate(fileId, template = { value: '{{{files.0.content.text}}}', helpers: '', - }) { + }, pdf = false) { const file = store.state.file.itemMap[fileId]; return localDbSvc.loadItem(`${fileId}/content`) .then((content) => { @@ -51,19 +55,19 @@ export default { const parsingCtx = markdownConversionSvc.parseSections(converter, content.text); const conversionCtx = markdownConversionSvc.convert(parsingCtx); const html = conversionCtx.htmlSectionList.map(htmlSanitizer.sanitizeHtml).join(''); - const elt = document.createElement('div'); - elt.innerHTML = html; + containerElt.innerHTML = html; + extensionSvc.sectionPreview(containerElt, options); // Unwrap tables - elt.querySelectorAll('.table-wrapper').cl_each((wrapperElt) => { + containerElt.querySelectorAll('.table-wrapper').cl_each((wrapperElt) => { while (wrapperElt.firstChild) { - wrapperElt.parentNode.appendChild(wrapperElt.firstChild); + wrapperElt.parentNode.insertBefore(wrapperElt.firstChild, wrapperElt.nextSibling); } wrapperElt.parentNode.removeChild(wrapperElt); }); // Make TOC - const headings = elt.querySelectorAll('h1,h2,h3,h4,h5,h6').cl_map(headingElt => ({ + const headings = containerElt.querySelectorAll('h1,h2,h3,h4,h5,h6').cl_map(headingElt => ({ title: headingElt.textContent, anchor: headingElt.id, level: parseInt(headingElt.tagName.slice(1), 10), @@ -71,17 +75,19 @@ export default { })); const toc = groupHeadings(headings); const view = { + pdf, files: [{ name: file.name, content: { text: content.text, properties, yamlProperties: content.properties, - html: elt.innerHTML, + html: containerElt.innerHTML, toc, }, }], }; + containerElt.innerHTML = ''; // Run template conversion in a Worker to prevent attacks from helpers const worker = new TemplateWorker(); @@ -111,8 +117,8 @@ export default { exportToDisk(fileId, type, template) { const file = store.state.file.itemMap[fileId]; return this.applyTemplate(fileId, template) - .then((res) => { - const blob = new Blob([res], { + .then((html) => { + const blob = new Blob([html], { type: 'text/plain;charset=utf-8', }); FileSaver.saveAs(blob, `${file.name}.${type}`); diff --git a/src/services/localDbSvc.js b/src/services/localDbSvc.js index f45829b1..3a2de9ac 100644 --- a/src/services/localDbSvc.js +++ b/src/services/localDbSvc.js @@ -1,21 +1,18 @@ -import 'babel-polyfill'; -import 'indexeddbshim/dist/indexeddbshim'; import FileSaver from 'file-saver'; import utils from './utils'; import store from '../store'; import welcomeFile from '../data/welcomeFile.md'; -const indexedDB = window.indexedDB; const dbVersion = 1; const dbVersionKey = `${utils.workspaceId}/localDbVersion`; const dbStoreName = 'objects'; const exportBackup = utils.queryParams.exportBackup; - -if (!indexedDB) { - throw new Error('Your browser is not supported. Please upgrade to the latest version.'); +if (exportBackup) { + location.hash = ''; } const deleteMarkerMaxAge = 1000; +const checkSponsorshipAfter = (5 * 60 * 1000) + (30 * 1000); // tokenExpirationMargin + 30 sec class Connection { constructor() { @@ -328,6 +325,15 @@ localDbSvc.sync() // Set the ready flag store.commit('setReady'); + // Save welcome file content hash if not done already + const hash = utils.hash(welcomeFile); + const welcomeFileHashes = store.getters['data/welcomeFileHashes']; + if (!welcomeFileHashes[hash]) { + store.dispatch('data/patchWelcomeFileHashes', { + [hash]: 1, + }); + } + // If app was last opened 7 days ago and synchronization is off if (!store.getters['data/loginToken'] && (utils.lastOpened + utils.cleanTrashAfter < Date.now()) @@ -338,6 +344,21 @@ localDbSvc.sync() .forEach(file => store.dispatch('deleteFile', file.id)); } + // Enable sponsorship + if (utils.queryParams.paymentSuccess) { + location.hash = ''; + store.dispatch('modal/paymentSuccess'); + const loginToken = store.getters['data/loginToken']; + // Force check sponsorship after a few seconds + const currentDate = Date.now(); + if (loginToken && loginToken.expiresOn > currentDate - checkSponsorshipAfter) { + store.dispatch('data/setGoogleToken', { + ...loginToken, + expiresOn: currentDate - checkSponsorshipAfter, + }); + } + } + // watch file changing store.watch( () => store.getters['file/current'].id, diff --git a/src/services/networkSvc.js b/src/services/networkSvc.js index 74e74d9b..91e1f3c1 100644 --- a/src/services/networkSvc.js +++ b/src/services/networkSvc.js @@ -93,6 +93,7 @@ export default { resolve({ accessToken: data.access_token, code: data.code, + idToken: data.id_token, expiresIn: data.expires_in, }); } @@ -107,7 +108,7 @@ export default { }, request(configParam, offlineCheck = false) { let retryAfter = 500; // 500 ms - const maxRetryAfter = 30 * 1000; // 30 sec + const maxRetryAfter = 10 * 1000; // 10 sec const config = Object.assign({}, configParam); config.timeout = config.timeout || networkTimeout; config.headers = Object.assign({}, config.headers); @@ -151,9 +152,9 @@ export default { const result = { status: xhr.status, headers: parseHeaders(xhr), - body: xhr.responseText, + body: config.blob ? xhr.response : xhr.responseText, }; - if (!config.raw) { + if (!config.raw && !config.blob) { try { result.body = JSON.parse(result.body); } catch (e) { @@ -197,6 +198,9 @@ export default { xhr.setRequestHeader(key, `${value}`); } }); + if (config.blob) { + xhr.responseType = 'blob'; + } xhr.send(config.body || null); }) .catch((err) => { @@ -227,17 +231,18 @@ function checkOffline() { new Promise((resolve, reject) => { const script = document.createElement('script'); let timeout; - const cleaner = (cb, res) => () => { + let clean = (cb) => { clearTimeout(timeout); - cb(res); document.head.removeChild(script); + clean = () => {}; // Prevent from cleaning several times + cb(); }; - script.onload = cleaner(resolve); - script.onerror = cleaner(reject); + script.onload = () => clean(resolve); + script.onerror = () => clean(reject); script.src = `https://apis.google.com/js/api.js?${Date.now()}`; try { document.head.appendChild(script); // This can fail with bad network - timeout = setTimeout(cleaner(reject), networkTimeout); + timeout = setTimeout(() => clean(reject), networkTimeout); } catch (e) { reject(e); } diff --git a/src/services/optional/shortcuts.js b/src/services/optional/shortcuts.js index ed7beb9e..94c0a167 100644 --- a/src/services/optional/shortcuts.js +++ b/src/services/optional/shortcuts.js @@ -13,6 +13,15 @@ const pagedownHandler = name => () => { return true; }; +const findReplaceOpener = type => () => { + store.dispatch('findReplace/open', { + type, + findText: editorEngineSvc.clEditor.selectionMgr.hasFocus() && + editorEngineSvc.clEditor.selectionMgr.getSelectedText(), + }); + return true; +}; + const methods = { bold: pagedownHandler('bold'), italic: pagedownHandler('italic'), @@ -30,13 +39,15 @@ const methods = { } return true; }, + find: findReplaceOpener('find'), + replace: findReplaceOpener('replace'), expand(param1, param2) { const text = `${param1 || ''}`; const replacement = `${param2 || ''}`; if (text && replacement) { setTimeout(() => { const selectionMgr = editorEngineSvc.clEditor.selectionMgr; - let offset = editorEngineSvc.clEditor.selectionMgr.selectionStart; + let offset = selectionMgr.selectionStart; if (offset === selectionMgr.selectionEnd) { const range = selectionMgr.createRange(offset - text.length, offset); if (`${range}` === text) { diff --git a/src/services/providers/githubProvider.js b/src/services/providers/githubProvider.js index 13c4dbc7..ff5ce3b8 100644 --- a/src/services/providers/githubProvider.js +++ b/src/services/providers/githubProvider.js @@ -2,6 +2,7 @@ import store from '../../store'; import githubHelper from './helpers/githubHelper'; import providerUtils from './providerUtils'; import providerRegistry from './providerRegistry'; +import utils from '../utils'; const savedSha = {}; @@ -65,6 +66,55 @@ export default providerRegistry.register({ }) .then(() => publishLocation); }, + openFile(token, syncLocation) { + return Promise.resolve() + .then(() => { + if (providerUtils.openFileWithLocation(store.getters['syncLocation/items'], syncLocation)) { + // File exists and has just been opened. Next... + return null; + } + // Download content from GitHub and create the file + return this.downloadContent(token, syncLocation) + .then((content) => { + const id = utils.uid(); + delete content.history; + store.commit('content/setItem', { + ...content, + id: `${id}/content`, + }); + let name = syncLocation.path; + const slashPos = name.lastIndexOf('/'); + if (slashPos > -1 && slashPos < name.length - 1) { + name = name.slice(slashPos + 1); + } + const dotPos = name.lastIndexOf('.'); + if (dotPos > 0 && slashPos < name.length) { + name = name.slice(0, dotPos); + } + store.commit('file/setItem', { + id, + name: utils.sanitizeName(name), + parentId: store.getters['file/current'].parentId, + }); + store.commit('syncLocation/setItem', { + ...syncLocation, + id: utils.uid(), + fileId: id, + }); + store.commit('file/setCurrentId', id); + store.dispatch('notification/info', `${store.getters['file/current'].name} was imported from GitHub.`); + }, () => { + store.dispatch('notification/error', `Could not open file ${syncLocation.path}.`); + }); + }); + }, + parseRepoUrl(url) { + const parsedRepo = url.match(/[/:]?([^/:]+)\/([^/]+?)(?:\.git|\/)?$/); + return parsedRepo && { + owner: parsedRepo[1], + repo: parsedRepo[2], + }; + }, makeLocation(token, owner, repo, branch, path) { return { providerId: this.id, diff --git a/src/services/providers/helpers/googleHelper.js b/src/services/providers/helpers/googleHelper.js index c022242a..545ad6dc 100644 --- a/src/services/providers/helpers/googleHelper.js +++ b/src/services/providers/helpers/googleHelper.js @@ -19,6 +19,16 @@ const photosScopes = ['https://www.googleapis.com/auth/photos']; const libraries = ['picker']; +const checkIdToken = (idToken) => { + try { + const token = idToken.split('.'); + const payload = JSON.parse(utils.decodeBase64(token[1])); + return payload.aud === clientId && Date.now() + tokenExpirationMargin < payload.exp * 1000; + } catch (e) { + return false; + } +}; + export default { request(token, options) { return networkSvc.request({ @@ -37,7 +47,7 @@ export default { expiresOn: 0, }); // Refresh token and retry - return this.refreshToken(token.scopes, token) + return this.refreshToken(token, token.scopes) .then(refreshedToken => this.request(refreshedToken, options)); } throw err; @@ -115,11 +125,12 @@ export default { return networkSvc.startOauth2( 'https://accounts.google.com/o/oauth2/v2/auth', { client_id: clientId, - response_type: 'token', - scope: ['openid', ...scopes].join(' '), // Need openid for user info + response_type: 'token id_token', + scope: ['openid', ...scopes].join(' '), hd: appsDomain, login_hint: sub, prompt: silent ? 'none' : null, + nonce: utils.uid(), }, silent) // Call the token info endpoint .then(data => networkSvc.request({ @@ -142,9 +153,11 @@ export default { scopes, accessToken: data.accessToken, expiresOn: Date.now() + (data.expiresIn * 1000), + idToken: data.idToken, sub: `${res.body.sub}`, isLogin: !store.getters['data/loginToken'] && scopes.indexOf('https://www.googleapis.com/auth/drive.appdata') !== -1, + isSponsor: false, isDrive: scopes.indexOf('https://www.googleapis.com/auth/drive') !== -1 || scopes.indexOf('https://www.googleapis.com/auth/drive.file') !== -1, isBlogger: scopes.indexOf('https://www.googleapis.com/auth/blogger') !== -1, @@ -163,19 +176,33 @@ export default { // That's no problem, token will be refreshed later with merged scopes. // Save flags token.isLogin = existingToken.isLogin || token.isLogin; + token.isSponsor = existingToken.isSponsor; token.isDrive = existingToken.isDrive || token.isDrive; token.isBlogger = existingToken.isBlogger || token.isBlogger; token.isPhotos = existingToken.isPhotos || token.isPhotos; token.driveFullAccess = existingToken.driveFullAccess || token.driveFullAccess; - // Save nextPageToken token.nextPageToken = existingToken.nextPageToken; } + return token.isLogin && networkSvc.request({ + method: 'GET', + url: 'userInfo', + params: { + idToken: token.idToken, + }, + }) + .then((res) => { + token.isSponsor = res.body.sponsorUntil > Date.now(); + }, () => { + // Ignore error + }); + }) + .then(() => { // Add token to googleTokens store.dispatch('data/setGoogleToken', token); return token; })); }, - refreshToken(scopes, token) { + refreshToken(token, scopes = []) { const sub = token.sub; const lastToken = store.getters['data/googleTokens'][sub]; const mergedScopes = [...new Set([ @@ -185,8 +212,13 @@ export default { return Promise.resolve() .then(() => { - if (mergedScopes.length === lastToken.scopes.length && - lastToken.expiresOn > Date.now() + tokenExpirationMargin + if ( + // If we already have permissions for the requested scopes + mergedScopes.length === lastToken.scopes.length && + // And lastToken is not expired + lastToken.expiresOn > Date.now() + tokenExpirationMargin && + // And in case of a login token, ID token is still valid + (!lastToken.isLogin || checkIdToken(lastToken.idToken)) ) { return lastToken; } @@ -222,6 +254,16 @@ export default { google = window.google; }); }, + getSponsorship(token) { + return this.refreshToken(token) + .then(refreshedToken => networkSvc.request({ + method: 'GET', + url: 'userInfo', + params: { + idToken: refreshedToken.idToken, + }, + }, true)); + }, signin() { return this.startOauth2(driveAppDataScopes); }, @@ -238,7 +280,7 @@ export default { const result = { changes: [], }; - return this.refreshToken(driveAppDataScopes, token) + return this.refreshToken(token, driveAppDataScopes) .then((refreshedToken) => { const getPage = (pageToken = '1') => this.request(refreshedToken, { method: 'GET', @@ -262,25 +304,25 @@ export default { }); }, uploadFile(token, name, parents, media, mediaType, fileId, ifNotTooLate) { - return this.refreshToken(getDriveScopes(token), token) + return this.refreshToken(token, getDriveScopes(token)) .then(refreshedToken => this.uploadFileInternal( refreshedToken, name, parents, media, mediaType, fileId, ifNotTooLate)); }, uploadAppDataFile(token, name, parents, media, fileId, ifNotTooLate) { - return this.refreshToken(driveAppDataScopes, token) + return this.refreshToken(token, driveAppDataScopes) .then(refreshedToken => this.uploadFileInternal( refreshedToken, name, parents, media, undefined, fileId, ifNotTooLate)); }, downloadFile(token, id) { - return this.refreshToken(getDriveScopes(token), token) + return this.refreshToken(token, getDriveScopes(token)) .then(refreshedToken => this.downloadFileInternal(refreshedToken, id)); }, downloadAppDataFile(token, id) { - return this.refreshToken(driveAppDataScopes, token) + return this.refreshToken(token, driveAppDataScopes) .then(refreshedToken => this.downloadFileInternal(refreshedToken, id)); }, removeAppDataFile(token, id, ifNotTooLate = cb => res => cb(res)) { - return this.refreshToken(driveAppDataScopes, token) + return this.refreshToken(token, driveAppDataScopes) // Refreshing a token can take a while if an oauth window pops up, so check if it's too late .then(ifNotTooLate(refreshedToken => this.request(refreshedToken, { method: 'DELETE', @@ -290,7 +332,7 @@ export default { uploadBlogger( token, blogUrl, blogId, postId, title, content, labels, isDraft, published, isPage, ) { - return this.refreshToken(bloggerScopes, token) + return this.refreshToken(token, bloggerScopes) .then(refreshedToken => Promise.resolve() .then(() => { if (blogId) { @@ -356,7 +398,7 @@ export default { openPicker(token, type = 'doc') { const scopes = type === 'img' ? photosScopes : getDriveScopes(token); return this.loadClientScript() - .then(() => this.refreshToken(scopes, token)) + .then(() => this.refreshToken(token, scopes)) .then(refreshedToken => new Promise((resolve) => { let picker; const pickerBuilder = new google.picker.PickerBuilder() diff --git a/src/services/sponsorSvc.js b/src/services/sponsorSvc.js new file mode 100644 index 00000000..c6f75462 --- /dev/null +++ b/src/services/sponsorSvc.js @@ -0,0 +1,53 @@ +import store from '../store'; +import networkSvc from './networkSvc'; +import utils from './utils'; + +const checkPaymentEvery = 15 * 60 * 1000; // 15 min +let lastCheck = 0; + +const appId = 'ESTHdCYOi18iLhhO'; +let monetize; + +const getMonetize = () => Promise.resolve() + .then(() => networkSvc.loadScript('https://cdn.monetizejs.com/api/js/latest/monetize.min.js')) + .then(() => { + monetize = monetize || new window.MonetizeJS({ + applicationID: appId, + }); + }); + +const isGoogleSponsor = () => { + const loginToken = store.getters['data/loginToken']; + return loginToken && loginToken.isSponsor; +}; + +const checkPayment = () => { + const currentDate = Date.now(); + if (!isGoogleSponsor() && utils.isUserActive() && !store.state.offline && + lastCheck + checkPaymentEvery < currentDate + ) { + lastCheck = currentDate; + getMonetize() + .then(() => monetize.getPaymentsImmediate((err, payments) => { + const isSponsor = payments && payments.app === appId && ( + (payments.chargeOption && payments.chargeOption.alias === 'once') || + (payments.subscriptionOption && payments.subscriptionOption.alias === 'yearly')); + if (isSponsor !== store.state.monetizeSponsor) { + store.commit('setMonetizeSponsor', isSponsor); + } + })); + } +}; + +utils.setInterval(checkPayment, 2000); + +export default { + getToken() { + if (isGoogleSponsor() || store.state.offline) { + return Promise.resolve(); + } + return getMonetize() + .then(() => new Promise(resolve => + monetize.getTokenImmediate((err, result) => resolve(result)))); + }, +}; diff --git a/src/services/syncSvc.js b/src/services/syncSvc.js index 835d82bb..58d1d49a 100644 --- a/src/services/syncSvc.js +++ b/src/services/syncSvc.js @@ -123,23 +123,45 @@ function createSyncLocation(syncLocation) { }); } -function syncFile(fileId, needSyncRestartParam = false) { - let needSyncRestart = needSyncRestartParam; +class SyncContext { + constructor() { + this.restart = false; + this.synced = {}; + } +} + +class FileSyncContext { + constructor() { + this.downloaded = {}; + this.errors = {}; + } +} + +function syncFile(fileId, syncContext = new SyncContext()) { + const fileSyncContext = new FileSyncContext(); + syncContext.synced[`${fileId}/content`] = true; return localDbSvc.loadSyncedContent(fileId) .then(() => localDbSvc.loadItem(`${fileId}/content`) .catch(() => {})) // Item may not exist if content has not been downloaded yet .then(() => { + const getFile = () => store.state.file.itemMap[fileId]; const getContent = () => store.state.content.itemMap[`${fileId}/content`]; const getSyncedContent = () => store.state.syncedContent.itemMap[`${fileId}/syncedContent`]; const getSyncHistoryItem = syncLocationId => getSyncedContent().syncHistory[syncLocationId]; - const downloadedLocations = {}; - const errorLocations = {}; const isLocationSynced = (syncLocation) => { const syncHistoryItem = getSyncHistoryItem(syncLocation.id); return syncHistoryItem && syncHistoryItem[LAST_SENT] === getContent().hash; }; + const isWelcomeFile = () => { + const file = getFile(); + const content = getContent(); + const welcomeFileHashes = store.getters['data/welcomeFileHashes']; + const hash = content ? utils.hash(content.text) : 0; + return file.name === 'Welcome file' && welcomeFileHashes[hash]; + }; + const syncOneContentLocation = () => { const syncLocations = [ ...store.getters['syncLocation/groupedByFileId'][fileId] || [], @@ -149,16 +171,21 @@ function syncFile(fileId, needSyncRestartParam = false) { } let result; syncLocations.some((syncLocation) => { - if (!errorLocations[syncLocation.id] && - (!downloadedLocations[syncLocation.id] || !isLocationSynced(syncLocation)) + const provider = providerRegistry.providers[syncLocation.providerId]; + if ( + // Skip if it previously threw an error + !fileSyncContext.errors[syncLocation.id] && + // Skip if it has previously been downloaded and has not changed since then + (!fileSyncContext.downloaded[syncLocation.id] || !isLocationSynced(syncLocation)) && + // Skip welcome file if not synchronized explicitly + (syncLocations.length > 1 || !isWelcomeFile()) ) { - const provider = providerRegistry.providers[syncLocation.providerId]; const token = provider.getToken(syncLocation); result = provider && token && store.dispatch('queue/doWithLocation', { location: syncLocation, promise: provider.downloadContent(token, syncLocation) .then((serverContent = null) => { - downloadedLocations[syncLocation.id] = true; + fileSyncContext.downloaded[syncLocation.id] = true; const syncedContent = getSyncedContent(); const syncHistoryItem = getSyncHistoryItem(syncLocation.id); @@ -192,7 +219,7 @@ function syncFile(fileId, needSyncRestartParam = false) { })(); if (!mergedContent) { - errorLocations[syncLocation.id] = true; + fileSyncContext.errors[syncLocation.id] = true; return null; } @@ -274,9 +301,10 @@ function syncFile(fileId, needSyncRestartParam = false) { } // If content was just created, restart sync to create the file as well - const syncDataByItemId = store.getters['data/syncDataByItemId']; - if (!syncDataByItemId[fileId]) { - needSyncRestart = true; + if (provider === mainProvider && + !store.getters['data/syncDataByItemId'][fileId] + ) { + syncContext.restart = true; } }); }) @@ -286,7 +314,7 @@ function syncFile(fileId, needSyncRestartParam = false) { } console.error(err); // eslint-disable-line no-console store.dispatch('notification/error', err); - errorLocations[syncLocation.id] = true; + fileSyncContext.errors[syncLocation.id] = true; }), }) .then(() => syncOneContentLocation()); @@ -304,15 +332,13 @@ function syncFile(fileId, needSyncRestartParam = false) { .then(() => { throw err; })) - .then( - () => needSyncRestart, - (err) => { - if (err && err.message === 'TOO_LATE') { - // Restart sync - return syncFile(fileId, needSyncRestart); - } - throw err; - }); + .catch((err) => { + if (err && err.message === 'TOO_LATE') { + // Restart sync + return syncFile(fileId, syncContext); + } + throw err; + }); } @@ -385,6 +411,7 @@ function syncDataItem(dataId) { } function sync() { + const syncContext = new SyncContext(); const mainToken = store.getters['data/loginToken']; return mainProvider.getChanges(mainToken) .then((changes) => { @@ -481,8 +508,12 @@ function sync() { const loadedContent = store.state.content.itemMap[contentId]; const hash = loadedContent ? loadedContent.hash : localDbSvc.hashMap.content[contentId]; const syncData = store.getters['data/syncDataByItemId'][contentId]; - // Sync if item hash and syncData hash are inconsistent - if (!syncData || hash !== syncData.hash) { + if ( + // Sync if syncData does not exist and content syncing was not attempted yet + (!syncData && !syncContext.synced[contentId]) || + // Or if content hash and syncData hash are inconsistent + (syncData && hash !== syncData.hash) + ) { [fileId] = contentId.split('/'); } return fileId; @@ -490,13 +521,13 @@ function sync() { return fileId; }; - const syncNextFile = (needSyncRestartParam) => { + const syncNextFile = () => { const fileId = getOneFileIdToSync(); if (!fileId) { - return needSyncRestartParam; + return null; } - return syncFile(fileId, needSyncRestartParam) - .then(needSyncRestart => syncNextFile(needSyncRestart)); + return syncFile(fileId, syncContext) + .then(() => syncNextFile()); }; return Promise.resolve() @@ -508,14 +539,14 @@ function sync() { const currentFileId = store.getters['file/current'].id; if (currentFileId) { // Sync current file first - return syncFile(currentFileId) - .then(needSyncRestart => syncNextFile(needSyncRestart)); + return syncFile(currentFileId, syncContext) + .then(() => syncNextFile()); } return syncNextFile(); }) .then( - (needSyncRestart) => { - if (needSyncRestart) { + () => { + if (syncContext.restart) { // Restart sync return sync(); } diff --git a/src/services/utils.js b/src/services/utils.js index 0de1d604..543656a4 100644 --- a/src/services/utils.js +++ b/src/services/utils.js @@ -1,4 +1,5 @@ import yaml from 'js-yaml'; +import '../libs/clunderscore'; import defaultProperties from '../data/defaultFileProperties.yml'; const workspaceId = 'main'; @@ -157,4 +158,57 @@ export default { urlParser.search += keys.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&'); return urlParser.href; }, + wrapRange(range, eltProperties) { + const rangeLength = `${range}`.length; + let wrappedLength = 0; + const treeWalker = document.createTreeWalker( + range.commonAncestorContainer, NodeFilter.SHOW_TEXT); + let startOffset = range.startOffset; + treeWalker.currentNode = range.startContainer; + if (treeWalker.currentNode.nodeType === Node.TEXT_NODE || treeWalker.nextNode()) { + do { + if (treeWalker.currentNode.nodeValue !== '\n') { + if (treeWalker.currentNode === range.endContainer && + range.endOffset < treeWalker.currentNode.nodeValue.length + ) { + treeWalker.currentNode.splitText(range.endOffset); + } + if (startOffset) { + treeWalker.currentNode = treeWalker.currentNode.splitText(startOffset); + startOffset = 0; + } + const elt = document.createElement('span'); + Object.keys(eltProperties).forEach((key) => { + elt[key] = eltProperties[key]; + }); + treeWalker.currentNode.parentNode.insertBefore(elt, treeWalker.currentNode); + elt.appendChild(treeWalker.currentNode); + } + wrappedLength += treeWalker.currentNode.nodeValue.length; + if (wrappedLength >= rangeLength) { + break; + } + } + while (treeWalker.nextNode()); + } + }, + unwrapRange(eltCollection) { + Array.prototype.slice.call(eltCollection).forEach((elt) => { + // Loop in case another wrapper has been added inside + for (let child = elt.firstChild; child; child = elt.firstChild) { + if (child.nodeType === 3) { + if (elt.previousSibling && elt.previousSibling.nodeType === 3) { + child.nodeValue = elt.previousSibling.nodeValue + child.nodeValue; + elt.parentNode.removeChild(elt.previousSibling); + } + if (!child.nextSibling && elt.nextSibling && elt.nextSibling.nodeType === 3) { + child.nodeValue += elt.nextSibling.nodeValue; + elt.parentNode.removeChild(elt.nextSibling); + } + } + elt.parentNode.insertBefore(child, elt); + } + elt.parentNode.removeChild(elt); + }); + }, }; diff --git a/src/store/index.js b/src/store/index.js index 3f85fe0a..4d7b3e98 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -6,6 +6,7 @@ import contentState from './modules/contentState'; import syncedContent from './modules/syncedContent'; import content from './modules/content'; import file from './modules/file'; +import findReplace from './modules/findReplace'; import folder from './modules/folder'; import publishLocation from './modules/publishLocation'; import syncLocation from './modules/syncLocation'; @@ -26,6 +27,7 @@ const store = new Vuex.Store({ ready: false, offline: false, lastOfflineCheck: 0, + monetizeSponsor: false, }, getters: { allItemMap: (state) => { @@ -33,6 +35,10 @@ const store = new Vuex.Store({ utils.types.forEach(type => Object.assign(result, state[type].itemMap)); return result; }, + isSponsor: (state, getters) => { + const loginToken = getters['data/loginToken']; + return state.monetizeSponsor || (loginToken && loginToken.isSponsor); + }, }, mutations: { setReady: (state) => { @@ -44,6 +50,12 @@ const store = new Vuex.Store({ updateLastOfflineCheck: (state) => { state.lastOfflineCheck = Date.now(); }, + setMonetizeSponsor: (state, value) => { + state.monetizeSponsor = value; + }, + setGoogleSponsor: (state, value) => { + state.googleSponsor = value; + }, }, actions: { setOffline: ({ state, commit, dispatch }, value) => { @@ -91,6 +103,7 @@ const store = new Vuex.Store({ syncedContent, content, file, + findReplace, folder, publishLocation, syncLocation, diff --git a/src/store/modules/data.js b/src/store/modules/data.js index ff1b9066..17fb3860 100644 --- a/src/store/modules/data.js +++ b/src/store/modules/data.js @@ -6,6 +6,7 @@ import defaultSettings from '../../data/defaultSettings.yml'; import defaultLocalSettings from '../../data/defaultLocalSettings'; import plainHtmlTemplate from '../../data/plainHtmlTemplate.html'; import styledHtmlTemplate from '../../data/styledHtmlTemplate.html'; +import styledHtmlWithTocTemplate from '../../data/styledHtmlWithTocTemplate.html'; import jekyllSiteTemplate from '../../data/jekyllSiteTemplate.html'; const itemTemplate = (id, data = {}) => ({ id, type: 'data', data, hash: 0 }); @@ -61,15 +62,37 @@ module.actions.toggleNavigationBar = localSettingsToggler('showNavigationBar'); module.actions.toggleEditor = localSettingsToggler('showEditor'); module.actions.toggleSidePreview = localSettingsToggler('showSidePreview'); module.actions.toggleStatusBar = localSettingsToggler('showStatusBar'); -module.actions.toggleSideBar = ({ getters, dispatch }, value) => { - dispatch('setSideBarPanel'); // Reset side bar - dispatch('patchLocalSettings', { - showSideBar: value === undefined ? !getters.localSettings.showSideBar : value, - }); -}; -module.actions.toggleExplorer = localSettingsToggler('showExplorer'); module.actions.toggleScrollSync = localSettingsToggler('scrollSync'); module.actions.toggleFocusMode = localSettingsToggler('focusMode'); +const notEnoughSpace = (rootGetters) => { + const constants = rootGetters['layout/constants']; + return document.body.clientWidth < constants.editorMinWidth + + constants.explorerWidth + + constants.sideBarWidth + + constants.buttonBarWidth; +}; +module.actions.toggleSideBar = ({ getters, dispatch, rootGetters }, value) => { + // Reset side bar + dispatch('setSideBarPanel'); + // Close explorer if not enough space + const patch = { + showSideBar: value === undefined ? !getters.localSettings.showSideBar : value, + }; + if (patch.showSideBar && notEnoughSpace(rootGetters)) { + patch.showExplorer = false; + } + dispatch('patchLocalSettings', patch); +}; +module.actions.toggleExplorer = ({ getters, dispatch, rootGetters }, value) => { + // Close side bar if not enough space + const patch = { + showExplorer: value === undefined ? !getters.localSettings.showExplorer : value, + }; + if (patch.showExplorer && notEnoughSpace(rootGetters)) { + patch.showSideBar = false; + } + dispatch('patchLocalSettings', patch); +}; module.actions.setSideBarPanel = ({ dispatch }, value) => dispatch('patchLocalSettings', { sideBarPanel: value === undefined ? 'menu' : value, }); @@ -112,6 +135,7 @@ const additionalTemplates = { plainText: makeAdditionalTemplate('Plain text', '{{{files.0.content.text}}}'), plainHtml: makeAdditionalTemplate('Plain HTML', plainHtmlTemplate), styledHtml: makeAdditionalTemplate('Styled HTML', styledHtmlTemplate), + styledHtmlWithToc: makeAdditionalTemplate('Styled HTML with TOC', styledHtmlWithTocTemplate), jekyllSite: makeAdditionalTemplate('Jekyll site', jekyllSiteTemplate), }; module.getters.allTemplates = (state, getters) => ({ @@ -187,6 +211,10 @@ module.actions.setSyncData = setter('syncData'); module.getters.dataSyncData = getter('dataSyncData'); module.actions.patchDataSyncData = patcher('dataSyncData'); +// Welcome file content hashes (used as a file sync blacklist) +module.getters.welcomeFileHashes = getter('welcomeFileHashes'); +module.actions.patchWelcomeFileHashes = patcher('welcomeFileHashes'); + // Tokens module.getters.tokens = getter('tokens'); module.getters.googleTokens = (state, getters) => getters.tokens.google || {}; diff --git a/src/store/modules/findReplace.js b/src/store/modules/findReplace.js new file mode 100644 index 00000000..e6b01ad0 --- /dev/null +++ b/src/store/modules/findReplace.js @@ -0,0 +1,32 @@ +export default { + namespaced: true, + state: { + type: null, + lastOpen: 0, + findText: '', + replaceText: '', + }, + mutations: { + setType: (state, value) => { + state.type = value; + }, + setLastOpen: (state) => { + state.lastOpen = Date.now(); + }, + setFindText: (state, value) => { + state.findText = value; + }, + setReplaceText: (state, value) => { + state.replaceText = value; + }, + }, + actions: { + open({ commit, state }, { type, findText }) { + commit('setType', type); + if (findText) { + commit('setFindText', findText); + } + commit('setLastOpen'); + }, + }, +}; diff --git a/src/store/modules/layout.js b/src/store/modules/layout.js index a7be8c64..0f008da1 100644 --- a/src/store/modules/layout.js +++ b/src/store/modules/layout.js @@ -1,4 +1,3 @@ -const editorMinWidth = 320; const minPadding = 20; const previewButtonWidth = 55; const editorTopPadding = 10; @@ -13,6 +12,7 @@ const maxTitleMaxWidth = 800; const minTitleMaxWidth = 200; const constants = { + editorMinWidth: 320, explorerWidth: 250, sideBarWidth: 280, navigationBarHeight: 44, @@ -28,6 +28,7 @@ function computeStyles(state, localSettings, getters, styles = { showPreview: localSettings.showSidePreview || !localSettings.showEditor, showSideBar: localSettings.showSideBar, showExplorer: localSettings.showExplorer, + layoutOverflow: false, }) { styles.innerHeight = state.bodyHeight; if (styles.showNavigationBar) { @@ -46,14 +47,16 @@ function computeStyles(state, localSettings, getters, styles = { } let doublePanelWidth = styles.innerWidth - constants.buttonBarWidth; - if (doublePanelWidth < editorMinWidth) { - doublePanelWidth = editorMinWidth; - styles.innerWidth = editorMinWidth + constants.buttonBarWidth; + if (doublePanelWidth < constants.editorMinWidth) { + doublePanelWidth = constants.editorMinWidth; + styles.innerWidth = constants.editorMinWidth + constants.buttonBarWidth; + styles.layoutOverflow = true; } - if (styles.showSidePreview && doublePanelWidth / 2 < editorMinWidth) { + if (styles.showSidePreview && doublePanelWidth / 2 < constants.editorMinWidth) { styles.showSidePreview = false; styles.showPreview = false; + styles.layoutOverflow = false; return computeStyles(state, localSettings, getters, styles); } diff --git a/src/store/modules/modal.js b/src/store/modules/modal.js index 7201448b..aa11df57 100644 --- a/src/store/modules/modal.js +++ b/src/store/modules/modal.js @@ -46,10 +46,6 @@ export default { throw err; }); }, - notImplemented: ({ dispatch }) => dispatch('open', { - content: '

Sorry, this feature is not available yet...

', - rejectText: 'Ok', - }), fileDeletion: ({ dispatch }, item) => dispatch('open', { content: `

You are about to delete the file ${item.name}. Are you sure?

`, resolveText: 'Yes, delete', @@ -75,5 +71,19 @@ export default { rejectText: 'Cancel', onResolve, }), + signInRequired: ({ dispatch }) => dispatch('open', { + content: `

We have to sign you in with Google in order to activate your sponsorship.

+ `, + resolveText: 'Ok, sign in!', + rejectText: 'Cancel', + }), + sponsorOnly: ({ dispatch }) => dispatch('open', { + content: '

This feature is restricted to sponsor users as it relies on server resources.

', + resolveText: 'Ok, I understand', + }), + paymentSuccess: ({ dispatch }) => dispatch('open', { + content: '

Thank you for your payment! Your sponsorship will be active in a minute.

', + resolveText: 'Ok', + }), }, }; diff --git a/yarn.lock b/yarn.lock index 52143093..524628e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32,7 +32,7 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" -acorn@^3.0.4: +acorn@^3.0.0, acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -80,12 +80,6 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -ansi-cyan@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" - dependencies: - ansi-wrap "0.1.0" - ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -98,12 +92,6 @@ ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" -ansi-red@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" - dependencies: - ansi-wrap "0.1.0" - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -122,10 +110,6 @@ ansi-styles@^3.1.0: dependencies: color-convert "^1.9.0" -ansi-wrap@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - anymatch@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" @@ -158,13 +142,6 @@ argsarray@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb" -arr-diff@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" - dependencies: - arr-flatten "^1.0.1" - array-slice "^0.2.3" - arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -175,10 +152,6 @@ arr-flatten@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.3.tgz#a274ed85ac08849b6bd7847c4580745dc51adfb1" -arr-union@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" - array-differ@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" @@ -211,10 +184,6 @@ array-reduce@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" -array-slice@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" - array-slice@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.0.0.tgz#e73034f00dcc1f40876008fd20feae77bd4b7c2f" @@ -271,7 +240,11 @@ async-foreach@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" -async@^1.4.0: +async@^0.9.0: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + +async@^1.3.0, async@^1.4.0, async@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -281,6 +254,10 @@ async@^2.1.2, async@^2.1.5: dependencies: lodash "^4.14.0" +async@~0.2.6: + version "0.2.10" + resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -296,6 +273,21 @@ autoprefixer@^6.0.0, autoprefixer@^6.3.1, autoprefixer@^6.7.2: postcss "^5.2.16" postcss-value-parser "^3.2.3" +aws-sdk@^2.133.0: + version "2.145.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.145.0.tgz#085bb4553231defd93b96b0d95023717c9c3c093" + dependencies: + buffer "4.9.1" + crypto-browserify "1.0.9" + events "^1.1.1" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.1.0" + xml2js "0.4.17" + xmlbuilder "4.2.1" + aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" @@ -918,6 +910,10 @@ big.js@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" +bignumber.js@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8" + binary-extensions@^1.0.0: version "1.8.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" @@ -936,10 +932,33 @@ bluebird@^3.0.5, bluebird@^3.1.1, bluebird@^3.4.7: version "3.5.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" +bmp-js@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.0.1.tgz#5ad0147099d13a9f38aa7b99af1d6e78666ed37f" + +bmp-js@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.0.3.tgz#64113e9c7cf1202b376ed607bf30626ebe57b18a" + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" +body-parser@^1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -988,6 +1007,12 @@ brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" +browserify-aes@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c" + dependencies: + inherits "^2.0.1" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" @@ -1053,6 +1078,25 @@ browserslist@^2.1.2: caniuse-lite "^1.0.30000670" electron-to-chromium "^1.3.11" +buffer-alloc-unsafe@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-0.1.1.tgz#ffe1f67551dd055737de253337bfe853dfab1a6a" + +buffer-alloc@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.1.0.tgz#05514d33bf1656d3540c684f65b1202e90eca303" + dependencies: + buffer-alloc-unsafe "^0.1.0" + buffer-fill "^0.1.0" + +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + +buffer-fill@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-0.1.0.tgz#ca9470e8d4d1b977fd7543f4e2ab6a7dc95101a8" + buffer-shims@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" @@ -1061,7 +1105,7 @@ buffer-xor@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" -buffer@^4.3.0: +buffer@4.9.1, buffer@^4.3.0, buffer@^4.9.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: @@ -1077,17 +1121,14 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" -bump-regex@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/bump-regex/-/bump-regex-2.7.0.tgz#4a21e2537113476c026be588b8a7dddef1934641" - dependencies: - semver "^5.1.0" - xtend "^4.0.1" - bytes@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a" +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" @@ -1116,7 +1157,7 @@ camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" -camelcase@^2.0.0: +camelcase@^2.0.0, camelcase@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -1170,7 +1211,17 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" -chokidar@^1.4.3: +cheerio@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.19.0.tgz#772e7015f2ee29965096d71ea4175b75ab354925" + dependencies: + css-select "~1.0.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "~3.8.1" + lodash "^3.2.0" + +chokidar@^1.0.0, chokidar@^1.4.3: version "1.7.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: @@ -1243,7 +1294,7 @@ cliui@^2.1.0: right-align "^0.1.1" wordwrap "0.0.2" -cliui@^3.2.0: +cliui@^3.0.3, cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" dependencies: @@ -1300,10 +1351,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^1.0.6" through2 "^2.0.1" -clunderscore@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clunderscore/-/clunderscore-1.0.3.tgz#a9f99088702d919404d82781f067872ff209d92c" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -1369,7 +1416,7 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@~1.1.2: +colors@^1.1.2, colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1411,7 +1458,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.2: +concat-stream@1.6.0, concat-stream@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -1468,6 +1515,10 @@ content-type@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + convert-source-map@^1.1.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" @@ -1559,6 +1610,19 @@ cryptiles@3.x.x: dependencies: boom "5.x.x" +crypto-browserify@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-1.0.9.tgz#cc5449685dfb85eb11c9828acc7cb87ab5bbfcc0" + +crypto-browserify@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.3.0.tgz#b9fc75bb4a0ed61dcf1cd5dae96eb30c9c3e506c" + dependencies: + browserify-aes "0.4.0" + pbkdf2-compat "2.0.1" + ripemd160 "0.2.0" + sha.js "2.2.6" + crypto-browserify@^3.11.0: version "3.11.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522" @@ -1582,9 +1646,9 @@ css-color-names@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" -css-loader@^0.28.4: - version "0.28.4" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.4.tgz#6cf3579192ce355e8b38d5f42dd7a1f2ec898d0f" +css-loader@^0.28.7: + version "0.28.7" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.7.tgz#5f2ee989dd32edd907717f953317656160999c1b" dependencies: babel-code-frame "^6.11.0" css-selector-tokenizer "^0.7.0" @@ -1599,7 +1663,7 @@ css-loader@^0.28.4: postcss-modules-scope "^1.0.0" postcss-modules-values "^1.1.0" postcss-value-parser "^3.3.0" - source-list-map "^0.1.7" + source-list-map "^2.0.0" css-rule-stream@^1.1.0: version "1.1.0" @@ -1619,6 +1683,15 @@ css-select@^1.1.0: domutils "1.5.1" nth-check "~1.0.1" +css-select@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.0.0.tgz#b1121ca51848dd264e2244d058cee254deeb44b0" + dependencies: + boolbase "~1.0.0" + css-what "1.0" + domutils "1.4" + nth-check "~1.0.0" + css-selector-tokenizer@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" @@ -1634,6 +1707,10 @@ css-tokenize@^1.0.1: inherits "^2.0.1" readable-stream "^1.0.33" +css-what@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-1.0.0.tgz#d7cc2df45180666f99d2b14462639469e00f736c" + css-what@2.1: version "2.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" @@ -1692,12 +1769,32 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +d3@3.5.17: + version "3.5.17" + resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" + d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" dependencies: es5-ext "^0.10.9" +dagre-d3-renderer@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.4.24.tgz#b36ce2fe4ea20de43e7698627c6ede2a9f15ec45" + dependencies: + d3 "3.5.17" + dagre-layout "^0.8.0" + graphlib "^2.1.1" + lodash "^4.17.4" + +dagre-layout@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/dagre-layout/-/dagre-layout-0.8.0.tgz#7147b6afb655602f855158dfea171db9aa98d4ff" + dependencies: + graphlib "^2.1.1" + lodash "^4.17.4" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1708,13 +1805,6 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -dateformat@^1.0.11: - version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" - dateformat@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.0.0.tgz#2743e3abb5c3fc2462e527dca445e04e9f4dee17" @@ -1723,7 +1813,7 @@ de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" -debug@2.2.0: +debug@2.2.0, debug@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -1877,13 +1967,17 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: domelementtype "~1.1.1" entities "~1.1.1" +dom-walk@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" + domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" @@ -1902,6 +1996,12 @@ domhandler@2.1: dependencies: domelementtype "1" +domhandler@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + dependencies: + domelementtype "1" + domhandler@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" @@ -1914,7 +2014,13 @@ domutils@1.1: dependencies: domelementtype "1" -domutils@1.5.1: +domutils@1.4: + version "1.4.3" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.4.3.tgz#0865513796c6b306031850e175516baf80b72a6f" + dependencies: + domelementtype "1" + +domutils@1.5, domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -2012,6 +2118,10 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" +entities@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" + entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" @@ -2060,6 +2170,14 @@ es6-map@^0.1.3: es6-symbol "~3.1.1" event-emitter "~0.3.5" +es6-promise@^3.0.2: + version "3.3.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" + +es6-promise@^4.0.3: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" + es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" @@ -2282,7 +2400,7 @@ eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" -events@^1.0.0: +events@^1.0.0, events@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -2302,6 +2420,10 @@ execall@^1.0.0: dependencies: clone-regexp "^1.0.0" +exif-parser@^0.1.9: + version "0.1.12" + resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -2396,12 +2518,6 @@ express@^4.15.5: utils-merge "1.0.0" vary "~1.1.1" -extend-shallow@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" - dependencies: - kind-of "^1.1.0" - extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -2429,6 +2545,15 @@ extract-text-webpack-plugin@^2.0.0: loader-utils "^1.0.2" webpack-sources "^0.1.0" +extract-zip@^1.6.5: + version "1.6.6" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c" + dependencies: + concat-stream "1.6.0" + debug "2.6.9" + mkdirp "0.5.0" + yauzl "2.4.1" + extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" @@ -2452,6 +2577,44 @@ fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" +favicons-webpack-plugin@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/favicons-webpack-plugin/-/favicons-webpack-plugin-0.0.7.tgz#253a46a4f93d137d1096762877f8a8ef12e28648" + dependencies: + favicons "^4.7.1" + loader-utils "^0.2.14" + lodash "^4.11.1" + webpack "^1.13.0" + +favicons@^4.7.1: + version "4.8.6" + resolved "https://registry.yarnpkg.com/favicons/-/favicons-4.8.6.tgz#a2b13800ab3fec2715bc8f27fa841d038d4761e2" + dependencies: + async "^1.5.0" + cheerio "^0.19.0" + clone "^1.0.2" + colors "^1.1.2" + harmony-reflect "^1.4.2" + image-size "^0.4.0" + jimp "^0.2.13" + jsontoxml "0.0.11" + merge-defaults "^0.2.1" + mkdirp "^0.5.1" + node-rest-client "^1.5.1" + require-directory "^2.1.1" + svg2png "~3.0.1" + through2 "^2.0.0" + tinycolor2 "^1.1.2" + to-ico "^1.1.2" + underscore "^1.8.3" + vinyl "^1.1.0" + +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + dependencies: + pend "~1.2.0" + figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -2482,6 +2645,10 @@ file-saver@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.3.tgz#cdd4c44d3aa264eac2f68ec165bc791c34af1232" +file-type@^3.1.0, file-type@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -2593,6 +2760,12 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" +for-each@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" + dependencies: + is-function "~1.0.0" + for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -2667,6 +2840,14 @@ fs-extra@^0.26.4: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2750,6 +2931,13 @@ get-stdin@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" +get-stream@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2846,6 +3034,13 @@ global-prefix@^0.1.4: is-windows "^0.2.0" which "^1.2.12" +global@~4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" + dependencies: + min-document "^2.19.0" + process "~0.5.1" + globals@^9.0.0, globals@^9.14.0: version "9.17.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.17.0.tgz#0c0ca696d9b9bb694d2e5470bd37777caad50286" @@ -2903,6 +3098,14 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" +google-id-token-verifier@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/google-id-token-verifier/-/google-id-token-verifier-0.2.3.tgz#9e6b78d4589e2d050da81613fb890adba30a4ea8" + dependencies: + request "^2.65.0" + rsa-pem-from-mod-exp "^0.8.4" + underscore "^1.8.3" + graceful-fs@^3.0.0: version "3.0.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" @@ -2921,15 +3124,11 @@ graceful-fs@~1.2.0: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -gulp-bump@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/gulp-bump/-/gulp-bump-2.7.0.tgz#4c3750bce93c5d816fe9a154e6619dd509a852d8" +graphlib@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.1.tgz#42352c52ba2f4d035cb566eb91f7395f76ebc951" dependencies: - bump-regex "^2.7.0" - plugin-error "^0.1.2" - plugin-log "^0.1.0" - semver "^5.3.0" - through2 "^2.0.1" + lodash "^4.11.1" gulp-concat@^2.6.1: version "2.6.1" @@ -2939,7 +3138,7 @@ gulp-concat@^2.6.1: through2 "^2.0.0" vinyl "^2.0.0" -gulp-util@^3.0.0, gulp-util@^3.0.8: +gulp-util@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -3024,6 +3223,10 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +harmony-reflect@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3070,6 +3273,13 @@ hash.js@^1.0.0, hash.js@^1.0.3: dependencies: inherits "^2.0.1" +hasha@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1" + dependencies: + is-stream "^1.0.1" + pinkie-promise "^2.0.0" + hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -3088,7 +3298,7 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -he@1.1.x, he@^1.1.0: +he@1.1.x, he@^1.1.0, he@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -3181,6 +3391,25 @@ htmlparser2@~3.3.0: domutils "1.1" readable-stream "1.0" +htmlparser2@~3.8.1: + version "3.8.3" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" + dependencies: + domelementtype "1" + domhandler "2.3" + domutils "1.5" + entities "1.0" + readable-stream "1.1" + +http-errors@1.6.2, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + http-errors@~1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" @@ -3190,15 +3419,6 @@ http-errors@~1.6.1: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" -http-errors@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" - dependencies: - depd "1.1.1" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - http-proxy-middleware@^0.17.3: version "0.17.4" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" @@ -3235,7 +3455,7 @@ https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" -iconv-lite@^0.4.17: +iconv-lite@0.4.19, iconv-lite@^0.4.17: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -3261,6 +3481,14 @@ ignore@^3.2.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" +image-size@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.4.0.tgz#d4b4e1f61952e4cbc1cea9a6b0c915fecb707510" + +image-size@^0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + immediate@^3.2.2: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" @@ -3354,6 +3582,10 @@ inquirer@^3.2.2: strip-ansi "^4.0.0" through "^2.3.6" +interpret@^0.6.4: + version "0.6.6" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" + interpret@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" @@ -3368,6 +3600,10 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" +ip-regex@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" + ipaddr.js@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" @@ -3453,6 +3689,10 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-function@^1.0.1, is-function@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -3544,6 +3784,10 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + is-supported-regexp-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz#8b520c85fae7a253382d4b02652e045576e13bb8" @@ -3602,12 +3846,45 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" +jimp@^0.2.13, jimp@^0.2.21: + version "0.2.28" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.2.28.tgz#dd529a937190f42957a7937d1acc3a7762996ea2" + dependencies: + bignumber.js "^2.1.0" + bmp-js "0.0.3" + es6-promise "^3.0.2" + exif-parser "^0.1.9" + file-type "^3.1.0" + jpeg-js "^0.2.0" + load-bmfont "^1.2.3" + mime "^1.3.4" + mkdirp "0.5.1" + pixelmatch "^4.0.0" + pngjs "^3.0.0" + read-chunk "^1.0.1" + request "^2.65.0" + stream-to-buffer "^0.1.0" + tinycolor2 "^1.1.2" + url-regex "^3.0.0" + +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + jodid25519@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" dependencies: jsbn "~0.1.0" +jpeg-js@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.1.2.tgz#135b992c0575c985cfa0f494a3227ed238583ece" + +jpeg-js@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.2.0.tgz#53e448ec9d263e683266467e9442d2c5a2ef5482" + js-base64@^2.1.8, js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" @@ -3715,6 +3992,10 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsontoxml@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/jsontoxml/-/jsontoxml-0.0.11.tgz#373ab5b2070be3737a5fb3e32fd1b7b81870caa4" + jsprim@^1.2.2: version "1.4.0" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" @@ -3730,9 +4011,9 @@ katex@^0.7.1: dependencies: match-at "^0.1.0" -kind-of@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" +kew@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b" kind-of@^2.0.1: version "2.0.1" @@ -3804,6 +4085,18 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" +load-bmfont@^1.2.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.3.0.tgz#bb7e7c710de6bcafcb13cb3b8c81e0c0131ecbc9" + dependencies: + buffer-equal "0.0.1" + mime "^1.3.4" + parse-bmfont-ascii "^1.0.3" + parse-bmfont-binary "^1.0.5" + parse-bmfont-xml "^1.1.0" + xhr "^2.0.1" + xtend "^4.0.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -3834,7 +4127,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@0.2.x, loader-utils@^0.2.15, loader-utils@^0.2.16: +loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.14, loader-utils@^0.2.15, loader-utils@^0.2.16: version "0.2.17" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" dependencies: @@ -4002,7 +4295,11 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^4.0.0, lodash@^4.1.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: +lodash@^3.2.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + +lodash@^4.0.0, lodash@^4.1.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -4010,6 +4307,10 @@ lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" +lodash@~2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" + lodash@~4.16.4: version "4.16.6" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777" @@ -4135,6 +4436,13 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +memory-fs@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.3.0.tgz#7bcc6b629e3a43e871d7e29aca6ae8a7f15cbb20" + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -4150,10 +4458,27 @@ meow@^3.3.0, meow@^3.7.0: redent "^1.0.0" trim-newlines "^1.0.0" +merge-defaults@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/merge-defaults/-/merge-defaults-0.2.1.tgz#dd42248eb96bb6a51521724321c72ff9583dde80" + dependencies: + lodash "~2.4.1" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +mermaid@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-7.1.0.tgz#c9080e7b517adb8adb582470755799348bf127a2" + dependencies: + d3 "3.5.17" + dagre-d3-renderer "^0.4.24" + dagre-layout "^0.8.0" + he "^1.1.1" + lodash "^4.17.4" + moment "^2.18.1" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -4215,6 +4540,12 @@ mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + dependencies: + dom-walk "^0.1.0" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -4261,12 +4592,22 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" +mkdirp@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + dependencies: + minimist "0.0.8" + mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" +moment@^2.18.1: + version "2.19.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167" + mousetrap@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9" @@ -4358,6 +4699,34 @@ node-gyp@^3.3.1: tar "^2.0.0" which "1" +node-libs-browser@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b" + dependencies: + assert "^1.1.1" + browserify-zlib "^0.1.4" + buffer "^4.9.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "3.3.0" + domain-browser "^1.1.1" + events "^1.0.0" + https-browserify "0.0.1" + os-browserify "^0.2.0" + path-browserify "0.0.0" + process "^0.11.0" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.0.5" + stream-browserify "^2.0.1" + stream-http "^2.3.1" + string_decoder "^0.10.25" + timers-browserify "^2.0.2" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.10.3" + vm-browserify "0.0.4" + node-libs-browser@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea" @@ -4428,6 +4797,13 @@ node-pre-gyp@^0.6.29, node-pre-gyp@~0.6.31: tar "^2.2.1" tar-pack "^3.4.0" +node-rest-client@^1.5.1: + version "1.8.0" + resolved "https://registry.yarnpkg.com/node-rest-client/-/node-rest-client-1.8.0.tgz#8d3c566b817e27394cb7273783a41caefe3e5955" + dependencies: + debug "~2.2.0" + xml2js ">=0.2.4" + node-sass@^4.5.3: version "4.5.3" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568" @@ -4521,7 +4897,7 @@ npm-bump@^0.0.23: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@~1.0.1: +nth-check@~1.0.0, nth-check@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" dependencies: @@ -4630,7 +5006,7 @@ opn@^4.0.2: object-assign "^4.0.1" pinkie-promise "^2.0.0" -optimist@^0.6.1: +optimist@^0.6.1, optimist@~0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" dependencies: @@ -4732,6 +5108,21 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" +parse-bmfont-ascii@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz#11ac3c3ff58f7c2020ab22769079108d4dfa0285" + +parse-bmfont-binary@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz#d038b476d3e9dd9db1e11a0b0e53a22792b69006" + +parse-bmfont-xml@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz#d6b66a371afd39c5007d9f0eeb262a4f2cce7b7c" + dependencies: + xml-parse-from-string "^1.0.0" + xml2js "^0.4.5" + parse-filepath@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.1.tgz#159d6155d43904d16c10ef698911da1e91969b73" @@ -4749,6 +5140,13 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" +parse-headers@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.1.tgz#6ae83a7aa25a9d9b700acc28698cd1f1ed7e9536" + dependencies: + for-each "^0.3.2" + trim "0.0.1" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -4759,6 +5157,12 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" +parse-png@^1.0.0, parse-png@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/parse-png/-/parse-png-1.1.2.tgz#f5c2ad7c7993490986020a284c19aee459711ff2" + dependencies: + pngjs "^3.2.0" + parseurl@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" @@ -4821,6 +5225,10 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" +pbkdf2-compat@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz#b6e0c8fa99494d94e0511575802a59a5c142f288" + pbkdf2@^3.0.3: version "3.0.12" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2" @@ -4831,6 +5239,10 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -4839,6 +5251,20 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" +phantomjs-prebuilt@^2.1.10: + version "2.1.16" + resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz#efd212a4a3966d3647684ea8ba788549be2aefef" + dependencies: + es6-promise "^4.0.3" + extract-zip "^1.6.5" + fs-extra "^1.0.0" + hasha "^2.2.0" + kew "^0.7.0" + progress "^1.1.8" + request "^2.81.0" + request-progress "^2.0.1" + which "^1.2.10" + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -4860,29 +5286,18 @@ pipetteur@^2.0.0: onecolor "^3.0.4" synesthesia "^1.0.1" +pixelmatch@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" + dependencies: + pngjs "^3.0.0" + pkg-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" dependencies: find-up "^1.0.0" -plugin-error@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" - dependencies: - ansi-cyan "^0.1.1" - ansi-red "^0.1.1" - arr-diff "^1.0.1" - arr-union "^2.0.1" - extend-shallow "^1.1.2" - -plugin-log@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/plugin-log/-/plugin-log-0.1.0.tgz#86049cf6ab10833398a931f3689cbaee7b5e1333" - dependencies: - chalk "^1.1.1" - dateformat "^1.0.11" - plur@^2.0.0, plur@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" @@ -4893,6 +5308,14 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +pn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9" + +pngjs@^3.0.0, pngjs@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.0.tgz#1f5730c189c94933b81beda2ab2f8e2855263a8f" + postcss-calc@^5.2.0: version "5.3.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" @@ -5245,6 +5668,10 @@ process@^0.11.0, process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" +process@~0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" + progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -5305,7 +5732,7 @@ qs@6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" -qs@~6.5.1: +qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -5343,6 +5770,15 @@ range-parser@^1.0.3, range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + raw-loader@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" @@ -5356,6 +5792,10 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +read-chunk@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-1.0.1.tgz#5f68cab307e663f19993527d9b589cace4661194" + read-file-stdin@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/read-file-stdin/-/read-file-stdin-0.2.1.tgz#25eccff3a153b6809afacb23ee15387db9e0ee61" @@ -5401,6 +5841,15 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0": isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@1.1: + version "1.1.13" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^1.0.33, readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -5567,6 +6016,12 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" +request-progress@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08" + dependencies: + throttleit "^1.0.0" + request@2, request@^2.79.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -5594,6 +6049,33 @@ request@2, request@^2.79.0, request@^2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" +request@^2.65.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + request@^2.82.0: version "2.82.0" resolved "https://registry.yarnpkg.com/request/-/request-2.82.0.tgz#2ba8a92cd7ac45660ea2b10a53ae67cd247516ea" @@ -5644,6 +6126,17 @@ requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" +resize-img@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/resize-img/-/resize-img-1.1.2.tgz#fad650faf3ef2c53ea63112bc272d95e9d92550e" + dependencies: + bmp-js "0.0.1" + file-type "^3.8.0" + get-stream "^2.0.0" + jimp "^0.2.21" + jpeg-js "^0.1.1" + parse-png "^1.1.1" + resolve-dir@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" @@ -5691,6 +6184,10 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.0, rimraf@^2.6.1: dependencies: glob "^7.0.5" +ripemd160@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" @@ -5698,6 +6195,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" +rsa-pem-from-mod-exp@^0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.4.tgz#362a42c6d304056d493b3f12bceabb2c6576a6d4" + run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -5751,6 +6252,14 @@ sass-loader@^6.0.5: lodash.tail "^4.1.1" pify "^2.3.0" +sax@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + sax@~1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" @@ -5780,7 +6289,7 @@ semver@^4.1.0: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" -semver@^5.1.0, semver@^5.4.1: +semver@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" @@ -5858,6 +6367,10 @@ setprototypeof@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" +sha.js@2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba" + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.8" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" @@ -5924,14 +6437,18 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -source-list-map@^0.1.7, source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - source-list-map@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + +source-list-map@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + source-map-support@^0.4.2: version "0.4.15" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" @@ -5942,7 +6459,7 @@ source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, sourc version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" -source-map@^0.4.2, source-map@^0.4.4: +source-map@^0.4.2, source-map@^0.4.4, source-map@~0.4.1: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -6044,6 +6561,16 @@ stream-http@^2.3.1: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-to-buffer@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz#26799d903ab2025c9bd550ac47171b00f8dd80a9" + dependencies: + stream-to "~0.2.0" + +stream-to@~0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stream-to/-/stream-to-0.2.2.tgz#84306098d85fdb990b9fa300b1b3ccf55e8ef01d" + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -6244,6 +6771,14 @@ svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" +svg2png@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg2png/-/svg2png-3.0.1.tgz#a2644d68b0231ac00af431aa163714ff17106447" + dependencies: + phantomjs-prebuilt "^2.1.10" + pn "^1.0.0" + yargs "^3.31.0" + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -6284,7 +6819,7 @@ table@^4.0.1: slice-ansi "0.0.4" string-width "^2.0.0" -tapable@^0.1.8: +tapable@^0.1.8, tapable@~0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" @@ -6317,6 +6852,10 @@ text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + through2@^0.6.1, through2@^0.6.3, through2@~0.6.1: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -6365,6 +6904,10 @@ tiny-queue@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" +tinycolor2@^1.1.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -6379,6 +6922,16 @@ to-fast-properties@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" +to-ico@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/to-ico/-/to-ico-1.1.5.tgz#1d32da5f2c90922edee6b686d610c54527b5a8d5" + dependencies: + arrify "^1.0.1" + buffer-alloc "^1.1.0" + image-size "^0.5.0" + parse-png "^1.0.0" + resize-img "^1.1.0" + toposort@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.3.tgz#f02cd8a74bd8be2fc0e98611c3bacb95a171869c" @@ -6389,7 +6942,7 @@ tough-cookie@~2.3.0: dependencies: punycode "^1.4.1" -tough-cookie@~2.3.2: +tough-cookie@~2.3.2, tough-cookie@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" dependencies: @@ -6403,6 +6956,10 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + tryit@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" @@ -6467,6 +7024,15 @@ uglify-js@^2.8.27: optionalDependencies: uglify-to-browserify "~1.0.0" +uglify-js@~2.7.3: + version "2.7.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" + dependencies: + async "~0.2.6" + source-map "~0.5.1" + uglify-to-browserify "~1.0.0" + yargs "~3.10.0" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -6505,7 +7071,7 @@ unique-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" -unpipe@~1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -6520,6 +7086,19 @@ url-loader@^0.5.8: loader-utils "^1.0.2" mime "1.3.x" +url-regex@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/url-regex/-/url-regex-3.2.0.tgz#dbad1e0c9e29e105dd0b1f09f6862f7fdb482724" + dependencies: + ip-regex "^1.0.1" + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -6559,14 +7138,14 @@ utils-merge@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" +uuid@3.1.0, uuid@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + uuid@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" -uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - v8flags@^2.0.2: version "2.1.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" @@ -6622,6 +7201,14 @@ vinyl@^0.5.0: clone-stats "^0.0.1" replace-ext "0.0.1" +vinyl@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + vinyl@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" @@ -6687,6 +7274,14 @@ vuex@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/vuex/-/vuex-2.3.1.tgz#cde8e997c1f9957719bc7dea154f9aa691d981a6" +watchpack@^0.2.1: + version "0.2.9" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b" + dependencies: + async "^0.9.0" + chokidar "^1.0.0" + graceful-fs "^4.1.2" + watchpack@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" @@ -6711,6 +7306,13 @@ webpack-bundle-analyzer@^2.2.1: opener "^1.4.3" ws "^2.3.1" +webpack-core@~0.6.9: + version "0.6.9" + resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" + dependencies: + source-list-map "~0.1.7" + source-map "~0.4.1" + webpack-dev-middleware@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.2.tgz#2e252ce1dfb020dbda1ccb37df26f30ab014dbd1" @@ -6749,6 +7351,26 @@ webpack-sources@^0.2.3: source-list-map "^1.1.1" source-map "~0.5.3" +webpack@^1.13.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.15.0.tgz#4ff31f53db03339e55164a9d468ee0324968fe98" + dependencies: + acorn "^3.0.0" + async "^1.3.0" + clone "^1.0.2" + enhanced-resolve "~0.9.0" + interpret "^0.6.4" + loader-utils "^0.2.11" + memory-fs "~0.3.0" + mkdirp "~0.5.0" + node-libs-browser "^0.7.0" + optimist "~0.6.0" + supports-color "^3.1.0" + tapable "~0.1.8" + uglify-js "~2.7.3" + watchpack "^0.2.1" + webpack-core "~0.6.9" + webpack@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07" @@ -6800,6 +7422,12 @@ which@1, which@^1.2.12, which@^1.2.9: dependencies: isexe "^2.0.0" +which@^1.2.10: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" @@ -6810,6 +7438,10 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +window-size@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" @@ -6857,15 +7489,52 @@ ws@^2.3.1: safe-buffer "~5.0.1" ultron "~1.1.0" +xhr@^2.0.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.4.0.tgz#e16e66a45f869861eeefab416d5eff722dc40993" + dependencies: + global "~4.3.0" + is-function "^1.0.1" + parse-headers "^2.0.0" + xtend "^4.0.0" + xml-char-classes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xml-parse-from-string@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" + +xml2js@0.4.17: + version "0.4.17" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + dependencies: + sax ">=0.6.0" + xmlbuilder "^4.1.0" + +xml2js@>=0.2.4, xml2js@^0.4.5: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@4.2.1, xmlbuilder@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + dependencies: + lodash "^4.0.0" + +xmlbuilder@~9.0.1: + version "9.0.4" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" -y18n@^3.2.1: +y18n@^3.2.0, y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" @@ -6889,6 +7558,18 @@ yargs@^1.2.6: version "1.3.3" resolved "https://registry.yarnpkg.com/yargs/-/yargs-1.3.3.tgz#054de8b61f22eefdb7207059eaef9d6b83fb931a" +yargs@^3.31.0: + version "3.32.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + dependencies: + camelcase "^2.0.1" + cliui "^3.0.3" + decamelize "^1.1.1" + os-locale "^1.4.0" + string-width "^1.0.1" + window-size "^0.1.4" + y18n "^3.2.0" + yargs@^3.5.4, yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" @@ -6933,3 +7614,9 @@ yargs@^7.0.0: which-module "^1.0.0" y18n "^3.2.1" yargs-parser "^5.0.0" + +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + dependencies: + fd-slicer "~1.0.1"