From e011b9ae63b2bf267457bcf59676a5ad0139f65a Mon Sep 17 00:00:00 2001 From: azivner Date: Tue, 6 Feb 2018 23:09:19 -0500 Subject: [PATCH] deleting attributes, closes #34 --- .../0073__add_isDeleted_to_attributes.sql | 1 + db/schema.sql | 3 +- src/entities/note.js | 2 +- src/public/javascripts/dialogs/attributes.js | 35 ++++++++++++++----- src/routes/api/attributes.js | 20 +++++++---- src/services/app_info.js | 2 +- src/services/attributes.js | 14 +++++--- src/views/index.ejs | 8 +++-- 8 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 db/migrations/0073__add_isDeleted_to_attributes.sql diff --git a/db/migrations/0073__add_isDeleted_to_attributes.sql b/db/migrations/0073__add_isDeleted_to_attributes.sql new file mode 100644 index 000000000..17951cb1f --- /dev/null +++ b/db/migrations/0073__add_isDeleted_to_attributes.sql @@ -0,0 +1 @@ +ALTER TABLE attributes ADD COLUMN isDeleted INT NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index 1d9f7a3fc..729a2ca1c 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -86,7 +86,8 @@ CREATE TABLE IF NOT EXISTS "attributes" name TEXT NOT NULL, value TEXT, dateCreated TEXT NOT NULL, - dateModified TEXT NOT NULL + dateModified TEXT NOT NULL, + isDeleted INT NOT NULL ); CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` ( `entityName`, diff --git a/src/entities/note.js b/src/entities/note.js index f9c504a56..ac9f99a41 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -24,7 +24,7 @@ class Note extends Entity { } async getAttributes() { - return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ?", [this.noteId]); + return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ? AND isDeleted = 0", [this.noteId]); } async getAttribute(name) { diff --git a/src/public/javascripts/dialogs/attributes.js b/src/public/javascripts/dialogs/attributes.js index 9043bb60c..585b4c8cf 100644 --- a/src/public/javascripts/dialogs/attributes.js +++ b/src/public/javascripts/dialogs/attributes.js @@ -26,6 +26,19 @@ const attributesDialog = (function() { setTimeout(() => $(".attribute-name:last").focus(), 100); }; + this.deleteAttribute = function(data, event) { + const attr = self.getTargetAttribute(event); + const attrData = attr(); + + if (attrData) { + attrData.isDeleted = 1; + + attr(attrData); + + addLastEmptyRow(); + } + }; + function isValid() { for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) { if (self.isEmptyName(i)) { @@ -65,26 +78,25 @@ const attributesDialog = (function() { }; function addLastEmptyRow() { - const attrs = self.attributes(); + const attrs = self.attributes().filter(attr => attr().isDeleted === 0); const last = attrs.length === 0 ? null : attrs[attrs.length - 1](); if (!last || last.name.trim() !== "" || last.value !== "") { self.attributes.push(ko.observable({ attributeId: '', name: '', - value: '' + value: '', + isDeleted: 0 })); } } - this.attributeChanged = function (row) { + this.attributeChanged = function (data, event) { addLastEmptyRow(); - for (const attr of self.attributes()) { - if (row.attributeId === attr().attributeId) { - attr.valueHasMutated(); - } - } + const attr = self.getTargetAttribute(event); + + attr.valueHasMutated(); }; this.isNotUnique = function(index) { @@ -109,6 +121,13 @@ const attributesDialog = (function() { const cur = self.attributes()[index](); return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); + }; + + this.getTargetAttribute = function(event) { + const context = ko.contextFor(event.target); + const index = context.$index(); + + return self.attributes()[index]; } } diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index f3215c701..950d21e1b 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -12,7 +12,7 @@ const attributes = require('../../services/attributes'); router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { const noteId = req.params.noteId; - res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId])); + res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); })); router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { @@ -23,10 +23,15 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, await sql.doInTransaction(async () => { for (const attr of attributes) { if (attr.attributeId) { - await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ? WHERE attributeId = ?", - [attr.name, attr.value, now, attr.attributeId]); + await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ? WHERE attributeId = ?", + [attr.name, attr.value, now, attr.isDeleted, attr.attributeId]); } else { + // if it was "created" and then immediatelly deleted, we just don't create it at all + if (attr.isDeleted) { + continue; + } + attr.attributeId = utils.newAttributeId(); await sql.insert("attributes", { @@ -35,7 +40,8 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, name: attr.name, value: attr.value, dateCreated: now, - dateModified: now + dateModified: now, + isDeleted: false }); } @@ -43,11 +49,11 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, } }); - res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId])); + res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); })); router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { - const names = await sql.getColumn("SELECT DISTINCT name FROM attributes"); + const names = await sql.getColumn("SELECT DISTINCT name FROM attributes WHERE isDeleted = 0"); for (const attr of attributes.BUILTIN_ATTRIBUTES) { if (!names.includes(attr)) { @@ -63,7 +69,7 @@ router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) = router.get('/attributes/values/:attributeName', auth.checkApiAuth, wrap(async (req, res, next) => { const attributeName = req.params.attributeName; - const values = await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE name = ? AND value != '' ORDER BY value", [attributeName]); + const values = await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [attributeName]); res.send(values); })); diff --git a/src/services/app_info.js b/src/services/app_info.js index 509dcace9..1b862a4bf 100644 --- a/src/services/app_info.js +++ b/src/services/app_info.js @@ -3,7 +3,7 @@ const build = require('./build'); const packageJson = require('../../package'); -const APP_DB_VERSION = 72; +const APP_DB_VERSION = 73; module.exports = { app_version: packageJson.version, diff --git a/src/services/attributes.js b/src/services/attributes.js index 3b680b831..1581eabe0 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -13,7 +13,10 @@ async function getNoteAttributeMap(noteId) { async function getNoteIdWithAttribute(name, value) { return await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); + WHERE notes.isDeleted = 0 + AND attributes.isDeleted = 0 + AND attributes.name = ? + AND attributes.value = ?`, [name, value]); } async function getNotesWithAttribute(dataKey, name, value) { @@ -23,11 +26,11 @@ async function getNotesWithAttribute(dataKey, name, value) { if (value !== undefined) { notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); + WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); } else { notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.name = ?`, [name]); + WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ?`, [name]); } return notes; @@ -41,7 +44,7 @@ async function getNoteWithAttribute(dataKey, name, value) { async function getNoteIdsWithAttribute(name) { return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.name = ?`, [name]); + WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.isDeleted = 0`, [name]); } async function createAttribute(noteId, name, value = null, sourceId = null) { @@ -54,7 +57,8 @@ async function createAttribute(noteId, name, value = null, sourceId = null) { name: name, value: value, dateModified: now, - dateCreated: now + dateCreated: now, + isDeleted: false }); await sync_table.addAttributeSync(attributeId, sourceId); diff --git a/src/views/index.ejs b/src/views/index.ejs index 88f2fde8b..0341904e5 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -376,7 +376,7 @@