From 6f3e70c5d6755735aa2366835a0293619c34c022 Mon Sep 17 00:00:00 2001 From: Thomas E Tuoti Date: Sat, 21 Dec 2024 11:06:44 -0700 Subject: [PATCH] Add filtering via tags, text, and title in get-bag template --- .../modules/routes/handlers/get-bag.js | 36 +++- .../modules/store/sql-tiddler-database.js | 43 +++++ .../modules/store/sql-tiddler-store.js | 80 ++++---- .../multiwikiserver/templates/get-bag.tid | 181 +++++++++++++++++- 4 files changed, 287 insertions(+), 53 deletions(-) diff --git a/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/get-bag.js b/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/get-bag.js index 7d262b83f..b6a52bd1d 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/get-bag.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/get-bag.js @@ -22,21 +22,40 @@ exports.useACL = true; exports.entityName = "bag" exports.handler = function (request, response, state) { - // Redirect if there is no trailing slash. We do this so that the relative URL specified in the upload form works correctly + // Redirect if there is no trailing slash if (state.params[1] !== "/") { state.redirect(301, state.urlInfo.path + "/"); return; } - // Get the parameters + + // Get the parameters and bag list var bag_name = $tw.utils.decodeURIComponentSafe(state.params[0]), - bagTiddlers = bag_name && $tw.mws.store.getBagTiddlers(bag_name); + filterText = state.queryParameters ? state.queryParameters.filter || "" : "", + filterField = state.queryParameters ? state.queryParameters.field || "title" : "title", + bagTiddlers = bag_name && (filterText ? + $tw.mws.store.sqlTiddlerDatabase.getFilteredBagTiddlers(bag_name, filterText, filterField) : + $tw.mws.store.getBagTiddlers(bag_name)), + bagList = $tw.mws.store.listBags(); + + if (bag_name && bagTiddlers) { - // If application/json is requested then this is an API request, and gets the response in JSON + // Handle JSON API request if (request.headers.accept && request.headers.accept.indexOf("application/json") !== -1) { state.sendResponse(200, { "Content-Type": "application/json" }, JSON.stringify(bagTiddlers), "utf8"); } else { if (!response.headersSent) { - // This is not a JSON API request, we should return the raw tiddler content + // Filter bags by user's read access from ACL + var allowedBags = bagList.filter(bag => + bag.bag_name.startsWith("$:/") || + state.server.sqlTiddlerDatabase.hasBagPermission(state.authenticatedUser?.user_id, bag.bag_name, 'READ') || + state.allowAnon && state.allowAnonReads + ); + + // Filter out system tiddlers unless explicitly shown + if (!state.queryParameters.show_system || state.queryParameters.show_system === "off") { + allowedBags = allowedBags.filter(bag => !bag.bag_name.startsWith("$:/")); + } + response.writeHead(200, "OK", { "Content-Type": "text/html" }); @@ -45,7 +64,10 @@ exports.handler = function (request, response, state) { "page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-bag", "bag-name": bag_name, "bag-titles": JSON.stringify(bagTiddlers.map(bagTiddler => bagTiddler.title)), - "bag-tiddlers": JSON.stringify(bagTiddlers) + "bag-tiddlers": JSON.stringify(bagTiddlers), + "bag-list": JSON.stringify(allowedBags), + "current-filter": filterText, + "current-field": filterField } }); response.write(html); @@ -60,4 +82,4 @@ exports.handler = function (request, response, state) { } }; -}()); +}()); \ No newline at end of file diff --git a/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js b/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js index df68b08df..e2be18ce5 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js @@ -41,6 +41,49 @@ SqlTiddlerDatabase.prototype.close = function() { this.engine.close(); }; +SqlTiddlerDatabase.prototype.getFilteredBagTiddlers = function(bag_name, filterText, filterField) { + let query = ` + SELECT DISTINCT t.title, t.tiddler_id + FROM tiddlers t + LEFT JOIN fields f ON t.tiddler_id = f.tiddler_id + WHERE t.bag_id IN ( + SELECT bag_id + FROM bags + WHERE bag_name = $bag_name + ) + AND t.is_deleted = FALSE + `; + + const params = { + $bag_name: bag_name, + $filterText: '%' + filterText + '%' + }; + + // Add field-specific filtering + if (filterField === "tag") { + query += ` AND EXISTS ( + SELECT 1 FROM fields + WHERE tiddler_id = t.tiddler_id + AND field_name = 'tags' + AND field_value LIKE $filterText + )`; + } else if (filterField === "text") { + query += ` AND EXISTS ( + SELECT 1 FROM fields + WHERE tiddler_id = t.tiddler_id + AND field_name = 'text' + AND field_value LIKE $filterText + )`; + } else { + // Default to title search + query += ` AND t.title LIKE $filterText`; + } + + query += ` ORDER BY t.title ASC`; + + return this.engine.runStatementGetAll(query, params); +}; + SqlTiddlerDatabase.prototype.transaction = function(fn) { return this.engine.transaction(fn); diff --git a/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-store.js b/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-store.js index bb32eba18..405e827a8 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-store.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-store.js @@ -54,6 +54,10 @@ SqlTiddlerStore.prototype.removeEventListener = function(type,listener) { } }; +SqlTiddlerStore.prototype.getFilteredBagTiddlers = function (bag_name, searchTerm) { + return this.sqlTiddlerDatabase.getFilteredBagTiddlers(bag_name, searchTerm); +}; + SqlTiddlerStore.prototype.dispatchEvent = function(type /*, args */) { const self = this; if(!this.eventOutstanding[type]) { @@ -144,50 +148,50 @@ SqlTiddlerStore.prototype.processOutgoingTiddler = function(tiddlerFields,tiddle /* */ SqlTiddlerStore.prototype.processIncomingTiddler = function(tiddlerFields, existing_attachment_blob, existing_canonical_uri) { - let attachmentSizeLimit = $tw.utils.parseNumber(this.adminWiki.getTiddlerText("$:/config/MultiWikiServer/AttachmentSizeLimit")); + let attachmentSizeLimit = $tw.utils.parseNumber(this.adminWiki.getTiddlerText("$:/config/MultiWikiServer/AttachmentSizeLimit")); if(attachmentSizeLimit < 100 * 1024) { attachmentSizeLimit = 100 * 1024; } - const attachmentsEnabled = this.adminWiki.getTiddlerText("$:/config/MultiWikiServer/EnableAttachments", "yes") === "yes"; - const contentTypeInfo = $tw.config.contentTypeInfo[tiddlerFields.type || "text/vnd.tiddlywiki"]; - const isBinary = !!contentTypeInfo && contentTypeInfo.encoding === "base64"; + const attachmentsEnabled = this.adminWiki.getTiddlerText("$:/config/MultiWikiServer/EnableAttachments", "yes") === "yes"; + const contentTypeInfo = $tw.config.contentTypeInfo[tiddlerFields.type || "text/vnd.tiddlywiki"]; + const isBinary = !!contentTypeInfo && contentTypeInfo.encoding === "base64"; - let shouldProcessAttachment = tiddlerFields.text && tiddlerFields.text.length > attachmentSizeLimit; + let shouldProcessAttachment = tiddlerFields.text && tiddlerFields.text.length > attachmentSizeLimit; - if(existing_attachment_blob) { - const fileSize = this.attachmentStore.getAttachmentFileSize(existing_attachment_blob); - if(fileSize <= attachmentSizeLimit) { - const existingAttachmentMeta = this.attachmentStore.getAttachmentMetadata(existing_attachment_blob); - const hasCanonicalField = !!tiddlerFields._canonical_uri; - const skipAttachment = hasCanonicalField && (tiddlerFields._canonical_uri === (existingAttachmentMeta ? existingAttachmentMeta._canonical_uri : existing_canonical_uri)); - shouldProcessAttachment = !skipAttachment; - } else { - shouldProcessAttachment = false; - } - } + if(existing_attachment_blob) { + const fileSize = this.attachmentStore.getAttachmentFileSize(existing_attachment_blob); + if(fileSize <= attachmentSizeLimit) { + const existingAttachmentMeta = this.attachmentStore.getAttachmentMetadata(existing_attachment_blob); + const hasCanonicalField = !!tiddlerFields._canonical_uri; + const skipAttachment = hasCanonicalField && (tiddlerFields._canonical_uri === (existingAttachmentMeta ? existingAttachmentMeta._canonical_uri : existing_canonical_uri)); + shouldProcessAttachment = !skipAttachment; + } else { + shouldProcessAttachment = false; + } + } - if(attachmentsEnabled && isBinary && shouldProcessAttachment) { - const attachment_blob = existing_attachment_blob || this.attachmentStore.saveAttachment({ - text: tiddlerFields.text, - type: tiddlerFields.type, - reference: tiddlerFields.title, - _canonical_uri: tiddlerFields._canonical_uri - }); - - if(tiddlerFields && tiddlerFields._canonical_uri) { - delete tiddlerFields._canonical_uri; - } - - return { - tiddlerFields: Object.assign({}, tiddlerFields, { text: undefined }), - attachment_blob: attachment_blob - }; - } else { - return { - tiddlerFields: tiddlerFields, - attachment_blob: existing_attachment_blob - }; - } + if(attachmentsEnabled && isBinary && shouldProcessAttachment) { + const attachment_blob = existing_attachment_blob || this.attachmentStore.saveAttachment({ + text: tiddlerFields.text, + type: tiddlerFields.type, + reference: tiddlerFields.title, + _canonical_uri: tiddlerFields._canonical_uri + }); + + if(tiddlerFields && tiddlerFields._canonical_uri) { + delete tiddlerFields._canonical_uri; + } + + return { + tiddlerFields: Object.assign({}, tiddlerFields, { text: undefined }), + attachment_blob: attachment_blob + }; + } else { + return { + tiddlerFields: tiddlerFields, + attachment_blob: existing_attachment_blob + }; + } }; SqlTiddlerStore.prototype.saveTiddlersFromPath = function(tiddler_files_path,bag_name) { diff --git a/plugins/tiddlywiki/multiwikiserver/templates/get-bag.tid b/plugins/tiddlywiki/multiwikiserver/templates/get-bag.tid index a48650c52..cb5066d4e 100644 --- a/plugins/tiddlywiki/multiwikiserver/templates/get-bag.tid +++ b/plugins/tiddlywiki/multiwikiserver/templates/get-bag.tid @@ -6,6 +6,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-bag width="32px" /> Bag <$text text={{{ []}}}/> +
+ + +
+ + + + > placeholder="Enter search term..."/> + + addprefix[/bags/]addsuffix[/]] }}} class="button">Show All + +
+ + + + +
+
+ Move Selected Tiddlers +
+
+ + + + + >/> + + +
+ <$list filter="[jsonget[]sort[]]"> +
+ <$set name="tidID" value={{{ [encodeuricomponent[]] }}}> + + +
+ +
+ + + + + +