Ariver
2026-06-05 eeb97e9c41b6939b29fd42402aa5c4d3f97663e9
Apptag/TagEditorView.swift
@@ -15,10 +15,15 @@
    @State private var newTagNameText = ""
    @State private var newTagColorIndex = 0
    private let contentMaxWidth: CGFloat = 980
    private let contentHorizontalPadding: CGFloat = 34
    private var sortedTagNames: [String] {
        tagColors.keys
            .filter { !excludedTagNames.contains($0) }
            .sorted { $0.localizedStandardCompare($1) == .orderedAscending }
        let order = TagEditor.orderedTagNames()
        let filtered = tagColors.keys.filter { !excludedTagNames.contains($0) }
        let ordered = order.filter { filtered.contains($0) }
        let remaining = filtered.filter { !ordered.contains($0) }.sorted()
        return ordered + remaining
    }
    var body: some View {
@@ -31,34 +36,49 @@
                    newTagNameText = ""
                    newTagColorIndex = 0
                } label: {
                    Label("New Tag", systemImage: "plus")
                    Label(tr("tag.newTag"), systemImage: "plus")
                        .font(.system(size: 12))
                }
                .buttonStyle(.bordered)
            }
            .padding(.horizontal, 16).padding(.vertical, 8)
            .frame(maxWidth: contentMaxWidth)
            .padding(.horizontal, contentHorizontalPadding)
            .padding(.vertical, 8)
            Divider().opacity(0.3)
            Divider()
                .opacity(0.3)
                .frame(maxWidth: contentMaxWidth)
                .padding(.horizontal, contentHorizontalPadding)
            ScrollView {
                VStack(spacing: 0) {
                    if sortedTagNames.isEmpty && !addingNewTag {
                        Text("No tags yet. Click \"New Tag\" to create one.")
                        Text(tr("edit.noTags"))
                            .font(.caption)
                            .foregroundStyle(.secondary)
                            .padding(.vertical, 40)
                    }
                    ForEach(sortedTagNames, id: \.self) { tagName in
                        tagEditRow(tagName)
                        Divider().opacity(0.15).padding(.leading, 16)
                    }
                    if addingNewTag {
                        newTagRow()
                        Divider().opacity(0.15).padding(.leading, 16)
                        rowDivider
                    }
                    ForEach(sortedTagNames, id: \.self) { tagName in
                        tagEditRow(tagName)
                        rowDivider
                    }
                }
                .frame(maxWidth: contentMaxWidth)
                .padding(.horizontal, contentHorizontalPadding)
                .frame(maxWidth: .infinity, alignment: .center)
            }
        }
        .frame(maxWidth: .infinity, alignment: .center)
    }
    private var rowDivider: some View {
        Divider()
            .opacity(0.15)
            .frame(maxWidth: contentMaxWidth)
    }
    // MARK: - Tag Row
@@ -79,16 +99,21 @@
            if editingTagName == tagName {
                MacTextField(
                    text: $editingTagText,
                    placeholder: "Tag name",
                    placeholder: tr("tag.name"),
                    onSubmit: { commitTagRename(tagName) }
                )
                .frame(width: 160, height: 24)
                Button("Save") { commitTagRename(tagName) }
                .frame(minWidth: 220, maxWidth: .infinity, minHeight: 24, maxHeight: 24)
                Button(tr("tag.save")) { commitTagRename(tagName) }
                    .buttonStyle(.borderedProminent).controlSize(.small)
                Button("Cancel") { editingTagName = nil }
                Button(tr("tag.cancel")) { editingTagName = nil }
                    .buttonStyle(.plain).foregroundStyle(.secondary)
            } else {
                Text(tagName).font(.system(size: 14)).frame(width: 160, alignment: .leading)
                Text(tagName)
                    .font(.system(size: 14))
                    .lineLimit(1)
                    .truncationMode(.middle)
                    .frame(minWidth: 220, maxWidth: .infinity, alignment: .leading)
                    .help(tagName)
                Button { startRename(tagName) } label: {
                    Image(systemName: "pencil").font(.system(size: 11))
                }
@@ -98,9 +123,9 @@
                }
                .buttonStyle(.plain).foregroundStyle(.red)
            }
            Spacer()
        }
        .padding(.horizontal, 20).padding(.vertical, 10)
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(.vertical, 10)
    }
    // MARK: - New Tag Row
@@ -116,17 +141,17 @@
            }
            MacTextField(
                text: $newTagNameText,
                placeholder: "New tag name",
                placeholder: tr("tag.newName"),
                onSubmit: { addNewTag() }
            )
            .frame(width: 160, height: 24)
            Button("Add") { addNewTag() }
            .frame(minWidth: 220, maxWidth: .infinity, minHeight: 24, maxHeight: 24)
            Button(tr("tag.add")) { addNewTag() }
                .buttonStyle(.borderedProminent).controlSize(.small)
            Button("Cancel") { addingNewTag = false; newTagNameText = "" }
            Button(tr("tag.cancel")) { addingNewTag = false; newTagNameText = "" }
                .buttonStyle(.plain).foregroundStyle(.secondary)
            Spacer()
        }
        .padding(.horizontal, 20).padding(.vertical, 10)
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(.vertical, 10)
    }
    // MARK: - Actions
@@ -143,8 +168,7 @@
        tagColors[newName] = tagColors[oldName]
        tagColors.removeValue(forKey: oldName)
        editingTagName = nil
        // Don't call onRefresh — re-scan may re-discover old tag from
        // SIP-protected apps where xattr write silently fails.
        onRefresh?()
    }
    /// Add a new tag name+color and persist to the database.