-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathkeepsync_note_ops.py
More file actions
143 lines (114 loc) · 4.74 KB
/
Copy pathkeepsync_note_ops.py
File metadata and controls
143 lines (114 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""Pure note comparison, merge, and filter helpers."""
import difflib
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from keepsync_models import Note, NoteType, normalize_keep_color
def normalize_import_labels(labels: List[str], source: str = "", markdown: bool = False) -> List[str]:
normalized = []
if source:
normalized.append(source)
if markdown:
normalized.append(".md")
for label in labels or []:
label_text = str(label or "").strip()
if label_text and label_text not in normalized:
normalized.append(label_text)
return normalized
def note_diff_body(note: Note) -> str:
if note.note_type == NoteType.CHECKLIST:
return "\n".join(
f"{' ' * item.indent}{'[x]' if item.checked else '[ ]'} {item.text}"
for item in note.checklist_items
)
return note.content or ""
def notes_equivalent(left: Note, right: Note) -> bool:
return (
(left.title or "").strip() == (right.title or "").strip()
and note_diff_body(left).strip() == note_diff_body(right).strip()
and sorted(left.labels) == sorted(right.labels)
and left.note_type == right.note_type
)
def note_conflict_diff(local_note: Note, imported_note: Note) -> str:
local_lines = [f"# {local_note.title or 'Untitled'}", *note_diff_body(local_note).splitlines()]
imported_lines = [f"# {imported_note.title or 'Untitled'}", *note_diff_body(imported_note).splitlines()]
return "\n".join(difflib.unified_diff(
local_lines,
imported_lines,
fromfile="local",
tofile="imported",
lineterm="",
))
def merge_note_conflict(local_note: Note, imported_note: Note) -> Note:
merged = Note.from_dict(local_note.to_dict())
merged.labels = normalize_import_labels(local_note.labels + imported_note.labels)
merged.attachments = local_note.attachments + [
attachment for attachment in imported_note.attachments
if attachment.filename not in {existing.filename for existing in local_note.attachments}
]
merged.updated_at = datetime.now(timezone.utc)
merged.local_modified = merged.updated_at
if local_note.note_type == NoteType.CHECKLIST or imported_note.note_type == NoteType.CHECKLIST:
merged.note_type = NoteType.NOTE
local_body = note_diff_body(local_note)
imported_body = note_diff_body(imported_note)
else:
local_body = local_note.content or ""
imported_body = imported_note.content or ""
if local_body.strip() == imported_body.strip():
merged.content = local_body
else:
merged.content = (
f"{local_body.strip()}\n\n"
"--- Imported version ---\n\n"
f"{imported_body.strip()}"
).strip()
merged.checklist_items = []
return merged
def default_advanced_filters() -> Dict[str, Any]:
return {
"mode": "AND",
"label": "",
"color": "",
"date_from": "",
"date_to": "",
"has_image": False,
"has_checklist": False,
"is_archived": False,
}
def advanced_filters_active(filters: Dict[str, Any]) -> bool:
defaults = default_advanced_filters()
return any(filters.get(key) != value for key, value in defaults.items() if key != "mode")
def parse_filter_date(value: str) -> Optional[datetime]:
text = (value or "").strip()
if not text:
return None
try:
return datetime.strptime(text, "%Y-%m-%d").replace(tzinfo=timezone.utc)
except ValueError:
return None
def note_matches_advanced_filters(note: Note, filters: Dict[str, Any]) -> bool:
checks = []
label = (filters.get("label") or "").strip()
if label:
checks.append(any(label.lower() == existing.lower() for existing in note.labels))
color = normalize_keep_color(filters.get("color", ""))
if color:
checks.append(normalize_keep_color(note.color) == color)
date_from = parse_filter_date(filters.get("date_from", ""))
if date_from:
checks.append(note.updated_at.astimezone(timezone.utc) >= date_from)
date_to = parse_filter_date(filters.get("date_to", ""))
if date_to:
end_of_day = date_to.replace(hour=23, minute=59, second=59)
checks.append(note.updated_at.astimezone(timezone.utc) <= end_of_day)
if filters.get("has_image"):
checks.append(any(attachment.is_image for attachment in note.attachments))
if filters.get("has_checklist"):
checks.append(note.note_type == NoteType.CHECKLIST or bool(note.checklist_items))
if filters.get("is_archived"):
checks.append(note.archived)
if not checks:
return True
if str(filters.get("mode", "AND")).upper() == "OR":
return any(checks)
return all(checks)