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
# frozen_string_literal: true

require_relative "../views/sources/text"

module RankKing
  module Sources
    # A source whose items are typed in by hand. Its sync is an edit dialog
    # over the list's items, which matches them by id, so there is nothing
    # to fetch and no table of its own.
    class Text
      class Provider
        #: () -> String
        def title = "Manual"

        #: (List list) -> Text
        def source(list) = Text.new(list)

        #: (csrf: Method) -> _Component
        def fields(csrf:) = Views::Sources::TextFields.new

        #: (Roda::RodaRequest r) -> void
        def routes(r) = nil

        # Adds the items typed into the new-list form, of which there must
        # be at least two.
        #: (List list, Hash[String, untyped] params) -> void
        def create(list, params)
          names = params["items"].to_s.lines.map(&:strip).reject(&:empty?)
          raise Invalid, "A list needs at least two items." if names.size < 2

          names.each do |name|
            Item.create(list_id: list.id, name:)
          end
        end
      end

      #: (List list) -> void
      def initialize(list)
        @list = list
      end

      #: () -> String
      def label = "Edit items"

      #: (csrf: Method) -> _Component
      def form(csrf:) = Views::Sources::Text.new(list: @list, csrf:)

      #: (Item item) -> _Component
      def item(item) = Views::Sources::TextItem.new(item:)

      # Applies the dialog: each submitted name renames the item with that
      # id, a blank name archives it, and each line of new_items adds one.
      #: (Hash[String, untyped] params, ?now: Time) -> void
      def sync(params, now: Time.now)
        items = params["items"]
        names = items.is_a?(Hash) ? items : {} #: Hash[String, untyped]
        added = params["new_items"].to_s.lines.map(&:strip).reject(&:empty?)

        DB.transaction do
          @list.items.each do |item|
            name = names[item.id.to_s]
            next unless name.is_a?(String)

            name = name.strip
            if name.empty?
              item.update(archived_at: now)
            elsif name != item.name
              item.update(name:)
            end
          end

          added.each do |name|
            Item.create(list_id: @list.id, name:)
          end
        end
      end
    end
  end
end