mirror of
https://github.com/zadam/trilium.git
synced 2024-12-29 03:12:28 +08:00
deleting attributes, closes #34
This commit is contained in:
parent
7c74c77a2c
commit
e011b9ae63
8 changed files with 60 additions and 25 deletions
1
db/migrations/0073__add_isDeleted_to_attributes.sql
Normal file
1
db/migrations/0073__add_isDeleted_to_attributes.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE attributes ADD COLUMN isDeleted INT NOT NULL DEFAULT 0;
|
|
@ -86,7 +86,8 @@ CREATE TABLE IF NOT EXISTS "attributes"
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
value TEXT,
|
value TEXT,
|
||||||
dateCreated TEXT NOT NULL,
|
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` (
|
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
|
||||||
`entityName`,
|
`entityName`,
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Note extends Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAttributes() {
|
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) {
|
async getAttribute(name) {
|
||||||
|
|
|
@ -26,6 +26,19 @@ const attributesDialog = (function() {
|
||||||
setTimeout(() => $(".attribute-name:last").focus(), 100);
|
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() {
|
function isValid() {
|
||||||
for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
|
for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
|
||||||
if (self.isEmptyName(i)) {
|
if (self.isEmptyName(i)) {
|
||||||
|
@ -65,26 +78,25 @@ const attributesDialog = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
function addLastEmptyRow() {
|
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]();
|
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
|
||||||
|
|
||||||
if (!last || last.name.trim() !== "" || last.value !== "") {
|
if (!last || last.name.trim() !== "" || last.value !== "") {
|
||||||
self.attributes.push(ko.observable({
|
self.attributes.push(ko.observable({
|
||||||
attributeId: '',
|
attributeId: '',
|
||||||
name: '',
|
name: '',
|
||||||
value: ''
|
value: '',
|
||||||
|
isDeleted: 0
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.attributeChanged = function (row) {
|
this.attributeChanged = function (data, event) {
|
||||||
addLastEmptyRow();
|
addLastEmptyRow();
|
||||||
|
|
||||||
for (const attr of self.attributes()) {
|
const attr = self.getTargetAttribute(event);
|
||||||
if (row.attributeId === attr().attributeId) {
|
|
||||||
attr.valueHasMutated();
|
attr.valueHasMutated();
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isNotUnique = function(index) {
|
this.isNotUnique = function(index) {
|
||||||
|
@ -109,6 +121,13 @@ const attributesDialog = (function() {
|
||||||
const cur = self.attributes()[index]();
|
const cur = self.attributes()[index]();
|
||||||
|
|
||||||
return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== "");
|
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ const attributes = require('../../services/attributes');
|
||||||
router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
|
router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const noteId = req.params.noteId;
|
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) => {
|
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 () => {
|
await sql.doInTransaction(async () => {
|
||||||
for (const attr of attributes) {
|
for (const attr of attributes) {
|
||||||
if (attr.attributeId) {
|
if (attr.attributeId) {
|
||||||
await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ? WHERE attributeId = ?",
|
await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ? WHERE attributeId = ?",
|
||||||
[attr.name, attr.value, now, attr.attributeId]);
|
[attr.name, attr.value, now, attr.isDeleted, attr.attributeId]);
|
||||||
}
|
}
|
||||||
else {
|
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();
|
attr.attributeId = utils.newAttributeId();
|
||||||
|
|
||||||
await sql.insert("attributes", {
|
await sql.insert("attributes", {
|
||||||
|
@ -35,7 +40,8 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res,
|
||||||
name: attr.name,
|
name: attr.name,
|
||||||
value: attr.value,
|
value: attr.value,
|
||||||
dateCreated: now,
|
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) => {
|
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) {
|
for (const attr of attributes.BUILTIN_ATTRIBUTES) {
|
||||||
if (!names.includes(attr)) {
|
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) => {
|
router.get('/attributes/values/:attributeName', auth.checkApiAuth, wrap(async (req, res, next) => {
|
||||||
const attributeName = req.params.attributeName;
|
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);
|
res.send(values);
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
|
|
||||||
const APP_DB_VERSION = 72;
|
const APP_DB_VERSION = 73;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
app_version: packageJson.version,
|
app_version: packageJson.version,
|
||||||
|
|
|
@ -13,7 +13,10 @@ async function getNoteAttributeMap(noteId) {
|
||||||
|
|
||||||
async function getNoteIdWithAttribute(name, value) {
|
async function getNoteIdWithAttribute(name, value) {
|
||||||
return await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)
|
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) {
|
async function getNotesWithAttribute(dataKey, name, value) {
|
||||||
|
@ -23,11 +26,11 @@ async function getNotesWithAttribute(dataKey, name, value) {
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
|
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 {
|
else {
|
||||||
notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
|
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;
|
return notes;
|
||||||
|
@ -41,7 +44,7 @@ async function getNoteWithAttribute(dataKey, name, value) {
|
||||||
|
|
||||||
async function getNoteIdsWithAttribute(name) {
|
async function getNoteIdsWithAttribute(name) {
|
||||||
return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN attributes USING(noteId)
|
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) {
|
async function createAttribute(noteId, name, value = null, sourceId = null) {
|
||||||
|
@ -54,7 +57,8 @@ async function createAttribute(noteId, name, value = null, sourceId = null) {
|
||||||
name: name,
|
name: name,
|
||||||
value: value,
|
value: value,
|
||||||
dateModified: now,
|
dateModified: now,
|
||||||
dateCreated: now
|
dateCreated: now,
|
||||||
|
isDeleted: false
|
||||||
});
|
});
|
||||||
|
|
||||||
await sync_table.addAttributeSync(attributeId, sourceId);
|
await sync_table.addAttributeSync(attributeId, sourceId);
|
||||||
|
|
|
@ -376,7 +376,7 @@
|
||||||
<div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;">
|
<div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;">
|
||||||
<form data-bind="submit: save">
|
<form data-bind="submit: save">
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save <kbd>enter</kbd></button>
|
<button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save changes <kbd>enter</kbd></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="height: 97%; overflow: auto">
|
<div style="height: 97%; overflow: auto">
|
||||||
|
@ -386,10 +386,11 @@
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody data-bind="foreach: attributes">
|
<tbody data-bind="foreach: attributes">
|
||||||
<tr>
|
<tr data-bind="if: isDeleted == 0">
|
||||||
<td data-bind="text: attributeId"></td>
|
<td data-bind="text: attributeId"></td>
|
||||||
<td>
|
<td>
|
||||||
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
|
<!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event -->
|
||||||
|
@ -400,6 +401,9 @@
|
||||||
<td>
|
<td>
|
||||||
<input type="text" class="attribute-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/>
|
<input type="text" class="attribute-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/>
|
||||||
</td>
|
</td>
|
||||||
|
<td title="Delete" style="padding: 13px;">
|
||||||
|
<span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
Loading…
Reference in a new issue