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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +97 -9
- data/examples/add_elevenlabs_tts_audio_to_cloze_cards.rb +75 -0
- data/examples/basic_usage.rb +48 -0
- data/lib/anki_connect/cards.rb +56 -32
- data/lib/anki_connect/client.rb +144 -17
- data/lib/anki_connect/decks.rb +15 -5
- data/lib/anki_connect/graphical.rb +36 -13
- data/lib/anki_connect/media.rb +10 -7
- data/lib/anki_connect/miscellaneous.rb +10 -8
- data/lib/anki_connect/note_types.rb +293 -0
- data/lib/anki_connect/notes.rb +188 -40
- data/lib/anki_connect/params.rb +22 -0
- data/lib/anki_connect/statistics.rb +8 -8
- data/lib/anki_connect/version.rb +1 -1
- data/lib/anki_connect.rb +2 -1
- metadata +12 -4
- data/lib/anki_connect/models.rb +0 -238
data/lib/anki_connect/decks.rb
CHANGED
|
@@ -18,11 +18,19 @@ module AnkiConnect
|
|
|
18
18
|
request(:deckNamesAndIds)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Gets a deck name by ID.
|
|
22
|
+
#
|
|
23
|
+
# @param deck_id [Integer] Deck ID
|
|
24
|
+
# @return [String] Deck name
|
|
25
|
+
def deck_name_from_id(deck_id)
|
|
26
|
+
request(:deckNameFromId, deckId: deck_id)
|
|
27
|
+
end
|
|
28
|
+
|
|
21
29
|
# Gets deck membership for given cards.
|
|
22
30
|
#
|
|
23
31
|
# @param card_ids [Array<Integer>] Array of card IDs
|
|
24
32
|
# @return [Hash] Deck names mapped to arrays of card IDs
|
|
25
|
-
def
|
|
33
|
+
def decks_for_cards(card_ids)
|
|
26
34
|
request(:getDecks, cards: card_ids)
|
|
27
35
|
end
|
|
28
36
|
|
|
@@ -48,15 +56,17 @@ module AnkiConnect
|
|
|
48
56
|
# @param names [Array<String>] Array of deck names
|
|
49
57
|
# @param cards_too [Boolean] Must be true to confirm deletion
|
|
50
58
|
# @return [nil]
|
|
51
|
-
def delete_decks(names, cards_too:
|
|
59
|
+
def delete_decks(names, cards_too:)
|
|
60
|
+
raise ArgumentError, 'cards_too must be true to delete decks' unless cards_too == true
|
|
61
|
+
|
|
52
62
|
request(:deleteDecks, decks: names, cardsToo: cards_too)
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
# Gets configuration for a deck.
|
|
56
66
|
#
|
|
57
67
|
# @param name [String] Deck name
|
|
58
|
-
# @return [Hash] Configuration object
|
|
59
|
-
def
|
|
68
|
+
# @return [Hash, false] Configuration object, or false when the deck does not exist
|
|
69
|
+
def deck_config(name)
|
|
60
70
|
request(:getDeckConfig, deck: name)
|
|
61
71
|
end
|
|
62
72
|
|
|
@@ -100,7 +110,7 @@ module AnkiConnect
|
|
|
100
110
|
#
|
|
101
111
|
# @param names [Array<String>] Array of deck names
|
|
102
112
|
# @return [Hash] Deck IDs mapped to stats objects
|
|
103
|
-
def
|
|
113
|
+
def deck_stats(names)
|
|
104
114
|
request(:getDeckStats, decks: names)
|
|
105
115
|
end
|
|
106
116
|
end
|
|
@@ -5,14 +5,27 @@ module AnkiConnect
|
|
|
5
5
|
# Methods to interact with Anki's GUI windows and dialogs
|
|
6
6
|
# (card browser, review screen, editing interfaces).
|
|
7
7
|
module Graphical
|
|
8
|
+
REORDER_CARD_KEYS = {
|
|
9
|
+
'order' => :order,
|
|
10
|
+
'column_id' => :columnId
|
|
11
|
+
}.freeze
|
|
12
|
+
private_constant :REORDER_CARD_KEYS
|
|
13
|
+
|
|
8
14
|
# Opens Card Browser dialog and searches for query.
|
|
9
15
|
#
|
|
10
|
-
# @param query [String] Search query string
|
|
11
|
-
# @param reorder_cards [Hash, nil]
|
|
16
|
+
# @param query [String, nil] Search query string; nil preserves the current browser search
|
|
17
|
+
# @param reorder_cards [Hash, nil] Object with order and column_id
|
|
12
18
|
# @return [Array<Integer>] Array of card IDs found
|
|
13
|
-
def gui_browse(query, reorder_cards: nil)
|
|
14
|
-
params = {
|
|
15
|
-
params[:
|
|
19
|
+
def gui_browse(query = nil, reorder_cards: nil)
|
|
20
|
+
params = {}
|
|
21
|
+
params[:query] = query unless query.nil?
|
|
22
|
+
if reorder_cards
|
|
23
|
+
normalized = normalize_keys(reorder_cards, REORDER_CARD_KEYS, name: 'reorder_cards')
|
|
24
|
+
missing_keys = REORDER_CARD_KEYS.values.reject { |key| normalized.key?(key) }
|
|
25
|
+
raise ArgumentError, "missing reorder_cards keys: #{missing_keys.join(', ')}" unless missing_keys.empty?
|
|
26
|
+
|
|
27
|
+
params[:reorderCards] = normalized
|
|
28
|
+
end
|
|
16
29
|
request(:guiBrowse, **params)
|
|
17
30
|
end
|
|
18
31
|
|
|
@@ -34,10 +47,12 @@ module AnkiConnect
|
|
|
34
47
|
# Opens Add Cards dialog with preset values.
|
|
35
48
|
# Multiple invocations close old window and reopen with new values.
|
|
36
49
|
#
|
|
37
|
-
# @param note [Hash
|
|
50
|
+
# @param note [Hash, nil] Optional note preset
|
|
38
51
|
# @return [Integer] Note ID that would be created if user confirms
|
|
39
|
-
def gui_add_cards(note)
|
|
40
|
-
|
|
52
|
+
def gui_add_cards(note = nil)
|
|
53
|
+
params = {}
|
|
54
|
+
params[:note] = normalize_gui_note(note) unless note.nil?
|
|
55
|
+
request(:guiAddCards, **params)
|
|
41
56
|
end
|
|
42
57
|
|
|
43
58
|
# Opens Edit dialog for a note.
|
|
@@ -52,16 +67,24 @@ module AnkiConnect
|
|
|
52
67
|
# Sets fields/tags/deck/model in open Add Note dialog.
|
|
53
68
|
# Returns error if Add Note dialog not open. Deck/model always replace; fields/tags respect append flag.
|
|
54
69
|
#
|
|
55
|
-
# @param note [Hash] Note
|
|
70
|
+
# @param note [Hash] Note with optional deck_name, note_type_name, fields, tags, and media
|
|
56
71
|
# @param append [Boolean] If true, appends to fields/tags; otherwise replaces (default: false)
|
|
57
|
-
# @return [Boolean] true on success
|
|
72
|
+
# @return [Boolean, Hash] true on success, or an error hash when the dialog is closed
|
|
58
73
|
def gui_add_note_set_data(note, append: false)
|
|
59
|
-
request(:guiAddNoteSetData, note: note, append: append)
|
|
74
|
+
request(:guiAddNoteSetData, note: normalize_gui_note(note), append: append)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Checks whether the review screen has an active card.
|
|
78
|
+
#
|
|
79
|
+
# @return [Boolean] true when review is active
|
|
80
|
+
def gui_review_active?
|
|
81
|
+
request(:guiReviewActive)
|
|
60
82
|
end
|
|
61
83
|
|
|
62
84
|
# Gets information about current card in review.
|
|
63
85
|
#
|
|
64
|
-
# @return [Hash
|
|
86
|
+
# @return [Hash] Current card information
|
|
87
|
+
# @raise [APIError] If review is not active
|
|
65
88
|
def gui_current_card
|
|
66
89
|
request(:guiCurrentCard)
|
|
67
90
|
end
|
|
@@ -69,7 +92,7 @@ module AnkiConnect
|
|
|
69
92
|
# Starts/resets timer for current card.
|
|
70
93
|
# Useful for accurate time tracking when displaying cards via API.
|
|
71
94
|
#
|
|
72
|
-
# @return [Boolean]
|
|
95
|
+
# @return [Boolean] false when review is not active or no card is available
|
|
73
96
|
def gui_start_card_timer
|
|
74
97
|
request(:guiStartCardTimer)
|
|
75
98
|
end
|
data/lib/anki_connect/media.rb
CHANGED
|
@@ -10,13 +10,16 @@ module AnkiConnect
|
|
|
10
10
|
# @param data [String, nil] Base64-encoded contents
|
|
11
11
|
# @param path [String, nil] Absolute file path
|
|
12
12
|
# @param url [String, nil] URL to download from
|
|
13
|
+
# @param skip_hash [String, nil] MD5 hash that causes an identical upload to be skipped
|
|
13
14
|
# @param overwrite [Boolean] If true, overwrites existing file
|
|
14
|
-
# @return [String] Filename
|
|
15
|
-
def store_media(filename, data: nil, path: nil, url: nil, overwrite: true)
|
|
15
|
+
# @return [String, nil] Filename, or nil when skipped by hash
|
|
16
|
+
def store_media(filename, data: nil, path: nil, url: nil, skip_hash: nil, overwrite: true)
|
|
17
|
+
sources = { data: data, path: path, url: url }.reject { |_key, value| value.nil? }
|
|
18
|
+
raise ArgumentError, 'provide exactly one of data, path, or url' unless sources.one?
|
|
19
|
+
|
|
16
20
|
params = { filename: filename, deleteExisting: overwrite }
|
|
17
|
-
params
|
|
18
|
-
params[:
|
|
19
|
-
params[:url] = url if url
|
|
21
|
+
params.merge!(sources)
|
|
22
|
+
params[:skipHash] = skip_hash unless skip_hash.nil?
|
|
20
23
|
request(:storeMediaFile, **params)
|
|
21
24
|
end
|
|
22
25
|
|
|
@@ -32,14 +35,14 @@ module AnkiConnect
|
|
|
32
35
|
#
|
|
33
36
|
# @param pattern [String] Glob pattern
|
|
34
37
|
# @return [Array<String>] Array of filenames
|
|
35
|
-
def
|
|
38
|
+
def media_files(pattern: '*')
|
|
36
39
|
request(:getMediaFilesNames, pattern: pattern)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
# Gets the media folder path.
|
|
40
43
|
#
|
|
41
44
|
# @return [String] Absolute path
|
|
42
|
-
def
|
|
45
|
+
def media_directory
|
|
43
46
|
request(:getMediaDirPath)
|
|
44
47
|
end
|
|
45
48
|
|
|
@@ -8,7 +8,7 @@ module AnkiConnect
|
|
|
8
8
|
# Requests API permission (first call to establish trust).
|
|
9
9
|
# Only method accepting requests from any origin. Shows popup for untrusted origins.
|
|
10
10
|
#
|
|
11
|
-
# @return [Hash] Object with permission
|
|
11
|
+
# @return [Hash] Object with permission and optionally requireApikey and version
|
|
12
12
|
def request_permission
|
|
13
13
|
request(:requestPermission)
|
|
14
14
|
end
|
|
@@ -16,7 +16,7 @@ module AnkiConnect
|
|
|
16
16
|
# Gets AnkiConnect API version.
|
|
17
17
|
#
|
|
18
18
|
# @return [Integer] Version number (currently 6)
|
|
19
|
-
def
|
|
19
|
+
def api_version
|
|
20
20
|
request(:version)
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ module AnkiConnect
|
|
|
25
25
|
# @param scopes [Array<String>] Array of scope names (currently only "actions" supported)
|
|
26
26
|
# @param actions [Array<String>, nil] null for all actions, or array of action names to check (optional)
|
|
27
27
|
# @return [Hash] Object with scopes used and available actions
|
|
28
|
-
def
|
|
28
|
+
def api_capabilities(scopes, actions: nil)
|
|
29
29
|
params = { scopes: scopes }
|
|
30
30
|
params[:actions] = actions if actions
|
|
31
31
|
request(:apiReflect, **params)
|
|
@@ -55,16 +55,18 @@ module AnkiConnect
|
|
|
55
55
|
# Switches to specified profile.
|
|
56
56
|
#
|
|
57
57
|
# @param name [String] Profile name
|
|
58
|
-
# @return [Boolean] true on success
|
|
58
|
+
# @return [Boolean] true on success, false when the profile does not exist
|
|
59
59
|
def load_profile(name)
|
|
60
60
|
request(:loadProfile, name: name)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Performs multiple actions in one request.
|
|
64
|
+
# Unlike convenience wrappers, each action uses AnkiConnect's raw wire keys.
|
|
64
65
|
#
|
|
65
|
-
# @param actions [Array<Hash>]
|
|
66
|
+
# @param actions [Array<Hash>] Raw wire action objects with action, version, and params
|
|
66
67
|
# @return [Array] Array of responses in same order
|
|
67
|
-
|
|
68
|
+
# @see #request Use request for individual actions
|
|
69
|
+
def batch(actions)
|
|
68
70
|
request(:multi, actions: actions)
|
|
69
71
|
end
|
|
70
72
|
|
|
@@ -80,9 +82,9 @@ module AnkiConnect
|
|
|
80
82
|
|
|
81
83
|
# Imports .apkg file into collection.
|
|
82
84
|
#
|
|
83
|
-
# @param path [String]
|
|
85
|
+
# @param path [String] Package file path on the Anki host
|
|
84
86
|
# @return [Boolean] true on success, false otherwise
|
|
85
|
-
def
|
|
87
|
+
def import_package(path)
|
|
86
88
|
request(:importPackage, path: path)
|
|
87
89
|
end
|
|
88
90
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnkiConnect
|
|
4
|
+
class Client
|
|
5
|
+
# Methods to create and modify note types.
|
|
6
|
+
module NoteTypes
|
|
7
|
+
# Gets complete list of note type names.
|
|
8
|
+
#
|
|
9
|
+
# @return [Array<String>] Array of note type names
|
|
10
|
+
def note_type_names
|
|
11
|
+
request(:modelNames)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Gets note type names with their IDs.
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash] Note type names mapped to IDs
|
|
17
|
+
def note_type_names_and_ids
|
|
18
|
+
request(:modelNamesAndIds)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Gets a note type name by ID.
|
|
22
|
+
#
|
|
23
|
+
# @param note_type_id [Integer] Note type ID
|
|
24
|
+
# @return [String] Note type name
|
|
25
|
+
def note_type_name_from_id(note_type_id)
|
|
26
|
+
request(:modelNameFromId, modelId: note_type_id)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Gets note types by ID.
|
|
30
|
+
#
|
|
31
|
+
# @param ids [Array<Integer>] Array of note type IDs
|
|
32
|
+
# @return [Array<Hash>] Array of note type objects
|
|
33
|
+
def note_types_by_id(ids)
|
|
34
|
+
request(:findModelsById, modelIds: ids)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Gets note types by name.
|
|
38
|
+
#
|
|
39
|
+
# @param names [Array<String>] Array of note type names
|
|
40
|
+
# @return [Array<Hash>] Array of note type objects
|
|
41
|
+
def note_types_by_name(names)
|
|
42
|
+
request(:findModelsByName, modelNames: names)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Gets field names for a note type.
|
|
46
|
+
#
|
|
47
|
+
# @param note_type_name [String] Note type name
|
|
48
|
+
# @return [Array<String>] Array of field names in order
|
|
49
|
+
def note_type_field_names(note_type_name)
|
|
50
|
+
request(:modelFieldNames, modelName: note_type_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Gets field descriptions for a note type.
|
|
54
|
+
#
|
|
55
|
+
# @param note_type_name [String] Note type name
|
|
56
|
+
# @return [Array<String>] Array of description strings
|
|
57
|
+
def note_type_field_descriptions(note_type_name)
|
|
58
|
+
request(:modelFieldDescriptions, modelName: note_type_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Gets field fonts and sizes for a note type.
|
|
62
|
+
#
|
|
63
|
+
# @param note_type_name [String] Note type name
|
|
64
|
+
# @return [Hash] Field names mapped to { font:, size: }
|
|
65
|
+
def note_type_field_fonts(note_type_name)
|
|
66
|
+
request(:modelFieldFonts, modelName: note_type_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Gets fields used on templates.
|
|
70
|
+
#
|
|
71
|
+
# @param note_type_name [String] Note type name
|
|
72
|
+
# @return [Hash] Template names mapped to [questionFields, answerFields]
|
|
73
|
+
def note_type_fields_on_templates(note_type_name)
|
|
74
|
+
request(:modelFieldsOnTemplates, modelName: note_type_name)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Creates a new note type.
|
|
78
|
+
#
|
|
79
|
+
# @param name [String] Note type name
|
|
80
|
+
# @param fields [Array<String>] Field names in order
|
|
81
|
+
# @param templates [Array<Hash>] Templates with name, front, and back
|
|
82
|
+
# @param css [String, nil] CSS styling
|
|
83
|
+
# @param cloze [Boolean] true for a cloze note type
|
|
84
|
+
# @return [Hash] Complete note type object
|
|
85
|
+
def create_note_type(name:, fields:, templates:, css: nil, cloze: false)
|
|
86
|
+
params = {
|
|
87
|
+
modelName: name,
|
|
88
|
+
inOrderFields: fields,
|
|
89
|
+
cardTemplates: templates.map { |template| normalize_template(template) },
|
|
90
|
+
isCloze: cloze
|
|
91
|
+
}
|
|
92
|
+
params[:css] = css if css
|
|
93
|
+
request(:createModel, **params)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Gets templates for a note type.
|
|
97
|
+
#
|
|
98
|
+
# @param note_type_name [String] Note type name
|
|
99
|
+
# @return [Hash] Template names mapped to { Front:, Back: }
|
|
100
|
+
def note_type_templates(note_type_name)
|
|
101
|
+
request(:modelTemplates, modelName: note_type_name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Gets CSS styling for a note type.
|
|
105
|
+
#
|
|
106
|
+
# @param note_type_name [String] Note type name
|
|
107
|
+
# @return [Hash] Object with css property
|
|
108
|
+
def note_type_styling(note_type_name)
|
|
109
|
+
request(:modelStyling, modelName: note_type_name)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Updates templates for a note type.
|
|
113
|
+
#
|
|
114
|
+
# @param name [String] Note type name
|
|
115
|
+
# @param templates [Hash] Template names mapped to optional front/back values
|
|
116
|
+
# @return [nil]
|
|
117
|
+
def update_note_type_templates(name, templates)
|
|
118
|
+
request(:updateModelTemplates, model: { name: name, templates: normalize_template_updates(templates) })
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Updates CSS styling for a note type.
|
|
122
|
+
#
|
|
123
|
+
# @param name [String] Note type name
|
|
124
|
+
# @param css [String] CSS styling
|
|
125
|
+
# @return [nil]
|
|
126
|
+
def update_note_type_styling(name, css:)
|
|
127
|
+
request(:updateModelStyling, model: { name: name, css: css })
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Find and replace in note type templates/CSS.
|
|
131
|
+
#
|
|
132
|
+
# @param note_type_name [String, nil] Note type name, or nil for all note types
|
|
133
|
+
# @param find [String] Text to find
|
|
134
|
+
# @param replace [String] Replacement text
|
|
135
|
+
# @param front [Boolean] Search front templates
|
|
136
|
+
# @param back [Boolean] Search back templates
|
|
137
|
+
# @param css [Boolean] Search CSS
|
|
138
|
+
# @return [Integer] Number of note types changed
|
|
139
|
+
def find_and_replace_in_note_type(note_type_name: nil, find:, replace:, front: true, back: true, css: true)
|
|
140
|
+
request(
|
|
141
|
+
:findAndReplaceInModels,
|
|
142
|
+
modelName: note_type_name,
|
|
143
|
+
findText: find,
|
|
144
|
+
replaceText: replace,
|
|
145
|
+
front: front,
|
|
146
|
+
back: back,
|
|
147
|
+
css: css
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Renames a template.
|
|
152
|
+
#
|
|
153
|
+
# @param note_type_name [String] Note type name
|
|
154
|
+
# @param from [String] Current template name
|
|
155
|
+
# @param to [String] New template name
|
|
156
|
+
# @return [nil]
|
|
157
|
+
def rename_note_type_template(note_type_name, from:, to:)
|
|
158
|
+
request(:modelTemplateRename, modelName: note_type_name, oldTemplateName: from, newTemplateName: to)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Moves a template to a new position.
|
|
162
|
+
#
|
|
163
|
+
# @param note_type_name [String] Note type name
|
|
164
|
+
# @param template_name [String] Template name
|
|
165
|
+
# @param index [Integer] New position (0-based)
|
|
166
|
+
# @return [nil]
|
|
167
|
+
def reposition_note_type_template(note_type_name, template_name, index)
|
|
168
|
+
request(:modelTemplateReposition, modelName: note_type_name, templateName: template_name, index: index)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Adds a template to a note type.
|
|
172
|
+
#
|
|
173
|
+
# @param note_type_name [String] Note type name
|
|
174
|
+
# @param template [Hash] Template with Name, Front, Back
|
|
175
|
+
# @return [nil]
|
|
176
|
+
def add_note_type_template(note_type_name, template)
|
|
177
|
+
request(:modelTemplateAdd, modelName: note_type_name, template: normalize_template(template))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Removes a template from a note type.
|
|
181
|
+
#
|
|
182
|
+
# @param note_type_name [String] Note type name
|
|
183
|
+
# @param template_name [String] Template name
|
|
184
|
+
# @return [nil]
|
|
185
|
+
def remove_note_type_template(note_type_name, template_name)
|
|
186
|
+
request(:modelTemplateRemove, modelName: note_type_name, templateName: template_name)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Renames a field.
|
|
190
|
+
#
|
|
191
|
+
# @param note_type_name [String] Note type name
|
|
192
|
+
# @param from [String] Current field name
|
|
193
|
+
# @param to [String] New field name
|
|
194
|
+
# @return [nil]
|
|
195
|
+
def rename_note_type_field(note_type_name, from:, to:)
|
|
196
|
+
request(:modelFieldRename, modelName: note_type_name, oldFieldName: from, newFieldName: to)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Moves a field to a new position.
|
|
200
|
+
#
|
|
201
|
+
# @param note_type_name [String] Note type name
|
|
202
|
+
# @param field_name [String] Field name
|
|
203
|
+
# @param index [Integer] New position (0-based)
|
|
204
|
+
# @return [nil]
|
|
205
|
+
def reposition_note_type_field(note_type_name, field_name, index)
|
|
206
|
+
request(:modelFieldReposition, modelName: note_type_name, fieldName: field_name, index: index)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Adds a field to a note type.
|
|
210
|
+
#
|
|
211
|
+
# @param note_type_name [String] Note type name
|
|
212
|
+
# @param field_name [String] Field name
|
|
213
|
+
# @param index [Integer, nil] Position (defaults to end)
|
|
214
|
+
# @return [nil]
|
|
215
|
+
def add_note_type_field(note_type_name, field_name, index: nil)
|
|
216
|
+
params = { modelName: note_type_name, fieldName: field_name }
|
|
217
|
+
params[:index] = index if index
|
|
218
|
+
request(:modelFieldAdd, **params)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Removes a field from a note type.
|
|
222
|
+
#
|
|
223
|
+
# @param note_type_name [String] Note type name
|
|
224
|
+
# @param field_name [String] Field name
|
|
225
|
+
# @return [nil]
|
|
226
|
+
def remove_note_type_field(note_type_name, field_name)
|
|
227
|
+
request(:modelFieldRemove, modelName: note_type_name, fieldName: field_name)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Sets font for a field.
|
|
231
|
+
#
|
|
232
|
+
# @param note_type_name [String] Note type name
|
|
233
|
+
# @param field_name [String] Field name
|
|
234
|
+
# @param font [String] Font name
|
|
235
|
+
# @return [nil]
|
|
236
|
+
def set_note_type_field_font(note_type_name, field_name, font)
|
|
237
|
+
request(:modelFieldSetFont, modelName: note_type_name, fieldName: field_name, font: font)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Sets font size for a field.
|
|
241
|
+
#
|
|
242
|
+
# @param note_type_name [String] Note type name
|
|
243
|
+
# @param field_name [String] Field name
|
|
244
|
+
# @param size [Integer] Font size
|
|
245
|
+
# @return [nil]
|
|
246
|
+
def set_note_type_field_font_size(note_type_name, field_name, size)
|
|
247
|
+
request(:modelFieldSetFontSize, modelName: note_type_name, fieldName: field_name, fontSize: size)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Sets description for a field.
|
|
251
|
+
#
|
|
252
|
+
# @param note_type_name [String] Note type name
|
|
253
|
+
# @param field_name [String] Field name
|
|
254
|
+
# @param description [String] Description text
|
|
255
|
+
# @return [Boolean] true on success
|
|
256
|
+
def set_note_type_field_description(note_type_name, field_name, description)
|
|
257
|
+
request(:modelFieldSetDescription, modelName: note_type_name, fieldName: field_name, description: description)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private
|
|
261
|
+
|
|
262
|
+
TEMPLATE_KEYS = {
|
|
263
|
+
'name' => :Name,
|
|
264
|
+
'front' => :Front,
|
|
265
|
+
'back' => :Back
|
|
266
|
+
}.freeze
|
|
267
|
+
TEMPLATE_UPDATE_KEYS = {
|
|
268
|
+
'front' => :Front,
|
|
269
|
+
'back' => :Back
|
|
270
|
+
}.freeze
|
|
271
|
+
private_constant :TEMPLATE_KEYS, :TEMPLATE_UPDATE_KEYS
|
|
272
|
+
|
|
273
|
+
def normalize_template(template)
|
|
274
|
+
normalized = normalize_keys(template, TEMPLATE_KEYS, name: 'template')
|
|
275
|
+
missing_keys = TEMPLATE_KEYS.values.reject { |key| normalized.key?(key) }
|
|
276
|
+
raise ArgumentError, "missing template keys: #{missing_keys.join(', ')}" unless missing_keys.empty?
|
|
277
|
+
|
|
278
|
+
normalized
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def normalize_template_updates(templates)
|
|
282
|
+
raise ArgumentError, 'templates must be a Hash' unless templates.is_a?(Hash)
|
|
283
|
+
|
|
284
|
+
templates.to_h do |name, template|
|
|
285
|
+
normalized = normalize_keys(template, TEMPLATE_UPDATE_KEYS, name: 'template update')
|
|
286
|
+
raise ArgumentError, 'template update must include front or back' if normalized.empty?
|
|
287
|
+
|
|
288
|
+
[name, normalized]
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|