모듈:Sidebar
이 모듈에 대한 설명문서는 모듈:Sidebar/설명문서에서 만들 수 있습니다
1 require('strict')
2 local cfg = mw.loadData('Module:Sidebar/configuration')
3
4 local p = {}
5
6 local getArgs = require('Module:Arguments').getArgs
7
8 --[[
9 Categorizes calling templates and modules with a 'style' parameter of any sort
10 for tracking to convert to TemplateStyles.
11
12 TODO after a long cleanup: Catch sidebars in other namespaces than Template and Module.
13 TODO would probably want to remove /log and /archive as CS1 does
14 ]]
15 local function categorizeTemplatesWithInlineStyles(args)
16 local title = mw.title.getCurrentTitle()
17 if title.namespace ~= 10 and title.namespace ~= 828 then return '' end
18 for _, pattern in ipairs (cfg.i18n.pattern.uncategorized_conversion_titles) do
19 if title.text:match(pattern) then return '' end
20 end
21
22 for key, _ in pairs(args) do
23 if mw.ustring.find(key, cfg.i18n.pattern.style_conversion) or key == 'width' then
24 return cfg.i18n.category.conversion
25 end
26 end
27 end
28
29 --[[
30 For compatibility with the original {{sidebar with collapsible lists}}
31 implementation, which passed some parameters through {{#if}} to trim their
32 whitespace. This also triggered the automatic newline behavior.
33 ]]
34 -- See ([[meta:Help:Newlines and spaces#Automatic newline]])
35 local function trimAndAddAutomaticNewline(s)
36 s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1")
37 if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then
38 return '\n' .. s
39 else
40 return s
41 end
42 end
43
44 --[[
45 Finds whether a sidebar has a subgroup sidebar.
46 ]]
47 local function hasSubgroup(s)
48 if mw.ustring.find(s, cfg.i18n.pattern.subgroup) then
49 return true
50 else
51 return false
52 end
53 end
54
55 local function has_navbar(navbar_mode, sidebar_name)
56 return navbar_mode ~= cfg.i18n.navbar_none and
57 navbar_mode ~= cfg.i18n.navbar_off and
58 (
59 sidebar_name or
60 mw.getCurrentFrame():getParent():getTitle():gsub(cfg.i18n.pattern.sandbox, '') ~=
61 cfg.i18n.title_not_to_add_navbar
62 )
63 end
64
65 local function has_list_class(args, htmlclass)
66 local patterns = {
67 '^' .. htmlclass .. '$',
68 '%s' .. htmlclass .. '$',
69 '^' .. htmlclass .. '%s',
70 '%s' .. htmlclass .. '%s'
71 }
72
73 for arg, value in pairs(args) do
74 if type(arg) == 'string' and mw.ustring.find(arg, 'class') then
75 for _, pattern in ipairs(patterns) do
76 if mw.ustring.find(args[arg] or '', pattern) then
77 return true
78 end
79 end
80 end
81 end
82 return false
83 end
84
85 -- there are a lot of list classes in the wild, so we add their TemplateStyles
86 local function add_list_styles(args)
87 local frame = mw.getCurrentFrame()
88 local function add_list_templatestyles(htmlclass, templatestyles)
89 if has_list_class(args, htmlclass) then
90 return frame:extensionTag{
91 name = 'templatestyles', args = { src = templatestyles }
92 }
93 else
94 return ''
95 end
96 end
97
98 local plainlist_styles = add_list_templatestyles('plainlist', cfg.i18n.plainlist_templatestyles)
99 local hlist_styles = add_list_templatestyles('hlist', cfg.i18n.hlist_templatestyles)
100
101 -- a second workaround for [[phab:T303378]]
102 -- when that issue is fixed, we can actually use has_navbar not to emit the
103 -- tag here if we want
104 if has_navbar(args.navbar, args.name) and hlist_styles == '' then
105 hlist_styles = frame:extensionTag{
106 name = 'templatestyles', args = { src = cfg.i18n.hlist_templatestyles}
107 }
108 end
109
110 -- hlist -> plainlist is best-effort to preserve old Common.css ordering. [hlist_note]
111 return hlist_styles .. plainlist_styles
112 end
113
114 -- work around [[phab:T303378]]
115 -- for each arg: find all the templatestyles strip markers, insert them into a
116 -- table. then remove all templatestyles markers from the arg
117 local function move_hiding_templatestyles(args)
118 local gfind = string.gfind
119 local gsub = string.gsub
120 local templatestyles_markers = {}
121 local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
122 for k, arg in pairs(args) do
123 for marker in gfind(arg, strip_marker_pattern) do
124 table.insert(templatestyles_markers, marker)
125 end
126 args[k] = gsub(arg, strip_marker_pattern, '')
127 end
128 return templatestyles_markers
129 end
130
131 --[[
132 Main sidebar function. Takes the frame, args, and an optional collapsibleClass.
133 The collapsibleClass is and should be used only for sidebars with collapsible
134 lists, as in p.collapsible.
135 ]]
136 function p.sidebar(frame, args, collapsibleClass)
137 if not args then
138 args = getArgs(frame)
139 end
140 local hiding_templatestyles = table.concat(move_hiding_templatestyles(args))
141 local root = mw.html.create()
142 local child = args.child and mw.text.trim(args.child) == cfg.i18n.child_yes
143
144 root = root:tag('table')
145 if not child then
146 root
147 :addClass(cfg.i18n.class.sidebar)
148 -- force collapsibleclass to be sidebar-collapse otherwise output nothing
149 :addClass(collapsibleClass == cfg.i18n.class.collapse and cfg.i18n.class.collapse or nil)
150 :addClass('nomobile')
151 :addClass(args.float == cfg.i18n.float_none and cfg.i18n.class.float_none or nil)
152 :addClass(args.float == cfg.i18n.float_left and cfg.i18n.class.float_left or nil)
153 :addClass(args.wraplinks ~= cfg.i18n.wrap_true and cfg.i18n.class.wraplinks or nil)
154 :addClass(args.bodyclass or args.class)
155 :css('width', args.width or nil)
156 :cssText(args.bodystyle or args.style)
157
158 if args.outertitle then
159 root
160 :tag('caption')
161 :addClass(cfg.i18n.class.outer_title)
162 :addClass(args.outertitleclass)
163 :cssText(args.outertitlestyle)
164 :wikitext(args.outertitle)
165 end
166
167 if args.topimage then
168 local imageCell = root:tag('tr'):tag('td')
169
170 imageCell
171 :addClass(cfg.i18n.class.top_image)
172 :addClass(args.topimageclass)
173 :cssText(args.topimagestyle)
174 :wikitext(args.topimage)
175
176 if args.topcaption then
177 imageCell
178 :tag('div')
179 :addClass(cfg.i18n.class.top_caption)
180 :cssText(args.topcaptionstyle)
181 :wikitext(args.topcaption)
182 end
183 end
184
185 if args.pretitle then
186 root
187 :tag('tr')
188 :tag('td')
189 :addClass(args.topimage and cfg.i18n.class.pretitle_with_top_image
190 or cfg.i18n.class.pretitle)
191 :addClass(args.pretitleclass)
192 :cssText(args.basestyle)
193 :cssText(args.pretitlestyle)
194 :wikitext(args.pretitle)
195 end
196 else
197 root
198 :addClass(cfg.i18n.class.subgroup)
199 :addClass(args.bodyclass or args.class)
200 :cssText(args.bodystyle or args.style)
201 end
202
203 if args.title then
204 if child then
205 root
206 :wikitext(args.title)
207 else
208 root
209 :tag('tr')
210 :tag('th')
211 :addClass(args.pretitle and cfg.i18n.class.title_with_pretitle
212 or cfg.i18n.class.title)
213 :addClass(args.titleclass)
214 :cssText(args.basestyle)
215 :cssText(args.titlestyle)
216 :wikitext(args.title)
217 end
218 end
219
220 if args.image then
221 local imageCell = root:tag('tr'):tag('td')
222
223 imageCell
224 :addClass(cfg.i18n.class.image)
225 :addClass(args.imageclass)
226 :cssText(args.imagestyle)
227 :wikitext(args.image)
228
229 if args.caption then
230 imageCell
231 :tag('div')
232 :addClass(cfg.i18n.class.caption)
233 :cssText(args.captionstyle)
234 :wikitext(args.caption)
235 end
236 end
237
238 if args.above then
239 root
240 :tag('tr')
241 :tag('td')
242 :addClass(cfg.i18n.class.above)
243 :addClass(args.aboveclass)
244 :cssText(args.abovestyle)
245 :newline() -- newline required for bullet-points to work
246 :wikitext(args.above)
247 end
248
249 local rowNums = {}
250 for k, v in pairs(args) do
251 k = '' .. k
252 local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
253 if num then table.insert(rowNums, tonumber(num)) end
254 end
255 table.sort(rowNums)
256 -- remove duplicates from the list (e.g. 3 will be duplicated if both heading3
257 -- and content3 are specified)
258 for i = #rowNums, 1, -1 do
259 if rowNums[i] == rowNums[i - 1] then
260 table.remove(rowNums, i)
261 end
262 end
263
264 for i, num in ipairs(rowNums) do
265 local heading = args['heading' .. num]
266 if heading then
267 root
268 :tag('tr')
269 :tag('th')
270 :addClass(cfg.i18n.class.heading)
271 :addClass(args.headingclass)
272 :addClass(args['heading' .. num .. 'class'])
273 :cssText(args.basestyle)
274 :cssText(args.headingstyle)
275 :cssText(args['heading' .. num .. 'style'])
276 :newline()
277 :wikitext(heading)
278 end
279
280 local content = args['content' .. num]
281 if content then
282 root
283 :tag('tr')
284 :tag('td')
285 :addClass(hasSubgroup(content) and cfg.i18n.class.content_with_subgroup
286 or cfg.i18n.class.content)
287 :addClass(args.contentclass)
288 :addClass(args['content' .. num .. 'class'])
289 :cssText(args.contentstyle)
290 :cssText(args['content' .. num .. 'style'])
291 :newline()
292 :wikitext(content)
293 :done()
294 -- Without a linebreak after the </td>, a nested list like
295 -- "* {{hlist| ...}}" doesn't parse correctly.
296 :newline()
297 end
298 end
299
300 if args.below then
301 root
302 :tag('tr')
303 :tag('td')
304 :addClass(cfg.i18n.class.below)
305 :addClass(args.belowclass)
306 :cssText(args.belowstyle)
307 :newline()
308 :wikitext(args.below)
309 end
310
311 if not child and has_navbar(args.navbar, args.name) then
312 root
313 :tag('tr')
314 :tag('td')
315 :addClass(cfg.i18n.class.navbar)
316 :cssText(args.navbarstyle)
317 :wikitext(require('Module:Navbar')._navbar{
318 args.name,
319 mini = 1,
320 fontstyle = args.navbarfontstyle
321 })
322 end
323
324 local base_templatestyles = frame:extensionTag{
325 name = 'templatestyles', args = { src = cfg.i18n.templatestyles }
326 }
327
328 local templatestyles = ''
329 if args['templatestyles'] and args['templatestyles'] ~= '' then
330 templatestyles = frame:extensionTag{
331 name = 'templatestyles', args = { src = args['templatestyles'] }
332 }
333 end
334
335 local child_templatestyles = ''
336 if args['child templatestyles'] and args['child templatestyles'] ~= '' then
337 child_templatestyles = frame:extensionTag{
338 name = 'templatestyles', args = { src = args['child templatestyles'] }
339 }
340 end
341
342 local grandchild_templatestyles = ''
343 if args['grandchild templatestyles'] and args['grandchild templatestyles'] ~= '' then
344 grandchild_templatestyles = frame:extensionTag{
345 name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
346 }
347 end
348
349 return table.concat({
350 add_list_styles(args), -- see [hlist_note] above about ordering
351 base_templatestyles,
352 templatestyles,
353 child_templatestyles,
354 grandchild_templatestyles,
355 hiding_templatestyles,
356 tostring(root),
357 (child and cfg.i18n.category.child or ''),
358 categorizeTemplatesWithInlineStyles(args)
359 })
360 end
361
362 local function list_title(args, is_centered_list_titles, num)
363
364 local title_text = trimAndAddAutomaticNewline(args['list' .. num .. 'title']
365 or cfg.i18n.default_list_title)
366
367 local title
368 if is_centered_list_titles then
369 -- collapsible can be finicky, so provide some CSS/HTML to support
370 title = mw.html.create('div')
371 :addClass(cfg.i18n.class.list_title_centered)
372 :wikitext(title_text)
373 else
374 title = mw.html.create()
375 :wikitext(title_text)
376 end
377
378 local title_container = mw.html.create('div')
379 :addClass(cfg.i18n.class.list_title)
380 -- don't /need/ a listnumtitleclass because you can do
381 -- .templateclass .listnumclass .sidebar-list-title
382 :addClass(args.listtitleclass)
383 :cssText(args.basestyle)
384 :cssText(args.listtitlestyle)
385 :cssText('color: var(--color-base)')
386 :cssText(args['list' .. num .. 'titlestyle'])
387 :node(title)
388 :done()
389
390 return title_container
391 end
392
393 --[[
394 Main entry point for sidebar with collapsible lists.
395 Does the work of creating the collapsible lists themselves and including them
396 into the args.
397 ]]
398 function p.collapsible(frame)
399 local args = getArgs(frame)
400 if not args.name and
401 frame:getParent():getTitle():gsub(cfg.i18n.pattern.collapse_sandbox, '') ==
402 cfg.i18n.collapse_title_not_to_add_navbar then
403 args.navbar = cfg.i18n.navbar_none
404 end
405
406 local contentArgs = {}
407
408 local is_centered_list_titles = false
409 if args['centered list titles'] and args['centered list titles'] ~= '' then
410 is_centered_list_titles = true
411 end
412
413 for k, v in pairs(args) do
414 local num = string.match(k, '^list(%d+)$')
415 if num then
416 local expand = args.expanded and
417 (args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
418 local row = mw.html.create('div')
419 row
420 :addClass(cfg.i18n.class.list)
421 :addClass('mw-collapsible')
422 :addClass((not expand) and 'mw-collapsed' or nil)
423 :addClass(args['list' .. num .. 'class'])
424 :cssText(args.listframestyle)
425 :cssText(args['list' .. num .. 'framestyle'])
426 :node(list_title(args, is_centered_list_titles, num))
427 :tag('div')
428 :addClass(cfg.i18n.class.list_content)
429 :addClass('mw-collapsible-content')
430 -- don't /need/ a listnumstyleclass because you can do
431 -- .templatename .listnumclass .sidebar-list
432 :addClass(args.listclass)
433 :cssText(args.liststyle)
434 :cssText(args['list' .. num .. 'style'])
435 :wikitext(trimAndAddAutomaticNewline(args['list' .. num]))
436
437 contentArgs['content' .. num] = tostring(row)
438 end
439 end
440
441 for k, v in pairs(contentArgs) do
442 args[k] = v
443 end
444
445 return p.sidebar(frame, args, cfg.i18n.class.collapse)
446 end
447
448 return p