Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Excluded tag filters #37

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,19 @@ columns:
...
```

Filters can contain the same rules as columns, except for the `rootNotebookPath` property. This defines the notebook from which notes are displayed on the board. By default, it is the parent notebook of the config note, but you can set it to anything. It's a `/` separated path so with a notebook structure like
Filters can contain the same rules as columns, and more. Here's the list of supported rules apart from [columns](#columns) rules:

```
Parent/
├─ Nested Parent/
│ ├─ Kanban board/
```
* `rootNotebookPath: ...` This defines the notebook from which notes are displayed on the board. By default, it is the parent notebook of the config note, but you can set it to anything. It's a `/` separated path so with a notebook structure like

```
Parent/
├─ Nested Parent/
│ ├─ Kanban board/
```

To give the path to `Kanban board` you should write `"Parent/Nested Parent/Kanban board"`

To give the path to `Kanban board` you should write `"Parent/Nested Parent/Kanban board"`
* `-tag: ...` Exclude note if the note has the given tag. You can also use `-tags` to define a list tags.

To edit the filters via the config dialog, click the gear icon next to the board name.

Expand Down
6 changes: 3 additions & 3 deletions src/board.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getNotebookPath, getNoteById } from "./noteData";

import rules from "./rules";
import { rules, filtersRules } from "./rules";
import { parseConfigNote } from "./parser";
import { Action } from "./actions";
import {
Expand Down Expand Up @@ -135,8 +135,8 @@ export default class Board {
for (const key in configObj.filters) {
let val = configObj.filters[key];
if (typeof val === "boolean") val = `${val}`;
if (val && key in rules) {
const rule = await rules[key](val, rootNotebookPath, configObj);
if (val && key in filtersRules) {
const rule = await filtersRules[key](val, rootNotebookPath, configObj);
this.baseFilters.push(rule);
if (key === "tag") this.hiddenTags.push(val as string);
else if (key === "tags")
Expand Down
26 changes: 17 additions & 9 deletions src/configui/useConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import * as yaml from "js-yaml";

import type { Config, RuleValue } from "../types";
import { toggleSingularPlural } from "../utils";

export default function (editedPath: string, inputConfig: Config) {
const [editedKey, colIdxStr = null] = editedPath.split(".", 2) as [
Expand All @@ -28,20 +29,22 @@ export default function (editedPath: string, inputConfig: Config) {
});

useEffect(() => {
if ("tag" in editedObj) {
if ("tag" in editedObj || "-tag" in editedObj) {
const thisProp = "tag" in editedObj ? "tag" : ("-tag" in editedObj ? "-tag" : "");
const pluralProp = toggleSingularPlural(thisProp);
setEditedObj((obj) => {
const filteredEntries = Object.entries(obj).filter(
([k]) => k !== "tag"
([k]) => k !== thisProp
);
const newObj = Object.fromEntries(filteredEntries) as typeof obj;
(newObj as any).tags = [
editedObj.tag as string,
...((editedObj?.tags as string[]) || []),
(newObj as any)[pluralProp] = [
editedObj[thisProp] as string,
...((editedObj?.[pluralProp] as string[]) || []),
];
return newObj;
});
}
}, ["tag" in editedObj]);
}, ["tag" in editedObj, "-tag" in editedObj]);

const isBacklog = "backlog" in editedObj && editedObj.backlog;
useEffect(() => {
Expand All @@ -57,9 +60,14 @@ export default function (editedPath: string, inputConfig: Config) {
}, [isBacklog, Object.keys(editedObj).length]);

const outObjEntries = Object.entries(editedObj)
.map(([prop, val]) =>
prop === "tags" && val.length === 1 ? ["tag", val[0]] : [prop, val]
)
.map(([prop, val]) => {
if ((prop === "tags" || prop === "-tags") && val.length === 1) {
const thisProp = prop === "tags" ? "tags" : (prop === "-tags" ? "-tags" : "");
const singularProp = toggleSingularPlural(thisProp);
return [singularProp, val[0]];
}
return [prop, val]
})
.filter(([_, val]) => val !== "" && val !== null && val !== []);
const outObj = Object.fromEntries(outObjEntries);
const outConf =
Expand Down
4 changes: 2 additions & 2 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as yaml from "js-yaml";
import rules from "./rules";
import { rules, filtersRules } from "./rules";
import { Config, Message } from "./types";

const configRegex = /([\s\S]*?)```kanban([\s\S]*?)```([\s\S]*)/;
Expand Down Expand Up @@ -56,7 +56,7 @@ export const validateConfig = (config: Config | {} | null): Message | null => {
if (typeof config.filters !== "object" || Array.isArray(config.filters))
return configErr("Filters has to contain a dictionary of rules");
for (const key in config.filters) {
if (!(key in rules) && key !== "rootNotebookPath")
if (!(key in filtersRules) && key !== "rootNotebookPath")
return configErr(`Invalid rule type "${key}" in filters`);
}
}
Expand Down
32 changes: 29 additions & 3 deletions src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type RuleFactory = (
/**
* This map contains all supported rules.
*/
const rules: Record<string, RuleFactory> = {
export const rules: Record<string, RuleFactory> = {
async tag(arg: string | string[]) {
const tagName = Array.isArray(arg) ? arg[0] : arg;
const tagID = (await getTagId(tagName)) || (await createTag(tagName));
Expand Down Expand Up @@ -139,9 +139,37 @@ const rules: Record<string, RuleFactory> = {
},
};

const _filtersRules: Record<string, RuleFactory> = {
"-tag": async (arg: string | string[]) => {
const tagName = Array.isArray(arg) ? arg[0] : arg;

const ruleObj = await rules.tag(arg, "", {} as Config);
ruleObj.name = "-tag";
ruleObj.filterNote = (note: NoteData) => !note.tags.includes(tagName);
return ruleObj;
},

"-tags": async (tagNames: string | string[], rootNbPath: string, config: Config) => {
if (!Array.isArray(tagNames)) tagNames = [tagNames];
const tagRules = await Promise.all(
tagNames.map((t) => filtersRules["-tag"](t, rootNbPath, config))
);
return {
name: "-tags",
filterNote: (note: NoteData) =>
tagRules.every(({ filterNote }) => filterNote(note)),
set: (noteId: string) => tagRules.flatMap(({ set }) => set(noteId)),
unset: (noteId: string) => tagRules.flatMap(({ unset }) => unset(noteId)),
editorType: "text",
};
},
}
export const filtersRules: Record<string, RuleFactory> = Object.assign({}, rules, _filtersRules);

const editorTypes = {
filters: {
tags: "tags",
"-tags": "tags",
rootNotebookPath: "notebook",
completed: "checkbox",
},
Expand All @@ -158,5 +186,3 @@ export const getRuleEditorTypes = (targetPath: string) => {
if (targetPath.startsWith("column")) return editorTypes.columns;
throw new Error(`Unkown target path ${targetPath}`);
};

export default rules;
5 changes: 4 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export const capitalize = (s: string) =>
s.replace(/^\w/, (c) => c.toUpperCase());
s.replace(/^-?\w/, (c) => c.toUpperCase());

export const toggleSingularPlural = (s: string) =>
s.endsWith("s") ? s.slice(0, -1) : s + "s";