openstax_kitchen 11.0.0 → 11.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/Gemfile.lock +1 -1
  4. data/lib/kitchen/composite_page_element.rb +8 -0
  5. data/lib/kitchen/directions/bake_chapter_introductions/bake_chapter_objectives.rb +46 -0
  6. data/lib/kitchen/directions/bake_chapter_introductions/bake_chapter_outline.rb +14 -0
  7. data/lib/kitchen/directions/bake_chapter_introductions/main.rb +43 -0
  8. data/lib/kitchen/directions/bake_chapter_introductions/v1.rb +56 -0
  9. data/lib/kitchen/directions/bake_chapter_introductions/v2.rb +91 -0
  10. data/lib/kitchen/directions/bake_custom_sections/main.rb +14 -0
  11. data/lib/kitchen/directions/bake_custom_sections/v1.rb +42 -0
  12. data/lib/kitchen/directions/bake_equations.rb +2 -3
  13. data/lib/kitchen/directions/bake_example.rb +4 -5
  14. data/lib/kitchen/directions/bake_figure.rb +5 -3
  15. data/lib/kitchen/directions/bake_iframes/main.rb +11 -0
  16. data/lib/kitchen/directions/bake_iframes/v1.rb +25 -0
  17. data/lib/kitchen/directions/bake_index/main.rb +2 -2
  18. data/lib/kitchen/directions/bake_index/v1.rb +39 -7
  19. data/lib/kitchen/directions/bake_index/v1.xhtml.erb +3 -3
  20. data/lib/kitchen/directions/bake_injected_exercise.rb +18 -0
  21. data/lib/kitchen/directions/bake_injected_exercise_question.rb +71 -0
  22. data/lib/kitchen/directions/bake_link_placeholders.rb +15 -2
  23. data/lib/kitchen/directions/bake_lists_with_para.rb +3 -3
  24. data/lib/kitchen/directions/bake_notes/bake_autotitled_notes.rb +5 -5
  25. data/lib/kitchen/directions/bake_notes/bake_note_subtitle.rb +6 -2
  26. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/main.rb +13 -3
  27. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v1.rb +29 -25
  28. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v2.rb +22 -17
  29. data/lib/kitchen/directions/bake_notes/bake_numbered_notes/v3.rb +27 -22
  30. data/lib/kitchen/directions/bake_numbered_exercise/main.rb +3 -2
  31. data/lib/kitchen/directions/bake_numbered_exercise/v1.rb +6 -24
  32. data/lib/kitchen/directions/bake_numbered_table/bake_table_body.rb +3 -3
  33. data/lib/kitchen/directions/bake_numbered_table/main.rb +4 -4
  34. data/lib/kitchen/directions/bake_numbered_table/v1.rb +3 -3
  35. data/lib/kitchen/directions/bake_numbered_table/v2.rb +3 -3
  36. data/lib/kitchen/directions/bake_toc.rb +1 -1
  37. data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_exercise_section.rb +1 -7
  38. data/lib/kitchen/directions/move_solutions_to_answer_key/move_solutions_from_numbered_note.rb +1 -7
  39. data/lib/kitchen/directions/move_solutions_to_answer_key/strategies/contemporary_math.rb +17 -0
  40. data/lib/kitchen/element_base.rb +38 -2
  41. data/lib/kitchen/element_enumerator_base.rb +35 -0
  42. data/lib/kitchen/injected_question_element.rb +77 -0
  43. data/lib/kitchen/injected_question_element_enumerator.rb +21 -0
  44. data/lib/kitchen/selectors/base.rb +6 -0
  45. data/lib/kitchen/selectors/standard_1.rb +3 -0
  46. data/lib/kitchen/solution_element_enumerator.rb +21 -0
  47. data/lib/kitchen/version.rb +1 -1
  48. data/lib/locales/en.yml +6 -4
  49. data/lib/locales/es.yml +5 -4
  50. data/lib/locales/pl.yml +52 -7
  51. metadata +17 -6
  52. data/lib/kitchen/directions/bake_chapter_introductions/chapter_introduction.xhtml.erb +0 -0
  53. data/lib/kitchen/directions/bake_chapter_introductions.rb +0 -65
  54. data/lib/kitchen/directions/bake_notes/bake_note_iframes.rb +0 -27
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen
4
+ module Directions
5
+ module BakeIframes
6
+ def self.v1(outer_element:)
7
+ V1.new.bake(outer_element: outer_element)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BakeIframes
4
+ class V1
5
+ def bake(outer_element:)
6
+ iframes = outer_element.search('iframe')
7
+ return unless iframes.any?
8
+
9
+ iframes.each do |iframe|
10
+ iframe.wrap('<div class="os-has-iframe" data-type="alternatives">')
11
+ iframe.add_class('os-is-iframe')
12
+ link_ref = iframe[:src]
13
+ next unless link_ref
14
+
15
+ iframe = iframe.parent
16
+ iframe.add_class('os-has-link')
17
+ iframe.prepend(child:
18
+ <<~HTML
19
+ <a class="os-is-link" href="#{link_ref}" target="_window">#{I18n.t(:iframe_link_text)}</a>
20
+ HTML
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,8 +3,8 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeIndex
6
- def self.v1(book:)
7
- V1.new.bake(book: book)
6
+ def self.v1(book:, types: %w[main], uuid_prefix: nil)
7
+ V1.new.bake(book: book, types: types, uuid_prefix: uuid_prefix)
8
8
  end
9
9
  end
10
10
  end
@@ -65,7 +65,7 @@ module Kitchen::Directions::BakeIndex
65
65
  return -1 if force_first
66
66
  return 1 if other.force_first
67
67
 
68
- name <=> other.name
68
+ I18n.sort_strings(name, other.name)
69
69
  end
70
70
 
71
71
  protected
@@ -112,9 +112,15 @@ module Kitchen::Directions::BakeIndex
112
112
  end
113
113
  end
114
114
 
115
- def bake(book:)
115
+ def bake(book:, types: %w[main], uuid_prefix: '')
116
116
  @metadata_elements = book.metadata.children_to_keep.copy
117
- @index = Index.new
117
+ @uuid_prefix = uuid_prefix
118
+ @indexes = types.each.with_object({}) do |type, hash|
119
+ index_name = type == 'main' ? 'term' : type
120
+ hash[index_name] = Index.new
121
+ end
122
+
123
+ # Numbering of IDs doesn't depend on term type
118
124
 
119
125
  book.pages.terms.each do |term_element|
120
126
  page = term_element.ancestor(:page)
@@ -132,18 +138,44 @@ module Kitchen::Directions::BakeIndex
132
138
  add_term_to_index(term_element, page_title)
133
139
  end
134
140
 
135
- book.first('body').append(child: render(file: 'v1.xhtml.erb'))
141
+ types.each do |type|
142
+ @container_class = "os-index#{"-#{type}" unless type == 'main'}-container"
143
+ @uuid_key = "#{uuid_prefix}index#{"-#{type}" unless type == 'main'}"
144
+ @title = I18n.t("index.#{type}")
145
+
146
+ index_name = type == 'main' ? 'term' : type
147
+ @index = @indexes[index_name]
148
+
149
+ book.first('body').append(child: render(file: 'v1.xhtml.erb'))
150
+ end
136
151
  end
137
152
 
138
153
  def add_term_to_index(term_element, page_title)
139
- group_by = I18n.transliterate(term_element.text.strip[0])
154
+ type =
155
+ if !term_element.key?('index')
156
+ 'term'
157
+ elsif term_element['cxlxt:index'] == 'name'
158
+ 'name'
159
+ elsif term_element['cxlxt:index'] == 'foreign'
160
+ 'foreign'
161
+ end
162
+
163
+ if term_element.key?('reference')
164
+ term_reference = term_element['cmlnle:reference']
165
+ group_by = term_reference[0]
166
+ content = term_reference
167
+ else
168
+ group_by = I18n.transliterate(term_element.text.strip[0])
169
+ content = term_element.text
170
+ end
171
+
140
172
  group_by = I18n.t(:eob_index_symbols_group) unless group_by.match?(/[[:alpha:]]/)
141
173
  term_element['group-by'] = group_by
142
174
 
143
175
  # Add it to our index object
144
- @index.add_term(
176
+ @indexes[type].add_term(
145
177
  Term.new(
146
- text: term_element.text,
178
+ text: content,
147
179
  id: term_element.id,
148
180
  group_by: group_by,
149
181
  page_title: page_title.gsub(/\n/, '')
@@ -1,9 +1,9 @@
1
- <div class="os-eob os-index-container " data-type="composite-page" data-uuid-key="index">
1
+ <div class="os-eob <%= @container_class %>" data-type="composite-page" data-uuid-key="<%= @uuid_key %>">
2
2
  <h1 data-type="document-title">
3
- <span class="os-text"><%= I18n.t(:eob_index_title) %></span>
3
+ <span class="os-text"><%= @title %></span>
4
4
  </h1>
5
5
  <div data-type="metadata" style="display: none;">
6
- <h1 data-type="document-title" itemprop="name"><%= I18n.t(:eob_index_title) %></h1>
6
+ <h1 data-type="document-title" itemprop="name"><%= @title %></h1>
7
7
  <%= @metadata_elements.paste %>
8
8
  </div>
9
9
  <% @index.sections.each do |section| %>
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BakeInjectedExercise
4
+ def self.v1(exercise:)
5
+ V1.new.bake(exercise: exercise)
6
+ end
7
+
8
+ class V1
9
+ def bake(exercise:)
10
+ context = exercise.search('div[data-type="exercise-context"]')&.first
11
+ return unless context
12
+
13
+ # link replacement is done by BakeLinkPlaceholders
14
+ link = context.first('a').cut
15
+ context.replace_children(with: "#{I18n.t(:context_lead_text)}#{link.paste}")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kitchen::Directions::BakeInjectedExerciseQuestion
4
+ def self.v1(question:, number:, only_number_solution: false)
5
+ V1.new.bake(question: question, number: number, only_number_solution: only_number_solution)
6
+ end
7
+
8
+ class V1
9
+ def bake(question:, number:, only_number_solution:)
10
+ id = question.id
11
+
12
+ # Store label in pantry
13
+ unless only_number_solution
14
+ label_number = "#{question.ancestor(:chapter).count_in(:book)}.#{number}"
15
+ question.target_label(label_text: 'exercise', custom_content: label_number)
16
+ end
17
+
18
+ # Synthesize multiple choice solution
19
+ if question.answers
20
+ case question.answers[:type]
21
+ when 'a'
22
+ alphabet = *('a'..'z')
23
+ else
24
+ raise('Unsupported list type for multiple choice options')
25
+ end
26
+ letter_answers = question.correct_answer_letters(alphabet)
27
+ end
28
+ if letter_answers.present?
29
+ question.append(child:
30
+ <<~HTML
31
+ <div data-type="question-solution">#{letter_answers.join(', ')}</div>
32
+ HTML
33
+ )
34
+ end
35
+
36
+ # Bake question
37
+ unless only_number_solution
38
+ problem_number = "<span class='os-number'>#{number}</span>"
39
+ if question.solution
40
+ problem_number = "<a class='os-number' href='##{id}-solution'>#{number}</a>"
41
+ end
42
+ end
43
+
44
+ question.prepend(child:
45
+ <<~HTML
46
+ #{problem_number unless only_number_solution}
47
+ #{"<span class='os-divider'>. </span>" unless only_number_solution}
48
+ <div class="os-problem-container">
49
+ #{question.stimulus&.cut&.paste}
50
+ #{question.stem.cut.paste}
51
+ #{question.answers&.cut&.paste}
52
+ </div>
53
+ HTML
54
+ )
55
+
56
+ # Bake solution
57
+ solution = question.solution
58
+ return unless solution
59
+
60
+ question.add_class('os-hasSolution')
61
+ solution.id = "#{id}-solution"
62
+ solution.replace_children(with:
63
+ <<~HTML
64
+ <a class='os-number' href='##{id}'>#{number}</a>
65
+ <span class='os-divider'>. </span>
66
+ <div class="os-solution-container">#{solution.children}</div>
67
+ HTML
68
+ )
69
+ end
70
+ end
71
+ end
@@ -5,12 +5,25 @@ module Kitchen
5
5
  # Bake directions for link placeholders
6
6
  #
7
7
  module BakeLinkPlaceholders
8
- def self.v1(book:)
8
+ def self.v1(book:, cases: false)
9
9
  book.search('a').each do |anchor|
10
10
  next unless anchor.text == '[link]'
11
11
 
12
+ label_case = anchor['cmlnle:case']
12
13
  id = anchor[:href][1..-1]
13
- replacement = book.pantry(name: :link_text).get(id)
14
+
15
+ if cases
16
+ pantry_name = if anchor.key?('case')
17
+ "#{label_case}_link_text"
18
+ else
19
+ 'nominative_link_text'
20
+ end
21
+
22
+ replacement = book.pantry(name: pantry_name).get(id)
23
+ else
24
+ replacement = book.pantry(name: :link_text).get(id)
25
+ end
26
+
14
27
  if replacement.present?
15
28
  anchor.replace_children(with: replacement)
16
29
  else
@@ -3,13 +3,13 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  # Bakes lists with paragraphs
6
- # Should be used for books created by Adaptarr to put list text directly to <li>
6
+ # Should be used for books created by Adaptarr to put list content directly to <li>
7
7
  #
8
8
  module BakeListsWithPara
9
9
  def self.v1(book:)
10
10
  book.search('li').each do |item|
11
- item_content = item.first('p')&.text
12
- item.replace_children with: item_content unless item_content.nil?
11
+ item_para = item.first('p')&.cut
12
+ item.replace_children with: item_para.children unless item_para.nil?
13
13
  end
14
14
  end
15
15
  end
@@ -3,19 +3,19 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeAutotitledNotes
6
- def self.v1(book:, classes:)
6
+ def self.v1(book:, classes:, bake_subtitle: true, cases: false)
7
7
  book.notes.each do |note|
8
8
  next unless (note.classes & classes).any?
9
9
 
10
- bake_note(note: note)
10
+ bake_note(note: note, bake_subtitle: bake_subtitle, cases: cases)
11
11
  end
12
12
  end
13
13
 
14
- def self.bake_note(note:)
15
- BakeNoteIFrames.v1(note: note)
14
+ def self.bake_note(note:, bake_subtitle:, cases: false)
15
+ Kitchen::Directions::BakeIframes.v1(outer_element: note)
16
16
  note.wrap_children(class: 'os-note-body')
17
17
 
18
- BakeNoteSubtitle.v1(note: note)
18
+ BakeNoteSubtitle.v1(note: note, cases: cases) if bake_subtitle
19
19
 
20
20
  note.prepend(child:
21
21
  <<~HTML
@@ -3,14 +3,18 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeNoteSubtitle
6
- def self.v1(note:)
6
+ def self.v1(note:, cases: false)
7
7
  title = note.title&.cut
8
8
 
9
9
  return unless title
10
10
 
11
11
  # Store label information
12
12
  note_label = title.children
13
- note.pantry(name: :link_text).store note_label, label: note.id
13
+ if cases
14
+ note.target_label(label_text: 'note', custom_content: note_label.to_s, cases: cases)
15
+ else
16
+ note.target_label(custom_content: note_label.to_s)
17
+ end
14
18
 
15
19
  title.name = 'h4'
16
20
  title.add_class('os-subtitle')
@@ -3,8 +3,8 @@
3
3
  module Kitchen
4
4
  module Directions
5
5
  module BakeNumberedNotes
6
- def self.v1(book:, classes:)
7
- V1.new.bake(book: book, classes: classes)
6
+ def self.v1(book:, classes:, cases: false)
7
+ V1.new.bake(book: book, classes: classes, cases: cases)
8
8
  end
9
9
 
10
10
  def self.v2(book:, classes:)
@@ -21,6 +21,8 @@ module Kitchen
21
21
  # Used by V1, V2, V3
22
22
  def self.bake_note_exercise(note:, exercise:, divider: ' ', suppress_solution: false)
23
23
  exercise.add_class('unnumbered')
24
+ number = note.first('.os-number').text.gsub(/#/, '')
25
+
24
26
  # bake problem
25
27
  exercise.problem.wrap_children('div', class: 'os-problem-container')
26
28
  exercise.search('[data-type="commentary"]').each(&:trash)
@@ -33,11 +35,19 @@ module Kitchen
33
35
  else
34
36
  BakeNumberedExercise.bake_solution_v1(
35
37
  exercise: exercise,
36
- number: note.first('.os-number').text.gsub(/#/, ''),
38
+ number: number,
37
39
  divider: divider
38
40
  )
39
41
  end
40
42
  end
43
+
44
+ def self.bake_note_injected_question(note:, question:)
45
+ question.add_class('unnumbered')
46
+ number = note.first('.os-number').text.gsub(/#/, '')
47
+ Kitchen::Directions::BakeInjectedExerciseQuestion.v1(
48
+ question: question, number: number, only_number_solution: true
49
+ )
50
+ end
41
51
  end
42
52
  end
43
53
  end
@@ -1,37 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Kitchen::Directions::BakeNumberedNotes
4
-
5
- class V1
6
- def bake(book:, classes:)
7
- classes.each do |klass|
8
- book.chapters.notes("$.#{klass}").each do |note|
9
- bake_note(note: note)
10
- note.exercises.each do |exercise|
11
- Kitchen::Directions::BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise)
3
+ module Kitchen::Directions
4
+ module BakeNumberedNotes
5
+ class V1
6
+ def bake(book:, classes:, cases: false)
7
+ classes.each do |klass|
8
+ book.chapters.pages.notes("$.#{klass}").each do |note|
9
+ bake_note(note: note, cases: cases)
10
+ note.exercises.each do |exercise|
11
+ BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise)
12
+ end
13
+ note.injected_questions.each do |question|
14
+ BakeNumberedNotes.bake_note_injected_question(note: note, question: question)
15
+ end
12
16
  end
13
17
  end
14
18
  end
15
- end
16
19
 
17
- def bake_note(note:)
18
- note.wrap_children(class: 'os-note-body')
20
+ def bake_note(note:, cases: false)
21
+ note.wrap_children(class: 'os-note-body')
19
22
 
20
- chapter_count = note.ancestor(:chapter).count_in(:book)
21
- note_count = note.count_in(:chapter)
22
- note.prepend(child:
23
- <<~HTML
24
- <h3 class="os-title">
25
- <span class="os-title-label">#{note.autogenerated_title}</span>
26
- <span class="os-number">#{chapter_count}.#{note_count}</span>
27
- <span class="os-divider"> </span>
28
- </h3>
29
- HTML
30
- )
23
+ chapter_count = note.ancestor(:chapter).count_in(:book)
24
+ note_count = note.count_in(:chapter)
25
+ note.prepend(child:
26
+ <<~HTML
27
+ <h3 class="os-title">
28
+ <span class="os-title-label">#{note.autogenerated_title}</span>
29
+ <span class="os-number">#{chapter_count}.#{note_count}</span>
30
+ <span class="os-divider"> </span>
31
+ </h3>
32
+ HTML
33
+ )
31
34
 
32
- return unless note['use-subtitle']
35
+ return unless note['use-subtitle']
33
36
 
34
- Kitchen::Directions::BakeNoteSubtitle.v1(note: note)
37
+ BakeNoteSubtitle.v1(note: note, cases: cases)
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -1,22 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Kitchen::Directions::BakeNumberedNotes
4
- class V2
5
- def bake(book:, classes:)
6
- classes.each do |klass|
7
- book.chapters.pages.notes("$.#{klass}").each do |note|
8
- note.wrap_children(class: 'os-note-body')
9
- note_count = note.count_in(:page)
10
- note.prepend(child:
11
- <<~HTML
12
- <h3 class="os-title">
13
- <span class="os-title-label">#{note.autogenerated_title}</span>
14
- <span class="os-number">##{note_count}</span>
15
- </h3>
16
- HTML
17
- )
18
- note.exercises.each do |exercise|
19
- Kitchen::Directions::BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise, divider: '. ')
3
+ module Kitchen::Directions
4
+ module BakeNumberedNotes
5
+ class V2
6
+ def bake(book:, classes:)
7
+ classes.each do |klass|
8
+ book.chapters.pages.notes("$.#{klass}").each do |note|
9
+ note.wrap_children(class: 'os-note-body')
10
+ note_count = note.count_in(:page)
11
+ note.prepend(child:
12
+ <<~HTML
13
+ <h3 class="os-title">
14
+ <span class="os-title-label">#{note.autogenerated_title}</span>
15
+ <span class="os-number">##{note_count}</span>
16
+ </h3>
17
+ HTML
18
+ )
19
+ note.exercises.each do |exercise|
20
+ BakeNumberedNotes.bake_note_exercise(note: note, exercise: exercise, divider: '. ')
21
+ end
22
+ note.injected_questions.each do |question|
23
+ BakeNumberedNotes.bake_note_injected_question(note: note, question: question)
24
+ end
20
25
  end
21
26
  end
22
27
  end