Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
galaxyproject
GitHub Repository: galaxyproject/training-material
Path: blob/main/_plugins/gtn/boxify.rb
1677 views
1
# frozen_string_literal: true
2
3
require 'jekyll'
4
5
module Gtn
6
# Generate boxes
7
module Boxify
8
9
@@ICONS = {
10
'agenda' => '',
11
'code-in' => 'far fa-keyboard',
12
'code-out' => 'fas fa-laptop-code',
13
'comment' => 'far fa-comment-dots',
14
'details' => 'fas fa-info-circle',
15
'feedback' => 'far fa-comments',
16
'hands-on' => 'fas fa-pencil-alt',
17
'hands_on' => 'fas fa-pencil-alt',
18
'hidden' => '',
19
'matrix' => '',
20
'overview' => '',
21
'question' => 'far fa-question-circle',
22
'quote' => '',
23
'solution' => 'far fa-eye',
24
'spoken' => '',
25
'tip' => 'far fa-lightbulb',
26
'warning' => 'fas fa-exclamation-triangle',
27
}
28
29
@@ICONS_EMOJI = {
30
'tip' => '💡',
31
'code-in' => '⌨️',
32
'code-out' => '🖥',
33
'question' => '❓',
34
'solution' => '👁',
35
'warning' => '⚠️',
36
'comment' => '💬',
37
'feedback' => '⁉️',
38
'details' => '💬',
39
'hands_on' => '✏️',
40
}
41
42
@title_unique_offsets = {}
43
44
@@COLLAPSIBLE_BOXES = %w[
45
details solution tip
46
]
47
48
@@BOX_CLASSES = @@ICONS.keys.join '|'
49
@@TITLE_CLASSES = @@ICONS.keys.map { |x| "#{x}-title" }.join '|'
50
51
def self.box_classes
52
@@BOX_CLASSES
53
end
54
55
def self.title_classes
56
@@TITLE_CLASSES
57
end
58
59
def self.get_icon(icon_cls, emoji: false, a11y: false)
60
return @@ICONS_EMOJI.fetch(icon_cls, '') if emoji
61
62
icon = @@ICONS[icon_cls]
63
64
# We support announcing the proper label of the box, e.g. 'hands on box',
65
# but default to hiding this, as the icons are *mostly* decorative.
66
icon_a11y_title = icon_cls.gsub(/[-_]/, ' ')
67
icon_aria_label = a11y ? "title=\"#{icon_a11y_title} box\"" : ''
68
accessible_addition = a11y ? %(<span class="sr-only">#{icon_a11y_title} box</span>) : ''
69
70
if icon.nil?
71
%(<span class="visually-hidden"></span>)
72
elsif icon.start_with?('fa')
73
%(<i class="#{icon}" aria-hidden="true" #{icon_aria_label}></i>#{accessible_addition})
74
elsif icon.start_with?('ai')
75
%(<i class="ai #{icon}" aria-hidden="true" #{icon_aria_label}></i>#{accessible_addition})
76
end
77
end
78
79
def self.get_id(box_type, title, path)
80
box_id = "#{box_type}-#{title}"
81
box_safe = Jekyll::Utils.slugify(box_id)
82
key = "#{path}|#{box_type}|#{box_safe}"
83
84
if @title_unique_offsets.key?(key)
85
box_safe_final = "#{box_safe}-#{@title_unique_offsets[key]}"
86
else
87
@title_unique_offsets[key] = 0
88
box_safe_final = box_safe
89
end
90
@title_unique_offsets[key] += 1
91
92
box_safe_final
93
end
94
95
def self.format_box_title(title, box_type, lang = 'en', noprefix: false)
96
if box_type == 'hands_on'
97
box_type = "hands-on"
98
end
99
lang = 'en' if (lang == '') || lang.nil?
100
title_fmted = (!title.nil? && title.length.positive? ? ": #{title}" : '')
101
if noprefix && !title.nil?
102
title
103
else
104
box_title = Jekyll.sites.first.data['lang'][lang][box_type]
105
"#{box_title}#{title_fmted}"
106
end
107
end
108
109
def self.generate_collapsible_title(box_type, title, lang = 'en', key, contents: false, noprefix: false)
110
box_id = get_id(box_type, title, key)
111
box_title = format_box_title(title, box_type, lang, noprefix: noprefix)
112
refers_to_contents = contents ? '-contents' : ''
113
# These are all collapsed by default, details, tip, and solution.
114
# rubocop:disable Layout/LineLength
115
[box_id, %(
116
<div class="box-title #{box_type}-title" id="#{box_id}">
117
<button class="gtn-boxify-button #{box_type}" type="button" aria-controls="#{box_id}#{refers_to_contents}" aria-expanded="true">
118
#{get_icon(box_type)} <span>#{box_title}</span>
119
<span class="fold-unfold fa fa-minus-square"></span>
120
</button>
121
</div>
122
).split(/\n/).map(&:strip).join.strip]
123
# rubocop:enable Layout/LineLength
124
end
125
126
def self.generate_static_title(box_type, title, lang = 'en', key, noprefix: false)
127
box_id = get_id(box_type, title, key)
128
box_title = format_box_title(title, box_type, lang, noprefix: noprefix)
129
130
Jekyll.logger.debug "Static | typ=#{box_type} | t=#{title} | l=#{lang} | k=#{key}" if title.nil?
131
132
[box_id, %(
133
<div class="box-title #{box_type}-title" id="#{box_id}">
134
#{get_icon(box_type)} #{box_title}
135
</div>
136
).split(/\n/).map(&:strip).join.strip]
137
end
138
139
def self.safe_title(title)
140
title = '' if title.nil?
141
142
title.gsub(/"/, '&quot;')
143
end
144
145
def self.generate_title(box_type, title, lang = 'en', key, contents: false, noprefix: false)
146
title = safe_title(title)
147
if @@COLLAPSIBLE_BOXES.include?(box_type)
148
generate_collapsible_title(box_type, title, lang, key, contents: contents, noprefix: noprefix)
149
else
150
generate_static_title(box_type, title, lang, key, noprefix: noprefix)
151
end
152
end
153
154
def self.generate_box(box_type, title, lang = 'en', key)
155
title = safe_title(title)
156
box_id, box_title = generate_title(box_type, title, lang, key, contents: true)
157
%(
158
<div class="box #{box_type}" markdown=0>
159
#{box_title}
160
<div class="box-content" id="#{box_id}-contents" markdown=1>
161
).split(/\n/).map(&:strip).join.strip
162
end
163
164
def self.replace_elements(text, lang = 'en', key)
165
# We want to replace any `<x-title>(.*)</x-title>` bits
166
# And replace them one by one with "proper" boxes, based on generate_title.
167
#
168
# We're going to rely on never having two on one line
169
text.split("\n").map do |line|
170
line.gsub(%r{<(?<type>[a-z-]*)-title>(?<title>.*?)</[a-z-]*-title>}) do |_m|
171
title = Regexp.last_match[:title]
172
type = Regexp.last_match[:type]
173
_, box = generate_title(type, title, lang, key)
174
box
175
end
176
end.join("\n")
177
end
178
end
179
end
180
181
if $PROGRAM_NAME == __FILE__
182
require 'test/unit'
183
# Test the box ID algorithm
184
class Gtn::Boxify::BoxIdTest < Test::Unit::TestCase
185
def test_single_page
186
assert_equal(Gtn::Boxify.get_id('hands-on', 'a box', 'index.md'), 'hands-on-a-box')
187
assert_equal(Gtn::Boxify.get_id('hands-on', 'a box', 'index.md'), 'hands-on-a-box-1')
188
assert_equal(Gtn::Boxify.get_id('hands-on', 'a box', 'index.md'), 'hands-on-a-box-2')
189
assert_equal(Gtn::Boxify.get_id('hands-on', 'a box', 'index2.md'), 'hands-on-a-box')
190
assert_equal(Gtn::Boxify.get_id('hands-on', 'a box', 'index2.md'), 'hands-on-a-box-1')
191
assert_equal(Gtn::Boxify.get_id('hands-on', 'a box', 'index2.md'), 'hands-on-a-box-2')
192
193
assert_equal(Gtn::Boxify.get_id('hands-on', 'z-w-e', 'index2.md'), 'hands-on-z-w-e')
194
assert_equal(Gtn::Boxify.get_id('hands-on', 'z-w-e', 'index2.md'), 'hands-on-z-w-e-1')
195
assert_equal(Gtn::Boxify.get_id('hands-on', 'z-w-e', 'index2.md'), 'hands-on-z-w-e-2')
196
end
197
end
198
end
199
200
201