~kris/dots

srice

ref: e9b48d06a8541f3eda5c4db90382ab3c77183afb srice/.local/bin/misc/edit -rwxr-xr-x 12.0 KiB
e9b48d06 — Kris Yotam xprofile: systemd-aware pipewire start + blueman-applet; sb-internet: tolerate missing /proc/net/wireless 2 months ago
                                                                                
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#!/usr/bin/env bash
set -euo pipefail

###############################################################################
# KRIS EDIT — Content metadata editor for krisyotam.com
#
# Maintainer:   Kris Yotam <krisyotam@pm.me>
# License:      MIT
# Created:      2026-02-15
# Description:  Edit metadata fields (title, preview, category, tags, status,
#               confidence, importance, state) for any content entry in
#               content.db. Select entries via fzf or nnn.
###############################################################################

###############################################################################
# config
###############################################################################

DB="/home/krisyotam/dev/krisyotam.com/public/data/content.db"
CONTENT_DIR="$HOME/content"

CONTENT_TYPES=(
    blog diary essays fiction news notes ocs
    papers progymnasmata reviews verse
)

# Diary has no status/confidence/importance columns
DIARY_FIELDS=(title preview category_slug state)
STANDARD_FIELDS=(title preview category_slug status confidence importance state)

VALID_STATUSES=("Notes" "Draft" "In Progress" "Finished" "Abandoned")
VALID_CONFIDENCES=(
    "certain" "highly likely" "likely" "possible"
    "unlikely" "highly unlikely" "remote" "impossible"
)
VALID_STATES=("active" "hidden")

###############################################################################
# helpers
###############################################################################

have_cmd() { command -v "$1" >/dev/null 2>&1; }
die() { echo "ERROR: $1" >&2; exit 1; }

have_cmd fzf     || die "fzf not found"
have_cmd sqlite3 || die "sqlite3 not found"
[[ -f "$DB" ]]   || die "content.db not found at $DB"

sql() { sqlite3 "$DB" "$1"; }

###############################################################################
# ui
###############################################################################

print_banner() {
    local cols
    cols="$(tput cols 2>/dev/null || echo 80)"

    if have_cmd figlet; then
        figlet -w "$cols" -f big "EDIT" 2>/dev/null \
            || figlet -w "$cols" "EDIT"
    else
        echo "=== EDIT ==="
    fi

    echo
    echo "  Content metadata editor for krisyotam.com"
    echo
}

# Draw a box around metadata
show_metadata() {
    local type="$1" slug="$2"

    local title preview category status confidence importance state tags

    title="$(sql "SELECT title FROM $type WHERE slug='$slug'")"
    preview="$(sql "SELECT preview FROM $type WHERE slug='$slug'")"
    category="$(sql "SELECT category_slug FROM $type WHERE slug='$slug'")"
    state="$(sql "SELECT state FROM $type WHERE slug='$slug'")"

    # Get tags
    local id
    id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"
    tags="$(sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" | paste -sd', ' -)"

    echo
    echo "  ┌─ $slug ─────────────────────────────────"
    printf "  │ %-14s %s\n" "Title:" "$title"
    printf "  │ %-14s %s\n" "Preview:" "${preview:0:60}"
    printf "  │ %-14s %s\n" "Category:" "$category"
    printf "  │ %-14s %s\n" "Tags:" "$tags"

    if [[ "$type" != "diary" ]]; then
        status="$(sql "SELECT status FROM $type WHERE slug='$slug'")"
        confidence="$(sql "SELECT confidence FROM $type WHERE slug='$slug'")"
        importance="$(sql "SELECT importance FROM $type WHERE slug='$slug'")"
        printf "  │ %-14s %s\n" "Status:" "$status"
        printf "  │ %-14s %s\n" "Confidence:" "$confidence"
        printf "  │ %-14s %s\n" "Importance:" "$importance"
    fi

    printf "  │ %-14s %s\n" "State:" "$state"
    echo "  └──────────────────────────────────────────"
    echo
}

###############################################################################
# field editing
###############################################################################

edit_field() {
    local type="$1" slug="$2" field="$3"
    local new_value=""

    case "$field" in
        title|preview)
            local current
            current="$(sql "SELECT $field FROM $type WHERE slug='$slug'")"
            echo "  Current $field: $current"
            read -rp "  New $field: " new_value
            [[ -z "$new_value" ]] && return
            # Escape single quotes for SQL
            new_value="${new_value//\'/\'\'}"
            sql "UPDATE $type SET $field='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated $field."
            ;;
        category_slug)
            new_value="$(
                sql "SELECT slug FROM categories ORDER BY slug" |
                fzf --prompt="category> " --height=20 --reverse
            )" || return
            sql "UPDATE $type SET category_slug='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated category to: $new_value"
            ;;
        tags)
            edit_tags "$type" "$slug"
            ;;
        status)
            new_value="$(
                printf "%s\n" "${VALID_STATUSES[@]}" |
                fzf --prompt="status> " --height=10 --reverse
            )" || return
            sql "UPDATE $type SET status='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated status to: $new_value"
            ;;
        confidence)
            new_value="$(
                printf "%s\n" "${VALID_CONFIDENCES[@]}" |
                fzf --prompt="confidence> " --height=12 --reverse
            )" || return
            sql "UPDATE $type SET confidence='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated confidence to: $new_value"
            ;;
        importance)
            read -rp "  Importance (1-10): " new_value
            if [[ "$new_value" =~ ^[0-9]+$ ]] && (( new_value >= 1 && new_value <= 10 )); then
                sql "UPDATE $type SET importance=$new_value, updated_at=datetime('now') WHERE slug='$slug'"
                echo "  Updated importance to: $new_value"
            else
                echo "  Invalid. Must be 1-10."
            fi
            ;;
        state)
            new_value="$(
                printf "%s\n" "${VALID_STATES[@]}" |
                fzf --prompt="state> " --height=5 --reverse
            )" || return
            sql "UPDATE $type SET state='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
            echo "  Updated state to: $new_value"
            ;;
        *)
            echo "  Unknown field: $field"
            ;;
    esac
}

edit_tags() {
    local type="$1" slug="$2"

    local id
    id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"

    echo
    echo "  Current tags:"
    sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" | while read -r tag; do
        echo "    - $tag"
    done

    echo
    echo "  Actions: [a]dd tag, [r]emove tag, [d]one"
    while true; do
        read -rp "  tag> " action
        case "$action" in
            a|add)
                local new_tag
                new_tag="$(
                    sql "SELECT slug FROM tags ORDER BY slug" |
                    fzf --prompt="add tag> " --height=20 --reverse
                )" || continue

                # Check if tag already linked
                local existing
                existing="$(sql "SELECT COUNT(*) FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$new_tag')")"
                if [[ "$existing" -gt 0 ]]; then
                    echo "  Tag '$new_tag' already linked."
                    continue
                fi

                local tag_id
                tag_id="$(sql "SELECT id FROM tags WHERE slug='$new_tag'")"
                sql "INSERT INTO content_tags (content_type, content_id, tag_id) VALUES ('$type', $id, $tag_id)"
                echo "  Added tag: $new_tag"
                ;;
            r|remove)
                local rm_tag
                rm_tag="$(
                    sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" |
                    fzf --prompt="remove tag> " --height=10 --reverse
                )" || continue

                sql "DELETE FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$rm_tag')"
                echo "  Removed tag: $rm_tag"
                ;;
            d|done|q|quit)
                break
                ;;
            *)
                echo "  Unknown action. Use [a]dd, [r]emove, or [d]one."
                ;;
        esac
    done
}

###############################################################################
# entry selection
###############################################################################

select_entry_fzf() {
    local type="$1"

    sql "SELECT title || '  (' || slug || ')' || char(9) || slug FROM $type WHERE state != '' ORDER BY title" |
    fzf --prompt="$type> " --delimiter=$'\t' --with-nth=1 \
        --preview="sqlite3 '$DB' \"SELECT 'Title: ' || title || char(10) || 'Preview: ' || COALESCE(preview,'') || char(10) || 'Category: ' || COALESCE(category_slug,'') || char(10) || 'State: ' || COALESCE(state,'active') FROM $type WHERE slug='{2}'\"" \
        --height=80% --reverse |
    awk -F'\t' '{print $2}'
}

select_entry_nnn() {
    local type="$1"
    local content_path="$CONTENT_DIR/$type"

    [[ ! -d "$content_path" ]] && { echo ""; return; }

    # Use nnn to pick a file, extract slug from filename
    local tmpfile
    tmpfile="$(mktemp)"

    NNN_TMPFILE="$tmpfile" nnn -p "$tmpfile" "$content_path"

    if [[ -s "$tmpfile" ]]; then
        local selected
        selected="$(cat "$tmpfile")"
        rm -f "$tmpfile"
        # Extract slug from filename: /path/to/my-slug.mdx -> my-slug
        basename "$selected" .mdx
    else
        rm -f "$tmpfile"
        echo ""
    fi
}

###############################################################################
# main
###############################################################################

main() {
    clear
    print_banner

    while true; do
        # Select content type
        local type
        type="$(
            printf "%s\n" "${CONTENT_TYPES[@]}" "quit" |
            fzf --prompt="type> " --height=15 --reverse
        )" || exit 0

        [[ "$type" == "quit" ]] && exit 0

        # Select how to pick entry
        local mode
        mode="$(
            printf "%s\n" "fzf (search by title)" "nnn (browse files)" "back" |
            fzf --prompt="select via> " --height=5 --reverse
        )" || continue

        [[ "$mode" == "back" ]] && continue

        local slug=""
        if [[ "$mode" == *"fzf"* ]]; then
            slug="$(select_entry_fzf "$type")" || continue
        else
            slug="$(select_entry_nnn "$type")" || continue
        fi

        [[ -z "$slug" ]] && continue

        # Verify slug exists in DB
        local count
        count="$(sql "SELECT COUNT(*) FROM $type WHERE slug='$slug'")"
        if [[ "$count" -eq 0 ]]; then
            echo "  Slug '$slug' not found in $type table."
            read -rp "  Press Enter..." _
            continue
        fi

        # Edit loop
        while true; do
            clear
            print_banner
            show_metadata "$type" "$slug"

            # Build field list based on type
            local fields
            if [[ "$type" == "diary" ]]; then
                fields=("${DIARY_FIELDS[@]}" "tags" "done")
            else
                fields=("${STANDARD_FIELDS[@]}" "tags" "done")
            fi

            local field
            field="$(
                printf "%s\n" "${fields[@]}" |
                fzf --prompt="edit field> " --height=12 --reverse
            )" || break

            [[ "$field" == "done" ]] && break

            edit_field "$type" "$slug" "$field"
            read -rp "  Press Enter..." _
        done
    done
}

main