aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components
diff options
context:
space:
mode:
authorAndrii Mokhovyk <andrii.mokhovyk@gmail.com>2026-01-18 16:36:49 +0200
committerGitHub <noreply@github.com>2026-01-18 14:36:49 +0000
commitc56cf4e24f6134547fb9c5b58eb20840f5083e9e (patch)
treeec9792cfcc6cbc6e45490d02e140b9241dca3fae /apps/web/components
parent1b98014d6cb0e3eb824d58ccbd35f39864e6ec88 (diff)
downloadkarakeep-c56cf4e24f6134547fb9c5b58eb20840f5083e9e.tar.zst
feat(rules): add "Title Contains" condition to Rule Engine (#1670) (#2354)
* feat(rules): add "Title Contains" condition to Rule Engine (#1670) * feat(rules): hide title conditions for bookmark created trigger * fix typecheck
Diffstat (limited to 'apps/web/components')
-rw-r--r--apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx56
-rw-r--r--apps/web/components/dashboard/rules/RuleEngineRuleEditor.tsx1
2 files changed, 56 insertions, 1 deletions
diff --git a/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx b/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx
index a859a4cc..28bf690d 100644
--- a/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx
+++ b/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx
@@ -19,6 +19,7 @@ import {
ChevronDown,
ChevronRight,
FileType,
+ Heading,
Link,
PlusCircle,
Rss,
@@ -28,7 +29,10 @@ import {
} from "lucide-react";
import { useTranslation } from "react-i18next";
-import type { RuleEngineCondition } from "@karakeep/shared/types/rules";
+import type {
+ RuleEngineCondition,
+ RuleEngineEvent,
+} from "@karakeep/shared/types/rules";
import { FeedSelector } from "../feeds/FeedSelector";
import { TagAutocomplete } from "../tags/TagAutocomplete";
@@ -36,6 +40,7 @@ import { TagAutocomplete } from "../tags/TagAutocomplete";
interface ConditionBuilderProps {
value: RuleEngineCondition;
onChange: (condition: RuleEngineCondition) => void;
+ eventType: RuleEngineEvent["type"];
level?: number;
onRemove?: () => void;
}
@@ -43,6 +48,7 @@ interface ConditionBuilderProps {
export function ConditionBuilder({
value,
onChange,
+ eventType,
level = 0,
onRemove,
}: ConditionBuilderProps) {
@@ -57,6 +63,12 @@ export function ConditionBuilder({
case "urlDoesNotContain":
onChange({ type: "urlDoesNotContain", str: "" });
break;
+ case "titleContains":
+ onChange({ type: "titleContains", str: "" });
+ break;
+ case "titleDoesNotContain":
+ onChange({ type: "titleDoesNotContain", str: "" });
+ break;
case "importedFromFeed":
onChange({ type: "importedFromFeed", feedId: "" });
break;
@@ -93,6 +105,9 @@ export function ConditionBuilder({
case "urlContains":
case "urlDoesNotContain":
return <Link className="h-4 w-4" />;
+ case "titleContains":
+ case "titleDoesNotContain":
+ return <Heading className="h-4 w-4" />;
case "importedFromFeed":
return <Rss className="h-4 w-4" />;
case "bookmarkTypeIs":
@@ -134,6 +149,30 @@ export function ConditionBuilder({
</div>
);
+ case "titleContains":
+ return (
+ <div className="mt-2">
+ <Input
+ value={value.str}
+ onChange={(e) => onChange({ ...value, str: e.target.value })}
+ placeholder="Title contains..."
+ className="w-full"
+ />
+ </div>
+ );
+
+ case "titleDoesNotContain":
+ return (
+ <div className="mt-2">
+ <Input
+ value={value.str}
+ onChange={(e) => onChange({ ...value, str: e.target.value })}
+ placeholder="Title does not contain..."
+ className="w-full"
+ />
+ </div>
+ );
+
case "importedFromFeed":
return (
<div className="mt-2">
@@ -198,6 +237,7 @@ export function ConditionBuilder({
newConditions[index] = newCondition;
onChange({ ...value, conditions: newConditions });
}}
+ eventType={eventType}
level={level + 1}
onRemove={() => {
const newConditions = [...value.conditions];
@@ -233,6 +273,10 @@ export function ConditionBuilder({
}
};
+ // Title conditions are hidden for "bookmarkAdded" event because
+ // titles are not available at bookmark creation time (they're fetched during crawling)
+ const showTitleConditions = eventType !== "bookmarkAdded";
+
const ConditionSelector = () => (
<Select value={value.type} onValueChange={handleTypeChange}>
<SelectTrigger className="ml-2 h-8 border-none bg-transparent px-2">
@@ -254,6 +298,16 @@ export function ConditionBuilder({
<SelectItem value="urlDoesNotContain">
{t("settings.rules.conditions_types.url_does_not_contain")}
</SelectItem>
+ {showTitleConditions && (
+ <SelectItem value="titleContains">
+ {t("settings.rules.conditions_types.title_contains")}
+ </SelectItem>
+ )}
+ {showTitleConditions && (
+ <SelectItem value="titleDoesNotContain">
+ {t("settings.rules.conditions_types.title_does_not_contain")}
+ </SelectItem>
+ )}
<SelectItem value="importedFromFeed">
{t("settings.rules.conditions_types.imported_from_feed")}
</SelectItem>
diff --git a/apps/web/components/dashboard/rules/RuleEngineRuleEditor.tsx b/apps/web/components/dashboard/rules/RuleEngineRuleEditor.tsx
index d5658a70..e4859b4a 100644
--- a/apps/web/components/dashboard/rules/RuleEngineRuleEditor.tsx
+++ b/apps/web/components/dashboard/rules/RuleEngineRuleEditor.tsx
@@ -175,6 +175,7 @@ export function RuleEditor({ rule, onCancel }: RuleEditorProps) {
<ConditionBuilder
value={editedRule.condition}
onChange={handleConditionChange}
+ eventType={editedRule.event.type}
/>
</div>