tag prefixing via @ and #
This commit is contained in:
+21
-57
@@ -260,28 +260,16 @@ export const matchesFilter = (task: Task) => {
|
||||
task.tags?.includes(r.tagName || "");
|
||||
});
|
||||
|
||||
// Bucket visibility logic for "My Bucket":
|
||||
// Users requested: "Any user who adds that bucket to their workspaces list can view the workspace and see tasks in that bucket from all users."
|
||||
// AND "In settings a user should be able to see and edit all buckets... They should be able to add any bucket to their workspaces list."
|
||||
// When in 'mine', do we show bucket tasks?
|
||||
// Typically 'My Bucket' is just MY stuff + stuff shared explicitly with me.
|
||||
// Buckets seem to be separate "Contexts" in the sidebar.
|
||||
// However, if the user wants them mixed in, they didn't explicitly say "mix into my main list",
|
||||
// they said "add that bucket to their workspaces list". This usually implies a separate view.
|
||||
// BUT, if I create a task and tag it "Shop", I expect to see it.
|
||||
|
||||
// If I own the task, I see it.
|
||||
// If it's shared with me, I see it.
|
||||
|
||||
// Bucket tasks are now tagged with '@' + name.
|
||||
if (!isOwner && !isExplicitlyShared && !isTagRuleShared) return false;
|
||||
} else if (typeof ctx === 'object' && 'bucketId' in ctx) {
|
||||
// Bucket View
|
||||
const bucketProxy = store.buckets.find(b => b.id === (ctx as { bucketId: string }).bucketId);
|
||||
if (!bucketProxy) return false;
|
||||
|
||||
// Show tasks that have the bucket name as a tag
|
||||
// The requirement: "if any user adds a tag that is the name of a bucket to a task that task is then made public and moved to the bucket."
|
||||
if (!task.tags?.includes(bucketProxy.name)) return false;
|
||||
// Show tasks that have the bucket name (prefixed with @) as a tag
|
||||
const bucketTagName = `@${bucketProxy.name}`;
|
||||
if (!task.tags?.includes(bucketTagName)) return false;
|
||||
} else {
|
||||
// Context is Oversight for specific user
|
||||
// Show ONLY tasks owned by that user
|
||||
@@ -545,13 +533,9 @@ const isTaskInSubscribedBucket = (task: any): boolean => {
|
||||
if (taskTags.length === 0) return false;
|
||||
|
||||
// Check if any of the task's tags match a bucket we are subscribed to
|
||||
// We need to look up bucket names from our subscribed bucket IDs
|
||||
// This is slightly expensive if we iterate.
|
||||
// Optimization: Pre-calculate subscribed bucket names?
|
||||
// For now, simple find.
|
||||
return store.subscribedBuckets.some(subId => {
|
||||
const bucket = store.buckets.find(b => b.id === subId);
|
||||
return bucket && taskTags.includes(bucket.name);
|
||||
return bucket && taskTags.includes(`@${bucket.name}`);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1315,17 +1299,18 @@ const checkForHandoffs = (task: Task, updates: Partial<Task>): Partial<Task> =>
|
||||
|
||||
// Check for any tag that triggers a SEND_TASK rule
|
||||
for (const tag of updates.tags) {
|
||||
// Handle @ prefix in rule matching
|
||||
const rule = store.shareRules.find(r =>
|
||||
r.ownerId === currentUserId &&
|
||||
r.type === 'tag' &&
|
||||
r.tagName === tag &&
|
||||
(r.tagName === tag || `@${r.tagName}` === tag) &&
|
||||
r.share_mode === 'SEND_TASK'
|
||||
);
|
||||
|
||||
if (rule) {
|
||||
console.log(`Handoff triggered by tag "${tag}" -> Transferring to ${rule.targetUserId}`);
|
||||
|
||||
// Check if it's a bucket tag (target is self, but tag is a bucket)
|
||||
// Check if it's a bucket tag
|
||||
const isBucketRule = rule.targetUserId === currentUserId && store.tagDefinitions.find(t => t.name === tag)?.isBucket;
|
||||
|
||||
if (isBucketRule) {
|
||||
@@ -1633,12 +1618,13 @@ export const syncSystemTagsAndRules = async () => {
|
||||
|
||||
// --- Sync Bucket Tags ---
|
||||
for (const bucket of store.buckets) {
|
||||
const tagExists = tagDefinitions.find(t => t.name === bucket.name);
|
||||
const bucketTagName = `@${bucket.name}`;
|
||||
const tagExists = tagDefinitions.find(t => t.name === bucketTagName);
|
||||
if (!tagExists) {
|
||||
try {
|
||||
const record = await pb.collection(TAGS_COLLECTION).create({
|
||||
user: currentUserId,
|
||||
name: bucket.name,
|
||||
name: bucketTagName,
|
||||
value: 5,
|
||||
color: bucket.color || "#64748b",
|
||||
theme: "dark",
|
||||
@@ -1653,23 +1639,22 @@ export const syncSystemTagsAndRules = async () => {
|
||||
isBucket: true
|
||||
}]);
|
||||
} catch (e) {
|
||||
console.error(`Failed to create bucket tag ${bucket.name}`, e);
|
||||
console.error(`Failed to create bucket tag ${bucketTagName}`, e);
|
||||
}
|
||||
} else if (!tagExists.isBucket) {
|
||||
try {
|
||||
await pb.collection(TAGS_COLLECTION).update(tagExists.id, { isBucket: true });
|
||||
setStore("tagDefinitions", d => d.id === tagExists.id, { isBucket: true });
|
||||
} catch (e) {
|
||||
console.error(`Failed to flag tag ${bucket.name} as bucket`, e);
|
||||
console.error(`Failed to flag tag ${bucketTagName} as bucket`, e);
|
||||
}
|
||||
}
|
||||
// --- Sync Bucket System Rule ---
|
||||
// Ensure a ShareRule exists for this bucket so it appears in "System Rules"
|
||||
const bucketRuleExists = normalizedRules.some(r =>
|
||||
r.ownerId === currentUserId &&
|
||||
r.targetUserId === currentUserId &&
|
||||
r.type === 'tag' &&
|
||||
r.tagName === bucket.name
|
||||
(r.tagName === bucket.name || r.tagName === bucketTagName)
|
||||
);
|
||||
|
||||
if (!bucketRuleExists) {
|
||||
@@ -1678,19 +1663,20 @@ export const syncSystemTagsAndRules = async () => {
|
||||
ownerId: currentUserId,
|
||||
targetUserId: currentUserId,
|
||||
type: 'tag',
|
||||
tagName: bucket.name,
|
||||
tagName: bucketTagName,
|
||||
share_mode: 'SEND_TASK'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to ensure system rule for bucket ${bucket.name}`, e);
|
||||
console.error(`Failed to ensure system rule for bucket ${bucketTagName}`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sync User Tags ---
|
||||
for (const user of users) {
|
||||
const userName = user.name || user.email;
|
||||
if (!userName) continue;
|
||||
const originalName = user.name || user.email;
|
||||
if (!originalName) continue;
|
||||
const userName = `@${originalName}`;
|
||||
|
||||
const tagExists = tagDefinitions.find(t => t.name === userName);
|
||||
if (!tagExists) {
|
||||
@@ -1723,34 +1709,13 @@ export const syncSystemTagsAndRules = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// --- User Share Rule Sync ---
|
||||
// OPTIMIZATION: We no longer create individual share rules for every user-pair.
|
||||
// Instead, we rely on the "Global Rule" (Self-Rule) created above for the user's own name.
|
||||
// When User A tags a task "Bob", the Global Rule for Bob (Owner=Bob, Target=Bob, Tag=Bob)
|
||||
// will pick it up via the updated `shouldTaskBeVisible` logic.
|
||||
|
||||
// If we want to support "Collab" (Edit access), we might still need specific rules or specific shares,
|
||||
// but for "View" access (Handoff), the Global Rule is sufficient.
|
||||
|
||||
// We do NOT delete existing rules here to avoid data loss during transition,
|
||||
// unless strictly requested. The user said "I want to optimize share rules... make them ownerless".
|
||||
// So we stop creating new ones.
|
||||
|
||||
/*
|
||||
if (user.id !== currentUserId) {
|
||||
// Legacy loop removed to prevent exponential growth
|
||||
}
|
||||
*/
|
||||
|
||||
// --- Ensure Self-Rule for Current User (Global Subscription) ---
|
||||
// This is the critical piece for "Global Sharing".
|
||||
// We must ensure the user has a rule: Owner=Me, Target=Me, Tag=Me.
|
||||
if (user.id === currentUserId) {
|
||||
const selfRuleExists = normalizedRules.some(r =>
|
||||
r.ownerId === currentUserId &&
|
||||
r.targetUserId === currentUserId &&
|
||||
r.type === 'tag' &&
|
||||
r.tagName === userName
|
||||
(r.tagName === originalName || r.tagName === userName)
|
||||
);
|
||||
|
||||
if (!selfRuleExists) {
|
||||
@@ -1760,10 +1725,9 @@ export const syncSystemTagsAndRules = async () => {
|
||||
targetUserId: currentUserId,
|
||||
type: 'tag',
|
||||
tagName: userName,
|
||||
share_mode: 'ADD_USER' // Mode doesn't strictly matter for self-rule visibility, but ADD_USER is safe default
|
||||
share_mode: 'ADD_USER'
|
||||
});
|
||||
|
||||
// Update local store immediately to ensure loadAllHistory sees it
|
||||
const newRule: ShareRule = {
|
||||
id: record.id,
|
||||
ownerId: currentUserId,
|
||||
|
||||
Reference in New Issue
Block a user