mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-01-08 16:30:26 +00:00
Add filtering via tags, text, and title in get-bag template
This commit is contained in:
parent
bc6e58d0af
commit
6f3e70c5d6
@ -22,21 +22,40 @@ exports.useACL = true;
|
|||||||
exports.entityName = "bag"
|
exports.entityName = "bag"
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
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] !== "/") {
|
if (state.params[1] !== "/") {
|
||||||
state.redirect(301, state.urlInfo.path + "/");
|
state.redirect(301, state.urlInfo.path + "/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Get the parameters
|
|
||||||
|
// Get the parameters and bag list
|
||||||
var bag_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
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 (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) {
|
if (request.headers.accept && request.headers.accept.indexOf("application/json") !== -1) {
|
||||||
state.sendResponse(200, { "Content-Type": "application/json" }, JSON.stringify(bagTiddlers), "utf8");
|
state.sendResponse(200, { "Content-Type": "application/json" }, JSON.stringify(bagTiddlers), "utf8");
|
||||||
} else {
|
} else {
|
||||||
if (!response.headersSent) {
|
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", {
|
response.writeHead(200, "OK", {
|
||||||
"Content-Type": "text/html"
|
"Content-Type": "text/html"
|
||||||
});
|
});
|
||||||
@ -45,7 +64,10 @@ exports.handler = function (request, response, state) {
|
|||||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-bag",
|
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-bag",
|
||||||
"bag-name": bag_name,
|
"bag-name": bag_name,
|
||||||
"bag-titles": JSON.stringify(bagTiddlers.map(bagTiddler => bagTiddler.title)),
|
"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);
|
response.write(html);
|
||||||
|
@ -41,6 +41,49 @@ SqlTiddlerDatabase.prototype.close = function() {
|
|||||||
this.engine.close();
|
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) {
|
SqlTiddlerDatabase.prototype.transaction = function(fn) {
|
||||||
return this.engine.transaction(fn);
|
return this.engine.transaction(fn);
|
||||||
|
@ -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 */) {
|
SqlTiddlerStore.prototype.dispatchEvent = function(type /*, args */) {
|
||||||
const self = this;
|
const self = this;
|
||||||
if(!this.eventOutstanding[type]) {
|
if(!this.eventOutstanding[type]) {
|
||||||
|
@ -6,6 +6,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-bag
|
|||||||
width="32px"
|
width="32px"
|
||||||
/> Bag <$text text={{{ [<bag-name>]}}}/>
|
/> Bag <$text text={{{ [<bag-name>]}}}/>
|
||||||
|
|
||||||
|
<!-- Import form for Tiddlers -->
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
action="tiddlers/"
|
action="tiddlers/"
|
||||||
@ -35,11 +36,175 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-bag
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<$list filter="[<bag-titles>jsonget[]sort[]]">
|
|
||||||
<li>
|
<!-- Filter input for list-->
|
||||||
<a href=`/bags/${ [<bag-name>encodeuricomponent[]] }$/tiddlers/${ [<currentTiddler>encodeuricomponent[]] }$` rel="noopener noreferrer" target="_blank">
|
<div class="filter-section">
|
||||||
<$text text=<<currentTiddler>>/>
|
<form method="GET">
|
||||||
</a>
|
<label>Filter tiddlers:</label>
|
||||||
</li>
|
<select name="field">
|
||||||
</$list>
|
<option value="title" selected={{{ [<current-field>match[title]then[selected]] }}}>Title</option>
|
||||||
</ul>
|
<option value="tag" selected={{{ [<current-field>match[tag]then[selected]] }}}>Tags</option>
|
||||||
|
<option value="text" selected={{{ [<current-field>match[text]then[selected]] }}}>Text</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" name="filter" value=<<current-filter>> placeholder="Enter search term..."/>
|
||||||
|
<input type="submit" value="Filter"/>
|
||||||
|
<a href={{{ [<bag-name>addprefix[/bags/]addsuffix[/]] }}} class="button">Show All</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Tiddler List with Checkboxes -->
|
||||||
|
<form action="tiddlers/" method="post" class="mws-form" enctype="multipart/form-data">
|
||||||
|
<div class="mws-form-heading">
|
||||||
|
Move Selected Tiddlers
|
||||||
|
</div>
|
||||||
|
<div class="mws-form-fields">
|
||||||
|
<!-- Hidden operation field -->
|
||||||
|
<input type="hidden" name="operation" value="move"/>
|
||||||
|
|
||||||
|
<!-- Hidden source bag field -->
|
||||||
|
<input type="hidden" name="source-bag" value=<<bag-name>>/>
|
||||||
|
|
||||||
|
<!-- Tiddler List with Checkboxes -->
|
||||||
|
<div class="tiddler-list">
|
||||||
|
<$list filter="[<bag-titles>jsonget[]sort[]]">
|
||||||
|
<div class="tiddler-entry">
|
||||||
|
<$set name="tidID" value={{{ [<currentTiddler>encodeuricomponent[]] }}}>
|
||||||
|
<label for="chk_<<tidID>>">
|
||||||
|
<$link to=<<currentTiddler>>><<currentTiddler>></$link>
|
||||||
|
</label>
|
||||||
|
</$set>
|
||||||
|
</div>
|
||||||
|
</$list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bag-selector {
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bag-selector label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bag-selector select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #2d91ec;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #1a7fd1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
background-color: #166bb0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deselect-all {
|
||||||
|
margin-left: 10px;
|
||||||
|
background-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section .button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section .button:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section select {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-selected:disabled {
|
||||||
|
background-color: #cccccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mws-form-field input[readonly] {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all {
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-controls {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-controls button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selection-count {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2d91ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiddler-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiddler-entry:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiddler-entry input[type="checkbox"] {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user