make task suggestions relative to board

This commit is contained in:
marlon-erler
2026-09-22 15:12:45 +02:00
parent 241f9e137d
commit 959bee2c06
11 changed files with 118 additions and 43 deletions
+103 -27
View File
@@ -1,10 +1,9 @@
(() => { (() => {
// node_modules/bloatless-react/index.ts // ../bloatless-react/index.ts
var State = class { var State = class {
_value;
_bindings = /* @__PURE__ */ new Set();
// init // init
constructor(initialValue) { constructor(initialValue) {
this._bindings = /* @__PURE__ */ new Set();
this._value = initialValue; this._value = initialValue;
} }
// value // value
@@ -32,15 +31,17 @@
} }
}; };
var ListState = class extends State { var ListState = class extends State {
additionHandlers = /* @__PURE__ */ new Set();
removalHandlers = /* @__PURE__ */ new Map();
// init // init
constructor(initialItems) { constructor(initialItems) {
super(new Set(initialItems)); super(new Set(initialItems));
this.additionHandlers = /* @__PURE__ */ new Set();
this.removalHandlers = /* @__PURE__ */ new Map();
this.genericRemovalHandlers = /* @__PURE__ */ new Set();
} }
// list // list
add(...items) { add(...items) {
items.forEach((item) => { items.forEach((item) => {
if (this.value.has(item)) return;
this.value.add(item); this.value.add(item);
this.additionHandlers.forEach((handler) => handler(item)); this.additionHandlers.forEach((handler) => handler(item));
}); });
@@ -49,6 +50,7 @@
remove(...items) { remove(...items) {
items.forEach((item) => { items.forEach((item) => {
this.value.delete(item); this.value.delete(item);
this.genericRemovalHandlers.forEach((handler) => handler(item));
if (!this.removalHandlers.has(item)) return; if (!this.removalHandlers.has(item)) return;
this.removalHandlers.get(item).forEach((handler) => handler(item)); this.removalHandlers.get(item).forEach((handler) => handler(item));
this.removalHandlers.delete(item); this.removalHandlers.delete(item);
@@ -68,6 +70,9 @@
this.removalHandlers.set(item, /* @__PURE__ */ new Set()); this.removalHandlers.set(item, /* @__PURE__ */ new Set());
this.removalHandlers.get(item).add(handler); this.removalHandlers.get(item).add(handler);
} }
handleRemovals(handler) {
this.genericRemovalHandlers.add(handler);
}
// stringification // stringification
toString() { toString() {
const array = [...this.value.values()]; const array = [...this.value.values()];
@@ -76,11 +81,12 @@
} }
}; };
var MapState = class extends State { var MapState = class extends State {
additionHandlers = /* @__PURE__ */ new Set();
removalHandlers = /* @__PURE__ */ new Map();
// init // init
constructor(initialItems) { constructor(initialItems) {
super(new Map(initialItems)); super(new Map(initialItems));
this.additionHandlers = /* @__PURE__ */ new Set();
this.removalHandlers = /* @__PURE__ */ new Map();
this.genericRemovalHandlers = /* @__PURE__ */ new Set();
} }
// list // list
set(key, item) { set(key, item) {
@@ -94,6 +100,7 @@
if (!item) return; if (!item) return;
this.value.delete(key); this.value.delete(key);
this.callSubscriptions(); this.callSubscriptions();
this.genericRemovalHandlers.forEach((handler) => handler(item));
if (!this.removalHandlers.has(item)) return; if (!this.removalHandlers.has(item)) return;
this.removalHandlers.get(item).forEach((handler) => handler(item)); this.removalHandlers.get(item).forEach((handler) => handler(item));
this.removalHandlers.delete(item); this.removalHandlers.delete(item);
@@ -111,6 +118,9 @@
this.removalHandlers.set(item, /* @__PURE__ */ new Set()); this.removalHandlers.set(item, /* @__PURE__ */ new Set());
this.removalHandlers.get(item).add(handler); this.removalHandlers.get(item).add(handler);
} }
handleRemovals(handler) {
this.genericRemovalHandlers.add(handler);
}
// stringification // stringification
toString() { toString() {
const array = [...this.value.entries()]; const array = [...this.value.entries()];
@@ -294,10 +304,13 @@
general: { general: {
deleteItemButtonAudioLabel: "delete item", deleteItemButtonAudioLabel: "delete item",
searchButtonAudioLabel: "search", searchButtonAudioLabel: "search",
searchButtonClearAudioLabel: "clear search query",
abortButton: "Abort", abortButton: "Abort",
applyButton: "Apply",
backButton: "Back",
cancelButton: "Cancel", cancelButton: "Cancel",
closeButton: "Close", closeButton: "Close",
backButton: "Back", deleteButton: "Delete",
continueButton: "Continue", continueButton: "Continue",
confirmButton: "Confirm", confirmButton: "Confirm",
saveButton: "Save", saveButton: "Save",
@@ -305,6 +318,7 @@
reloadAppButton: "Reload App", reloadAppButton: "Reload App",
fileVersionLabel: "Version", fileVersionLabel: "Version",
searchLabel: "Search", searchLabel: "Search",
searchSuggestionsLabel: "Recent searches",
waitingLabel: "Waiting...", waitingLabel: "Waiting...",
restoreConnection: "Restore connection", restoreConnection: "Restore connection",
noPageSelected: "No page selected" noPageSelected: "No page selected"
@@ -1260,7 +1274,7 @@
this.connectionModel = connectionModel2; this.connectionModel = connectionModel2;
this.chatListModel = chatListModel2; this.chatListModel = chatListModel2;
this.fileTransferModel = fileTransferModel2; this.fileTransferModel = fileTransferModel2;
this.BUILD = "Build 26.09.21.B"; this.BUILD = "Build 26.09.21.C";
// CONTEXT // CONTEXT
this.contextStack = /* @__PURE__ */ new Map(); this.contextStack = /* @__PURE__ */ new Map();
this.closeContext = (contextId, fromHistoryEvent = false) => { this.closeContext = (contextId, fromHistoryEvent = false) => {
@@ -1301,7 +1315,6 @@
this.draggedObject = new State(void 0); this.draggedObject = new State(void 0);
// SUGGESTIONS // SUGGESTIONS
// boards & tasks // boards & tasks
this.boardFilterStringSuggestions = new ListState();
this.taskCategorySuggestions = new ListState(); this.taskCategorySuggestions = new ListState();
this.taskStatusSuggestions = new ListState(); this.taskStatusSuggestions = new ListState();
this.translations = allTranslations[settingsModel2.language] || allTranslations.en; this.translations = allTranslations[settingsModel2.language] || allTranslations.en;
@@ -2988,6 +3001,10 @@
var SearchViewModel = class { var SearchViewModel = class {
// init // init
constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) { constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) {
this.allObjects = allObjects;
this.matchingObjects = matchingObjects;
this.getStringsOfObject = getStringsOfObject;
this.suggestions = suggestions;
// state // state
this.appliedQuery = new State(""); this.appliedQuery = new State("");
this.searchInput = new State(""); this.searchInput = new State("");
@@ -2996,6 +3013,11 @@
[this.searchInput, this.appliedQuery], [this.searchInput, this.appliedQuery],
() => this.searchInput.value == this.appliedQuery.value () => this.searchInput.value == this.appliedQuery.value
); );
this.cannotClear = createProxyState(
[this.searchInput],
() => this.searchInput.value == ""
);
this.hasNoSuggestions = createProxyState([this.suggestions], () => this.suggestions.value.size == 0);
// methods // methods
this.search = (searchTerm) => { this.search = (searchTerm) => {
this.searchInput.value = searchTerm; this.searchInput.value = searchTerm;
@@ -3010,6 +3032,12 @@
this.matchingObjects.add(object); this.matchingObjects.add(object);
} }
}; };
this.clear = () => {
this.searchInput.value = "";
};
this.deleteSuggestion = (suggestion) => {
this.suggestions.remove(suggestion);
};
// utility // utility
this.checkDoesMatchSearch = (object) => { this.checkDoesMatchSearch = (object) => {
return checkDoesObjectMatchSearch( return checkDoesObjectMatchSearch(
@@ -3018,10 +3046,6 @@
object object
); );
}; };
this.allObjects = allObjects;
this.matchingObjects = matchingObjects;
this.getStringsOfObject = getStringsOfObject;
this.suggestions = suggestions;
this.allObjects.handleAddition((newObject) => { this.allObjects.handleAddition((newObject) => {
const doesMatch = this.checkDoesMatchSearch(newObject); const doesMatch = this.checkDoesMatchSearch(newObject);
if (doesMatch == false) { if (doesMatch == false) {
@@ -3056,6 +3080,7 @@
); );
this.isPresentingSettingsModal = new State(false); this.isPresentingSettingsModal = new State(false);
this.isPresentingFilterModal = new State(false); this.isPresentingFilterModal = new State(false);
this.searchSuggestions = new ListState();
this.filteredTaskViewModels = new ListState(); this.filteredTaskViewModels = new ListState();
// paths // paths
this.getBasePath = () => { this.getBasePath = () => {
@@ -3123,14 +3148,21 @@
searchTerm searchTerm
]; ];
this.coreViewModel.storageModel.write(suggestionPath, ""); this.coreViewModel.storageModel.write(suggestionPath, "");
if (!this.coreViewModel.boardFilterStringSuggestions.value.has( if (!this.searchSuggestions.value.has(
searchTerm searchTerm
)) { ) && searchTerm != "") {
this.coreViewModel.boardFilterStringSuggestions.add(searchTerm); this.searchSuggestions.add(searchTerm);
} }
const lastSearchPath = this.getLastSearchPath(); const lastSearchPath = this.getLastSearchPath();
this.coreViewModel.storageModel.write(lastSearchPath, searchTerm); this.coreViewModel.storageModel.write(lastSearchPath, searchTerm);
}; };
this.handleSearchRemoved = (searchTerm) => {
const suggestionPath = [
...this.getPreviousSearchesPath(),
searchTerm
];
this.coreViewModel.storageModel.remove(suggestionPath);
};
// view // view
this.showTask = (taskFileContent) => { this.showTask = (taskFileContent) => {
if (taskFileContent.boardId != this.boardInfo.fileId) { if (taskFileContent.boardId != this.boardInfo.fileId) {
@@ -3209,7 +3241,7 @@
this.loadSearchSuggestions = () => { this.loadSearchSuggestions = () => {
const dirPath = this.getPreviousSearchesPath(); const dirPath = this.getPreviousSearchesPath();
const searches = this.coreViewModel.storageModel.list(dirPath); const searches = this.coreViewModel.storageModel.list(dirPath);
this.coreViewModel.boardFilterStringSuggestions.add(...searches); this.searchSuggestions.add(...searches.filter((x) => x != ""));
}; };
this.restoreSearch = () => { this.restoreSearch = () => {
const lastSearchPath = this.getLastSearchPath(); const lastSearchPath = this.getLastSearchPath();
@@ -3255,12 +3287,13 @@
this.taskViewModels, this.taskViewModels,
this.filteredTaskViewModels, this.filteredTaskViewModels,
TaskViewModel.getStringsForFilter, TaskViewModel.getStringsForFilter,
this.coreViewModel.boardFilterStringSuggestions this.searchSuggestions
); );
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => { this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
this.handleNewSearch(newQuery); this.handleNewSearch(newQuery);
}); });
this.restoreSearch(); this.restoreSearch();
this.searchSuggestions.handleRemovals(this.handleSearchRemoved);
this.isFilterActive = createProxyState( this.isFilterActive = createProxyState(
[this.searchViewModel.appliedQuery], [this.searchViewModel.appliedQuery],
() => this.searchViewModel.appliedQuery.value != "" () => this.searchViewModel.appliedQuery.value != ""
@@ -5483,7 +5516,7 @@
} }
// src/View/Modals/searchModal.tsx // src/View/Modals/searchModal.tsx
function SearchModal(coreViewModel2, searchViewModel, headline, converter, isOpen) { function SearchModal(coreViewModel2, searchViewModel, headline, isOpen) {
function close() { function close() {
isOpen.value = false; isOpen.value = false;
} }
@@ -5492,10 +5525,11 @@
if (!isOpen2) return; if (!isOpen2) return;
ViewController.setFocusWithDelay(); ViewController.setFocusWithDelay();
}); });
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen, extended: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen, extended: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
"input", "input",
{ {
id: "focused", id: "focused",
style: "max-width: unset",
placeholder: coreViewModel2.translations.general.searchLabel, placeholder: coreViewModel2.translations.general.searchLabel,
"bind:value": searchViewModel.searchInput, "bind:value": searchViewModel.searchInput,
"on:enter": searchViewModel.applySearch, "on:enter": searchViewModel.applySearch,
@@ -5511,6 +5545,15 @@
StringToOption StringToOption
] ]
} }
), /* @__PURE__ */ createElement(
"button",
{
class: "standard",
"aria-label": coreViewModel2.translations.general.searchButtonClearAudioLabel,
"on:click": searchViewModel.clear,
"toggle:disabled": searchViewModel.cannotClear
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement(
"button", "button",
{ {
@@ -5520,18 +5563,52 @@
"toggle:disabled": searchViewModel.cannotApplySearch "toggle:disabled": searchViewModel.cannotApplySearch
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "search") /* @__PURE__ */ createElement("span", { class: "icon" }, "search")
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( )), /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "grid gap", "toggle:hidden": searchViewModel.hasNoSuggestions
style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", },
/* @__PURE__ */ createElement("hr", null),
/* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.general.searchSuggestionsLabel),
/* @__PURE__ */ createElement(
"div",
{
class: "flex-column gap",
"children:append": [ "children:append": [
searchViewModel.matchingObjects, searchViewModel.suggestions,
converter (suggestion) => SuggestionView(suggestion, searchViewModel, coreViewModel2)
] ]
} }
)
)), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); )), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
} }
function SuggestionView(suggestion, searchViewModel, coreViewModel2) {
const isApplied = createProxyState([searchViewModel.appliedQuery], () => searchViewModel.appliedQuery.value == suggestion);
function deleteSuggestion() {
searchViewModel.deleteSuggestion(suggestion);
}
function applySuggestion() {
searchViewModel.search(suggestion);
}
return /* @__PURE__ */ createElement("div", { class: "flex-row surface align-center" }, /* @__PURE__ */ createElement("span", { class: "width-100 flex-1 padding-h" }, suggestion), /* @__PURE__ */ createElement(
"button",
{
class: "danger",
"aria-label": coreViewModel2.translations.general.deleteButton,
"on:click": deleteSuggestion
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "delete")
), /* @__PURE__ */ createElement(
"button",
{
class: "primary",
"aria-label": coreViewModel2.translations.general.applyButton,
"on:click": applySuggestion,
"toggle:disabled": isApplied
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
));
}
// src/View/Components/colorPicker.tsx // src/View/Components/colorPicker.tsx
function ColorPicker(selectedColor) { function ColorPicker(selectedColor) {
@@ -5701,7 +5778,6 @@
coreViewModel2, coreViewModel2,
boardViewModel.searchViewModel, boardViewModel.searchViewModel,
coreViewModel2.translations.chatPage.task.filterTasksHeadline, coreViewModel2.translations.chatPage.task.filterTasksHeadline,
TaskViewModelToEntry,
boardViewModel.isPresentingFilterModal boardViewModel.isPresentingFilterModal
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
} }
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -113,7 +113,7 @@ export function TaskSettingsModal(
hidden hidden
id={categorySuggestionId} id={categorySuggestionId}
children:append={[ children:append={[
taskViewModel.coreViewModel.taskCategorySuggestions, taskViewModel.containingViewModel.taskCategorySuggestions,
StringToOption, StringToOption,
]} ]}
></datalist> ></datalist>
@@ -137,7 +137,7 @@ export function TaskSettingsModal(
hidden hidden
id={statusSuggestionId} id={statusSuggestionId}
children:append={[ children:append={[
taskViewModel.coreViewModel.taskStatusSuggestions, taskViewModel.containingViewModel.taskStatusSuggestions,
StringToOption, StringToOption,
]} ]}
></datalist> ></datalist>
Binary file not shown.
+1 -6
View File
@@ -9,7 +9,7 @@ import ConnectionModel from "../../Model/Global/connectionModel";
import ChatListModel from "../../Model/Chat/chatListModel"; import ChatListModel from "../../Model/Chat/chatListModel";
export default class CoreViewModel { export default class CoreViewModel {
readonly BUILD = "Build 26.09.21.B"; readonly BUILD = "Build 26.09.21.C";
translations: Translations; translations: Translations;
@@ -81,11 +81,6 @@ export default class CoreViewModel {
// DRAG & DROP // DRAG & DROP
draggedObject: React.State<any> = new React.State<any>(undefined); draggedObject: React.State<any> = new React.State<any>(undefined);
// SUGGESTIONS
// boards & tasks
taskCategorySuggestions: React.ListState<string> = new React.ListState();
taskStatusSuggestions: React.ListState<string> = new React.ListState();
// init // init
constructor( constructor(
public readonly storageModel: StorageModel, public readonly storageModel: StorageModel,
Binary file not shown.
@@ -18,6 +18,10 @@ export default class TaskContainingPageViewModel extends Context {
taskViewModels: React.MapState<TaskViewModel> = new React.MapState(); taskViewModels: React.MapState<TaskViewModel> = new React.MapState();
taskCategorySuggestions: React.ListState<string> = new React.ListState();
taskStatusSuggestions: React.ListState<string> = new React.ListState();
// methods // methods
createTaskFromBoardId = (boardId: string): void => { createTaskFromBoardId = (boardId: string): void => {
const taskFileContent: TaskFileContent = const taskFileContent: TaskFileContent =
+4 -4
View File
@@ -104,19 +104,19 @@ export default class TaskViewModel extends Context {
updateSuggestions = (): void => { updateSuggestions = (): void => {
if ( if (
this.coreViewModel.taskCategorySuggestions.value.has( this.containingViewModel.taskCategorySuggestions.value.has(
this.category.value, this.category.value,
) == false ) == false
) { ) {
this.coreViewModel.taskCategorySuggestions.add(this.category.value); this.containingViewModel.taskCategorySuggestions.add(this.category.value);
} }
if ( if (
this.coreViewModel.taskStatusSuggestions.value.has( this.containingViewModel.taskStatusSuggestions.value.has(
this.status.value, this.status.value,
) == false ) == false
) { ) {
this.coreViewModel.taskStatusSuggestions.add(this.status.value); this.containingViewModel.taskStatusSuggestions.add(this.status.value);
} }
}; };
Binary file not shown.