From b9f811c7907b33f0c48e15bff32e53561e8fc2f1 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:41:36 +0300 Subject: [PATCH 01/62] feat(datastore): add new datastore --- src/main/lib/datastore/datastore.js | 229 ++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 src/main/lib/datastore/datastore.js diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js new file mode 100644 index 0000000..117479e --- /dev/null +++ b/src/main/lib/datastore/datastore.js @@ -0,0 +1,229 @@ +import low from 'lowdb' +import fs from 'fs-extra' +import path from 'path' +import readline from 'readline' +import FileSync from 'lowdb/adapters/FileSync' +import Joi from '@hapi/joi' +import extendMethods from './extend-methods' +import electronStore from '../../store' +import { nestedToFlat } from '../../util/helpers' +import pull from 'lodash-es/pull' + +class Datastore { + /** + * @param config.path - path to file (required) + * @param config.backupPath - path to backup folder (required) + * @param config.collections - collections + */ + constructor (config) { + this.initPath(config) + + this.db = low(new FileSync(`${this.path}/db.json`)) + this.collections = {} + this.schema = Joi + this.migrateStore = {} + + this.createCollections(config.collections) + } + + initPath (config) { + if (!config.path) throw Error('config.path is required') + if (!config.backupPath) throw Error('config.backupPath is required') + + fs.ensureDirSync(config.path) + fs.ensureDirSync(config.backupPath) + + this.path = config.path + this.backupPath = config.backupPath + } + + createCollections (collections = []) { + if (collections.length) { + const collectionsObj = {} + + for (const i of collections) { + collectionsObj[i.name] = [] + } + + this.db.defaults(collectionsObj).write() + + for (const { name, schema } of collections) { + this.collections[name] = this.db.get(name) + this.collections[name]._name = name + this.collections[name]._schema = Joi.object(schema) + + for (const m in extendMethods) { + this.collections[name][m] = extendMethods[m] + } + } + } + } + + updateCollections () { + Object.entries(this.collections).map(([p, v]) => { + this.collections[p] = this.db.get(p) + this.collections[p]._name = v._name + this.collections[p]._schema = v._schema + + for (const m in extendMethods) { + this.collections[p][m] = extendMethods[m] + } + }) + } + + clearCollections () { + Object.keys(this.collections).map(i => this.db.set(i, []).write()) + } + + createDefaultFolders () { + if (this.collections.folders.size().value()) return + + const systemFolders = [ + { name: 'Inbox', isSystem: true, alias: 'inbox' }, + { name: 'Favorites', isSystem: true, alias: 'favorites' }, + { name: 'All Snippets', isSystem: true, alias: 'allSnippets' }, + { name: 'Trash', isSystem: true, alias: 'trash' } + ] + const defaultFolder = { + name: 'Default', + defaultLanguage: 'text' + } + const folders = [...systemFolders, defaultFolder] + + folders.map(i => { + this.collections.folders.$insert(i) + }) + } + + import (from) { + electronStore.preferences.set('storagePath', from) + this.updatePath(from) + } + + move (to) { + return new Promise((resolve, reject) => { + const src = path.resolve(`${this.path}/db.json`) + const dist = path.resolve(to, 'db.json') + + console.warn(src, dist) + + fs.readdir(to, (err, files) => { + console.log(files) + if (err) reject(err) + + if (files.includes('db.json')) { + reject(new Error('Folder already contains DB.')) + } + fs.moveSync(src, dist) + electronStore.preferences.set('storagePath', to) + this.updatePath(to) + resolve() + }) + }) + } + + async updatePath (path) { + this.path = path + this.db = low(new FileSync(`${path}/db.json`)) + this.updateCollections() + } + + async migrate (path) { + if (!path) throw Error('"path" is required') + + console.log('Migrate from v1 is started') + this.clearCollections() + this.createDefaultFolders() + + const convertDBToJSON = async file => { + const readInterface = readline.createInterface({ + input: fs.createReadStream(`${path}/${file}.db`), + output: process.stdout, + console: false + }) + const arr = [] + + return new Promise((resolve, reject) => { + readInterface.on('line', line => { + if (line) arr.push(JSON.parse(line)) + }) + + readInterface.on('close', () => { + resolve(arr) + }) + }) + } + + const masscodeJSON = await convertDBToJSON('masscode') + const snippetsJSON = await convertDBToJSON('snippets') + const tagsJSON = await convertDBToJSON('tags') + const masscodeJSONList = nestedToFlat(masscodeJSON[0].list) + + this.migrateStore.folderIdsMap = [] + this.migrateStore.tagIdsMap = [] + + return new Promise((resolve, reject) => { + // Folders + masscodeJSONList.map(({ id, ...rest }) => { + const { _id } = this.collections.folders.$insert(rest) + + this.migrateStore.folderIdsMap.push([id, _id]) + }) + this.migrateStore.folderIdsMap.map(([oldId, newId]) => { + this.collections.folders + .find({ parentId: oldId }) + .assign({ parentId: newId }) + .write() + }) + // Snippets + snippetsJSON.map( + ({ + _id, + tags, + folderId, + createdAt, + updatedAt, + folder, + tagsPopulated, + ...rest + }) => { + const [, newId] = + this.migrateStore.folderIdsMap.find(item => + item.includes(folderId) + ) || [] + + this.collections.snippets.$insert({ + ...rest, + tagIds: tags, + folderId: newId || null + }) + } + ) + // Tags + tagsJSON.map(({ _id: oldId, ...rest }) => { + const { _id } = this.collections.tags.$insert(rest) + this.migrateStore.tagIdsMap.push([oldId, _id]) + }) + + this.migrateStore.tagIdsMap.map(([oldId, newId]) => { + const snippetsWithTags = this.collections.snippets + .filter(i => i.tagIds.includes(oldId)) + .value() + + snippetsWithTags.map(s => { + const { _id, tagIds } = s + pull(tagIds, oldId) + tagIds.push(newId) + this.collections.snippets + .find({ _id }) + .assign(tagIds) + .write() + }) + }) + + resolve(console.log('Migrate from v1 is completed')) + }) + } +} + +export default Datastore From 4a8b0e0c512065f1336bcb3807efbc284fedc072 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:42:14 +0300 Subject: [PATCH 02/62] feat(datastore): add extend methods --- src/main/lib/datastore/extend-methods.js | 188 +++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/main/lib/datastore/extend-methods.js diff --git a/src/main/lib/datastore/extend-methods.js b/src/main/lib/datastore/extend-methods.js new file mode 100644 index 0000000..51ed8f1 --- /dev/null +++ b/src/main/lib/datastore/extend-methods.js @@ -0,0 +1,188 @@ +import { v4 as uuid } from 'uuid' +import cloneDeep from 'lodash-es/cloneDeep' +import { deleteTechProps } from './helpers' + +const extendMethods = { + /** + * Добавление документа в коллекцию + * @param doc {Object} - Документ + */ + $insert (doc, customId = false) { + const { error, value } = this._schema.validate(doc) + + if (error) { + throw new Error(error) + } else { + doc = value + doc._id = uuid() + doc.createdAt = new Date().getTime() + doc.updatedAt = new Date().getTime() + this.push(doc).write() + return this.$findOne({ _id: doc._id }) + } + }, + /** + * Поиск по фильтру с возвратом списка документов + * @param filter {Object} - Фильтр поиска + * @returns {Array} - Список найденных документов + */ + $find (filter = {}) { + return this.filter(filter) + .cloneDeep() + .value() + }, + /** + * Поиск по фильтру с возвратом документа + * @param filter {Object} - Фильтр поиска + * @returns {Object} - Найденный документ + */ + $findOne (filter = {}) { + return this.find(filter) + .cloneDeep() + .value() + }, + /** + * Обновление документа по ID + * @param filter {Object} - Фильтр поиска + * @param update - {Object} - Обновления для документа + * @returns {Object} - Обновленные документ + */ + $findOneAndUpdate (filter, update) { + update = deleteTechProps(update) + const { error } = this._schema.validate(update) + + if (error) { + throw new Error(error) + } else { + this.find(filter) + .assign(update) + .set('updatedAt', new Date().getTime()) + .write() + return this.find(filter) + } + }, + /** + * Агрегация значений + * @param pipeline[].$match {Object} - Фильтр поиска + * @param pipeline[].$lookup {Object} - Присоединение коллекции + */ + $aggregate (pipeline = []) { + let docs = [] + console.group('$aggregate') + pipeline.forEach(pipe => { + for (const [k, v] of Object.entries(pipe)) { + console.log(k, v) + // $match принимает следующие значения: + // Простые + // - key: value + // Комплексные + // - key: {$in: []} + // - key: {$elemMatch: []} + if (k === '$match') { + const simpleKV = {} + for (const [kM, vM] of Object.entries(v)) { + console.log(kM, vM) + if (typeof vM !== 'object') simpleKV[kM] = vM + // console.log('simpleKV', simpleKV) + if (vM.$in) { + // Найти все сниппеты у которых в поле kM (например folderId) + // присутствует одно из vM.$in значений + console.group('$in') + const docsVm = vM.$in.reduce((acc, item) => { + // console.log({ [kM]: item }) + const query = { + ...simpleKV, + [kM]: item + } + console.log('query', query) + const doc = this.filter(query) + .cloneDeep() + .value() + acc.push(...doc) + return acc + }, []) + console.log(docsVm) + console.groupEnd() + docs = [...docs, ...docsVm] + } else if (vM.$elemMatch) { + // Найти все сниппеты у которых в поле-массиве kM (например tagIds) + // присутствует одно из vM.$elemMatch значений + const docsVm = this.filter({}) + .cloneDeep() + .value() + .reduce((acc, item) => { + if (item.tagIds.includes(vM.$elemMatch)) { + acc.push(item) + } + return acc + }, []) + docs = [...docs, ...docsVm] + console.log(docsVm) + console.group('$elemMatch') + console.warn(kM, vM) + console.groupEnd() + } else { + docs = this.filter(v) + .cloneDeep() + .value() + } + } + + if (!Object.keys(v).length) { + console.log('query', v) + docs = this.filter(v) + .cloneDeep() + .value() + } + } + + if (k === '$lookup') { + if (docs) { + // Присоединенная коллекция + const collection = this.__wrapped__[v.from] + + docs.map(doc => { + let populate + + if (Array.isArray(doc[v.localField])) { + populate = doc[v.localField].map(id => { + return collection.find(i => i[v.foreignField] === id) + }) + } else { + populate = collection.find( + i => i[v.foreignField] === doc[v.localField] + ) + } + + if (populate) doc[v.as] = cloneDeep(populate) + + return doc + }) + } + } + + if (k === '$sort') { + for (const [sK, sV] of Object.entries(v)) { + if (docs) { + sV === 1 + ? docs.sort((a, b) => (a[sK] > b[sK] ? -1 : 1)) + : docs.sort((a, b) => (a[sK] < b[sK] ? 1 : -1)) + } + } + } + + if (k === '$limit') { + docs = docs.slice(0, v) + } + + if (k === '$skip') { + docs = docs.slice(v) + } + } + }) + console.groupEnd() + return docs + } +} + +export default extendMethods From 2be64900a0191f527f79e13d25966ff97c58df6c Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:42:58 +0300 Subject: [PATCH 03/62] feat(datastore): create datastore instance --- src/main/lib/datastore/index.js | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/lib/datastore/index.js diff --git a/src/main/lib/datastore/index.js b/src/main/lib/datastore/index.js new file mode 100644 index 0000000..51d7473 --- /dev/null +++ b/src/main/lib/datastore/index.js @@ -0,0 +1,34 @@ +import DB from './datastore' +import store from '../../store' +import { + FOLDERS_SCHEMA, + SNIPPETS_SCHEMA, + TAGS_SCHEMA +} from '../datastore/schema' + +const path = store.preferences.get('storagePath') +const backupPath = store.preferences.get('backupPath') +const collections = [ + { + name: 'folders', + schema: FOLDERS_SCHEMA + }, + { + name: 'snippets', + schema: SNIPPETS_SCHEMA + }, + { + name: 'tags', + schema: TAGS_SCHEMA + } +] + +const db = new DB({ + path, + backupPath, + collections +}) + +db.createDefaultFolders() + +export default db From bd58c587034d632832ddc23e955765b0ffb0b253 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:43:34 +0300 Subject: [PATCH 04/62] feat(datastore): add collection schemas --- src/main/lib/datastore/schema.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/lib/datastore/schema.js diff --git a/src/main/lib/datastore/schema.js b/src/main/lib/datastore/schema.js new file mode 100644 index 0000000..c90fc88 --- /dev/null +++ b/src/main/lib/datastore/schema.js @@ -0,0 +1,25 @@ +import Joi from '@hapi/joi' + +export const FOLDERS_SCHEMA = { + name: Joi.string().required(), + defaultLanguage: Joi.string(), + parentId: Joi.string().allow(null), + open: Joi.boolean().default(false), + isSystem: Joi.boolean().default(false), + alias: Joi.string() +} + +export const SNIPPETS_SCHEMA = { + name: Joi.string().required(), + content: Joi.array().default([]), + folderId: Joi.string() + .default(null) + .allow(null), + tagIds: Joi.array().default([]), + isFavorites: Joi.boolean().default(false), + isDeleted: Joi.boolean().default(false) +} + +export const TAGS_SCHEMA = { + name: Joi.string().required() +} From bb243d8cc90a6f1c5254712118ed7d8293c1e8e1 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:43:57 +0300 Subject: [PATCH 05/62] feat(datastore): add helpers --- src/main/lib/datastore/helpers.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/lib/datastore/helpers.js diff --git a/src/main/lib/datastore/helpers.js b/src/main/lib/datastore/helpers.js new file mode 100644 index 0000000..d22eaa6 --- /dev/null +++ b/src/main/lib/datastore/helpers.js @@ -0,0 +1,16 @@ +import cloneDeep from 'lodash-es/cloneDeep' + +/** + * Удаление технических полей базы данных + * @param obj - объект + * @returns {Object} - объект без технических полей + */ +export function deleteTechProps (obj) { + obj = cloneDeep(obj) + + delete obj._id + delete obj.createdAt + delete obj.updatedAt + + return obj +} From 30f63271ef36c1ecd59a3b1186ccbfb861f54b19 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:44:55 +0300 Subject: [PATCH 06/62] feat(util): add helpers for tree --- src/main/util/helpers.js | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/main/util/helpers.js diff --git a/src/main/util/helpers.js b/src/main/util/helpers.js new file mode 100644 index 0000000..1c66998 --- /dev/null +++ b/src/main/util/helpers.js @@ -0,0 +1,55 @@ +/** + * Конвертация вложенного строения дерева с 'children' в плоское + * @param {Array} items - массив элементов + * @param {String} link - связь по полю + * @returns {Array} - плоский массив со связями по полю 'parentId' + */ +export function nestedToFlat (items, link = 'id') { + const flatList = [] + + function flat (items) { + items.map(i => { + if (i.children && i.children.length) { + const children = i.children.map(item => { + return { + ...item, + parentId: i[link] + } + }) + + flatList.push(...children) + + if (!flatList.find(l => l[link] === i[link])) { + flatList.push({ + ...i, + parentId: null + }) + } + + flat(i.children) + } + }) + } + + flat(items) + + return flatList + .map(({ + children, + ...rest + }) => rest) +} +/** + * Конвертация плоского строения дерева в вложенный через 'children' + * @param {Array} items - массив элементов + * @param {String} id - ID элемента + * @param {String} link - связь по полю + */ +export function flatToNested (items, id = null, link = 'parentId') { + return items + .filter(item => item[link] === id) + .map(item => ({ + ...item, + children: flatToNested(items, item.id) + })) +} From 293a5f3c5dcade9d586e9f3addc0c723e88359cf Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:45:31 +0300 Subject: [PATCH 07/62] chore: update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a131fdb..a6a7f2e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ npm-debug.log.* thumbs.db .env !.gitkeep +.idea From 1ec8251501c50de5ee7775e9e85efdc8e7ba4fbd Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:46:45 +0300 Subject: [PATCH 08/62] chore: add lowdb, joi, uuid --- package.json | 3 ++ yarn.lock | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 43e7879..35c8b72 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ }, "dependencies": { "@babel/plugin-proposal-optional-chaining": "^7.10.1", + "@hapi/joi": "^17.1.1", "@johmun/vue-tags-input": "^2.1.0", "axios": "^0.19.1", "date-fns": "^2.8.1", @@ -80,6 +81,7 @@ "interactjs": "^1.8.0-alpha.6", "junk": "^3.1.0", "lodash-es": "^4.17.15", + "lowdb": "^1.0.0", "markdown-it": "^10.0.0", "markdown-it-link-attributes": "^3.0.0", "monaco-editor": "^0.19.0", @@ -93,6 +95,7 @@ "sanitize-html": "^1.21.1", "shortid": "^2.2.15", "universal-analytics": "^0.4.20", + "uuid": "^8.3.0", "vue": "^2.5.16", "vue-draggable-nested-tree": "github:massCodeIO/vue-draggable-nested-tree", "vue-electron": "^1.0.6", diff --git a/yarn.lock b/yarn.lock index 95d1b66..074b020 100644 --- a/yarn.lock +++ b/yarn.lock @@ -873,6 +873,46 @@ "@emmetio/field-parser" "^0.3.0" "@emmetio/output-renderer" "^0.1.1" +"@hapi/address@^4.0.1": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d" + integrity sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/formula@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" + integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== + +"@hapi/hoek@^9.0.0": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010" + integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw== + +"@hapi/joi@^17.1.1": + version "17.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-17.1.1.tgz#9cc8d7e2c2213d1e46708c6260184b447c661350" + integrity sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg== + dependencies: + "@hapi/address" "^4.0.1" + "@hapi/formula" "^2.0.0" + "@hapi/hoek" "^9.0.0" + "@hapi/pinpoint" "^2.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" + integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== + +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@interactjs/actions@1.8.0-alpha.6": version "1.8.0-alpha.6" resolved "https://registry.yarnpkg.com/@interactjs/actions/-/actions-1.8.0-alpha.6.tgz#54220c77db4521f6c29db77dbb9c99d3bb0cf6f1" @@ -4557,6 +4597,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -6033,6 +6078,11 @@ lodash.toplainobject@^3.0.0: lodash._basecopy "^3.0.0" lodash.keysin "^3.0.0" +lodash@4: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.3, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -6094,6 +6144,17 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lowdb@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" + integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== + dependencies: + graceful-fs "^4.1.3" + is-promise "^2.1.0" + lodash "4" + pify "^3.0.0" + steno "^0.4.1" + lower-case-first@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" @@ -8707,6 +8768,13 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" +steno@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb" + integrity sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs= + dependencies: + graceful-fs "^4.1.3" + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -9497,6 +9565,11 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== +uuid@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" + integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== + v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" @@ -9535,8 +9608,8 @@ vm-browserify@^1.0.1: integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== "vue-draggable-nested-tree@github:massCodeIO/vue-draggable-nested-tree": - version "2.2.19" - resolved "https://codeload.github.com/massCodeIO/vue-draggable-nested-tree/tar.gz/b4b6b2bda822aa47e95661748e7ef3e4342bcbc7" + version "2.2.20" + resolved "https://codeload.github.com/massCodeIO/vue-draggable-nested-tree/tar.gz/d33465513faba376c3f471bce4890ca8ef8b17dc" dependencies: draggable-helper "1.0.20" helper-js "^1.3.7" From e34348f906694d6155c822a5bbd04b3524240c2a Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:47:13 +0300 Subject: [PATCH 09/62] chore: add .prettierignore --- .prettierignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +node_modules From c7c0155bea407581ff6bd4615b8860613fc13903 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 11:47:43 +0300 Subject: [PATCH 10/62] chore: update .prettierrc --- .prettierrc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index b2095be..9f8ecd9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,6 @@ { "semi": false, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "none" } From 17ca136267eb931b8b33cfda3939ce0a8996c4fc Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 21 Jan 2021 12:05:02 +0300 Subject: [PATCH 11/62] chore(datastore): remove joi --- src/main/lib/datastore/datastore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index 117479e..9fb36e5 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -20,7 +20,6 @@ class Datastore { this.db = low(new FileSync(`${this.path}/db.json`)) this.collections = {} - this.schema = Joi this.migrateStore = {} this.createCollections(config.collections) From 1b68fc63038a868aaedb079d8637560361d8f593 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 22 Jan 2021 06:38:50 +0300 Subject: [PATCH 12/62] feat(datastore): check if db files not exist in migrate folder --- src/main/lib/datastore/datastore.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index 9fb36e5..ca75500 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -130,6 +130,18 @@ class Datastore { async migrate (path) { if (!path) throw Error('"path" is required') + const files = await fs.readdir(path) + const migrateFiles = ['masscode.db', 'snippets.db', 'tags.db'] + + const isFilesExist = migrateFiles + .reduce((acc, item) => { + acc.push(files.includes(item)) + return acc + }, []) + .every(i => i === true) + + if (!isFilesExist) throw Error('DB files not exist in this folder') + console.log('Migrate from v1 is started') this.clearCollections() this.createDefaultFolders() From d82425b0b5b9be5e1a357bd2495508d41aab22e4 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 22 Jan 2021 06:47:48 +0300 Subject: [PATCH 13/62] feat(datastore): add backup --- src/main/lib/datastore/datastore.js | 147 ++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index ca75500..6c39dce 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -8,6 +8,9 @@ import extendMethods from './extend-methods' import electronStore from '../../store' import { nestedToFlat } from '../../util/helpers' import pull from 'lodash-es/pull' +import { format, min, max, isSameDay } from 'date-fns' +import junk from 'junk' +import rimraf from 'rimraf' class Datastore { /** @@ -20,6 +23,7 @@ class Datastore { this.db = low(new FileSync(`${this.path}/db.json`)) this.collections = {} + this.backupLimit = 30 this.migrateStore = {} this.createCollections(config.collections) @@ -31,6 +35,7 @@ class Datastore { fs.ensureDirSync(config.path) fs.ensureDirSync(config.backupPath) + fs.ensureDirSync(this.backupPath) this.path = config.path this.backupPath = config.backupPath @@ -127,6 +132,148 @@ class Datastore { this.updateCollections() } + // Backup + + async createBackupDirByDate (date) { + const folderPath = this.convertDateToBackupPath(date) + await fs.ensureDir(folderPath) + + return folderPath + } + + convertDateToBackupPath (date) { + date = date || new Date() + const backupFolderDatePattern = 'yyyy-MM-dd_HH-mm-ss' + const suffixFolder = 'massCode' + const dirName = `${format(date, backupFolderDatePattern)}_${suffixFolder}` + + return path.resolve(this.backupPath, dirName) + } + + async backup () { + const dir = await this.createBackupDirByDate() + const src = path.resolve(`${this.path}/db.json`) + const dest = path.resolve(dir, 'db.json') + + await fs.copy(src, dest) + } + + autoBackup () { + const start = async () => { + const now = new Date() + const isEmpty = await this.isBackupEmpty() + + if (isEmpty) { + await this.backup() + } else { + const { date } = await this.getLatestBackupDir() + + if (!isSameDay(now, date)) { + await this.removeEarliestBackup() + await this.backup() + } + } + console.log('autobackup is started') + } + + start() + setInterval(() => { + start() + }, 1000 * 60 * 60 * 12) + } + + restoreFromBackup (date) { + return new Promise((resolve, reject) => { + const dir = this.convertDateToBackupPath(date) + fs.copyFileSync(`${dir}/db.json`, `${this.path}/db.json`) + this.updatePath(this.path) + resolve() + }) + } + + async moveBackup (to) { + const dirs = await this.getBackupDirs() + const src = dirs.map(i => path.resolve(this.backupPath, i)) + const dest = dirs.map(i => path.resolve(to, i)) + + src.forEach((dir, index) => { + fs.moveSync(dir, dest[index], { overwrite: true }) + }) + + this.backupPath = to + electronStore.preferences.set('backupPath', to) + } + + async getBackupDirs () { + let dirs = await fs.readdir(this.backupPath) + dirs = dirs.filter(junk.not).filter(i => i.includes('massCode')) + + return dirs + } + + async getBackupsDirsAsDate () { + const dirs = await this.getBackupDirs() + return this.convertBackupDirsToDate(dirs.filter(junk.not)) + } + + async getEarliestBackupDir () { + const dirs = await this.getBackupDirs() + + const dirsDate = this.convertBackupDirsToDate(dirs) + const minDate = min(dirsDate).getTime() + const dir = dirs[dirsDate.indexOf(minDate)] + + return { + date: minDate, + dir, + path: dir ? path.resolve(this.backupPath, dir) : null + } + } + + async getLatestBackupDir () { + const dirs = await this.getBackupDirs() + + const dirsDate = this.convertBackupDirsToDate(dirs) + const maxDate = max(dirsDate).getTime() + const dir = dirs[dirsDate.indexOf(maxDate)] + + return { + date: maxDate, + dir, + path: dir ? path.resolve(this.backupPath, dir) : null + } + } + + async removeEarliestBackup () { + const dirs = await this.getBackupDirs() + const { path } = await this.getEarliestBackupDir() + + if (dirs.length > this.backupLimit) { + rimraf(path, err => { + if (err) throw Error(err) + }) + } + } + + convertBackupDirsToDate (dirs) { + return dirs.map(i => { + const arr = i.split('_').splice(0, 2) + arr[1] = `T${arr[1].replace(/-/g, ':')}` + const date = new Date(arr.join('')).getTime() + + return date + }) + } + + async isBackupEmpty () { + let dirs = await this.getBackupDirs() + dirs = dirs.filter(junk.not) + + return dirs.length === 0 + } + + // Migrate + async migrate (path) { if (!path) throw Error('"path" is required') From 02c4e35bc05e0cd3ab350679fef40af6f5bcc49c Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 22 Jan 2021 09:27:25 +0300 Subject: [PATCH 14/62] fix(datastore): remove duplicate create backup dir --- src/main/lib/datastore/datastore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index 6c39dce..e6a2b18 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -35,7 +35,6 @@ class Datastore { fs.ensureDirSync(config.path) fs.ensureDirSync(config.backupPath) - fs.ensureDirSync(this.backupPath) this.path = config.path this.backupPath = config.backupPath From e4156208872566366b8d231e0b648fc565eea075 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Mon, 1 Feb 2021 11:41:57 +0300 Subject: [PATCH 15/62] fix(datastore): tags migrate --- src/main/lib/datastore/datastore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index e6a2b18..196a9f5 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -365,6 +365,7 @@ class Datastore { this.migrateStore.tagIdsMap.map(([oldId, newId]) => { const snippetsWithTags = this.collections.snippets .filter(i => i.tagIds.includes(oldId)) + .cloneDeep() .value() snippetsWithTags.map(s => { @@ -373,7 +374,7 @@ class Datastore { tagIds.push(newId) this.collections.snippets .find({ _id }) - .assign(tagIds) + .assign({ tagIds }) .write() }) }) From 99a344565b1cf2875b39e63657e0c011347062d8 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 09:33:28 +0300 Subject: [PATCH 16/62] refactor(store): folders store --- src/renderer/store/modules/folders.js | 271 ++++++++++---------------- 1 file changed, 98 insertions(+), 173 deletions(-) diff --git a/src/renderer/store/modules/folders.js b/src/renderer/store/modules/folders.js index 3aaa968..65408c9 100644 --- a/src/renderer/store/modules/folders.js +++ b/src/renderer/store/modules/folders.js @@ -1,7 +1,6 @@ -import db from '@/datastore' import electronStore from '@@/store' -import shortid from 'shortid' -import { defaultLibraryQuery } from '@/util/helpers' +import db from '@@/lib/datastore' +import { flatToNested } from '@@/util/helpers' export default { namespaced: true, @@ -13,9 +12,25 @@ export default { editableId: null }, getters: { - folders (state) { + all (state) { return state.list }, + folders (state) { + return state.list.filter(i => !i.isSystem) + }, + system (state) { + return state.list.filter(i => i.isSystem) + }, + systemAliases (state, getters) { + const aliases = {} + getters.system.map((i, index) => { + aliases[getters.system[index].alias] = i._id + }) + return aliases + }, + selected (state, getters) { + return getters.folders.find(i => i._id === state.selectedId) || null + }, selectedId (state) { return state.selectedId }, @@ -25,35 +40,23 @@ export default { editableId (state) { return state.editableId }, - defaultLanguage (state) { - if (state.selected) { - return state.selected.defaultLanguage - } - }, - defaultQueryBySystemFolder (state) { - let query - if (state.selectedId === 'trash') { + defaultQueryBySystemFolder (state, getters) { + let query = { isDeleted: false } + + if (state.selectedId === getters.systemAliases.trash) { query = { isDeleted: true } } - if (state.selectedId === 'favorites') { - query = { isFavorites: true } - } - if (state.selectedId === 'allSnippets') { - query = {} + if (state.selectedId === getters.systemAliases.favorites) { + query = { isFavorites: true, isDeleted: false } } - if (state.selectedId === 'inBox') { - query = { folderId: null } + if (state.selectedId === getters.systemAliases.inbox) { + query = { folderId: state.selectedId, isDeleted: false } } return query }, isSystemFolder (state) { - return ( - state.selectedId === 'trash' || - state.selectedId === 'favorites' || - state.selectedId === 'allSnippets' || - state.selectedId === 'inBox' - ) + return state.selected?.isSystem || false } }, mutations: { @@ -75,63 +78,50 @@ export default { }, actions: { getFolders ({ commit }) { - return new Promise((resolve, reject) => { - db.masscode.findOne({ _id: 'folders' }, (err, doc) => { - if (err) return - if (doc) { - commit('SET_FOLDERS', doc.list) - resolve() - } - }) - }) - }, - setSelectedFolder ({ state, commit, dispatch, getters }, id) { - const libraryItems = ['inBox', 'favorites', 'allSnippets', 'trash'] - - if (!id) { - commit('SET_SELECTED_ID', null) - electronStore.app.delete('selectedFolderId') - return - } - - commit('SET_SELECTED_ID', id) - electronStore.app.set('selectedFolderId', id) - - if (libraryItems.includes(id)) { - commit('SET_SELECTED', null) - commit('SET_SELECTED_IDS', null) - return - } + const folders = db.collections.folders.$find() + commit('SET_FOLDERS', flatToNested(folders, null)) + }, + setSelectedFolderById ( + { state, commit, dispatch, getters, rootGetters }, + id + ) { const { list } = state + const defaultFolderId = getters.systemAliases.allSnippets + let folder function findFolderById (folders, id) { - let found folders.forEach(i => { - if (i.id === id) found = i + if (i._id === id) folder = i if (i.children && i.children.length) { findFolderById(i.children, id) } }) - - if (found) { - commit('SET_SELECTED', found) - } } - findFolderById(list, id) + if (id) { + findFolderById(list, id) + commit('SET_SELECTED', folder) + commit('SET_SELECTED_ID', id) + electronStore.app.set('selectedFolderId', id) + } else { + const folder = list.find(i => i._id === defaultFolderId) + commit('SET_SELECTED', folder) + commit('SET_SELECTED_ID', defaultFolderId) + electronStore.app.set('selectedFolderId', defaultFolderId) + } - dispatch('setSelectedIds') + dispatch('getNestedFolderIds') }, - setSelectedIds ({ state, commit }) { + getNestedFolderIds ({ state, commit }) { if (!state.selected) return const ids = [] function getIds (arr) { arr.forEach(i => { - ids.push(i.id) + ids.push(i._id) if (i.children && i.children.length) { getIds(i.children) @@ -145,133 +135,68 @@ export default { }, addFolder ({ state, commit, dispatch }) { const folder = { - id: shortid(), name: 'Untitled', open: false, + parentId: null, defaultLanguage: 'text' } - db.masscode.update( - { _id: 'folders' }, - { $push: { list: folder } }, - (err, doc) => { - if (err) return + const { _id: id } = db.collections.folders.$insert(folder) - commit('SET_EDITABLE', folder.id) - commit('SET_SELECTED', folder) - commit('SET_SELECTED_ID', folder.id) - commit('SET_SELECTED_IDS', [folder.id]) - dispatch('getFolders') - } - ) + dispatch('getFolders') + dispatch('setSelectedFolderById', id) }, - updateFolderName ({ dispatch, rootGetters }, { id, payload }) { - db.masscode.findOne({ _id: 'folders' }, (err, doc) => { - if (err) return - - const { list } = doc - - function findAndUpdate (arr) { - arr.forEach((i, index) => { - if (i.id === id) { - i.name = payload - } + updateFolderById ({ dispatch, getters, rootGetters }, { id, payload }) { + db.collections.folders.$findOneAndUpdate({ _id: id }, payload) - if (i.children && i.children.length) { - findAndUpdate(i.children) - } - }) - } - findAndUpdate(list) - - const ids = rootGetters['folders/selectedIds'] - const folderId = rootGetters['folders/selectedId'] - const defaultQuery = { folderId: { $in: ids } } - const query = defaultLibraryQuery(defaultQuery, folderId) - - dispatch('updateFolders', list) - dispatch('snippets/getSnippets', query, { root: true }) - }) + dispatch('getFolders') }, - updateFolderLanguage ({ dispatch, rootGetters }, { id, payload }) { - db.masscode.findOne({ _id: 'folders' }, async (err, doc) => { - if (err) return + updateFolderNameById ({ dispatch, getters, rootGetters }, { id, payload }) { + dispatch('updateFolderById', { id, payload }) - const { list } = doc - - function findAndUpdate (arr) { - arr.forEach((i, index) => { - if (i.id === id) { - i.defaultLanguage = payload - } - - if (i.children && i.children.length) { - findAndUpdate(i.children) - } - }) - } - findAndUpdate(list) - - await dispatch('updateFolders', list) - const folderId = rootGetters['folders/selectedId'] - - if (folderId === id) { - dispatch('setSelectedFolder', id) - } - }) - }, - updateFolders ({ dispatch }, list) { - return new Promise((resolve, reject) => { - db.masscode.update({ _id: 'folders' }, { list }, async (err, doc) => { - if (err) return - await dispatch('getFolders') - resolve() - }) - }) - }, - deleteFolder ({ state, commit, dispatch, rootGetters }, id) { const ids = rootGetters['folders/selectedIds'] - const folderId = rootGetters['folders/selectedId'] - const defaultQuery = { folderId: { $in: ids } } - const query = defaultLibraryQuery(defaultQuery, folderId) - // Перемещаем все сниппеты из удаленной папки, - // включая вложенные сниппеты в подпапках, в корзину - db.snippets.update( + dispatch( + 'snippets/getSnippets', { folderId: { $in: ids } }, - { $set: { isDeleted: true } }, - { multi: true }, - (err, doc) => { - if (err) return - dispatch('snippets/getSnippets', query, { root: true }) - } + { root: true } ) - // Удаляем папку, включая все подпапки - db.masscode.findOne({ _id: 'folders' }, (err, doc) => { - if (err) return - - const { list } = doc - - function findAndRemove (arr) { - arr.forEach((i, index) => { - if (i.id === id) { - return arr.splice(index, 1) - } - - if (i.children && i.children.length) { - findAndRemove(i.children) - } - }) - } - findAndRemove(list) + dispatch('getFolders') + }, + updateFolders ({ dispatch }, folders) { + folders.map(i => { + console.log(i) + db.collections.folders.$findOneAndUpdate( + { + _id: i._id + }, + i + ) + }) + dispatch('getFolders') + }, + deleteFolderByIds ({ state, commit, dispatch, getters, rootGetters }, ids) { + const snippetIds = rootGetters['snippets/snippetsBySort'].map(i => i._id) + const payload = { + folderId: getters.systemAliases.inbox, + isDeleted: true + } - db.masscode.update({ _id: 'folders' }, { list }, (err, doc) => { - if (err) return + ids.map(id => db.collections.folders.remove({ _id: id }).write()) - dispatch('getFolders') - dispatch('snippets/setSelected', null, { root: true }) - dispatch('setSelectedFolder', 'allSnippets') - }) + dispatch('getFolders') + dispatch('setSelectedFolderById', getters.systemAliases.inbox) + dispatch( + 'snippets/updateSnippetsByIds', + { ids: snippetIds, payload }, + { root: true } + ) + dispatch('snippets/getSnippets', getters.defaultQueryBySystemFolder, { + root: true }) + dispatch('snippets/setSelected', null, { root: true }) + }, + isFolderExist ({ state }, id) { + return !!db.collections.folders.find({ _id: id }).value() } } } From d02c3df2e1a721b1d129b45f0fc9b310c8ea98eb Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 09:34:46 +0300 Subject: [PATCH 17/62] fix(electron-store): inBox -> inbox --- src/main/store/module/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/store/module/app.js b/src/main/store/module/app.js index 363c72a..151bf4b 100644 --- a/src/main/store/module/app.js +++ b/src/main/store/module/app.js @@ -15,7 +15,7 @@ const app = new Store({ default: 220 }, selectedFolderId: { - default: 'inBox' + default: 'inbox' }, selectedFolderIds: { default: null From 1b5aa229d7404327398e6e8d6549097d017b7f87 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 09:36:26 +0300 Subject: [PATCH 18/62] refactor(store): use new db in app store --- src/renderer/store/modules/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/store/modules/app.js b/src/renderer/store/modules/app.js index 28caf2b..75cce50 100644 --- a/src/renderer/store/modules/app.js +++ b/src/renderer/store/modules/app.js @@ -1,6 +1,6 @@ import electronStore from '@@/store' import { format } from 'date-fns' -import db from '@/datastore' +import db from '@@/lib/datastore' export default { namespaced: true, From 4c5141a82112630bd87f88f7917ee4bce10e91a8 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 09:37:52 +0300 Subject: [PATCH 19/62] feat(datastore): add fields in helpers --- src/main/lib/datastore/helpers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/lib/datastore/helpers.js b/src/main/lib/datastore/helpers.js index d22eaa6..7a3575a 100644 --- a/src/main/lib/datastore/helpers.js +++ b/src/main/lib/datastore/helpers.js @@ -9,6 +9,9 @@ export function deleteTechProps (obj) { obj = cloneDeep(obj) delete obj._id + delete obj.children + delete obj.tags + delete obj.folders delete obj.createdAt delete obj.updatedAt From a76cdd840ee94f34a72f954d52418880bbced45c Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 09:40:23 +0300 Subject: [PATCH 20/62] refactor(store): tags store --- src/renderer/store/modules/tags.js | 107 +++++++++-------------------- 1 file changed, 34 insertions(+), 73 deletions(-) diff --git a/src/renderer/store/modules/tags.js b/src/renderer/store/modules/tags.js index 133f760..3d23e9f 100644 --- a/src/renderer/store/modules/tags.js +++ b/src/renderer/store/modules/tags.js @@ -1,4 +1,5 @@ -import db from '@/datastore' +import db from '@@/lib/datastore' +import pull from 'lodash-es/pull' export default { namespaced: true, @@ -9,8 +10,10 @@ export default { getters: { tags (state) { return state.list.map(i => { - i.text = i.name - return i + return { + ...i, + text: i.name + } }) }, selectedId (state) { @@ -27,78 +30,36 @@ export default { }, actions: { getTags ({ commit }) { - return new Promise((resolve, reject) => { - db.tags - .find({}) - .sort({ name: 1 }) - .exec((err, doc) => { - if (err) reject(err) - - commit('SET_TAGS', doc) - resolve(doc) - }) - }) - }, - async addTag ({ dispatch }, tag) { - return new Promise((resolve, reject) => { - db.tags.findOne({ name: tag.name }, (err, doc) => { - if (err) return - - if (!doc) { - db.tags.insert(tag, (err, doc) => { - if (err) reject(err) - resolve(doc) - }) - dispatch('getTags') - } else { - resolve(null) - } - }) - }) + const tags = db.collections.tags.$find().sort((a, b) => (a > b ? 1 : -1)) + commit('SET_TAGS', tags) }, - async addTagToSnippet ({ commit, rootGetters }, { tagId, snippetId }) { - db.snippets.update({ _id: snippetId }, { $addToSet: { tags: tagId } }) + addTag ({ commit, dispatch }, tag) { + const newTag = db.collections.tags.$insert(tag) + dispatch('getTags') + return newTag }, - removeTag ({ state, commit, dispatch, rootGetters }, id) { - db.snippets.find({ tags: { $elemMatch: id } }, (err, doc) => { - if (err) return - // Собираем ids - if (doc) { - const ids = doc.reduce((acc, item) => { - acc.push(item._id) - return acc - }, []) - // Удаляем тег у всех найденных сниппетов с этим тегом - db.snippets.update( - { _id: { $in: ids } }, - { $pull: { tags: id } }, - { multi: true }, - (err, doc) => { - if (err) return - // Удаляем сам тег - db.tags.remove({ _id: id }, async (err, doc) => { - if (err) return - // Получаем обновленный список тегов - const tags = await dispatch('getTags') - const firstTag = tags[0] - // Если есть первый тег, то устанавливаем его как выбранный, - // затем получаем список снипеттов по тегу - if (firstTag) { - commit('SET_SELECTED_ID', firstTag._id) - await dispatch( - 'snippets/getSnippets', - { tags: { $elemMatch: firstTag._id } }, - { root: true } - ) - } else { - // Если нет, то переключаем на библиотеку - dispatch('app/setShowTags', false, { root: true }) - } - }) - } - ) - } - }) + removeTag ({ state, commit, dispatch, getters, rootGetters }, id) { + db.collections.snippets + .filter(i => i.tagIds.includes(id)) + .map(i => pull(i.tagIds, id)) + .write() + db.collections.tags.remove({ _id: id }).write() + + dispatch('getTags') + const firstTagId = getters.tags[0]?._id + + if (firstTagId) { + commit('SET_SELECTED_ID', firstTagId) + dispatch( + 'snippets/getSnippets', + { + tagIds: { $elemMatch: firstTagId } + }, + { root: true } + ) + } else { + dispatch('app/setShowTags', false, { root: true }) + } } } } From 30cca21dfe13901453bb3de3ed983de49289a85e Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:37:10 +0300 Subject: [PATCH 21/62] feat(util): add 'idLink' arg to flatToNested helper --- src/main/util/helpers.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/util/helpers.js b/src/main/util/helpers.js index 1c66998..956f768 100644 --- a/src/main/util/helpers.js +++ b/src/main/util/helpers.js @@ -33,23 +33,27 @@ export function nestedToFlat (items, link = 'id') { flat(items) - return flatList - .map(({ - children, - ...rest - }) => rest) + return flatList.map(({ children, ...rest }) => rest) } /** - * Конвертация плоского строения дерева в вложенный через 'children' - * @param {Array} items - массив элементов + * Конвертация плоского строения дерева во вложенный через 'children' + * @param {Array} items - массив элементов c полями связей + * с родительскими элементами * @param {String} id - ID элемента - * @param {String} link - связь по полю + * @param {String} idLink - имя свойства ID + * @param {String} link - имя связанного поля + * @example [{id:1, parentId: null }, {id:2, parentId: 1 }] -> [{id:1, children: [id:2] }] */ -export function flatToNested (items, id = null, link = 'parentId') { +export function flatToNested ( + items, + id = null, + idLink = '_id', + link = 'parentId' +) { return items .filter(item => item[link] === id) .map(item => ({ ...item, - children: flatToNested(items, item.id) + children: flatToNested(items, item[idLink]) })) } From 6cb2eecd6b313d512da4f5773462031fdd7f58d6 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:38:00 +0300 Subject: [PATCH 22/62] feat(datastore): init backup --- src/main/lib/datastore/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/lib/datastore/index.js b/src/main/lib/datastore/index.js index 51d7473..887e4a1 100644 --- a/src/main/lib/datastore/index.js +++ b/src/main/lib/datastore/index.js @@ -30,5 +30,6 @@ const db = new DB({ }) db.createDefaultFolders() +db.autoBackup() export default db From fd1025aecd95e27cdd7f4023e78ceca7a6f1ed66 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:39:17 +0300 Subject: [PATCH 23/62] feat(datastore): add required parentId in FOLDERS_SCHEMA --- src/main/lib/datastore/schema.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/lib/datastore/schema.js b/src/main/lib/datastore/schema.js index c90fc88..0d8ab78 100644 --- a/src/main/lib/datastore/schema.js +++ b/src/main/lib/datastore/schema.js @@ -3,7 +3,9 @@ import Joi from '@hapi/joi' export const FOLDERS_SCHEMA = { name: Joi.string().required(), defaultLanguage: Joi.string(), - parentId: Joi.string().allow(null), + parentId: Joi.string() + .allow(null) + .required(), open: Joi.boolean().default(false), isSystem: Joi.boolean().default(false), alias: Joi.string() From 46d477d7600452f590d328efe33dd82c7e233035 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:39:55 +0300 Subject: [PATCH 24/62] feat(datastore): add parentId in createDefaultFolders --- src/main/lib/datastore/datastore.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index 196a9f5..84c460e 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -82,13 +82,19 @@ class Datastore { if (this.collections.folders.size().value()) return const systemFolders = [ - { name: 'Inbox', isSystem: true, alias: 'inbox' }, - { name: 'Favorites', isSystem: true, alias: 'favorites' }, - { name: 'All Snippets', isSystem: true, alias: 'allSnippets' }, - { name: 'Trash', isSystem: true, alias: 'trash' } + { name: 'Inbox', isSystem: true, parentId: null, alias: 'inbox' }, + { name: 'Favorites', isSystem: true, parentId: null, alias: 'favorites' }, + { + name: 'All Snippets', + isSystem: true, + parentId: null, + alias: 'allSnippets' + }, + { name: 'Trash', isSystem: true, parentId: null, alias: 'trash' } ] const defaultFolder = { name: 'Default', + parentId: null, defaultLanguage: 'text' } const folders = [...systemFolders, defaultFolder] From 205a5864c5c2113d3d7d942c7b915140da5b5e2e Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:41:18 +0300 Subject: [PATCH 25/62] feat(datastore): add 'massCode_v2' as suffix backup folder --- src/main/lib/datastore/datastore.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index 84c460e..99b2947 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -149,7 +149,7 @@ class Datastore { convertDateToBackupPath (date) { date = date || new Date() const backupFolderDatePattern = 'yyyy-MM-dd_HH-mm-ss' - const suffixFolder = 'massCode' + const suffixFolder = 'massCode_v2' const dirName = `${format(date, backupFolderDatePattern)}_${suffixFolder}` return path.resolve(this.backupPath, dirName) @@ -211,7 +211,7 @@ class Datastore { async getBackupDirs () { let dirs = await fs.readdir(this.backupPath) - dirs = dirs.filter(junk.not).filter(i => i.includes('massCode')) + dirs = dirs.filter(junk.not).filter(i => i.includes('massCode_v2')) return dirs } From d97d4a5b6da29f8419cdf9294069903ff091e642 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:42:38 +0300 Subject: [PATCH 26/62] fix(datastore): map folder ids in loop --- src/main/lib/datastore/datastore.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index 99b2947..b4478d0 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -331,12 +331,12 @@ class Datastore { const { _id } = this.collections.folders.$insert(rest) this.migrateStore.folderIdsMap.push([id, _id]) - }) - this.migrateStore.folderIdsMap.map(([oldId, newId]) => { - this.collections.folders - .find({ parentId: oldId }) - .assign({ parentId: newId }) - .write() + this.migrateStore.folderIdsMap.map(([oldId, newId]) => { + this.collections.folders + .find({ parentId: oldId }) + .assign({ parentId: newId }) + .write() + }) }) // Snippets snippetsJSON.map( From 290fb2d651c0e43f8783d9a923b1f4e06e9066d2 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:43:35 +0300 Subject: [PATCH 27/62] feat(store): check for exist tag before add new --- src/renderer/store/modules/tags.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/store/modules/tags.js b/src/renderer/store/modules/tags.js index 3d23e9f..334a1be 100644 --- a/src/renderer/store/modules/tags.js +++ b/src/renderer/store/modules/tags.js @@ -34,6 +34,9 @@ export default { commit('SET_TAGS', tags) }, addTag ({ commit, dispatch }, tag) { + const isExist = db.collections.tags.find({ name: tag }).value() + if (isExist) return + const newTag = db.collections.tags.$insert(tag) dispatch('getTags') return newTag From 5eb0f878f6d40f0ca4bcb3b93833a397386558af Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:46:15 +0300 Subject: [PATCH 28/62] refactor(App): init state --- src/renderer/App.vue | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/renderer/App.vue b/src/renderer/App.vue index d8b4539..4783103 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -29,7 +29,8 @@ export default { ...mapGetters('folders', [ 'selectedIds', 'defaultQueryBySystemFolder', - 'isSystemFolder' + 'isSystemFolder', + 'systemAliases' ]), ...mapGetters('snippets', ['snippetsBySort']), isTray () { @@ -64,31 +65,23 @@ export default { const selectedSnippetId = electronStore.app.get('selectedSnippetId') if (selectedFolderId) { - this.$store.dispatch('folders/setSelectedFolder', selectedFolderId) - - let query = {} - - if (this.isSystemFolder) { - query = this.defaultQueryBySystemFolder - } - - if (this.selectedIds) { - query = { folderId: { $in: this.selectedIds } } - } + const isFolderExist = await this.$store.dispatch( + 'folders/isFolderExist', + selectedFolderId + ) + const folderId = isFolderExist + ? selectedFolderId + : this.systemAliases.inbox - await this.$store.dispatch('snippets/getSnippets', query) + await this.$store.dispatch('folders/setSelectedFolderById', folderId) + await this.$store.dispatch('snippets/getSnippetsBySelectedFolders') } else { - await this.$store.dispatch('snippets/getSnippets', { folderId: null }) + await this.$store.dispatch('snippets/getSnippets') } if (selectedSnippetId) { - const snippet = this.snippetsBySort.find( - i => i._id === selectedSnippetId - ) - if (snippet) { - this.$store.dispatch('snippets/setSelected', snippet) - this.$store.commit('snippets/SET_SELECTED_SNIPPETS', [snippet]) - } + await this.$store.dispatch('snippets/setSelected', selectedSnippetId) + this.$store.commit('snippets/SET_SELECTED_IDS', [selectedSnippetId]) } this.$store.commit('app/SET_INIT', true) From 93b21849a65b45119ecf971c745e8068d3049b6e Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:47:24 +0300 Subject: [PATCH 29/62] chore(main): open devtools on bottom --- src/main/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/main.js b/src/main/main.js index 994e48a..d96e267 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -35,7 +35,7 @@ function createMainWindow () { mainWindow.loadURL(winURL) if (isDev) { - mainWindow.webContents.openDevTools({ mode: 'detach' }) + mainWindow.webContents.openDevTools({ mode: 'bottom' }) } if (process.platform === 'darwin') { From 1e68a9bdfb5d15085c1caf372d43dbc8f2142ddc Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Tue, 2 Feb 2021 11:48:39 +0300 Subject: [PATCH 30/62] feat(datastore): add atomic update in $findOneAndUpdate --- src/main/lib/datastore/extend-methods.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/lib/datastore/extend-methods.js b/src/main/lib/datastore/extend-methods.js index 51ed8f1..e6ccf51 100644 --- a/src/main/lib/datastore/extend-methods.js +++ b/src/main/lib/datastore/extend-methods.js @@ -1,5 +1,6 @@ import { v4 as uuid } from 'uuid' import cloneDeep from 'lodash-es/cloneDeep' +import merge from 'lodash-es/merge' import { deleteTechProps } from './helpers' const extendMethods = { @@ -45,20 +46,24 @@ const extendMethods = { * Обновление документа по ID * @param filter {Object} - Фильтр поиска * @param update - {Object} - Обновления для документа - * @returns {Object} - Обновленные документ + * @returns {Object} - Патч обновление */ $findOneAndUpdate (filter, update) { - update = deleteTechProps(update) - const { error } = this._schema.validate(update) + const doc = this.find(filter) + const docData = doc.cloneDeep().value() + const updatedDoc = merge(deleteTechProps(docData), deleteTechProps(update)) + + const { error } = this._schema.validate(updatedDoc) if (error) { throw new Error(error) } else { - this.find(filter) - .assign(update) + doc + .assign(updatedDoc) .set('updatedAt', new Date().getTime()) .write() - return this.find(filter) + + return doc } }, /** From fc4f96419cd94094cdaa0f349e2087c138559eeb Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 10:55:36 +0300 Subject: [PATCH 31/62] refactor(store): snippets store --- src/renderer/store/modules/snippets.js | 575 ++++++++++++------------- 1 file changed, 264 insertions(+), 311 deletions(-) diff --git a/src/renderer/store/modules/snippets.js b/src/renderer/store/modules/snippets.js index b4504e1..d26eebe 100644 --- a/src/renderer/store/modules/snippets.js +++ b/src/renderer/store/modules/snippets.js @@ -1,23 +1,17 @@ -import db from '@/datastore' import electronStore from '@@/store' -import { defaultLibraryQuery } from '@/util/helpers' import uniqBy from 'lodash-es/uniqBy' +import db from '@@/lib/datastore' +import pull from 'lodash-es/pull' -// Fallback stored sorting value -// @see https://github.com/antonreshetov/massCode/pull/74 -// TODO: Remove in future -const sort = - electronStore.app.get('snippetsSort') === 'updateAt' - ? 'updatedAt' - : electronStore.app.get('snippetsSort') +const sort = electronStore.app.get('snippetsSort') || 'updatedAt' export default { namespaced: true, state: { snippets: [], - snippetsLatest: [], + snippetsTray: [], selectedId: null, - selectedSnippets: [], + selectedIds: [], searched: [], searchedTray: [], search: false, @@ -48,8 +42,14 @@ export default { } return state.snippets }, - snippetsLatest (state) { - return state.snippetsLatest + snippetFirst (state, getters) { + return getters.snippetsBySort?.[0] + }, + snippetLatest (state, getters) { + return getters.snippetsBySort[getters.snippetsBySort.length - 1] + }, + snippetsTray (state) { + return state.snippetsTray }, snippetsFavorites (state) { return state.snippets.filter(i => i.isFavorites) @@ -70,16 +70,29 @@ export default { return state.searchQueryTray }, selected (state) { - return state.snippets.find(i => i._id === state.selectedId) + if (state.search) { + return state.searched.find(i => i._id === state.selectedId) + } else { + return state.snippets.find(i => i._id === state.selectedId) + } }, selectedId (state) { return state.selectedId }, + selectedIds (state) { + return state.selectedIds + }, selectedIndex (state, getters) { return getters.snippetsBySort.findIndex(i => i._id === state.selectedId) }, - selectedSnippets (state) { - return state.selectedSnippets + selectedSnippets (state, getters) { + return ( + getters.selectedIds?.reduce((acc, id) => { + const snippet = state.snippets.find(i => i._id === id) + acc.push(snippet) + return acc + }, []) || [] + ) }, newSnippetId (state) { return state.newSnippetId @@ -96,6 +109,9 @@ export default { isSelected (state) { return !!state.selectedId }, + isSelectedMultiple (state, getters) { + return getters.selectedIds?.length > 1 || false + }, isSearched (state) { return state.search }, @@ -107,14 +123,14 @@ export default { SET_SNIPPETS (state, snippets) { state.snippets = snippets }, - SET_LATEST_SNIPPETS (state, snippets) { - state.snippetsLatest = snippets + SET_SNIPPETS_FOR_TRAY (state, snippets) { + state.snippetsTray = snippets }, SET_SELECTED_ID (state, id) { state.selectedId = id }, - SET_SELECTED_SNIPPETS (state, snippets) { - state.selectedSnippets = snippets + SET_SELECTED_IDS (state, ids) { + state.selectedIds = ids }, SET_NEW (state, snippet) { state.newSnippetId = snippet @@ -143,354 +159,291 @@ export default { SET_ACTIVE_FRAGMENT (state, payload) { state.activeFragment = payload }, + RESET_ACTIVE_FRAGMENT (state) { + state.activeFragment = { snippetId: null, index: 0 } + }, SET_COUNT (state, count) { state.count = count } }, actions: { - async getSnippets ({ commit }, query = {}) { + async getSnippets ({ commit, rootGetters }, query = {}) { const defaultQuery = { isDeleted: false, ...query } - function getSnippets () { - return new Promise((resolve, reject) => { - db.snippets.find(defaultQuery, (err, snippets) => { - if (err) reject(err) - resolve(snippets) - }) - }) - } - - function getFolders () { - return new Promise((resolve, reject) => { - db.masscode.findOne({ _id: 'folders' }, (err, doc) => { - if (err) reject(err) - resolve(doc.list) - }) - }) - } - - function getTags () { - return new Promise((resolve, reject) => { - db.tags.find({}, (err, doc) => { - if (err) reject(err) - resolve(doc) - }) - }) - } - - const snippets = await getSnippets() - const folders = await getFolders() - const tags = await getTags() - - // Добавляем связь folder - snippets.map(snippet => { - function findFolderById (folders, id) { - folders.forEach(i => { - if (i.id === id) snippet.folder = i - - if (i.children && i.children.length) { - findFolderById(i.children, id) - } - }) + const snippets = db.collections.snippets.$aggregate([ + { + $match: defaultQuery + }, + { + $lookup: { + from: 'tags', + localField: 'tagIds', + foreignField: '_id', + as: 'tags' + } + }, + { + $lookup: { + from: 'folders', + localField: 'folderId', + foreignField: '_id', + as: 'folder' + } + }, + { + $sort: { updatedAt: 1 } } + ]) - findFolderById(folders, snippet.folderId) + commit('SET_SNIPPETS', snippets) + }, + getSnippetsBySelectedFolders ({ dispatch, rootGetters }) { + const foldersIds = rootGetters['folders/selectedIds'] + const isSystemFolder = rootGetters['folders/isSystemFolder'] + const defaultQueryBySystemFolder = + rootGetters['folders/defaultQueryBySystemFolder'] + let query = { folderId: { $in: foldersIds } } - return snippet - }) + if (isSystemFolder) query = defaultQueryBySystemFolder - // Добавляем связь tags - snippets.map(snippet => { - snippet.tagsPopulated = [] - snippet.tags.forEach(tagId => { - const foundedTag = tags.find(tag => tag._id === tagId) - if (foundedTag) { - foundedTag.text = foundedTag.name - snippet.tagsPopulated.push(foundedTag) - } - }) - }) - - commit('SET_SNIPPETS', snippets) + dispatch('getSnippets', query) }, - async getLatestSnippets ({ commit, dispatch }, limit = 20) { - const query = { + async getSnippetsForTray ({ commit, dispatch }, limit = 10) { + const defaultQuery = { isDeleted: false } - - return new Promise((resolve, reject) => { - db.snippets - .find(query) - .sort({ updatedAt: -1 }) - .limit(limit) - .exec((err, snippets) => { - if (err) return - // Добавляем связь folder по его id у snippet - db.masscode.findOne({ _id: 'folders' }, (err, doc) => { - if (err) return - - const { list } = doc - - snippets.map(snippet => { - function findFolderById (folders, id) { - folders.forEach(i => { - if (i.id === id) snippet.folder = i - - if (i.children && i.children.length) { - findFolderById(i.children, id) - } - }) - } - - findFolderById(list, snippet.folderId) - - return snippet - }) - - commit('SET_LATEST_SNIPPETS', snippets) - resolve() - }) - }) - }) + const snippets = db.collections.snippets.$aggregate([ + { + $match: defaultQuery + }, + { + $lookup: { + from: 'tags', + localField: 'tagIds', + foreignField: '_id', + as: 'tags' + } + }, + { + $lookup: { + from: 'folders', + localField: 'folderId', + foreignField: '_id', + as: 'folder' + } + }, + { + $sort: { updatedAt: 1 } + }, + { + $limit: limit + } + ]) + commit('SET_SNIPPETS_FOR_TRAY', snippets) }, setSelected ({ commit }, snippet) { if (snippet) { - commit('SET_SELECTED_ID', snippet._id) - electronStore.app.set('selectedSnippetId', snippet._id) + if (typeof snippet === 'object') { + commit('SET_SELECTED_ID', snippet._id) + commit('SET_ACTIVE_FRAGMENT', { + snippetId: snippet._id, + index: 0 + }) + electronStore.app.set('selectedSnippetId', snippet._id) + } + if (typeof snippet === 'string') { + commit('SET_SELECTED_ID', snippet) + commit('SET_ACTIVE_FRAGMENT', { + snippetId: snippet, + index: 0 + }) + electronStore.app.set('selectedSnippetId', snippet) + } } else { commit('SET_SELECTED_ID', null) electronStore.app.delete('selectedSnippetId') } }, - addSnippet ({ commit, dispatch, rootGetters }, { folderId, snippet }) { - const ids = rootGetters['folders/selectedIds'] - const defaultLanguage = rootGetters['folders/defaultLanguage'] - const defaultQuery = { folderId: { $in: ids } } - const query = defaultLibraryQuery(defaultQuery, folderId) + async addSnippet ( + { commit, dispatch, getters, rootGetters }, + { folderId, snippet } + ) { + const isFolderExist = await dispatch('folders/isFolderExist', folderId, { + root: true + }) + const { trash, favorites, allSnippets, inbox } = rootGetters[ + 'folders/systemAliases' + ] - if (!snippet) { - snippet = { - name: 'Untitled snippet', - folderId: folderId, - content: [ - { label: 'Fragment 1', language: defaultLanguage, value: '' } - ], - tags: [], - isFavorites: false, - isDeleted: false, - createdAt: new Date(), - updatedAt: new Date() - } - } + if (folderId === trash || folderId === favorites) return + if (!folderId || !isFolderExist) folderId = inbox - if (folderId === 'trash') { - snippet.folderId = null - snippet.isDeleted = true - } - if (folderId === 'favorites') { - snippet.folderId = null - snippet.isFavorites = true - } - if (folderId === 'allSnippets') { - snippet.folderId = null - } - if (folderId === 'inBox') { - snippet.folderId = null - } + const defaultLanguage = + rootGetters['folders/selected']?.defaultLanguage || 'text' - db.snippets.insert(snippet, (err, snippet) => { - if (err) return - dispatch('getSnippets', query) - commit('SET_SELECTED_ID', snippet._id) - commit('SET_NEW', snippet._id) + db.collections.snippets.$insert({ + name: 'Untitled snippet', + folderId: folderId === allSnippets ? inbox : folderId, + content: [{ label: 'Fragment 1', language: defaultLanguage, value: '' }] }) - }, - updateSnippets ({ commit, dispatch, rootGetters }, { ids, payload }) { - const foldersIds = rootGetters['folders/selectedIds'] - const folderId = rootGetters['folders/selectedId'] - const isTagsShow = rootGetters['app/isTagsShow'] - const defaultQuery = { folderId: { $in: foldersIds } } - const query = defaultLibraryQuery(defaultQuery, folderId) - return new Promise((resolve, reject) => { - db.snippets.update( - { _id: { $in: ids } }, - payload, - { multi: true }, - async (err, num) => { - if (err) return - if (!isTagsShow) { - await dispatch('getSnippets', query) - } else { - const selectedTagId = rootGetters['tags/selectedId'] - await dispatch('getSnippets', { - tags: { $elemMatch: selectedTagId } - }) - } - resolve() - } - ) - commit('SET_NEW', null) + dispatch('getSnippetsBySelectedFolders') + const first = getters.snippetsBySort[0] + dispatch('setSelected', first) + }, + updateSnippetsByIds ({ commit, dispatch, rootGetters }, { ids, payload }) { + ids.forEach(id => { + try { + db.collections.snippets.$findOneAndUpdate( + { + _id: id + }, + payload + ) + } catch (err) { + console.error(err) + } }) - }, - deleteSnippets ({ dispatch, rootGetters }, ids) { - const foldersIds = rootGetters['folders/selectedIds'] - const folderId = rootGetters['folders/selectedId'] - const defaultQuery = { folderId: { $in: foldersIds } } - const query = defaultLibraryQuery(defaultQuery, folderId) - db.snippets.remove({ _id: { $in: ids } }, { multi: true }, (err, num) => { - if (err) return - dispatch('getSnippets', query) + dispatch('getSnippetsBySelectedFolders') + }, + addToFavoritesByIds ({ state, dispatch }, ids) { + ids.map(id => { + db.collections.snippets + .find({ _id: id }) + .assign({ isFavorites: true }) + .write() + }) + dispatch('getSnippetsBySelectedFolders') + }, + removeFromFavoritesByIds ({ state, dispatch }, ids) { + ids.map(id => { + db.collections.snippets + .find({ _id: id }) + .assign({ isFavorites: false }) + .write() }) + dispatch('getSnippetsBySelectedFolders') }, - emptyTrash ({ dispatch, rootGetters }) { - const ids = rootGetters['folders/selectedIds'] - const folderId = rootGetters['folders/selectedId'] - const defaultQuery = { folderId: { $in: ids } } - const query = defaultLibraryQuery(defaultQuery, folderId) - - db.snippets.remove({ isDeleted: true }, { multi: true }, (err, num) => { - if (err) return - dispatch('getSnippets', query) + deleteSnippetByIds ({ dispatch, rootGetters }, ids) { + ids.map(id => { + db.collections.snippets.remove({ _id: id }).write() }) + dispatch('getSnippetsBySelectedFolders') }, - searchSnippets ({ commit }, query) { - db.snippets.find({}, (err, doc) => { - if (err) return - query = query.toLowerCase() - - doc = doc - .filter(snippet => !snippet.isDeleted) - .sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)) - - const resultBySnippetContent = doc.filter(snippet => - snippet.content.some(content => - content.value ? content.value.toLowerCase().includes(query) : false - ) - ) - - const resultBySnippetName = doc.filter(snippet => - snippet.name.toLowerCase().includes(query) - ) - - const results = uniqBy( - [...resultBySnippetContent, ...resultBySnippetName], - '_id' + emptyTrash ({ dispatch, rootGetters }) { + db.collections.snippets.remove({ isDeleted: true }).write() + dispatch('setSelected', null) + dispatch('getSnippetsBySelectedFolders') + }, + async searchSnippets ( + { state, commit, dispatch, getters, rootGetters }, + query + ) { + await dispatch('getSnippets') + commit('RESET_ACTIVE_FRAGMENT') + + query = query.toLowerCase() + const snippets = getters.snippetsBySort + + const resultBySnippetContent = snippets.filter(snippet => + snippet.content.some(content => + content.value ? content.value.toLowerCase().includes(query) : false ) - - if (query) { - commit('SET_SEARCH', true) - commit('SET_SEARCH_QUERY', query) - - if (results.length) { - const first = results[0] - commit('SET_SELECTED_ID', first._id) - commit('folders/SET_SELECTED_ID', 'allSnippets', { root: true }) - } else { - commit('SET_SELECTED_ID', null) - } + ) + + const resultBySnippetName = snippets.filter(snippet => + snippet.name.toLowerCase().includes(query) + ) + + const results = uniqBy( + [...resultBySnippetContent, ...resultBySnippetName], + '_id' + ) + + if (query) { + commit('SET_SEARCH', true) + commit('SET_SEARCH_QUERY', query) + + if (results.length) { + const first = results[0] + commit('SET_SELECTED_ID', first._id) + const { allSnippets } = rootGetters['folders/systemAliases'] + commit('folders/SET_SELECTED_ID', allSnippets, { root: true }) } else { - const selectedSnippetId = electronStore.app.get('selectedSnippetId') - const selectedFolderId = electronStore.app.get('selectedFolderId') - commit('SET_SEARCH', false) - commit('SET_SEARCH_QUERY', null) - commit('SET_SELECTED_ID', selectedSnippetId) - commit('folders/SET_SELECTED_ID', selectedFolderId, { root: true }) + commit('SET_SELECTED_ID', null) } - commit('SET_SEARCHED', results) - }) + } else { + const selectedSnippetId = electronStore.app.get('selectedSnippetId') + const selectedFolderId = electronStore.app.get('selectedFolderId') + commit('SET_SEARCH', false) + commit('SET_SEARCH_QUERY', null) + commit('SET_SELECTED_ID', selectedSnippetId) + commit('folders/SET_SELECTED_ID', selectedFolderId, { root: true }) + } + + commit('SET_SEARCHED', results) }, - searchSnippetsTray ({ commit }, query) { - db.snippets.find({}, (err, doc) => { - if (err) return - query = query.toLowerCase() + async searchSnippetsTray ({ commit, dispatch, getters }, query) { + await dispatch('getSnippets') - doc = doc - .filter(snippet => !snippet.isDeleted) - .sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)) + query = query.toLowerCase() + const snippets = getters.snippetsBySort - const resultBySnippetContent = doc.filter(snippet => - snippet.content.some(content => - content.value ? content.value.toLowerCase().includes(query) : false - ) + const resultBySnippetContent = snippets.filter(snippet => + snippet.content.some(content => + content.value ? content.value.toLowerCase().includes(query) : false ) + ) - const resultBySnippetName = doc.filter(snippet => - snippet.name.toLowerCase().includes(query) - ) + const resultBySnippetName = snippets.filter(snippet => + snippet.name.toLowerCase().includes(query) + ) - const results = uniqBy( - [...resultBySnippetContent, ...resultBySnippetName], - '_id' - ) + const results = uniqBy( + [...resultBySnippetContent, ...resultBySnippetName], + '_id' + ) - if (query) { - commit('SET_SEARCH_TRAY', true) - commit('SET_SEARCH_QUERY_TRAY', query) - } else { - commit('SET_SEARCH_TRAY', false) - commit('SET_SEARCH_QUERY_TRAY', null) - } + if (query) { + commit('SET_SEARCH_TRAY', true) + commit('SET_SEARCH_QUERY_TRAY', query) + } else { + commit('SET_SEARCH_TRAY', false) + commit('SET_SEARCH_QUERY_TRAY', null) + } - commit('SET_SEARCHED_TRAY', results) - }) + commit('SET_SEARCHED_TRAY', results) }, setSort ({ commit }, sort) { commit('SET_SORT', sort) electronStore.app.set('snippetsSort', sort) }, - async addTag ({ dispatch, rootGetters }, { snippetId, tagId }) { - db.snippets.update({ _id: snippetId }, { $addToSet: { tags: tagId } }) - const isTagsShow = rootGetters['app/isTagsShow'] - - if (!isTagsShow) { - const selectedFolderIds = rootGetters['folders/selectedIds'] - const isSystemFolder = rootGetters['folders/isSystemFolder'] - const defaultQuery = rootGetters['folders/defaultQueryBySystemFolder'] - - let query = { folderId: { $in: selectedFolderIds } } + async addTag ({ dispatch, getters, rootGetters }, { snippetId, tagId }) { + const snippetDoc = db.collections.snippets.find({ _id: snippetId }) + const { tagIds } = snippetDoc.cloneDeep().value() - if (isSystemFolder) { - query = defaultQuery - } - - await dispatch('getSnippets', query) - } else { - const selectedTagId = rootGetters['tags/selectedId'] - await dispatch('getSnippets', { tags: { $elemMatch: selectedTagId } }) - } + tagIds.push(tagId) + snippetDoc.assign({ tagIds }).write() + dispatch('getSnippetsBySelectedFolders') }, - async removeTag ({ dispatch, rootGetters }, { snippetId, tagId }) { - db.snippets.update({ _id: snippetId }, { $pull: { tags: tagId } }) - const isTagsShow = rootGetters['app/isTagsShow'] - - if (!isTagsShow) { - const selectedFolderIds = rootGetters['folders/selectedIds'] - const isSystemFolder = rootGetters['folders/isSystemFolder'] - const defaultQuery = rootGetters['folders/defaultQueryBySystemFolder'] - - let query = { folderId: { $in: selectedFolderIds } } + async removeTag ({ dispatch, getters, rootGetters }, { snippetId, tagId }) { + const snippetDoc = db.collections.snippets.find({ _id: snippetId }) + const { tagIds } = snippetDoc.cloneDeep().value() - if (isSystemFolder) { - query = defaultQuery - } - - await dispatch('getSnippets', query) - } else { - const selectedTagId = rootGetters['tags/selectedId'] - await dispatch('getSnippets', { tags: { $elemMatch: selectedTagId } }) - } + pull(tagIds, tagId) + snippetDoc.assign({ tagIds }).write() + dispatch('getSnippetsBySelectedFolders') }, getSnippetsCount ({ commit }) { - db.snippets.count({ isDeleted: false }, (err, count) => { - if (err) throw new Error(err) - - commit('SET_COUNT', count) - }) + const count = db.collections.snippets + .filter({ isDeleted: false }) + .size() + .value() + commit('SET_COUNT', count) } } } From c4a03aaad36d4958338c65118a96239e30976dce Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:01:30 +0300 Subject: [PATCH 32/62] feat(Storage): use new db, add migrate functionality --- .../components/preferences/Storage.vue | 83 +++++++++++++++---- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/src/renderer/components/preferences/Storage.vue b/src/renderer/components/preferences/Storage.vue index 04661d8..f3036b5 100644 --- a/src/renderer/components/preferences/Storage.vue +++ b/src/renderer/components/preferences/Storage.vue @@ -9,10 +9,10 @@ size="small" class="preferences__input" /> - + Move storage - + Open storage @@ -50,7 +50,6 @@ :key="index" class="backups__item" > - {{ i.label }} + +
+ + Open folder + +
+
+ You can migrate from massCode v1, select the folder with old DB files. +
+
{{ countText }} @@ -69,8 +78,8 @@ import { mapState, mapGetters } from 'vuex' import { dialog } from '@@/lib' import { ipcRenderer } from 'electron' -import db from '@/datastore' import PerfectScrollbar from 'perfect-scrollbar' +import db from '@@/lib/datastore' export default { name: 'Storage', @@ -85,6 +94,7 @@ export default { ...mapState(['app']), ...mapGetters('app', ['backups']), ...mapGetters('snippets', ['count']), + ...mapGetters('folders', ['systemAliases']), storagePath: { get () { return this.app.storagePath @@ -117,7 +127,7 @@ export default { }, methods: { - async onChangeStorage () { + async onMoveStorage () { const dir = dialog.showOpenDialogSync({ properties: ['openDirectory', 'createDirectory'] }) @@ -125,39 +135,34 @@ export default { const path = dir[0] try { await db.move(path) - this.updateData() this.$store.commit('app/SET_STORAGE_PATH', path) } catch (err) { ipcRenderer.send('message', { message: 'Error', type: 'error', - detail: - 'Folder already contains db files. Please select another folder.' + detail: 'Folder already contains DB. Please select another folder.' }) } } }, - async onOpenStorage () { + onOpenStorageFolder () { const dir = dialog.showOpenDialogSync({ properties: ['openDirectory'] }) if (dir) { const path = dir[0] db.import(path) - this.updateData() this.$store.commit('app/SET_STORAGE_PATH', path) + this.$store.dispatch('folders/getFolders') + this.$store.dispatch('snippets/getSnippets') + this.$store.dispatch('snippets/getSnippetsCount') + this.$store.dispatch('tags/getTags') } }, - async updateData () { - this.$store.dispatch('snippets/setSelected', null) - this.$store.dispatch('folders/setSelectedFolder', 'allSnippets') - await this.$store.dispatch('folders/getFolders') - await this.$store.dispatch('snippets/getSnippets') - }, async onBackup () { await db.removeEarliestBackup() await db.backup() - this.$store.dispatch('app/getBackups') + await this.$store.dispatch('app/getBackups') this.$nextTick(() => { this.ps.update() }) @@ -195,6 +200,50 @@ export default { } } }, + async onOpenMigrateFolder () { + const dir = dialog.showOpenDialogSync({ + properties: ['openDirectory'] + }) + const path = dir ? dir[0] : null + + if (!path) return + + const buttonId = dialog.showMessageBoxSync({ + message: 'Are you sure you want to migrate from v1?', + detail: + 'During migrate from old DB, the current library will be overwritten.', + buttons: ['Confirm', 'Cancel'], + defaultId: 0, + cancelId: 1 + }) + + if (buttonId === 1) return + + try { + await db.migrate(path) + this.$store.dispatch('folders/getFolders') + this.$store.dispatch('snippets/getSnippets') + this.$store.dispatch('snippets/getSnippetsCount') + this.$store.dispatch('tags/getTags') + } catch (err) { + dialog.showMessageBoxSync({ + title: 'Error', + message: 'DB files not exist in this folder', + type: 'error' + }) + } + }, + async updateData () { + this.$store.dispatch('snippets/setSelected', null) + this.$store.dispatch( + 'folders/setSelectedFolderById', + this.systemAliases.allSnippets + ) + this.$store.dispatch('folders/getFolders') + this.$store.dispatch('snippets/getSnippets') + this.$store.dispatch('snippets/getSnippetsCount') + this.$store.dispatch('tags/getTags') + }, getData () { this.$store.dispatch('app/getBackups') this.$store.dispatch('snippets/getSnippetsCount') From 7c025391522b772618461843d521992c51538bf0 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:03:37 +0300 Subject: [PATCH 33/62] refactor(TheLibrary): use folders from store --- .../components/sidebar/TheLibrary.vue | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/renderer/components/sidebar/TheLibrary.vue b/src/renderer/components/sidebar/TheLibrary.vue index 1aae386..074d0f3 100644 --- a/src/renderer/components/sidebar/TheLibrary.vue +++ b/src/renderer/components/sidebar/TheLibrary.vue @@ -13,12 +13,12 @@ > @@ -56,14 +56,7 @@ export default { }, data () { - return { - library: [ - { label: 'Inbox', id: 'inBox', icon: 'inbox' }, - { label: 'Favorites', id: 'favorites', icon: 'star' }, - { label: 'All Snippets', id: 'allSnippets', icon: 'archive' }, - { label: 'Trash', id: 'trash', icon: 'trash' } - ] - } + return {} }, computed: { @@ -80,14 +73,11 @@ export default { }, methods: { - icon (index) { - let icon - if (index === 0) icon = 'inbox' - if (index === 1) icon = 'star' - if (index === 2) icon = 'archive' - if (index === 3) icon = 'trash' - if (index > 3) icon = 'folder' - return icon + icon (name) { + if (name === 'Inbox') return 'inbox' + if (name === 'Favorites') return 'star' + if (name === 'All Snippets') return 'archive' + if (name === 'Trash') return 'trash' } } } From a9061061850b320cf7ac5dc8dc3fc754cdb4582a Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:04:36 +0300 Subject: [PATCH 34/62] refactor(Tray): use new db --- src/renderer/views/Tray.vue | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/renderer/views/Tray.vue b/src/renderer/views/Tray.vue index 3c8544b..d3dfaff 100644 --- a/src/renderer/views/Tray.vue +++ b/src/renderer/views/Tray.vue @@ -51,7 +51,6 @@ import { mapGetters } from 'vuex' import PerfectScrollbar from 'perfect-scrollbar' import '@/lib/ipcRenderer' import { ipcRenderer } from 'electron' -import db from '@/datastore' export default { name: 'TrayView', @@ -78,7 +77,7 @@ export default { computed: { ...mapGetters('snippets', [ - 'snippetsLatest', + 'snippetsTray', 'searchQueryTray', 'isSearchedTray', 'snippetsSearchedTray' @@ -93,9 +92,7 @@ export default { } }, snippetList () { - return this.isSearchedTray - ? this.snippetsSearchedTray - : this.snippetsLatest + return this.isSearchedTray ? this.snippetsSearchedTray : this.snippetsTray } }, @@ -121,9 +118,7 @@ export default { methods: { async init () { // Загружаем последние изменения БД - db.updatePath() - - await this.$store.dispatch('snippets/getLatestSnippets') + await this.$store.dispatch('snippets/getSnippetsForTray') if (this.ps) { this.$nextTick(() => { this.ps.update() From 90fe03fbc51bb9543f24b65e1d45548d667fabbf Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:06:33 +0300 Subject: [PATCH 35/62] fix(datastore): helpers --- src/main/lib/datastore/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/lib/datastore/helpers.js b/src/main/lib/datastore/helpers.js index 7a3575a..3f4dfd6 100644 --- a/src/main/lib/datastore/helpers.js +++ b/src/main/lib/datastore/helpers.js @@ -11,7 +11,7 @@ export function deleteTechProps (obj) { delete obj._id delete obj.children delete obj.tags - delete obj.folders + delete obj.folder delete obj.createdAt delete obj.updatedAt From 7a0846c403cae0c259d9c17cda1ecb6597437717 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:07:49 +0300 Subject: [PATCH 36/62] refactor(ipc): rename dispatch method --- src/renderer/lib/ipcRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/lib/ipcRenderer.js b/src/renderer/lib/ipcRenderer.js index 4320bd7..3f96b6f 100644 --- a/src/renderer/lib/ipcRenderer.js +++ b/src/renderer/lib/ipcRenderer.js @@ -47,7 +47,7 @@ ipcRenderer.on('menu:markdown-preview', (e, value) => { }) ipcRenderer.on('menu:favorites', () => { - store.dispatch('folders/setSelectedFolder', 'favorites') + store.dispatch('folders/setSelectedFolderById', 'favorites') }) ipcRenderer.on('update-available', () => { From faa40e0d980e114897c4942f268a555bb2f96940 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:08:47 +0300 Subject: [PATCH 37/62] chore(main:render): clear --- src/renderer/main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/main.js b/src/renderer/main.js index b8eface..270e09b 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -2,7 +2,6 @@ import Vue from 'vue' import App from './App' import store from './store' import router from './router' -import db from '@/datastore' import eventBus from '@/event-bus' import { clickOutside } from './directives' import UiKit from '@/components/uikit' @@ -10,7 +9,6 @@ import UiKit from '@/components/uikit' if (!process.env.IS_WEB) Vue.use(require('vue-electron')) Vue.config.productionTip = false -Vue.prototype.$db = db Vue.prototype.$bus = eventBus Vue.directive('click-outside', clickOutside) From 252081b3f52b5f9617eaeda65fa0ef33cc2bffee Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:10:21 +0300 Subject: [PATCH 38/62] refactor(SnippetView): rename selectedTags -> selectedTags --- .../components/snippets/SnippetView.vue | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/snippets/SnippetView.vue b/src/renderer/components/snippets/SnippetView.vue index e9331f5..9da4598 100644 --- a/src/renderer/components/snippets/SnippetView.vue +++ b/src/renderer/components/snippets/SnippetView.vue @@ -42,7 +42,7 @@ { + return { + text: i.name, + _id: i._id + } + }) } return [] }, @@ -154,7 +159,7 @@ export default { return this.tags .filter( i => - !this.selectedTags.some( + !this.snippetTags.some( tag => tag.text.toLowerCase() === i.text.toLowerCase() ) ) @@ -209,7 +214,7 @@ export default { this.$emit('edit') const ids = [this.localSnippet._id] const payload = this.localSnippet - this.$store.dispatch('snippets/updateSnippets', { ids, payload }) + this.$store.dispatch('snippets/updateSnippetsByIds', { ids, payload }) }, 300), { deep: true } ) @@ -222,6 +227,9 @@ export default { }, onChangeLanguage (lang, index) { this.localSnippet.content[index].language = lang + const ids = [this.localSnippet._id] + const payload = this.localSnippet + this.$store.dispatch('snippets/updateSnippetsByIds', { ids, payload }) }, onClickOutside () { if (this.newSnippetId) { @@ -232,7 +240,7 @@ export default { const index = this.localSnippet.content.length const fragment = { label: `Fragment ${index + 1}`, - language: null, + language: 'text', value: null } this.localSnippet.content.push(fragment) @@ -310,16 +318,16 @@ export default { !tag.tiClasses.includes('ti-invalid') ) { addTag() - const newTag = await this.$store.dispatch('tags/addTag', { + const { _id } = await this.$store.dispatch('tags/addTag', { name: tag.text.trim() }) + const payload = { snippetId: this.selected._id, - tagId: newTag._id + tagId: _id } - track('tags/new') this.$store.dispatch('snippets/addTag', payload) - + track('tags/new') track('tags/new-snippet-tag') } }, From bce2be35c092cea48a813eb993ea695821ba1739 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:13:05 +0300 Subject: [PATCH 39/62] refactor(SidebarListItem): use new db methods --- .../components/sidebar/SidebarListItem.vue | 101 +++++++++++++++--- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/src/renderer/components/sidebar/SidebarListItem.vue b/src/renderer/components/sidebar/SidebarListItem.vue index e77ea64..d366da2 100644 --- a/src/renderer/components/sidebar/SidebarListItem.vue +++ b/src/renderer/components/sidebar/SidebarListItem.vue @@ -94,8 +94,18 @@ export default { }, computed: { - ...mapGetters('folders', ['selectedId', 'editableId']), + ...mapGetters('folders', [ + 'selected', + 'selectedId', + 'selectedIds', + 'editableId', + 'system', + 'isSystemFolder', + 'defaultQueryBySystemFolder', + 'systemAliases' + ]), ...mapGetters('tags', { selectedTagId: 'selectedId' }), + ...mapGetters('snippets', ['snippetsBySort']), languagesMenu () { return languages .map(i => { @@ -103,8 +113,8 @@ export default { i.checked = i.value === this.model.defaultLanguage i.click = e => { const id = this.id - const payload = e.value - this.$store.dispatch('folders/updateFolderLanguage', { + const payload = { defaultLanguage: e.value } + this.$store.dispatch('folders/updateFolderById', { id, payload }) @@ -134,10 +144,6 @@ export default { }, isEditable () { return this.editableId === this.id - }, - isSystem () { - const libraryItems = ['inBox', 'favorites', 'allSnippets', 'trash'] - return libraryItems.includes(this.id) } }, @@ -148,23 +154,23 @@ export default { }, methods: { - onClick () { + async onClick () { if (!this.tag) { - this.$store.dispatch('folders/setSelectedFolder', this.id) + await this.getSnippetsByFolder() } else { - this.$store.commit('tags/SET_SELECTED_ID', this.id) + await this.getSnippetsByTag(this.id) } }, onDblClick () { - if (!this.isSystem) this.setEditable() + if (!this.isSystemFolder) this.setEditable() }, onContext () { if (!this.tag) { - if (this.id === 'trash') { + if (this.id === this.systemAliases.trash) { return this.trashContext() } - if (this.isSystem) return + if (this.isSystemFolder) return this.folderContext() } else { @@ -190,7 +196,10 @@ export default { cancelId: 1 }) if (buttonId === 0) { - this.$store.dispatch('folders/deleteFolder', this.id) + this.$store.dispatch( + 'folders/deleteFolderByIds', + this.selectedIds + ) track('folders/delete') } } @@ -270,8 +279,53 @@ export default { } if (this.updatedFolderName) { const id = this.id - const payload = this.updatedFolderName - this.$store.dispatch('folders/updateFolderName', { id, payload }) + const payload = { + name: this.updatedFolderName + } + this.$store.dispatch('folders/updateFolderNameById', { + id, + payload + }) + } + }, + async getSnippetsByFolder () { + await this.$store.dispatch('folders/setSelectedFolderById', this.id) + + let query = { folderId: { $in: this.selectedIds } } + + if (this.isSystemFolder) { + query = this.defaultQueryBySystemFolder + } + + await this.$store.dispatch('snippets/getSnippets', query) + const firstSnippet = this.snippetsBySort[0] + + if (firstSnippet) { + await this.$store.dispatch('snippets/setSelected', firstSnippet._id) + this.$store.commit('snippets/SET_SELECTED_IDS', [firstSnippet._id]) + } else { + await this.$store.dispatch('snippets/setSelected', null) + this.$store.commit('snippets/SET_SELECTED_IDS', []) + } + }, + async getSnippetsByTag (id) { + this.$store.commit('tags/SET_SELECTED_ID', this.id) + await this.$store.dispatch('snippets/getSnippets', { + tagIds: { $elemMatch: id } + }) + const firstSnippet = this.snippetsBySort[0] + if (firstSnippet) { + this.$store.commit('snippets/SET_SELECTED_ID', firstSnippet._id) + this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { + snippetId: firstSnippet._id, + index: 0 + }) + } else { + this.$store.commit('snippets/SET_SELECTED_ID', null) + this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { + snippetId: null, + index: 0 + }) } } } @@ -287,25 +341,30 @@ export default { cursor: default; user-select: none; position: relative; + &__input { width: 100%; border: 1px solid transparent; background-color: transparent; outline: none; + &[disabled] { color: var(--color-text); } + &.is-editable { border: 1px solid var(--color-primary); background-color: var(--color-contrast-lower); color: var(--color-contrast-higher); } } + .folder-name { display: flex; height: 20px; align-items: center; } + svg { width: 16px; height: 16px; @@ -314,25 +373,32 @@ export default { margin-right: var(--spacing-xs); stroke: var(--color-contrast-medium); } + &:last-child { margin-bottom: 0; } + &--selected { background-color: var(--color-contrast-low); } + &--drag-hovered { background-color: var(--color-primary); color: #fff; position: relative; + #{$r}__input { color: #fff; } + svg { stroke: #fff; } } + &--context { position: relative; + &::after { content: ''; position: absolute; @@ -344,15 +410,18 @@ export default { border-radius: 4px; } } + &__child-icon { position: absolute; top: 6px; left: 2px; + &.is-open { svg { transform: rotate(90deg); } } + svg { transition: all 0.1s; width: 14px; From da5d026f68721a6be34eca2904e385c9678c2267 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:15:30 +0300 Subject: [PATCH 40/62] chore(SidebarList): clear --- .../components/snippets/SnippetList.vue | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/src/renderer/components/snippets/SnippetList.vue b/src/renderer/components/snippets/SnippetList.vue index 6d8f94b..4ee930b 100644 --- a/src/renderer/components/snippets/SnippetList.vue +++ b/src/renderer/components/snippets/SnippetList.vue @@ -55,64 +55,12 @@ export default { 'isSearched', 'snippetsBySort' ]), - ...mapGetters('folders', [ - 'selectedId', - 'selectedIds', - 'allSnippetsId', - 'isSystemFolder', - 'defaultQueryBySystemFolder' - ]), - ...mapGetters('tags', { selectedTagId: 'selectedId' }), snippetsList () { return this.isSearched ? this.snippetsSearched : this.snippetsBySort } }, watch: { - async selectedId (id) { - let query = { folderId: { $in: this.selectedIds } } - - if (this.isSystemFolder) { - query = this.defaultQueryBySystemFolder - } - - await this.$store.dispatch('snippets/getSnippets', query) - const firstSnippet = this.snippetsList[0] - - if (firstSnippet) { - this.$store.commit('snippets/SET_SELECTED_ID', firstSnippet._id) - this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { - snippetId: firstSnippet._id, - index: 0 - }) - } else { - this.$store.commit('snippets/SET_SELECTED_ID', null) - this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { - snippetId: null, - index: 0 - }) - } - }, - - async selectedTagId (id) { - await this.$store.dispatch('snippets/getSnippets', { - tags: { $elemMatch: id } - }) - const firstSnippet = this.snippetsList[0] - if (firstSnippet) { - this.$store.commit('snippets/SET_SELECTED_ID', firstSnippet._id) - this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { - snippetId: firstSnippet._id, - index: 0 - }) - } else { - this.$store.commit('snippets/SET_SELECTED_ID', null) - this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { - snippetId: null, - index: 0 - }) - } - }, snippetsList () { this.$nextTick(() => { this.ps.update() From 2c54835fe268a2f47ececf6d1e69e1a1d3e5b773 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:25:14 +0300 Subject: [PATCH 41/62] fix(store): pass snippet to 'addSnippet' --- src/renderer/store/modules/snippets.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/renderer/store/modules/snippets.js b/src/renderer/store/modules/snippets.js index d26eebe..5924498 100644 --- a/src/renderer/store/modules/snippets.js +++ b/src/renderer/store/modules/snippets.js @@ -284,11 +284,17 @@ export default { const defaultLanguage = rootGetters['folders/selected']?.defaultLanguage || 'text' - db.collections.snippets.$insert({ - name: 'Untitled snippet', - folderId: folderId === allSnippets ? inbox : folderId, - content: [{ label: 'Fragment 1', language: defaultLanguage, value: '' }] - }) + if (!snippet) { + db.collections.snippets.$insert({ + name: 'Untitled snippet', + folderId: folderId === allSnippets ? inbox : folderId, + content: [ + { label: 'Fragment 1', language: defaultLanguage, value: '' } + ] + }) + } else { + db.collections.snippets.$insert(snippet) + } dispatch('getSnippetsBySelectedFolders') const first = getters.snippetsBySort[0] From f52c923a3fdf3c18728edc6becfa6ec421a0da53 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 11:27:44 +0300 Subject: [PATCH 42/62] feat(AppInputTags): add clear input tag by click outside, avoid duplicates --- src/renderer/components/uikit/AppInputTags.vue | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/uikit/AppInputTags.vue b/src/renderer/components/uikit/AppInputTags.vue index 5e2a211..1c214bb 100644 --- a/src/renderer/components/uikit/AppInputTags.vue +++ b/src/renderer/components/uikit/AppInputTags.vue @@ -2,9 +2,12 @@
@@ -195,6 +198,11 @@ export default { }, initPS () { this.ps = new PerfectScrollbar(this.$refs.popper.$el.children[0]) + }, + onClickOutside () { + this.showPopper = false + this.$refs.tagsInput.$emit('input', '') + this.$nextTick(() => {}) } } } From 7cc50a761c59c8fec6473bf8cbbaf1de9a52a4c1 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 15:40:55 +0300 Subject: [PATCH 43/62] refactor(SidebarListItem): use new db methods --- .../components/snippets/SnippetListItem.vue | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/renderer/components/snippets/SnippetListItem.vue b/src/renderer/components/snippets/SnippetListItem.vue index 0cd8d8e..5a76702 100644 --- a/src/renderer/components/snippets/SnippetListItem.vue +++ b/src/renderer/components/snippets/SnippetListItem.vue @@ -32,6 +32,7 @@ import { mapGetters } from 'vuex' import { format, isSameDay } from 'date-fns' import { menu, dialog } from '@@/lib' import { track } from '@@/lib/analytics' +import cloneDeep from 'lodash-es/cloneDeep' export default { name: 'SnippetListItem', @@ -72,12 +73,13 @@ export default { 'sort' ]), ...mapGetters('folders', { selectedFolderId: 'selectedId' }), + ...mapGetters('folders', ['systemAliases']), isSelected () { if (!this.selectedId) return null let selected - if (this.selectedSnippets.length > 1) { - selected = !!this.selectedSnippets.find(i => i._id === this.model._id) + if (this.selectedSnippets?.length > 1) { + selected = !!this.selectedSnippets.find(i => i?._id === this.model._id) } else { selected = this.selectedId === this.model._id } @@ -113,19 +115,16 @@ export default { this.selectedIndex + 1 ) } - this.$store.commit('snippets/SET_SELECTED_SNIPPETS', snippets) + const ids = snippets.map(i => i._id) + this.$store.commit('snippets/SET_SELECTED_IDS', ids) } else { this.onSelect() - this.$store.commit('snippets/SET_SELECTED_SNIPPETS', [this.model]) + this.$store.commit('snippets/SET_SELECTED_IDS', [this.model._id]) } }, onSelect () { this.focus = true this.$store.dispatch('snippets/setSelected', this.model) - this.$store.commit('snippets/SET_ACTIVE_FRAGMENT', { - snippetId: this.model._id, - index: 0 - }) }, onDrag (e, id) { const isDraggableInSelected = this.selectedSnippets.some( @@ -185,29 +184,26 @@ export default { }, onContext () { this.context = true - let ids + let snippets = cloneDeep(this.selectedSnippets) let isFavorites const inSelected = - this.selectedSnippets.findIndex(i => i._id === this.model._id) !== -1 + this.selectedSnippets.findIndex(i => i?._id === this.model?._id) !== -1 if (!inSelected) { - ids = [this.model._id] + snippets = [cloneDeep(this.model)] isFavorites = this.model.isFavorites } else { - ids = this.selectedSnippets.map(i => i._id) isFavorites = this.selectedSnippets.some(i => i.isFavorites) } + const ids = snippets.map(i => i._id) + let menuItems = [ { label: 'Add to Favorites', click: () => { - const payload = { - $set: { isFavorites: true } - } - - this.$store.dispatch('snippets/updateSnippets', { ids, payload }) + this.$store.dispatch('snippets/addToFavoritesByIds', ids) track('snippets/add-to-favorites') } }, @@ -218,9 +214,7 @@ export default { label: 'Duplicate', click: () => { const snippet = Object.assign({}, this.model) - snippet.createdAt = new Date() - snippet.updatedAt = new Date() - delete snippet._id + snippet.name = `${snippet.name} (copy)` this.$store.dispatch('snippets/addSnippet', { folderId: snippet.folderId, @@ -233,10 +227,10 @@ export default { label: 'Delete', click: async () => { const payload = { - $set: { isDeleted: true } + isDeleted: true } - await this.$store.dispatch('snippets/updateSnippets', { + await this.$store.dispatch('snippets/updateSnippetsByIds', { ids, payload }) @@ -292,22 +286,18 @@ export default { const removeFromFavorites = { label: 'Remove from Favorites', click: () => { - const payload = { - $set: { isFavorites: false } - } - - this.$store.dispatch('snippets/updateSnippets', { ids, payload }) + this.$store.dispatch('snippets/removeFromFavoritesByIds', ids) track('snippets/remove-from-favorites') } } - menuItems.splice(1, 0, removeFromFavorites) + menuItems.splice(0, 1, removeFromFavorites) } - if (this.selectedFolderId === 'trash') { + if (this.selectedFolderId === this.systemAliases.trash) { const deleteNow = { label: 'Delete Now', click: async () => { - const plural = ids.length > 1 ? 'snippets' : 'snippet' + const plural = snippets.length > 1 ? 'snippets' : 'snippet' const buttonId = dialog.showMessageBoxSync({ message: `Are you sure you want to permanently delete ${plural}?`, detail: 'You cannot undo this action.', @@ -316,7 +306,9 @@ export default { cancelId: 1 }) if (buttonId === 0) { - await this.$store.dispatch('snippets/deleteSnippets', ids) + // const ids = snippets.map(i => i._id) + // const ids = ids + await this.$store.dispatch('snippets/deleteSnippetByIds', ids) const firstSnippet = this.snippetsBySort[0] if (firstSnippet) { From 81c9787636bd14937b5f6d4bc63da047257d7a5a Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 15:42:22 +0300 Subject: [PATCH 44/62] fix(store): delete tech props on add new snippet --- src/renderer/store/modules/snippets.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/store/modules/snippets.js b/src/renderer/store/modules/snippets.js index 5924498..312084f 100644 --- a/src/renderer/store/modules/snippets.js +++ b/src/renderer/store/modules/snippets.js @@ -2,6 +2,7 @@ import electronStore from '@@/store' import uniqBy from 'lodash-es/uniqBy' import db from '@@/lib/datastore' import pull from 'lodash-es/pull' +import { deleteTechProps } from '@@/lib/datastore/helpers' const sort = electronStore.app.get('snippetsSort') || 'updatedAt' @@ -293,6 +294,7 @@ export default { ] }) } else { + snippet = deleteTechProps(snippet) db.collections.snippets.$insert(snippet) } From 051f5f84acfd78b6f382002cf1c029d125c7c158 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Thu, 4 Feb 2021 15:43:46 +0300 Subject: [PATCH 45/62] refactor(TheFolders): use new db --- .../components/sidebar/TheFolders.vue | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/renderer/components/sidebar/TheFolders.vue b/src/renderer/components/sidebar/TheFolders.vue index 2bfcb71..dbed07c 100644 --- a/src/renderer/components/sidebar/TheFolders.vue +++ b/src/renderer/components/sidebar/TheFolders.vue @@ -13,18 +13,18 @@
@@ -69,6 +69,7 @@ export default { computed: { ...mapGetters('folders', ['folders']), + ...mapGetters('snippets', ['selectedSnippets', 'selectedIds']), tree () { return this.$refs.tree } @@ -96,14 +97,9 @@ export default { this.dragHoveredFolderId = null if (data) { - const ids = JSON.parse(data).value - const payload = { - $set: { - folderId, - isDeleted: false - } - } - this.$store.dispatch('snippets/updateSnippets', { ids, payload }) + const ids = this.selectedIds + const payload = { folderId } + this.$store.dispatch('snippets/updateSnippetsByIds', { ids, payload }) } }, onTreeChange (node, newTree, oldTree) { @@ -111,7 +107,7 @@ export default { this.$store.dispatch('folders/updateFolders', folders) this.ps.update() }, - onNodeToggle (data, store) { + onNodeToggle (data, store, node) { store.toggleOpen(data) const folders = this.tree.getPureData() this.$store.dispatch('folders/updateFolders', folders) From 7024f68834a1848981de6491e1419479e944ec48 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 5 Feb 2021 09:55:07 +0300 Subject: [PATCH 46/62] chore: bump to electron v11 --- package.json | 8 +- src/main/main.js | 3 +- yarn.lock | 212 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 162 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 35c8b72..7146cf3 100644 --- a/package.json +++ b/package.json @@ -68,12 +68,11 @@ } }, "dependencies": { - "@babel/plugin-proposal-optional-chaining": "^7.10.1", "@hapi/joi": "^17.1.1", "@johmun/vue-tags-input": "^2.1.0", "axios": "^0.19.1", "date-fns": "^2.8.1", - "electron-store": "^5.1.0", + "electron-store": "^7.0.1", "emmet-monaco-es": "^4.3.3", "feather-icons": "^4.25.0", "fs-extra": "^8.1.0", @@ -105,6 +104,7 @@ "devDependencies": { "@babel/core": "^7.7.7", "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/plugin-proposal-optional-chaining": "^7.10.1", "@babel/polyfill": "^7.7.0", "@babel/preset-env": "^7.7.7", "@babel/register": "^7.7.7", @@ -120,10 +120,10 @@ "css-loader": "^3.4.0", "del": "^5.1.0", "devtron": "^1.4.0", - "electron": "^7.1.6", + "electron": "^11.2.2", "electron-builder": "^22.4.1", "electron-debug": "^3.0.1", - "electron-devtools-installer": "^2.2.4", + "electron-devtools-installer": "^3.1.1", "eslint": "^6.7.2", "eslint-config-standard": "^14.1.0", "eslint-friendly-formatter": "^4.0.1", diff --git a/src/main/main.js b/src/main/main.js index d96e267..851d415 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -27,7 +27,8 @@ function createMainWindow () { transparent: process.platform === 'darwin', backgroundColor, webPreferences: { - nodeIntegration: true + nodeIntegration: true, + enableRemoteModule: true } }) diff --git a/yarn.lock b/yarn.lock index 074b020..93bb5ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,11 +7,6 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f" integrity sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA== -"7zip@0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/7zip/-/7zip-0.0.6.tgz#9cafb171af82329490353b4816f03347aa150a30" - integrity sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA= - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -1338,6 +1333,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.0, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^7.0.3: + version "7.0.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.4.tgz#827e5f5ae32f5e5c1637db61f253a112229b5e2f" + integrity sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -1654,6 +1659,11 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +atomically@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe" + integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -2569,21 +2579,21 @@ concat-stream@1.6.2, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -conf@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/conf/-/conf-6.2.0.tgz#274d37a0a2e50757ffb89336e954d08718eb359a" - integrity sha512-fvl40R6YemHrFsNiyP7TD0tzOe3pQD2dfT2s20WvCaq57A1oV+RImbhn2Y4sQGDz1lB0wNSb7dPcPIvQB69YNA== +conf@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/conf/-/conf-9.0.0.tgz#86fd1a8e58da81a8fae2a8a9feeb3a45ef68d303" + integrity sha512-TGrWTD/tviifvdeoOZIYBmlceYJ0wq2lCzqbv7bT4KgostaCyyYe9uYWZdzPdhl4mROQXwMcc/iuAUsMTzf7pQ== dependencies: - ajv "^6.10.2" - debounce-fn "^3.0.1" - dot-prop "^5.0.0" + ajv "^7.0.3" + atomically "^1.7.0" + debounce-fn "^4.0.0" + dot-prop "^6.0.1" env-paths "^2.2.0" - json-schema-typed "^7.0.1" - make-dir "^3.0.0" - onetime "^5.1.0" - pkg-up "^3.0.1" - semver "^6.2.0" - write-file-atomic "^3.0.0" + json-schema-typed "^7.0.3" + make-dir "^3.1.0" + onetime "^5.1.2" + pkg-up "^3.1.0" + semver "^7.3.4" config-chain@^1.1.11: version "1.1.12" @@ -2813,11 +2823,6 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -cross-unzip@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/cross-unzip/-/cross-unzip-0.0.2.tgz#5183bc47a09559befcf98cc4657964999359372f" - integrity sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8= - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2917,12 +2922,12 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= -debounce-fn@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-3.0.1.tgz#034afe8b904d985d1ec1aa589cd15f388741d680" - integrity sha512-aBoJh5AhpqlRoHZjHmOzZlRx+wz2xVwGL9rjs+Kj0EWUrL4/h4K7OD176thl2Tdoqui/AaA4xhHrNArGLAaI3Q== +debounce-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7" + integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ== dependencies: - mimic-fn "^2.1.0" + mimic-fn "^3.0.0" debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" @@ -3248,13 +3253,20 @@ dot-case@^2.1.0: dependencies: no-case "^2.2.0" -dot-prop@^5.0.0, dot-prop@^5.2.0: +dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== dependencies: is-obj "^2.0.0" +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + dotenv-expand@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" @@ -3341,15 +3353,14 @@ electron-debug@^3.0.1: electron-is-dev "^1.1.0" electron-localshortcut "^3.1.0" -electron-devtools-installer@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-2.2.4.tgz#261a50337e37121d338b966f07922eb4939a8763" - integrity sha512-b5kcM3hmUqn64+RUcHjjr8ZMpHS2WJ5YO0pnG9+P/RTdx46of/JrEjuciHWux6pE+On6ynWhHJF53j/EDJN0PA== +electron-devtools-installer@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/electron-devtools-installer/-/electron-devtools-installer-3.1.1.tgz#7b56c8c86475c5e4e10de6917d150c53c9ceb55e" + integrity sha512-g2D4J6APbpsiIcnLkFMyKZ6bOpEJ0Ltcc2m66F7oKUymyGAt628OWeU9nRZoh1cNmUs/a6Cls2UfOmsZtE496Q== dependencies: - "7zip" "0.0.6" - cross-unzip "0.0.2" - rimraf "^2.5.2" - semver "^5.3.0" + rimraf "^3.0.2" + semver "^7.2.1" + unzip-crx-3 "^0.2.0" electron-is-accelerator@^0.1.0: version "0.1.2" @@ -3385,23 +3396,23 @@ electron-publish@22.4.1: lazy-val "^1.0.4" mime "^2.4.4" -electron-store@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-5.1.0.tgz#0b3cb66b15d0002678fc5c13e8b0c38a8678d670" - integrity sha512-uhAF/4+zDb+y0hWqlBirEPEAR4ciCZDp4fRWGFNV62bG+ArdQPpXk7jS0MEVj3CfcG5V7hx7Dpq5oD+1j6GD8Q== +electron-store@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-7.0.1.tgz#4179fc4d2408b607c7d2ddc0ac966590f65cd92d" + integrity sha512-/LkvbwFAMLKTj6f11gZry9KDtvM7z3/idg7FheeSz0/WATa9Vfj53LpqAugF1Q5TyhV4ZggsZMISdTrC/A8qqQ== dependencies: - conf "^6.2.0" - type-fest "^0.7.1" + conf "^9.0.0" + type-fest "^0.20.2" electron-to-chromium@^1.3.322: version "1.3.322" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" integrity sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA== -electron@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/electron/-/electron-7.1.6.tgz#bf10ce06765ca5049dd94870b90cc1186ad93aee" - integrity sha512-0QSxQYYzSrBRbctKgAWS79k/I+vm95I7bz/zTuF0Qv4PvTtQf5hK21q6wtyKVPPJFFXnmSyvfQ2ce6iktfgK8g== +electron@^11.2.2: + version "11.2.2" + resolved "https://registry.yarnpkg.com/electron/-/electron-11.2.2.tgz#c2e53eb56bd21ae1dc01bc781f2f6bcbe0c18033" + integrity sha512-+OitkBrnCFwOF5LXAeNnfIJDKhdBm77jboc16WCIpDsCyT+JpGsKK4y6o30nRZq3zC+wZggUm5w6Ujw5n76CGg== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" @@ -3991,6 +4002,11 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + fast-glob@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" @@ -5574,7 +5590,12 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-typed@^7.0.1: +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema-typed@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9" integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A== @@ -5642,6 +5663,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jszip@^3.1.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6" + integrity sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + junk@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" @@ -5746,6 +5777,13 @@ lie@3.1.1: dependencies: immediate "~3.0.5" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -6192,6 +6230,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -6207,6 +6252,13 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + mamacro@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" @@ -6413,6 +6465,11 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -6974,6 +7031,13 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + opencollective-postinstall@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" @@ -7151,6 +7215,11 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + pako@~1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" @@ -7424,7 +7493,7 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pkg-up@^3.0.1: +pkg-up@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== @@ -8014,6 +8083,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -8133,7 +8207,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@2, rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -8337,7 +8411,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -8357,6 +8431,13 @@ semver@^7.1.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== +semver@^7.2.1, semver@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -8429,6 +8510,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -9271,16 +9357,16 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" - integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== - type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -9433,6 +9519,15 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unzip-crx-3@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/unzip-crx-3/-/unzip-crx-3-0.2.0.tgz#d5324147b104a8aed9ae8639c95521f6f7cda292" + integrity sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ== + dependencies: + jszip "^3.1.0" + mkdirp "^0.5.1" + yaku "^0.16.6" + upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -10024,6 +10119,11 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +yaku@^0.16.6: + version "0.16.7" + resolved "https://registry.yarnpkg.com/yaku/-/yaku-0.16.7.tgz#1d195c78aa9b5bf8479c895b9504fd4f0847984e" + integrity sha1-HRlceKqbW/hHnIlblQT9TwhHmE4= + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" From 63490c0f01538bb5c67478c726f075145dbf179c Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 5 Feb 2021 10:12:07 +0300 Subject: [PATCH 47/62] fix(datastore): 'nestedToFlat' helper --- src/main/util/helpers.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/util/helpers.js b/src/main/util/helpers.js index 956f768..bb9b53a 100644 --- a/src/main/util/helpers.js +++ b/src/main/util/helpers.js @@ -27,6 +27,13 @@ export function nestedToFlat (items, link = 'id') { } flat(i.children) + } else { + if (!flatList.find(l => l[link] === i[link])) { + flatList.push({ + ...i, + parentId: null + }) + } } }) } From 34ed24959fe00ee8ec6e80531a77eb3e63ecc80b Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 5 Feb 2021 10:53:49 +0300 Subject: [PATCH 48/62] fix: open devtools --- src/main/index.dev.js | 2 +- src/main/main.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/index.dev.js b/src/main/index.dev.js index de6891d..0618dcc 100644 --- a/src/main/index.dev.js +++ b/src/main/index.dev.js @@ -8,7 +8,7 @@ /* eslint-disable */ // Install `electron-debug` with `devtron` -require('electron-debug')({ showDevTools: true }) +require('electron-debug')() // Install `vue-devtools` require('electron').app.on('ready', () => { diff --git a/src/main/main.js b/src/main/main.js index 851d415..8dd33a9 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -36,7 +36,12 @@ function createMainWindow () { mainWindow.loadURL(winURL) if (isDev) { - mainWindow.webContents.openDevTools({ mode: 'bottom' }) + mainWindow.webContents.on('did-frame-finish-load', () => { + mainWindow.webContents.once('devtools-opened', () => { + mainWindow.focus() + }) + mainWindow.webContents.openDevTools({ mode: 'bottom' }) + }) } if (process.platform === 'darwin') { From 6024bc272c242a4fc5fad131378fdb1a44796d69 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Fri, 5 Feb 2021 11:06:03 +0300 Subject: [PATCH 49/62] fix(tray): set 'enableRemoteModule' --- src/main/tray.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/tray.js b/src/main/tray.js index dfeee53..ac10d0e 100644 --- a/src/main/tray.js +++ b/src/main/tray.js @@ -46,7 +46,8 @@ function createTrayWindow () { movable: false, webPreferences: { nodeIntegration: true, - devTools: false + devTools: false, + enableRemoteModule: true } }) From e4da163b6ede558df47d7c52ff8310aa44e8b592 Mon Sep 17 00:00:00 2001 From: Anton Reshetov Date: Wed, 17 Feb 2021 06:11:55 +0300 Subject: [PATCH 50/62] refactor: folders tree (#139) --- package.json | 3 +- src/main/lib/datastore/datastore.js | 3 +- src/main/lib/datastore/schema.js | 1 + src/main/main.js | 1 + src/main/util/helpers.js | 9 +- .../components/sidebar/FolderItem.vue | 280 ++++++++++++ .../components/sidebar/TheFolders.vue | 140 +++--- .../components/snippets/SnippetListItem.vue | 2 +- .../components/uikit/AppTree/AppTree.vue | 156 +++++++ .../components/uikit/AppTree/AppTreeNode.vue | 421 ++++++++++++++++++ .../components/uikit/AppTree/helpers.js | 82 ++++ src/renderer/components/uikit/index.js | 6 +- src/renderer/store/modules/folders.js | 32 +- yarn.lock | 46 +- 14 files changed, 1075 insertions(+), 107 deletions(-) create mode 100644 src/renderer/components/sidebar/FolderItem.vue create mode 100644 src/renderer/components/uikit/AppTree/AppTree.vue create mode 100644 src/renderer/components/uikit/AppTree/AppTreeNode.vue create mode 100644 src/renderer/components/uikit/AppTree/helpers.js diff --git a/package.json b/package.json index 7146cf3..793243a 100644 --- a/package.json +++ b/package.json @@ -96,15 +96,14 @@ "universal-analytics": "^0.4.20", "uuid": "^8.3.0", "vue": "^2.5.16", - "vue-draggable-nested-tree": "github:massCodeIO/vue-draggable-nested-tree", "vue-electron": "^1.0.6", "vue-router": "^3.1.3", "vuex": "^3.0.1" }, "devDependencies": { "@babel/core": "^7.7.7", - "@babel/plugin-transform-runtime": "^7.7.6", "@babel/plugin-proposal-optional-chaining": "^7.10.1", + "@babel/plugin-transform-runtime": "^7.7.6", "@babel/polyfill": "^7.7.0", "@babel/preset-env": "^7.7.7", "@babel/register": "^7.7.7", diff --git a/src/main/lib/datastore/datastore.js b/src/main/lib/datastore/datastore.js index b4478d0..232ec0a 100644 --- a/src/main/lib/datastore/datastore.js +++ b/src/main/lib/datastore/datastore.js @@ -95,7 +95,8 @@ class Datastore { const defaultFolder = { name: 'Default', parentId: null, - defaultLanguage: 'text' + defaultLanguage: 'text', + index: -1 } const folders = [...systemFolders, defaultFolder] diff --git a/src/main/lib/datastore/schema.js b/src/main/lib/datastore/schema.js index 0d8ab78..8977ea0 100644 --- a/src/main/lib/datastore/schema.js +++ b/src/main/lib/datastore/schema.js @@ -6,6 +6,7 @@ export const FOLDERS_SCHEMA = { parentId: Joi.string() .allow(null) .required(), + index: Joi.number().allow(null), open: Joi.boolean().default(false), isSystem: Joi.boolean().default(false), alias: Joi.string() diff --git a/src/main/main.js b/src/main/main.js index 8dd33a9..40d6414 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -36,6 +36,7 @@ function createMainWindow () { mainWindow.loadURL(winURL) if (isDev) { + // @see https://github.com/SimulatedGREG/electron-vue/issues/389 mainWindow.webContents.on('did-frame-finish-load', () => { mainWindow.webContents.once('devtools-opened', () => { mainWindow.focus() diff --git a/src/main/util/helpers.js b/src/main/util/helpers.js index bb9b53a..6ad7983 100644 --- a/src/main/util/helpers.js +++ b/src/main/util/helpers.js @@ -8,11 +8,12 @@ export function nestedToFlat (items, link = 'id') { const flatList = [] function flat (items) { - items.map(i => { + items.map((i, index) => { if (i.children && i.children.length) { - const children = i.children.map(item => { + const children = i.children.map((item, idx) => { return { ...item, + index: idx, parentId: i[link] } }) @@ -22,6 +23,7 @@ export function nestedToFlat (items, link = 'id') { if (!flatList.find(l => l[link] === i[link])) { flatList.push({ ...i, + index, parentId: null }) } @@ -31,6 +33,7 @@ export function nestedToFlat (items, link = 'id') { if (!flatList.find(l => l[link] === i[link])) { flatList.push({ ...i, + index, parentId: null }) } @@ -59,7 +62,7 @@ export function flatToNested ( ) { return items .filter(item => item[link] === id) - .map(item => ({ + .map((item, index) => ({ ...item, children: flatToNested(items, item[idLink]) })) diff --git a/src/renderer/components/sidebar/FolderItem.vue b/src/renderer/components/sidebar/FolderItem.vue new file mode 100644 index 0000000..3dc0d91 --- /dev/null +++ b/src/renderer/components/sidebar/FolderItem.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/src/renderer/components/sidebar/TheFolders.vue b/src/renderer/components/sidebar/TheFolders.vue index dbed07c..9b0db02 100644 --- a/src/renderer/components/sidebar/TheFolders.vue +++ b/src/renderer/components/sidebar/TheFolders.vue @@ -1,34 +1,24 @@