anki_connect 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,38 +7,59 @@ module AnkiConnect
7
7
  # Creates a new note.
8
8
  #
9
9
  # @param deck_name [String] Target deck
10
- # @param model_name [String] Note type
10
+ # @param note_type_name [String] Note type
11
11
  # @param fields [Hash] Field names to values
12
12
  # @param tags [Array<String>] Tags (optional)
13
13
  # @param media [Hash, nil] Media to add (audio:, video:, picture: arrays)
14
- # @param options [Hash, nil] Options (allowDuplicate, duplicateScope, etc.)
15
- # @return [Integer, nil] Note ID on success, nil on failure
16
- def add_note(deck_name:, model_name:, fields:, tags: [], media: nil, options: nil)
17
- note = { deckName: deck_name, modelName: model_name, fields: fields, tags: tags }
18
- note.merge!(media) if media
19
- note[:options] = options if options
20
- request(:addNote, note: note)
14
+ # @param options [Hash, nil] Options such as allow_duplicate and duplicate_scope
15
+ # @return [Integer] Note ID on success
16
+ def add_note(deck_name:, note_type_name:, fields:, tags: [], media: nil, options: nil)
17
+ note = { deck_name: deck_name, note_type_name: note_type_name, fields: fields, tags: tags }
18
+ note[:media] = media unless media.nil?
19
+ note[:options] = options unless options.nil?
20
+ request(:addNote, note: normalize_note(note))
21
21
  end
22
22
 
23
23
  # Creates multiple notes.
24
24
  #
25
25
  # @param notes [Array<Hash>] Array of note hashes (same keys as add_note)
26
- # @return [Array<Integer, nil>] Array of note IDs (nil for failed notes)
26
+ # The operation is all-or-nothing; AnkiConnect rolls back all additions if any note fails.
27
+ #
28
+ # @return [Array<Integer>] Array of note IDs
27
29
  def add_notes(notes)
28
- request(:addNotes, notes: notes)
30
+ request(:addNotes, notes: notes.map { |note| normalize_note(note) })
29
31
  end
30
32
 
31
- # Checks if notes can be added.
33
+ # Checks if one note can be added.
32
34
  #
33
- # @param notes [Array<Hash>] Array of candidate note objects
34
- # @param details [Boolean] If true, returns error details (default: false)
35
- # @return [Array<Boolean>, Array<Hash>] Array of booleans, or hashes with canAdd and error if details=true
36
- def can_add_notes(notes, details: false)
37
- if details
38
- request(:canAddNotesWithErrorDetail, notes: notes)
39
- else
40
- request(:canAddNotes, notes: notes)
41
- end
35
+ # @param note [Hash] Candidate note using snake_case keys
36
+ # @return [Boolean] Whether the note can be added
37
+ def note_addable?(note)
38
+ request(:canAddNote, note: normalize_note(note))
39
+ end
40
+
41
+ # Gets addability details for one note.
42
+ #
43
+ # @param note [Hash] Candidate note
44
+ # @return [Hash] Hash with canAdd and optional error
45
+ def note_addability(note)
46
+ request(:canAddNoteWithErrorDetail, note: normalize_note(note))
47
+ end
48
+
49
+ # Gets addability status for each note, preserving input order.
50
+ #
51
+ # @param notes [Array<Hash>] Candidate notes
52
+ # @return [Array<Boolean>] Addability statuses
53
+ def note_addability_statuses(notes)
54
+ request(:canAddNotes, notes: notes.map { |note| normalize_note(note) })
55
+ end
56
+
57
+ # Gets addability details for each note, preserving input order.
58
+ #
59
+ # @param notes [Array<Hash>] Candidate notes
60
+ # @return [Array<Hash>] Hashes with canAdd and optional error
61
+ def note_addability_details(notes)
62
+ request(:canAddNotesWithErrorDetail, notes: notes.map { |note| normalize_note(note) })
42
63
  end
43
64
 
44
65
  # Updates a note's fields, tags, or media.
@@ -49,29 +70,56 @@ module AnkiConnect
49
70
  # @param media [Hash, nil] Media to add (audio:, video:, picture: arrays)
50
71
  # @return [nil]
51
72
  def update_note(id, fields: nil, tags: nil, media: nil)
73
+ if fields.nil? && tags.nil? && media.nil?
74
+ raise ArgumentError, 'provide fields, tags, or media to update'
75
+ end
76
+
52
77
  note = { id: id }
53
- note[:fields] = fields if fields
54
- note[:tags] = tags if tags
55
- note.merge!(media) if media
78
+ note[:fields] = fields || {} unless fields.nil? && media.nil?
79
+ note[:tags] = tags unless tags.nil?
80
+ merge_media!(note, media) unless media.nil?
56
81
  request(:updateNote, note: note)
57
82
  end
58
83
 
59
- # Changes a note's model type.
84
+ # Updates a note's fields and optional media.
60
85
  #
61
86
  # @param id [Integer] Note ID
62
- # @param model_name [String] New model name
63
- # @param fields [Hash] New field values
64
- # @param tags [Array<String>] New tags
87
+ # @param fields [Hash] Field names to new values
88
+ # @param media [Hash, nil] Media to add (audio:, video:, picture:)
65
89
  # @return [nil]
66
- def change_note_model(id, model_name:, fields:, tags:)
67
- request(:updateNoteModel, note: { id: id, modelName: model_name, fields: fields, tags: tags })
90
+ def update_note_fields(id, fields:, media: nil)
91
+ note = { id: id, fields: fields }
92
+ merge_media!(note, media) unless media.nil?
93
+ request(:updateNoteFields, note: note)
94
+ end
95
+
96
+ # Replaces all tags on a note.
97
+ #
98
+ # @param id [Integer] Note ID
99
+ # @param tags [String, Array<String>] Replacement tags
100
+ # @return [nil]
101
+ def update_note_tags(id, tags)
102
+ request(:updateNoteTags, note: id, tags: normalize_note_tags(tags))
103
+ end
104
+
105
+ # Changes a note's note type.
106
+ #
107
+ # @param id [Integer] Note ID
108
+ # @param note_type_name [String] New note type name
109
+ # All fields not supplied are cleared when the note type changes.
110
+ #
111
+ # @param fields [Hash] Complete values for fields in the new note type
112
+ # @param tags [Array<String>] Replacement tags
113
+ # @return [nil]
114
+ def change_note_type(id, note_type_name:, fields:, tags:)
115
+ request(:updateNoteModel, note: { id: id, modelName: note_type_name, fields: fields, tags: tags })
68
116
  end
69
117
 
70
118
  # Gets tags for a note.
71
119
  #
72
120
  # @param note_id [Integer] Note ID
73
121
  # @return [Array<String>] Array of tag strings
74
- def get_note_tags(note_id)
122
+ def note_tags(note_id)
75
123
  request(:getNoteTags, note: note_id)
76
124
  end
77
125
 
@@ -81,7 +129,7 @@ module AnkiConnect
81
129
  # @param tags [String, Array<String>] Tag(s) to add
82
130
  # @return [nil]
83
131
  def add_tags(note_ids, tags)
84
- request(:addTags, notes: note_ids, tags: tags)
132
+ request(:addTags, notes: note_ids, tags: normalize_tag_query(tags))
85
133
  end
86
134
 
87
135
  # Removes tags from notes.
@@ -90,13 +138,13 @@ module AnkiConnect
90
138
  # @param tags [String, Array<String>] Tag(s) to remove
91
139
  # @return [nil]
92
140
  def remove_tags(note_ids, tags)
93
- request(:removeTags, notes: note_ids, tags: tags)
141
+ request(:removeTags, notes: note_ids, tags: normalize_tag_query(tags))
94
142
  end
95
143
 
96
144
  # Gets all tags in collection.
97
145
  #
98
146
  # @return [Array<String>] Array of all tag strings
99
- def all_tags
147
+ def tags
100
148
  request(:getTags)
101
149
  end
102
150
 
@@ -134,10 +182,12 @@ module AnkiConnect
134
182
  # @param note_ids [Array<Integer>, nil] Array of note IDs
135
183
  # @param query [String, nil] Search query string
136
184
  # @return [Array<Hash>] Array of note objects
137
- def get_notes(note_ids: nil, query: nil)
138
- params = {}
139
- params[:notes] = note_ids if note_ids
140
- params[:query] = query if query
185
+ def notes(note_ids: nil, query: nil)
186
+ unless [note_ids, query].count { |selector| !selector.nil? } == 1
187
+ raise ArgumentError, 'provide exactly one of note_ids or query'
188
+ end
189
+
190
+ params = note_ids.nil? ? { query: query } : { notes: note_ids }
141
191
  request(:notesInfo, **params)
142
192
  end
143
193
 
@@ -145,7 +195,7 @@ module AnkiConnect
145
195
  #
146
196
  # @param note_ids [Array<Integer>] Array of note IDs
147
197
  # @return [Array<Hash>] Array of objects with noteId and mod
148
- def get_notes_mod_time(note_ids)
198
+ def note_modification_times(note_ids)
149
199
  request(:notesModTime, notes: note_ids)
150
200
  end
151
201
 
@@ -157,12 +207,110 @@ module AnkiConnect
157
207
  request(:deleteNotes, notes: note_ids)
158
208
  end
159
209
 
160
- # Removes all empty notes.
210
+ # Removes note types that are not used by any notes.
211
+ # The upstream action name is misleading; it does not remove notes with empty fields.
161
212
  #
162
213
  # @return [nil]
163
- def remove_empty_notes
214
+ def remove_unused_note_types
164
215
  request(:removeEmptyNotes)
165
216
  end
217
+
218
+ private
219
+
220
+ NOTE_KEYS = {
221
+ 'deck_name' => :deckName,
222
+ 'note_type_name' => :modelName,
223
+ 'fields' => :fields,
224
+ 'tags' => :tags,
225
+ 'options' => :options,
226
+ 'media' => :media,
227
+ 'audio' => :audio,
228
+ 'video' => :video,
229
+ 'picture' => :picture
230
+ }.freeze
231
+ GUI_NOTE_KEYS = NOTE_KEYS.reject { |key, _value| key == 'options' }.freeze
232
+ MEDIA_KEYS = {
233
+ 'audio' => :audio,
234
+ 'video' => :video,
235
+ 'picture' => :picture
236
+ }.freeze
237
+ OPTION_KEYS = {
238
+ 'allow_duplicate' => :allowDuplicate,
239
+ 'duplicate_scope' => :duplicateScope,
240
+ 'duplicate_scope_options' => :duplicateScopeOptions
241
+ }.freeze
242
+ SCOPE_OPTION_KEYS = {
243
+ 'deck_name' => :deckName,
244
+ 'check_children' => :checkChildren,
245
+ 'check_all_note_types' => :checkAllModels
246
+ }.freeze
247
+ MEDIA_ITEM_KEYS = {
248
+ 'filename' => :filename,
249
+ 'fields' => :fields,
250
+ 'data' => :data,
251
+ 'path' => :path,
252
+ 'url' => :url,
253
+ 'skip_hash' => :skipHash,
254
+ 'overwrite' => :deleteExisting
255
+ }.freeze
256
+ private_constant :NOTE_KEYS, :GUI_NOTE_KEYS, :MEDIA_KEYS, :OPTION_KEYS, :SCOPE_OPTION_KEYS, :MEDIA_ITEM_KEYS
257
+
258
+ def normalize_note(note, key_map: NOTE_KEYS)
259
+ normalized = normalize_keys(note, key_map, name: 'note') do |key, value|
260
+ if key == :options
261
+ normalize_options(value)
262
+ elsif MEDIA_KEYS.value?(key)
263
+ normalize_media(value)
264
+ else
265
+ value
266
+ end
267
+ end
268
+
269
+ merge_media!(normalized, normalized.delete(:media)) if normalized.key?(:media)
270
+ normalized
271
+ end
272
+
273
+ def normalize_gui_note(note)
274
+ normalize_note(note, key_map: GUI_NOTE_KEYS)
275
+ end
276
+
277
+ def merge_media!(note, media)
278
+ normalized_media = normalize_keys(media, MEDIA_KEYS, name: 'media') do |_key, value|
279
+ normalize_media(value)
280
+ end
281
+ normalized_media.each do |key, value|
282
+ raise ArgumentError, "duplicate media key: #{key}" if note.key?(key)
283
+
284
+ note[key] = value
285
+ end
286
+ end
287
+
288
+ def normalize_note_tags(tags)
289
+ tags = [tags] if tags.is_a?(String)
290
+ unless tags.is_a?(Array) && tags.all? { |tag| tag.is_a?(String) }
291
+ raise ArgumentError, 'tags must be a String or an Array of Strings'
292
+ end
293
+
294
+ tags
295
+ end
296
+
297
+ def normalize_tag_query(tags)
298
+ normalize_note_tags(tags).join(' ')
299
+ end
300
+
301
+ def normalize_options(options)
302
+ normalize_keys(options, OPTION_KEYS, name: 'options') do |key, value|
303
+ key == :duplicateScopeOptions ? normalize_keys(value, SCOPE_OPTION_KEYS, name: 'duplicate_scope_options') : value
304
+ end
305
+ end
306
+
307
+ def normalize_media(media)
308
+ media.is_a?(Array) ? media.map { |item| normalize_media_item(item) } : normalize_media_item(media)
309
+ end
310
+
311
+ def normalize_media_item(item)
312
+ normalize_keys(item, MEDIA_ITEM_KEYS, name: 'media item')
313
+ end
166
314
  end
167
315
  end
168
316
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnkiConnect
4
+ class Client
5
+ # Shared normalization for snake_case convenience inputs.
6
+ module Params
7
+ private
8
+
9
+ def normalize_keys(hash, key_map, name: 'parameters')
10
+ raise ArgumentError, "#{name} must be a Hash" unless hash.is_a?(Hash)
11
+
12
+ hash.each_with_object({}) do |(key, value), normalized|
13
+ normalized_key = key_map[key.to_s]
14
+ raise ArgumentError, "unknown #{name} key: #{key}" unless normalized_key
15
+ raise ArgumentError, "duplicate #{name} key: #{normalized_key}" if normalized.key?(normalized_key)
16
+
17
+ normalized[normalized_key] = block_given? ? yield(normalized_key, value) : value
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -28,12 +28,12 @@ module AnkiConnect
28
28
  request(:getCollectionStatsHTML, wholeCollection: whole_collection)
29
29
  end
30
30
 
31
- # Gets all card reviews for a deck after a certain time.
31
+ # Gets all card reviews for a deck after a review log ID.
32
32
  #
33
33
  # @param deck_name [String] Deck name
34
- # @param after [Integer] Unix timestamp (reviews after this time, exclusive)
34
+ # @param after [Integer] Review log ID in epoch milliseconds (exclusive)
35
35
  # @return [Array<Array>] Array of 9-tuples: (reviewTime, cardID, usn, buttonPressed, newInterval, previousInterval, newFactor, reviewDuration, reviewType)
36
- def get_reviews(deck_name, after:)
36
+ def reviews(deck_name, after:)
37
37
  request(:cardReviews, deck: deck_name, startID: after)
38
38
  end
39
39
 
@@ -41,21 +41,21 @@ module AnkiConnect
41
41
  #
42
42
  # @param card_ids [Array<Integer>] Array of card IDs
43
43
  # @return [Hash] Dictionary mapping card IDs to arrays of review objects with id, usn, ease, ivl, lastIvl, factor, time, type
44
- def get_reviews_for_cards(card_ids)
44
+ def reviews_for_cards(card_ids)
45
45
  request(:getReviewsOfCards, cards: card_ids)
46
46
  end
47
47
 
48
- # Gets unix time of latest review for a deck.
48
+ # Gets the latest review log ID for a deck.
49
49
  #
50
50
  # @param deck_name [String] Deck name
51
- # @return [Integer] Unix timestamp, or 0 if no reviews
52
- def latest_review_time(deck_name)
51
+ # @return [Integer] Review log ID in epoch milliseconds, or 0 if no reviews
52
+ def latest_review_id(deck_name)
53
53
  request(:getLatestReviewID, deck: deck_name)
54
54
  end
55
55
 
56
56
  # Inserts review records into database.
57
57
  #
58
- # @param reviews [Array<Array>] Array of 9-tuples (same format as get_reviews output)
58
+ # @param reviews [Array<Array>] Array of 9-tuples (same format as reviews output)
59
59
  # @return [nil]
60
60
  def insert_reviews(reviews)
61
61
  request(:insertReviews, reviews: reviews)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AnkiConnect
4
4
  # The version of the anki_connect gem
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
 
7
7
  # The AnkiConnect API version this gem is compatible with
8
8
  API_VERSION = 6
data/lib/anki_connect.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'anki_connect/version'
4
+ require_relative 'anki_connect/params'
4
5
  require_relative 'anki_connect/cards'
5
6
  require_relative 'anki_connect/decks'
6
- require_relative 'anki_connect/models'
7
+ require_relative 'anki_connect/note_types'
7
8
  require_relative 'anki_connect/notes'
8
9
  require_relative 'anki_connect/media'
9
10
  require_relative 'anki_connect/graphical'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anki_connect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - vadik49b
@@ -17,8 +17,11 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - CHANGELOG.md
20
21
  - LICENSE
21
22
  - README.md
23
+ - examples/add_elevenlabs_tts_audio_to_cloze_cards.rb
24
+ - examples/basic_usage.rb
22
25
  - lib/anki_connect.rb
23
26
  - lib/anki_connect/cards.rb
24
27
  - lib/anki_connect/client.rb
@@ -26,8 +29,9 @@ files:
26
29
  - lib/anki_connect/graphical.rb
27
30
  - lib/anki_connect/media.rb
28
31
  - lib/anki_connect/miscellaneous.rb
29
- - lib/anki_connect/models.rb
32
+ - lib/anki_connect/note_types.rb
30
33
  - lib/anki_connect/notes.rb
34
+ - lib/anki_connect/params.rb
31
35
  - lib/anki_connect/statistics.rb
32
36
  - lib/anki_connect/version.rb
33
37
  homepage: https://github.com/vadik49b/anki-connect.rb
@@ -35,7 +39,11 @@ licenses:
35
39
  - MIT
36
40
  metadata:
37
41
  homepage_uri: https://github.com/vadik49b/anki-connect.rb
38
- source_code_uri: https://github.com/vadik49b/anki-connect.rb
42
+ source_code_uri: https://github.com/vadik49b/anki-connect.rb/tree/main
43
+ documentation_uri: https://rubydoc.info/gems/anki_connect
44
+ bug_tracker_uri: https://github.com/vadik49b/anki-connect.rb/issues
45
+ changelog_uri: https://github.com/vadik49b/anki-connect.rb/blob/main/CHANGELOG.md
46
+ rubygems_mfa_required: 'true'
39
47
  rdoc_options: []
40
48
  require_paths:
41
49
  - lib
@@ -50,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
58
  - !ruby/object:Gem::Version
51
59
  version: '0'
52
60
  requirements: []
53
- rubygems_version: 3.6.9
61
+ rubygems_version: 4.0.10
54
62
  specification_version: 4
55
63
  summary: Ruby wrapper for the Anki-Connect HTTP API
56
64
  test_files: []
@@ -1,238 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AnkiConnect
4
- class Client
5
- # Methods to create and modify note types (models).
6
- module Models
7
- # Gets complete list of model names.
8
- #
9
- # @return [Array<String>] Array of model name strings
10
- def model_names
11
- request(:modelNames)
12
- end
13
-
14
- # Gets model names with their IDs.
15
- #
16
- # @return [Hash] Model names mapped to IDs
17
- def model_names_and_ids
18
- request(:modelNamesAndIds)
19
- end
20
-
21
- # Gets models by ID.
22
- #
23
- # @param ids [Array<Integer>] Array of model IDs
24
- # @return [Array<Hash>] Array of model objects
25
- def get_models_by_id(ids)
26
- request(:findModelsById, modelIds: ids)
27
- end
28
-
29
- # Gets models by name.
30
- #
31
- # @param names [Array<String>] Array of model names
32
- # @return [Array<Hash>] Array of model objects
33
- def get_models_by_name(names)
34
- request(:findModelsByName, modelNames: names)
35
- end
36
-
37
- # Gets field names for a model.
38
- #
39
- # @param model_name [String] Model name
40
- # @return [Array<String>] Array of field names in order
41
- def get_field_names(model_name)
42
- request(:modelFieldNames, modelName: model_name)
43
- end
44
-
45
- # Gets field descriptions for a model.
46
- #
47
- # @param model_name [String] Model name
48
- # @return [Array<String>] Array of description strings
49
- def get_field_descriptions(model_name)
50
- request(:modelFieldDescriptions, modelName: model_name)
51
- end
52
-
53
- # Gets field fonts and sizes for a model.
54
- #
55
- # @param model_name [String] Model name
56
- # @return [Hash] Field names mapped to { font:, size: }
57
- def get_field_fonts(model_name)
58
- request(:modelFieldFonts, modelName: model_name)
59
- end
60
-
61
- # Gets fields used on templates.
62
- #
63
- # @param model_name [String] Model name
64
- # @return [Hash] Template names mapped to [questionFields, answerFields]
65
- def get_fields_on_templates(model_name)
66
- request(:modelFieldsOnTemplates, modelName: model_name)
67
- end
68
-
69
- # Creates a new model.
70
- #
71
- # @param name [String] Model name
72
- # @param fields [Array<String>] Field names in order
73
- # @param templates [Array<Hash>] Template objects with Name, Front, Back
74
- # @param css [String, nil] CSS styling
75
- # @param is_cloze [Boolean] true for cloze type
76
- # @return [Hash] Complete model object
77
- def create_model(name:, fields:, templates:, css: nil, is_cloze: false)
78
- params = { modelName: name, inOrderFields: fields, cardTemplates: templates, isCloze: is_cloze }
79
- params[:css] = css if css
80
- request(:createModel, **params)
81
- end
82
-
83
- # Gets templates for a model.
84
- #
85
- # @param model_name [String] Model name
86
- # @return [Hash] Template names mapped to { Front:, Back: }
87
- def get_templates(model_name)
88
- request(:modelTemplates, modelName: model_name)
89
- end
90
-
91
- # Gets CSS styling for a model.
92
- #
93
- # @param model_name [String] Model name
94
- # @return [Hash] Object with css property
95
- def get_styling(model_name)
96
- request(:modelStyling, modelName: model_name)
97
- end
98
-
99
- # Updates a model's templates and/or CSS.
100
- #
101
- # @param name [String] Model name
102
- # @param templates [Hash, nil] Template names mapped to Front/Back
103
- # @param css [String, nil] CSS styling
104
- # @return [nil]
105
- def update_model(name, templates: nil, css: nil)
106
- request(:updateModelTemplates, model: { name: name, templates: templates }) if templates
107
- return unless css
108
-
109
- request(:updateModelStyling, model: { name: name, css: css })
110
- end
111
-
112
- # Find and replace in model templates/CSS.
113
- #
114
- # @param model_name [String] Model name
115
- # @param find [String] Text to find
116
- # @param replace [String] Replacement text
117
- # @param front [Boolean] Search front templates
118
- # @param back [Boolean] Search back templates
119
- # @param css [Boolean] Search CSS
120
- # @return [Integer] Number of replacements made
121
- def find_and_replace_in_model(model_name:, find:, replace:, front: true, back: true, css: true)
122
- request(:findAndReplaceInModels, model: {
123
- modelName: model_name, findText: find, replaceText: replace,
124
- front: front, back: back, css: css
125
- })
126
- end
127
-
128
- # Renames a template.
129
- #
130
- # @param model_name [String] Model name
131
- # @param from [String] Current template name
132
- # @param to [String] New template name
133
- # @return [nil]
134
- def rename_template(model_name, from:, to:)
135
- request(:modelTemplateRename, modelName: model_name, oldTemplateName: from, newTemplateName: to)
136
- end
137
-
138
- # Moves a template to a new position.
139
- #
140
- # @param model_name [String] Model name
141
- # @param template_name [String] Template name
142
- # @param index [Integer] New position (0-based)
143
- # @return [nil]
144
- def reposition_template(model_name, template_name, index)
145
- request(:modelTemplateReposition, modelName: model_name, templateName: template_name, index: index)
146
- end
147
-
148
- # Adds a template to a model.
149
- #
150
- # @param model_name [String] Model name
151
- # @param template [Hash] Template with Name, Front, Back
152
- # @return [nil]
153
- def add_template(model_name, template)
154
- request(:modelTemplateAdd, modelName: model_name, template: template)
155
- end
156
-
157
- # Removes a template from a model.
158
- #
159
- # @param model_name [String] Model name
160
- # @param template_name [String] Template name
161
- # @return [nil]
162
- def remove_template(model_name, template_name)
163
- request(:modelTemplateRemove, modelName: model_name, templateName: template_name)
164
- end
165
-
166
- # Renames a field.
167
- #
168
- # @param model_name [String] Model name
169
- # @param from [String] Current field name
170
- # @param to [String] New field name
171
- # @return [nil]
172
- def rename_field(model_name, from:, to:)
173
- request(:modelFieldRename, modelName: model_name, oldFieldName: from, newFieldName: to)
174
- end
175
-
176
- # Moves a field to a new position.
177
- #
178
- # @param model_name [String] Model name
179
- # @param field_name [String] Field name
180
- # @param index [Integer] New position (0-based)
181
- # @return [nil]
182
- def reposition_field(model_name, field_name, index)
183
- request(:modelFieldReposition, modelName: model_name, fieldName: field_name, index: index)
184
- end
185
-
186
- # Adds a field to a model.
187
- #
188
- # @param model_name [String] Model name
189
- # @param field_name [String] Field name
190
- # @param index [Integer, nil] Position (defaults to end)
191
- # @return [nil]
192
- def add_field(model_name, field_name, index: nil)
193
- params = { modelName: model_name, fieldName: field_name }
194
- params[:index] = index if index
195
- request(:modelFieldAdd, **params)
196
- end
197
-
198
- # Removes a field from a model.
199
- #
200
- # @param model_name [String] Model name
201
- # @param field_name [String] Field name
202
- # @return [nil]
203
- def remove_field(model_name, field_name)
204
- request(:modelFieldRemove, modelName: model_name, fieldName: field_name)
205
- end
206
-
207
- # Sets font for a field.
208
- #
209
- # @param model_name [String] Model name
210
- # @param field_name [String] Field name
211
- # @param font [String] Font name
212
- # @return [nil]
213
- def set_field_font(model_name, field_name, font)
214
- request(:modelFieldSetFont, modelName: model_name, fieldName: field_name, font: font)
215
- end
216
-
217
- # Sets font size for a field.
218
- #
219
- # @param model_name [String] Model name
220
- # @param field_name [String] Field name
221
- # @param size [Integer] Font size
222
- # @return [nil]
223
- def set_field_font_size(model_name, field_name, size)
224
- request(:modelFieldSetFontSize, modelName: model_name, fieldName: field_name, fontSize: size)
225
- end
226
-
227
- # Sets description for a field.
228
- #
229
- # @param model_name [String] Model name
230
- # @param field_name [String] Field name
231
- # @param description [String] Description text
232
- # @return [Boolean] true on success
233
- def set_field_description(model_name, field_name, description)
234
- request(:modelFieldSetDescription, modelName: model_name, fieldName: field_name, description: description)
235
- end
236
- end
237
- end
238
- end