├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .poirc.js ├── .postcssrc.js ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc.js ├── LICENSE ├── README.md ├── package.json └── src ├── App.vue ├── index.js ├── plugins └── vue-directus │ ├── api │ └── index.js │ ├── assets │ └── css │ │ └── lib │ │ └── quill.theme.css │ ├── components │ ├── index.js │ └── modules │ │ ├── VueDirectusApp.vue │ │ ├── VueDirectusCollection.vue │ │ ├── VueDirectusImage.vue │ │ ├── VueDirectusItem.vue │ │ └── VueDirectusText.vue │ ├── index.js │ └── store │ ├── index.js │ └── modules │ ├── items.js │ ├── settings.js │ └── users.js └── store └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | lib/ 4 | *.min.* 5 | *.html -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const poi = require('poi') 2 | const poiConfig = require('./.poirc.js') 3 | const poiApp = poi(poiConfig) 4 | const webpackConfig = poiApp.createWebpackConfig() 5 | 6 | module.exports = { 7 | root: true, 8 | parserOptions: { 9 | parser: 'babel-eslint', 10 | ecmaVersion: 2017, 11 | sourceType: 'module' 12 | }, 13 | env: { 14 | browser: true, 15 | commonjs: true, 16 | node: true, 17 | es6: true 18 | }, 19 | extends: [ 20 | 'standard', 21 | 'plugin:import/errors', 22 | 'plugin:promise/recommended', 23 | 'plugin:prettier/recommended', 24 | 'plugin:vue/recommended' 25 | ], 26 | settings: { 27 | 'import/resolver': { 28 | webpack: { 29 | config: webpackConfig 30 | } 31 | } 32 | }, 33 | rules: { 34 | 'promise/catch-or-return': 0, 35 | 'promise/avoid-new': 0, 36 | 'arrow-parens': 0, 37 | 'capitalized-comments': 0, 38 | 'no-var': 2, 39 | 'no-new': 0, 40 | 'no-new-object': 2, 41 | 'no-new-require': 2, 42 | 'no-new-symbol': 2, 43 | 'no-new-wrappers': 2, 44 | 'no-unused-vars': [2, { vars: 'all', args: 'none' }], 45 | 'vue/max-attributes-per-line': 0, 46 | 'no-unused-vars': process.env.NODE_ENV === 'production' ? 2 : 1, 47 | 'no-console': process.env.NODE_ENV === 'production' ? 2 : 1, 48 | 'padding-line-between-statements': [ 49 | 2, 50 | { 51 | blankLine: 'always', 52 | prev: '*', 53 | next: ['import', 'directive', 'export', 'block', 'block-like', 'if', 'const', 'let'] 54 | }, 55 | { 56 | blankLine: 'always', 57 | prev: ['import', 'directive', 'export', 'block', 'block-like', 'if', 'const', 'let'], 58 | next: '*' 59 | }, 60 | { 61 | blankLine: 'never', 62 | prev: ['import', 'directive'], 63 | next: ['import', 'directive'] 64 | }, 65 | { 66 | blankLine: 'never', 67 | prev: ['const', 'let'], 68 | next: ['const', 'let'] 69 | } 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node,linux,windows,visualstudiocode,sublimetext,database,data,executable,sass,less,stylus 2 | 3 | ### Data ### 4 | *.csv 5 | *.dat 6 | *.efx 7 | *.gbr 8 | *.key 9 | *.pps 10 | *.ppt 11 | *.pptx 12 | *.sdf 13 | *.tax2010 14 | *.vcf 15 | *.xml 16 | 17 | ### Database ### 18 | *.accdb 19 | *.db 20 | *.dbf 21 | *.mdb 22 | *.pdb 23 | *.sql 24 | *.sqlite3 25 | 26 | ### Executable ### 27 | *.app 28 | *.bat 29 | *.cgi 30 | *.com 31 | *.exe 32 | *.gadget 33 | *.jar 34 | *.pif 35 | *.vb 36 | *.wsf 37 | 38 | ### Less ### 39 | *.less 40 | 41 | ### Linux ### 42 | *~ 43 | 44 | # temporary files which can be created if a process still has a handle open of a deleted file 45 | .fuse_hidden* 46 | 47 | # KDE directory preferences 48 | .directory 49 | 50 | # Linux trash folder which might appear on any partition or disk 51 | .Trash-* 52 | 53 | # .nfs files are created when an open file is removed but is still being accessed 54 | .nfs* 55 | 56 | ### Node ### 57 | # Logs 58 | logs 59 | *.log 60 | npm-debug.log* 61 | yarn-debug.log* 62 | yarn-error.log* 63 | 64 | # Runtime data 65 | pids 66 | *.pid 67 | *.seed 68 | *.pid.lock 69 | 70 | # Directory for instrumented libs generated by jscoverage/JSCover 71 | lib-cov 72 | 73 | # Coverage directory used by tools like istanbul 74 | coverage 75 | 76 | # nyc test coverage 77 | .nyc_output 78 | 79 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 80 | .grunt 81 | 82 | # Bower dependency directory (https://bower.io/) 83 | bower_components 84 | 85 | # node-waf configuration 86 | .lock-wscript 87 | 88 | # Compiled binary addons (http://nodejs.org/api/addons.html) 89 | build/Release 90 | 91 | # Dependency directories 92 | node_modules/ 93 | jspm_packages/ 94 | 95 | # Typescript v1 declaration files 96 | typings/ 97 | 98 | # Optional npm cache directory 99 | .npm 100 | 101 | # Optional eslint cache 102 | .eslintcache 103 | 104 | # Optional REPL history 105 | .node_repl_history 106 | 107 | # Output of 'npm pack' 108 | *.tgz 109 | 110 | # Yarn Integrity file 111 | .yarn-integrity 112 | 113 | # dotenv environment variables file 114 | .env 115 | 116 | 117 | ### Sass ### 118 | .sass-cache/ 119 | *.css.map 120 | 121 | ### SublimeText ### 122 | # cache files for sublime text 123 | *.tmlanguage.cache 124 | *.tmPreferences.cache 125 | *.stTheme.cache 126 | 127 | # workspace files are user-specific 128 | *.sublime-workspace 129 | 130 | # project files should be checked into the repository, unless a significant 131 | # proportion of contributors will probably not be using SublimeText 132 | # *.sublime-project 133 | 134 | # sftp configuration file 135 | sftp-config.json 136 | 137 | # Package control specific files 138 | Package Control.last-run 139 | Package Control.ca-list 140 | Package Control.ca-bundle 141 | Package Control.system-ca-bundle 142 | Package Control.cache/ 143 | Package Control.ca-certs/ 144 | Package Control.merged-ca-bundle 145 | Package Control.user-ca-bundle 146 | oscrypto-ca-bundle.crt 147 | bh_unicode_properties.cache 148 | 149 | # Sublime-github package stores a github token in this file 150 | # https://packagecontrol.io/packages/sublime-github 151 | GitHub.sublime-settings 152 | 153 | ### VisualStudioCode ### 154 | .vscode/* 155 | !.vscode/settings.json 156 | !.vscode/tasks.json 157 | !.vscode/launch.json 158 | !.vscode/extensions.json 159 | .history 160 | 161 | ### Windows ### 162 | # Windows thumbnail cache files 163 | Thumbs.db 164 | ehthumbs.db 165 | ehthumbs_vista.db 166 | 167 | # Folder config file 168 | Desktop.ini 169 | 170 | # Recycle Bin used on file shares 171 | $RECYCLE.BIN/ 172 | 173 | # Windows Installer files 174 | *.cab 175 | *.msi 176 | *.msm 177 | *.msp 178 | 179 | # Windows shortcuts 180 | *.lnk 181 | 182 | # End of https://www.gitignore.io/api/node,linux,windows,visualstudiocode,sublimetext,database,data,executable,sass,less,stylus 183 | 184 | ### Project Specific ### 185 | # Built files 186 | dist/ 187 | yarn.lock -------------------------------------------------------------------------------- /.poirc.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const StylelintPlugin = require('stylelint-webpack-plugin') 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | presets: [require('poi-preset-eslint')({ mode: '*' })], 7 | webpack(config) { 8 | config.plugins.push( 9 | new StylelintPlugin({ 10 | files: ['./src/**/*.{vue,css,scss,sass,less}'] 11 | }), 12 | new webpack.ProvidePlugin({ 13 | 'window.Quill': 'quill' 14 | }) 15 | ) 16 | return config 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import')({ 4 | path: ['node_modules/', 'src/assets/css/'] 5 | }), 6 | require('postcss-cssnext')(), 7 | require('postcss-nested')() 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | lib/ 4 | package.json 5 | *.min.* 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | useTabs: false, 5 | singleQuote: true, 6 | trailingComma: 'none', 7 | semi: false, 8 | arrowParens: 'avoid' 9 | } 10 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | lib/ 4 | *.min.* 5 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-prettier'], 3 | rules: { 4 | indentation: 2, 5 | 'at-rule-no-unknown': [true, { ignoreAtRules: ['tailwind', 'apply'] }], 6 | 'selector-nested-pattern': '.', 7 | 'no-missing-end-of-source-newline': true, 8 | 'no-empty-source': true, 9 | 'no-duplicate-selectors': true, 10 | 'selector-pseudo-class-no-unknown': [true, { ignorePseudoClasses: ['/^local|^global/'] }], 11 | 'max-nesting-depth': 5, 12 | 'selector-max-compound-selectors': 4, 13 | 'selector-combinator-space-after': 'always', 14 | 'selector-attribute-operator-space-before': 'never', 15 | 'color-no-invalid-hex': true, 16 | 'color-named': 'never', 17 | 'color-hex-length': 'long', 18 | 'string-quotes': 'single', 19 | 'max-empty-lines': 1, 20 | 'rule-empty-line-before': ['always', { except: ['after-single-line-comment', 'first-nested'] }], 21 | 'at-rule-empty-line-before': ['always', { except: ['after-same-name', 'inside-block'] }], 22 | 'declaration-empty-line-before': [ 23 | 'always', 24 | { except: ['first-nested', 'after-comment', 'after-declaration'] } 25 | ], 26 | 'no-eol-whitespace': [true, { ignore: ['empty-lines'] }], 27 | 'custom-property-empty-line-before': [ 28 | 'always', 29 | { except: ['first-nested', 'after-comment', 'after-custom-property'] } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Oskar Rainer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-directus 2 | 3 | A Vue.js plugin for front-end content editing. 4 | > Note: this is a proof on concept and has not been tested outside of my local dev environment. 5 | 6 | ## Requirements 7 | 8 | - [Vue.Js](https://vuejs.org/) 9 | - [Directus CMS Api](https://getdirectus.com/) 10 | - [Directus Javascript SDK](https://github.com/directus/directus-sdk-javascript) 11 | 12 | ## Usage 13 | 14 | TBD 15 | 16 | ## Contributing 17 | 18 | [Vue.Js](https://vuejs.org/) and [Directus CMS](https://getdirectus.com/) 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-directus", 3 | "version": "1.0.0", 4 | "description": "A Vue.js plugin for front-end content editing.", 5 | "author": "Oskar Rainer", 6 | "private": true, 7 | "browserslist": [ 8 | "> 1%", 9 | "last 2 versions", 10 | "not IE < 11" 11 | ], 12 | "scripts": { 13 | "dev": "cross-env NODE_ENV=development poi --config .poirc.js", 14 | "build": "cross-env NODE_ENV=production poi build --config .poirc.js", 15 | "lint:js": "eslint --ext .js,.vue src", 16 | "lint:css": "stylelint **/*.{vue,css,scss,sass,less}", 17 | "lint": "npm run -s lint:js; npm run -s lint:css; exit 0" 18 | }, 19 | "devDependencies": { 20 | "babel-eslint": "^8.2.1", 21 | "cross-env": "^5.1.3", 22 | "eslint": "^4.16.0", 23 | "eslint-config-prettier": "^2.9.0", 24 | "eslint-config-standard": "^11.0.0-beta.0", 25 | "eslint-import-resolver-webpack": "^0.8.4", 26 | "eslint-plugin-import": "^2.8.0", 27 | "eslint-plugin-node": "^5.2.1", 28 | "eslint-plugin-prettier": "^2.5.0", 29 | "eslint-plugin-promise": "^3.6.0", 30 | "eslint-plugin-standard": "^3.0.1", 31 | "eslint-plugin-vue": "^4.2.0", 32 | "poi": "^9.6.13", 33 | "poi-preset-eslint": "^9.1.1", 34 | "postcss": "^6.0.16", 35 | "postcss-cssnext": "^3.1.0", 36 | "postcss-import": "^11.0.0", 37 | "postcss-nested": "^3.0.0", 38 | "prettier": "^1.10.2", 39 | "stylelint": "^8.4.0", 40 | "stylelint-config-prettier": "^2.0.0", 41 | "stylelint-webpack-plugin": "^0.10.1" 42 | }, 43 | "dependencies": { 44 | "directus-sdk-javascript": "^2.7.0", 45 | "lodash": "^4.17.5", 46 | "normalize.css": "^8.0.0", 47 | "quill-image-drop-module": "^1.0.3", 48 | "quill-image-resize-module": "^3.0.0", 49 | "shortid": "^2.2.8", 50 | "vue": "^2.5.13", 51 | "vue-croppie": "^1.3.10", 52 | "vue-dragula": "^1.3.1", 53 | "vue-images-loaded": "^1.1.2", 54 | "vue2-editor": "^2.4.2", 55 | "vuex": "^3.0.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Failed to load projects. 13 | 14 | 15 | 16 | 17 | 18 | 42 | 43 | 57 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from '@/App' 3 | import store from '@/store' 4 | import VueDirectus from '@/plugins/vue-directus' 5 | 6 | Vue.config.productionTip = false 7 | 8 | Vue.use(VueDirectus, { store }) 9 | 10 | new Vue({ 11 | el: '#app', 12 | store, 13 | render: h => h(App) 14 | }) 15 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/api/index.js: -------------------------------------------------------------------------------- 1 | import { RemoteInstance } from 'directus-sdk-javascript' 2 | 3 | export default new RemoteInstance({ 4 | url: 'http://192.168.33.6/api/1.1/', 5 | accessToken: 'xMV8l9zJORKBnkllHB6A0XIUPMy8CAt5' 6 | }) 7 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/assets/css/lib/quill.theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Quill Editor v1.3.5 3 | * https://quilljs.com/ 4 | * Copyright (c) 2014, Jason Chen 5 | * Copyright (c) 2013, salesforce.com 6 | */ 7 | 8 | .ql-editor { 9 | font-family: inherit !important; 10 | font-size: inherit !important; 11 | line-height: inherit !important; 12 | min-height: 100% !important; 13 | overflow: hidden !important; 14 | padding: 0 !important; 15 | text-align: inherit !important; 16 | } 17 | 18 | .ql-container { 19 | font-family: inherit !important; 20 | font-size: inherit !important; 21 | line-height: inherit !important; 22 | } 23 | 24 | .ql-bubble.ql-toolbar:after, 25 | .ql-bubble .ql-toolbar:after { 26 | clear: both; 27 | content: ''; 28 | display: table; 29 | } 30 | .ql-bubble.ql-toolbar button, 31 | .ql-bubble .ql-toolbar button { 32 | background: none; 33 | border: none; 34 | cursor: pointer; 35 | display: inline-block; 36 | float: left; 37 | height: 24px; 38 | padding: 3px 5px; 39 | width: 28px; 40 | } 41 | .ql-bubble.ql-toolbar button svg, 42 | .ql-bubble .ql-toolbar button svg { 43 | float: left; 44 | height: 100%; 45 | } 46 | .ql-bubble.ql-toolbar button:active:hover, 47 | .ql-bubble .ql-toolbar button:active:hover { 48 | outline: none; 49 | } 50 | .ql-bubble.ql-toolbar input.ql-image[type=file], 51 | .ql-bubble .ql-toolbar input.ql-image[type=file] { 52 | display: none; 53 | } 54 | .ql-bubble.ql-toolbar button:hover, 55 | .ql-bubble .ql-toolbar button:hover, 56 | .ql-bubble.ql-toolbar button:focus, 57 | .ql-bubble .ql-toolbar button:focus, 58 | .ql-bubble.ql-toolbar button.ql-active, 59 | .ql-bubble .ql-toolbar button.ql-active, 60 | .ql-bubble.ql-toolbar .ql-picker-label:hover, 61 | .ql-bubble .ql-toolbar .ql-picker-label:hover, 62 | .ql-bubble.ql-toolbar .ql-picker-label.ql-active, 63 | .ql-bubble .ql-toolbar .ql-picker-label.ql-active, 64 | .ql-bubble.ql-toolbar .ql-picker-item:hover, 65 | .ql-bubble .ql-toolbar .ql-picker-item:hover, 66 | .ql-bubble.ql-toolbar .ql-picker-item.ql-selected, 67 | .ql-bubble .ql-toolbar .ql-picker-item.ql-selected { 68 | color: #fff; 69 | } 70 | .ql-bubble.ql-toolbar button:hover .ql-fill, 71 | .ql-bubble .ql-toolbar button:hover .ql-fill, 72 | .ql-bubble.ql-toolbar button:focus .ql-fill, 73 | .ql-bubble .ql-toolbar button:focus .ql-fill, 74 | .ql-bubble.ql-toolbar button.ql-active .ql-fill, 75 | .ql-bubble .ql-toolbar button.ql-active .ql-fill, 76 | .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-fill, 77 | .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-fill, 78 | .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-fill, 79 | .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-fill, 80 | .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-fill, 81 | .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-fill, 82 | .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-fill, 83 | .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-fill, 84 | .ql-bubble.ql-toolbar button:hover .ql-stroke.ql-fill, 85 | .ql-bubble .ql-toolbar button:hover .ql-stroke.ql-fill, 86 | .ql-bubble.ql-toolbar button:focus .ql-stroke.ql-fill, 87 | .ql-bubble .ql-toolbar button:focus .ql-stroke.ql-fill, 88 | .ql-bubble.ql-toolbar button.ql-active .ql-stroke.ql-fill, 89 | .ql-bubble .ql-toolbar button.ql-active .ql-stroke.ql-fill, 90 | .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, 91 | .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, 92 | .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, 93 | .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, 94 | .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, 95 | .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, 96 | .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, 97 | .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill { 98 | fill: #fff; 99 | } 100 | .ql-bubble.ql-toolbar button:hover .ql-stroke, 101 | .ql-bubble .ql-toolbar button:hover .ql-stroke, 102 | .ql-bubble.ql-toolbar button:focus .ql-stroke, 103 | .ql-bubble .ql-toolbar button:focus .ql-stroke, 104 | .ql-bubble.ql-toolbar button.ql-active .ql-stroke, 105 | .ql-bubble .ql-toolbar button.ql-active .ql-stroke, 106 | .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-stroke, 107 | .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-stroke, 108 | .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-stroke, 109 | .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-stroke, 110 | .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-stroke, 111 | .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-stroke, 112 | .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, 113 | .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, 114 | .ql-bubble.ql-toolbar button:hover .ql-stroke-miter, 115 | .ql-bubble .ql-toolbar button:hover .ql-stroke-miter, 116 | .ql-bubble.ql-toolbar button:focus .ql-stroke-miter, 117 | .ql-bubble .ql-toolbar button:focus .ql-stroke-miter, 118 | .ql-bubble.ql-toolbar button.ql-active .ql-stroke-miter, 119 | .ql-bubble .ql-toolbar button.ql-active .ql-stroke-miter, 120 | .ql-bubble.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, 121 | .ql-bubble .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, 122 | .ql-bubble.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, 123 | .ql-bubble .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, 124 | .ql-bubble.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, 125 | .ql-bubble .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, 126 | .ql-bubble.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, 127 | .ql-bubble .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { 128 | stroke: #fff; 129 | } 130 | @media (pointer: coarse) { 131 | .ql-bubble.ql-toolbar button:hover:not(.ql-active), 132 | .ql-bubble .ql-toolbar button:hover:not(.ql-active) { 133 | color: #ccc; 134 | } 135 | .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-fill, 136 | .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-fill, 137 | .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill, 138 | .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill { 139 | fill: #ccc; 140 | } 141 | .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-stroke, 142 | .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-stroke, 143 | .ql-bubble.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter, 144 | .ql-bubble .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter { 145 | stroke: #ccc; 146 | } 147 | } 148 | .ql-bubble { 149 | box-sizing: border-box; 150 | } 151 | .ql-bubble * { 152 | box-sizing: border-box; 153 | } 154 | .ql-bubble .ql-hidden { 155 | display: none; 156 | } 157 | .ql-bubble .ql-out-bottom, 158 | .ql-bubble .ql-out-top { 159 | visibility: hidden; 160 | } 161 | .ql-bubble .ql-tooltip { 162 | min-width: 410px; 163 | position: absolute; 164 | transform: translateY(10px); 165 | z-index: 999999; 166 | } 167 | .ql-bubble .ql-tooltip a { 168 | cursor: pointer; 169 | text-decoration: none; 170 | } 171 | .ql-bubble .ql-tooltip.ql-flip { 172 | transform: translateY(-10px); 173 | } 174 | .ql-bubble .ql-formats { 175 | display: inline-block; 176 | vertical-align: middle; 177 | } 178 | .ql-bubble .ql-formats:after { 179 | clear: both; 180 | content: ''; 181 | display: table; 182 | } 183 | .ql-bubble .ql-stroke { 184 | fill: none; 185 | stroke: #ccc; 186 | stroke-linecap: round; 187 | stroke-linejoin: round; 188 | stroke-width: 2; 189 | } 190 | .ql-bubble .ql-stroke-miter { 191 | fill: none; 192 | stroke: #ccc; 193 | stroke-miterlimit: 10; 194 | stroke-width: 2; 195 | } 196 | .ql-bubble .ql-fill, 197 | .ql-bubble .ql-stroke.ql-fill { 198 | fill: #ccc; 199 | } 200 | .ql-bubble .ql-empty { 201 | fill: none; 202 | } 203 | .ql-bubble .ql-even { 204 | fill-rule: evenodd; 205 | } 206 | .ql-bubble .ql-thin, 207 | .ql-bubble .ql-stroke.ql-thin { 208 | stroke-width: 1; 209 | } 210 | .ql-bubble .ql-transparent { 211 | opacity: 0.4; 212 | } 213 | .ql-bubble .ql-direction svg:last-child { 214 | display: none; 215 | } 216 | .ql-bubble .ql-direction.ql-active svg:last-child { 217 | display: inline; 218 | } 219 | .ql-bubble .ql-direction.ql-active svg:first-child { 220 | display: none; 221 | } 222 | .ql-bubble .ql-editor strong { 223 | font-weight: bold; 224 | } 225 | .ql-bubble .ql-editor em { 226 | font-style: italic; 227 | } 228 | .ql-bubble .ql-editor h1 { 229 | font-size: 2em; 230 | } 231 | .ql-bubble .ql-editor h2 { 232 | font-size: 1.5em; 233 | } 234 | .ql-bubble .ql-editor h3 { 235 | font-size: 1.17em; 236 | } 237 | .ql-bubble .ql-editor h4 { 238 | font-size: 1em; 239 | } 240 | .ql-bubble .ql-editor h5 { 241 | font-size: 0.83em; 242 | } 243 | .ql-bubble .ql-editor h6 { 244 | font-size: 0.67em; 245 | } 246 | .ql-bubble .ql-editor a { 247 | text-decoration: underline; 248 | } 249 | .ql-bubble .ql-editor blockquote { 250 | border-left: 4px solid #ccc; 251 | margin-bottom: 5px; 252 | margin-top: 5px; 253 | padding-left: 16px; 254 | } 255 | .ql-bubble .ql-editor code, 256 | .ql-bubble .ql-editor pre { 257 | background-color: #f0f0f0; 258 | border-radius: 3px; 259 | } 260 | .ql-bubble .ql-editor pre { 261 | white-space: pre-wrap; 262 | margin-bottom: 5px; 263 | margin-top: 5px; 264 | padding: 5px 10px; 265 | } 266 | .ql-bubble .ql-editor code { 267 | font-size: 85%; 268 | padding: 2px 4px; 269 | } 270 | .ql-bubble .ql-editor pre.ql-syntax { 271 | background-color: #23241f; 272 | color: #f8f8f2; 273 | overflow: visible; 274 | } 275 | .ql-bubble .ql-editor img { 276 | max-width: 100%; 277 | } 278 | .ql-bubble .ql-picker { 279 | color: #ccc; 280 | display: inline-block; 281 | float: left; 282 | font-size: 14px; 283 | font-weight: 500; 284 | height: 24px; 285 | position: relative; 286 | vertical-align: middle; 287 | } 288 | .ql-bubble .ql-picker-label { 289 | cursor: pointer; 290 | display: inline-block; 291 | height: 100%; 292 | padding-left: 8px; 293 | padding-right: 2px; 294 | position: relative; 295 | width: 100%; 296 | } 297 | .ql-bubble .ql-picker-label::before { 298 | display: inline-block; 299 | line-height: 22px; 300 | } 301 | .ql-bubble .ql-picker-options { 302 | background-color: #444; 303 | display: none; 304 | min-width: 100%; 305 | padding: 4px 8px; 306 | position: absolute; 307 | white-space: nowrap; 308 | } 309 | .ql-bubble .ql-picker-options .ql-picker-item { 310 | cursor: pointer; 311 | display: block; 312 | padding-bottom: 5px; 313 | padding-top: 5px; 314 | } 315 | .ql-bubble .ql-picker.ql-expanded .ql-picker-label { 316 | color: #777; 317 | z-index: 2; 318 | } 319 | .ql-bubble .ql-picker.ql-expanded .ql-picker-label .ql-fill { 320 | fill: #777; 321 | } 322 | .ql-bubble .ql-picker.ql-expanded .ql-picker-label .ql-stroke { 323 | stroke: #777; 324 | } 325 | .ql-bubble .ql-picker.ql-expanded .ql-picker-options { 326 | display: block; 327 | margin-top: -1px; 328 | top: 100%; 329 | z-index: 1; 330 | } 331 | .ql-bubble .ql-color-picker, 332 | .ql-bubble .ql-icon-picker { 333 | width: 28px; 334 | } 335 | .ql-bubble .ql-color-picker .ql-picker-label, 336 | .ql-bubble .ql-icon-picker .ql-picker-label { 337 | padding: 2px 4px; 338 | } 339 | .ql-bubble .ql-color-picker .ql-picker-label svg, 340 | .ql-bubble .ql-icon-picker .ql-picker-label svg { 341 | right: 4px; 342 | } 343 | .ql-bubble .ql-icon-picker .ql-picker-options { 344 | padding: 4px 0px; 345 | } 346 | .ql-bubble .ql-icon-picker .ql-picker-item { 347 | height: 24px; 348 | width: 24px; 349 | padding: 2px 4px; 350 | } 351 | .ql-bubble .ql-color-picker .ql-picker-options { 352 | padding: 3px 5px; 353 | width: 152px; 354 | } 355 | .ql-bubble .ql-color-picker .ql-picker-item { 356 | border: 1px solid transparent; 357 | float: left; 358 | height: 16px; 359 | margin: 2px; 360 | padding: 0px; 361 | width: 16px; 362 | } 363 | .ql-bubble .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg { 364 | position: absolute; 365 | margin-top: -9px; 366 | right: 0; 367 | top: 50%; 368 | width: 18px; 369 | } 370 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before, 371 | .ql-bubble .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before, 372 | .ql-bubble .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before, 373 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before, 374 | .ql-bubble .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before, 375 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before { 376 | content: attr(data-label); 377 | } 378 | .ql-bubble .ql-picker.ql-header { 379 | width: 98px; 380 | } 381 | .ql-bubble .ql-picker.ql-header .ql-picker-label::before, 382 | .ql-bubble .ql-picker.ql-header .ql-picker-item::before { 383 | content: 'Normal'; 384 | } 385 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, 386 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { 387 | content: 'Heading 1'; 388 | } 389 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, 390 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { 391 | content: 'Heading 2'; 392 | } 393 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, 394 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { 395 | content: 'Heading 3'; 396 | } 397 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, 398 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { 399 | content: 'Heading 4'; 400 | } 401 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="5"]::before, 402 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { 403 | content: 'Heading 5'; 404 | } 405 | .ql-bubble .ql-picker.ql-header .ql-picker-label[data-value="6"]::before, 406 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { 407 | content: 'Heading 6'; 408 | } 409 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { 410 | font-size: 2em; 411 | } 412 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { 413 | font-size: 1.5em; 414 | } 415 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { 416 | font-size: 1.17em; 417 | } 418 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { 419 | font-size: 1em; 420 | } 421 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="5"]::before { 422 | font-size: 0.83em; 423 | } 424 | .ql-bubble .ql-picker.ql-header .ql-picker-item[data-value="6"]::before { 425 | font-size: 0.67em; 426 | } 427 | .ql-bubble .ql-picker.ql-font { 428 | width: 108px; 429 | } 430 | .ql-bubble .ql-picker.ql-font .ql-picker-label::before, 431 | .ql-bubble .ql-picker.ql-font .ql-picker-item::before { 432 | content: 'Sans Serif'; 433 | } 434 | .ql-bubble .ql-picker.ql-font .ql-picker-label[data-value=serif]::before, 435 | .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { 436 | content: 'Serif'; 437 | } 438 | .ql-bubble .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before, 439 | .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { 440 | content: 'Monospace'; 441 | } 442 | .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=serif]::before { 443 | font-family: Georgia, Times New Roman, serif; 444 | } 445 | .ql-bubble .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before { 446 | font-family: Monaco, Courier New, monospace; 447 | } 448 | .ql-bubble .ql-picker.ql-size { 449 | width: 98px; 450 | } 451 | .ql-bubble .ql-picker.ql-size .ql-picker-label::before, 452 | .ql-bubble .ql-picker.ql-size .ql-picker-item::before { 453 | content: 'Normal'; 454 | } 455 | .ql-bubble .ql-picker.ql-size .ql-picker-label[data-value=small]::before, 456 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=small]::before { 457 | content: 'Small'; 458 | } 459 | .ql-bubble .ql-picker.ql-size .ql-picker-label[data-value=large]::before, 460 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=large]::before { 461 | content: 'Large'; 462 | } 463 | .ql-bubble .ql-picker.ql-size .ql-picker-label[data-value=huge]::before, 464 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { 465 | content: 'Huge'; 466 | } 467 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=small]::before { 468 | font-size: 10px; 469 | } 470 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=large]::before { 471 | font-size: 18px; 472 | } 473 | .ql-bubble .ql-picker.ql-size .ql-picker-item[data-value=huge]::before { 474 | font-size: 32px; 475 | } 476 | .ql-bubble .ql-color-picker.ql-background .ql-picker-item { 477 | background-color: #fff; 478 | } 479 | .ql-bubble .ql-color-picker.ql-color .ql-picker-item { 480 | background-color: #000; 481 | } 482 | .ql-bubble .ql-toolbar .ql-formats { 483 | margin: 8px 12px 8px 0px; 484 | } 485 | .ql-bubble .ql-toolbar .ql-formats:first-child { 486 | margin-left: 12px; 487 | } 488 | .ql-bubble .ql-color-picker svg { 489 | margin: 1px; 490 | } 491 | .ql-bubble .ql-color-picker .ql-picker-item.ql-selected, 492 | .ql-bubble .ql-color-picker .ql-picker-item:hover { 493 | border-color: #fff; 494 | } 495 | .ql-bubble .ql-tooltip { 496 | background-color: #444; 497 | border-radius: 25px; 498 | color: #fff; 499 | } 500 | .ql-bubble .ql-tooltip-arrow { 501 | border-left: 6px solid transparent; 502 | border-right: 6px solid transparent; 503 | content: " "; 504 | display: block; 505 | left: 50%; 506 | margin-left: -6px; 507 | position: absolute; 508 | } 509 | .ql-bubble .ql-tooltip:not(.ql-flip) .ql-tooltip-arrow { 510 | border-bottom: 6px solid #444; 511 | top: -6px; 512 | } 513 | .ql-bubble .ql-tooltip.ql-flip .ql-tooltip-arrow { 514 | border-top: 6px solid #444; 515 | bottom: -6px; 516 | } 517 | .ql-bubble .ql-tooltip.ql-editing .ql-tooltip-editor { 518 | display: block; 519 | } 520 | .ql-bubble .ql-tooltip.ql-editing .ql-formats { 521 | visibility: hidden; 522 | } 523 | .ql-bubble .ql-tooltip-editor { 524 | display: none; 525 | } 526 | .ql-bubble .ql-tooltip-editor input[type=text] { 527 | background: transparent; 528 | border: none; 529 | color: #fff; 530 | font-size: 13px; 531 | height: 100%; 532 | outline: none; 533 | padding: 10px 20px; 534 | position: absolute; 535 | width: 100%; 536 | } 537 | .ql-bubble .ql-tooltip-editor a { 538 | top: 10px; 539 | position: absolute; 540 | right: 20px; 541 | } 542 | .ql-bubble .ql-tooltip-editor a:before { 543 | color: #ccc; 544 | content: "\D7"; 545 | font-size: 16px; 546 | font-weight: bold; 547 | } 548 | .ql-container.ql-bubble:not(.ql-disabled) a { 549 | position: relative; 550 | white-space: nowrap; 551 | } 552 | .ql-container.ql-bubble:not(.ql-disabled) a::before { 553 | background-color: #444; 554 | border-radius: 15px; 555 | top: -5px; 556 | font-size: 12px; 557 | color: #fff; 558 | content: attr(href); 559 | font-weight: normal; 560 | overflow: hidden; 561 | padding: 5px 15px; 562 | text-decoration: none; 563 | z-index: 1; 564 | } 565 | .ql-container.ql-bubble:not(.ql-disabled) a::after { 566 | border-top: 6px solid #444; 567 | border-left: 6px solid transparent; 568 | border-right: 6px solid transparent; 569 | top: 0; 570 | content: " "; 571 | height: 0; 572 | width: 0; 573 | } 574 | .ql-container.ql-bubble:not(.ql-disabled) a::before, 575 | .ql-container.ql-bubble:not(.ql-disabled) a::after { 576 | left: 0; 577 | margin-left: 50%; 578 | position: absolute; 579 | transform: translate(-50%, -100%); 580 | transition: visibility 0s ease 200ms; 581 | visibility: hidden; 582 | } 583 | .ql-container.ql-bubble:not(.ql-disabled) a:hover::before, 584 | .ql-container.ql-bubble:not(.ql-disabled) a:hover::after { 585 | visibility: visible; 586 | } 587 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/components/index.js: -------------------------------------------------------------------------------- 1 | import VueDirectusApp from './modules/VueDirectusApp' 2 | import VueDirectusCollection from './modules/VueDirectusCollection' 3 | import VueDirectusItem from './modules/VueDirectusItem' 4 | import VueDirectusText from './modules/VueDirectusText' 5 | import VueDirectusImage from './modules/VueDirectusImage' 6 | 7 | export { VueDirectusApp, VueDirectusCollection, VueDirectusItem, VueDirectusText, VueDirectusImage } 8 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/components/modules/VueDirectusApp.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 53 | 54 | 94 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/components/modules/VueDirectusCollection.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 37 | 38 | 58 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/components/modules/VueDirectusImage.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 161 | 162 | 225 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/components/modules/VueDirectusItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 56 | 57 | 83 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/components/modules/VueDirectusText.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 128 | 129 | 132 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/index.js: -------------------------------------------------------------------------------- 1 | import VueDirectusApi from './api' 2 | import * as VueDirectusStore from './store' 3 | import * as VueDirectusComponents from './components' 4 | 5 | const VueDirectus = { 6 | install(Vue, { store }) { 7 | if (!store) { 8 | throw Error(`VueDirectus requires the vuex store.`) 9 | } 10 | 11 | if (!VueDirectusApi) { 12 | throw Error(`VueDirectus requires the directus-sdk client.`) 13 | } 14 | 15 | // Register new store module 16 | store.registerModule('VueDirectus', { 17 | namespaced: true, 18 | state: { 19 | commited: [], 20 | busy: false 21 | }, 22 | modules: { 23 | ...VueDirectusStore 24 | }, 25 | getters: { 26 | isBusy: state => state.busy, 27 | // Return length of mutations ignoring all SYNC and FETCH mutations 28 | hasCommits: state => state.commited.filter(m => !/SYNC|FETCH/.test(m.type)).length > 0 29 | }, 30 | mutations: { 31 | RESET_ITEMS(state) { 32 | state.items.fetched = [] 33 | }, 34 | SET_STATUS(state, payload) { 35 | state.busy = payload 36 | } 37 | }, 38 | actions: { 39 | busy({ commit }, payload) { 40 | commit('SET_STATUS', payload) 41 | }, 42 | undo({ state, commit }) { 43 | state.commited.pop() 44 | commit('RESET_ITEMS') 45 | // Recommit all previous commits except the last one, which we 46 | // have just removed. Than remove the restored commits from the history. 47 | state.commited.forEach(({ type, payload }) => { 48 | commit(type, payload, { root: true }) 49 | state.commited.pop() 50 | }) 51 | } 52 | } 53 | }) 54 | 55 | // Subscribe to item mutations 56 | // Ignore internal STATUS and RESET mutations 57 | store.subscribe((mutation, state) => { 58 | if (!/STATUS|RESET/.test(mutation.type)) { 59 | state.VueDirectus.commited.push(mutation) 60 | } 61 | }) 62 | 63 | // Register plugin components 64 | Object.entries(VueDirectusComponents).forEach(([name, component]) => 65 | Vue.component(name, component) 66 | ) 67 | } 68 | } 69 | 70 | export default VueDirectus 71 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/store/index.js: -------------------------------------------------------------------------------- 1 | import settings from './modules/settings' 2 | import users from './modules/users' 3 | import items from './modules/items' 4 | 5 | export { settings, users, items } 6 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/store/modules/items.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable padding-line-between-statements */ 2 | import _ from 'lodash' 3 | import shortid from 'shortid' 4 | import VueDirectusApi from '../../api' 5 | 6 | const namespaced = true 7 | 8 | const state = { 9 | local: {}, 10 | remote: {} 11 | } 12 | 13 | const mutations = { 14 | SYNC: state => { 15 | state.local = _.cloneDeep(state.remote) 16 | }, 17 | 18 | FETCH: (state, { table, items }) => { 19 | state.remote = { ...state.remote, [table]: items } 20 | }, 21 | 22 | ADD: (state, { table, item }) => { 23 | state.local[table].data.push(item) 24 | }, 25 | 26 | REMOVE: (state, { table, index }) => { 27 | state.local[table].data.splice(index, 1) 28 | }, 29 | 30 | EDIT: (state, { table, index, column, value }) => { 31 | state.local[table].data[index][column] = value 32 | } 33 | } 34 | 35 | const actions = { 36 | // Fetch items from API, add internal _id and 37 | // apply sorting based on its current position 38 | async fetch({ commit, dispatch }, table) { 39 | dispatch('VueDirectus/busy', true, { root: true }) 40 | const items = await VueDirectusApi.getItems(table) 41 | _.each(items.data, (set, index) => { 42 | set.sort = index 43 | set._id = shortid() 44 | set._hasImage = !!_.find(set, o => _.isObject(o) && _.get(o, 'data.type').includes('image')) 45 | }) 46 | commit('FETCH', { table, items }) 47 | commit('SYNC') 48 | dispatch('VueDirectus/busy', false, { root: true }) 49 | }, 50 | 51 | // Add item by cloning its latest sibling 52 | add({ commit }, table) { 53 | let item = _.clone(_.last(state.local[table].data)) 54 | item._id = shortid() 55 | item.sort += 1 56 | commit('ADD', { table, item }) 57 | }, 58 | 59 | // Remove item based on its _id 60 | remove({ commit }, { table, id }) { 61 | const index = _.findIndex(state.local[table].data, set => set._id === id) 62 | commit('REMOVE', { table, index }) 63 | }, 64 | 65 | // Edit item based on its _id 66 | edit({ commit }, { table, id, column, value }) { 67 | const index = _.findIndex(state.local[table].data, set => set._id === id) 68 | commit('EDIT', { table, index, column, value }) 69 | }, 70 | 71 | // Save all changes 72 | save({ commit, getters }) { 73 | const diff = getters.diff 74 | console.log(diff) 75 | } 76 | } 77 | 78 | const getters = { 79 | // Return all items of a given table 80 | table: state => table => (state.local[table] ? state.local[table].data : []), 81 | 82 | // Return item count of a given table 83 | count: state => table => (state.local[table] ? state.local[table].meta.total : 0), 84 | 85 | // Return wheter the local state contains uncommited diffs 86 | // hasDiff: (state, getters) => { 87 | // return ( 88 | // _.size( 89 | // _.find(getters.diff, (set, table) => { 90 | // return !_.isEmpty(set.toDelete) || !_.isEmpty(set.toCreate) || !_.isEmpty(set.toUpdate) 91 | // }) 92 | // ) > 0 93 | // ) 94 | // }, 95 | 96 | // Get diff between local and remote state 97 | diff: state => { 98 | let diff = {} 99 | 100 | // Loop over all data sets in local branch 101 | _.forOwn(state.local, (set, table) => { 102 | const remote = state.remote[table].data 103 | const local = set.data 104 | const toDelete = [] 105 | const toCreate = [] 106 | const toUpdate = [] 107 | 108 | // Find missing items 109 | _.forOwn(remote, el => { 110 | if (!_.find(local, el)) { 111 | toDelete.push(el) 112 | } 113 | }) 114 | 115 | // Find new items 116 | _.forOwn(local, el => { 117 | if (!_.find(remote, el)) { 118 | toCreate.push(el) 119 | } 120 | }) 121 | 122 | // Find changed items 123 | _.forOwn(local, el => { 124 | let copy = _.find(remote, el) 125 | if (!_.isEqual(copy, el) && !_.includes(toCreate, el) && !_.includes(toDelete, el)) { 126 | toUpdate.push(el) 127 | } 128 | }) 129 | 130 | // Merge all arrays 131 | diff = _.set(diff, `${table}.toDelete`, toDelete) 132 | diff = _.set(diff, `${table}.toCreate`, toCreate) 133 | diff = _.set(diff, `${table}.toUpdate`, toUpdate) 134 | }) 135 | 136 | return diff 137 | } 138 | } 139 | 140 | export default { 141 | namespaced, 142 | state, 143 | mutations, 144 | getters, 145 | actions 146 | } 147 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable padding-line-between-statements */ 2 | const namespaced = true 3 | 4 | const state = { 5 | // 6 | } 7 | 8 | const mutations = { 9 | // 10 | } 11 | 12 | const actions = { 13 | // 14 | } 15 | 16 | const getters = { 17 | // 18 | } 19 | 20 | export default { 21 | namespaced, 22 | state, 23 | mutations, 24 | getters, 25 | actions 26 | } 27 | -------------------------------------------------------------------------------- /src/plugins/vue-directus/store/modules/users.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable padding-line-between-statements */ 2 | const namespaced = true 3 | 4 | const state = { 5 | // 6 | } 7 | 8 | const mutations = { 9 | // 10 | } 11 | 12 | const actions = { 13 | // 14 | } 15 | 16 | const getters = { 17 | // 18 | } 19 | 20 | export default { 21 | namespaced, 22 | state, 23 | mutations, 24 | getters, 25 | actions 26 | } 27 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({}) 7 | --------------------------------------------------------------------------------