jekyll-theme-modootoday 1.0.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.
Files changed (352) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +182 -0
  3. data/LICENSE +21 -0
  4. data/README.ko.md +115 -0
  5. data/README.md +112 -0
  6. data/RULES.md +120 -0
  7. data/_includes/footer-custom.html +5 -0
  8. data/_includes/head-custom.html +1 -0
  9. data/_includes/modootoday/actions.html +17 -0
  10. data/_includes/modootoday/archive-years.html +30 -0
  11. data/_includes/modootoday/archive.html +44 -0
  12. data/_includes/modootoday/blog/card.html +23 -0
  13. data/_includes/modootoday/blog/config.html +17 -0
  14. data/_includes/modootoday/blog/list.html +13 -0
  15. data/_includes/modootoday/blog/pager.html +15 -0
  16. data/_includes/modootoday/blog/reading-time.html +10 -0
  17. data/_includes/modootoday/blog/select.html +18 -0
  18. data/_includes/modootoday/brand/icon.html +39 -0
  19. data/_includes/modootoday/brand/logo.html +7 -0
  20. data/_includes/modootoday/breadcrumb.html +22 -0
  21. data/_includes/modootoday/config.html +41 -0
  22. data/_includes/modootoday/detail/cover.html +5 -0
  23. data/_includes/modootoday/detail/cta.html +13 -0
  24. data/_includes/modootoday/detail/facts.html +31 -0
  25. data/_includes/modootoday/detail/jsonld.html +34 -0
  26. data/_includes/modootoday/directory/date-tile.html +2 -0
  27. data/_includes/modootoday/directory/filter.html +7 -0
  28. data/_includes/modootoday/directory/ics.html +17 -0
  29. data/_includes/modootoday/directory/item.html +18 -0
  30. data/_includes/modootoday/directory/items.html +4 -0
  31. data/_includes/modootoday/directory/jsonld.html +5 -0
  32. data/_includes/modootoday/directory/list.html +16 -0
  33. data/_includes/modootoday/directory/values.html +6 -0
  34. data/_includes/modootoday/doc/page-actions.html +15 -0
  35. data/_includes/modootoday/doc/pagination.html +24 -0
  36. data/_includes/modootoday/doc/sidebar-item.html +11 -0
  37. data/_includes/modootoday/doc/sidebar.html +28 -0
  38. data/_includes/modootoday/doc/twin.html +8 -0
  39. data/_includes/modootoday/docs-extra/head.html +7 -0
  40. data/_includes/modootoday/docs-extra/jsonld.html +19 -0
  41. data/_includes/modootoday/head-meta.html +90 -0
  42. data/_includes/modootoday/home-url.html +4 -0
  43. data/_includes/modootoday/i18n/ar.html +2 -0
  44. data/_includes/modootoday/i18n/de.html +2 -0
  45. data/_includes/modootoday/i18n/el.html +2 -0
  46. data/_includes/modootoday/i18n/es.html +2 -0
  47. data/_includes/modootoday/i18n/fa.html +2 -0
  48. data/_includes/modootoday/i18n/fr.html +2 -0
  49. data/_includes/modootoday/i18n/id.html +2 -0
  50. data/_includes/modootoday/i18n/it.html +2 -0
  51. data/_includes/modootoday/i18n/ja.html +2 -0
  52. data/_includes/modootoday/i18n/ko.html +2 -0
  53. data/_includes/modootoday/i18n/ml.html +2 -0
  54. data/_includes/modootoday/i18n/name.html +2 -0
  55. data/_includes/modootoday/i18n/pl.html +2 -0
  56. data/_includes/modootoday/i18n/pt-BR.html +2 -0
  57. data/_includes/modootoday/i18n/ro.html +2 -0
  58. data/_includes/modootoday/i18n/ru.html +2 -0
  59. data/_includes/modootoday/i18n/th.html +2 -0
  60. data/_includes/modootoday/i18n/tr.html +2 -0
  61. data/_includes/modootoday/i18n/uk.html +2 -0
  62. data/_includes/modootoday/i18n/ur-Latn.html +2 -0
  63. data/_includes/modootoday/i18n/vi.html +2 -0
  64. data/_includes/modootoday/i18n/zh-CN.html +2 -0
  65. data/_includes/modootoday/i18n/zh-TW.html +2 -0
  66. data/_includes/modootoday/i18n.html +163 -0
  67. data/_includes/modootoday/jsonld/article.html +15 -0
  68. data/_includes/modootoday/jsonld/author.html +8 -0
  69. data/_includes/modootoday/jsonld/breadcrumb.html +17 -0
  70. data/_includes/modootoday/jsonld/entity.html +16 -0
  71. data/_includes/modootoday/jsonld/graph.html +24 -0
  72. data/_includes/modootoday/jsonld/profile.html +20 -0
  73. data/_includes/modootoday/jsonld/props.html +13 -0
  74. data/_includes/modootoday/jsonld/publisher.html +11 -0
  75. data/_includes/modootoday/jsonld/type.html +29 -0
  76. data/_includes/modootoday/jsonld/webpage.html +17 -0
  77. data/_includes/modootoday/jsonld/website.html +9 -0
  78. data/_includes/modootoday/lang-switch.html +31 -0
  79. data/_includes/modootoday/link.html +3 -0
  80. data/_includes/modootoday/links/attrs.html +9 -0
  81. data/_includes/modootoday/links/content.html +16 -0
  82. data/_includes/modootoday/markdown/c/details.html +6 -0
  83. data/_includes/modootoday/markdown/c/faq.html +2 -0
  84. data/_includes/modootoday/markdown/c/filetree.html +3 -0
  85. data/_includes/modootoday/markdown/c/gallery.html +5 -0
  86. data/_includes/modootoday/markdown/c/linkcards.html +2 -0
  87. data/_includes/modootoday/markdown/c/params.html +3 -0
  88. data/_includes/modootoday/markdown/c/pricing.html +10 -0
  89. data/_includes/modootoday/markdown/c/quote.html +6 -0
  90. data/_includes/modootoday/markdown/c/subscribe.html +6 -0
  91. data/_includes/modootoday/markdown/components.html +114 -0
  92. data/_includes/modootoday/markdown/inline.html +46 -0
  93. data/_includes/modootoday/markdown/llms-pages.html +14 -0
  94. data/_includes/modootoday/markdown/raw-body.html +6 -0
  95. data/_includes/modootoday/markdown/sections.html +43 -0
  96. data/_includes/modootoday/marketing/head.html +6 -0
  97. data/_includes/modootoday/meta/byline.html +24 -0
  98. data/_includes/modootoday/meta/post-nav.html +16 -0
  99. data/_includes/modootoday/meta/related.html +20 -0
  100. data/_includes/modootoday/meta/series.html +11 -0
  101. data/_includes/modootoday/meta/share.html +4 -0
  102. data/_includes/modootoday/nav.html +19 -0
  103. data/_includes/modootoday/news/badge.html +4 -0
  104. data/_includes/modootoday/news/front.html +30 -0
  105. data/_includes/modootoday/news/notes.html +10 -0
  106. data/_includes/modootoday/page-meta.html +16 -0
  107. data/_includes/modootoday/pages/error.html +13 -0
  108. data/_includes/modootoday/pages/hero/scene.html +27 -0
  109. data/_includes/modootoday/pages/hero/sparks.html +6 -0
  110. data/_includes/modootoday/pages/hero.html +11 -0
  111. data/_includes/modootoday/pages/search-form.html +14 -0
  112. data/_includes/modootoday/search.json +11 -0
  113. data/_includes/modootoday/stylesheets.html +36 -0
  114. data/_includes/modootoday/t.html +7 -0
  115. data/_includes/modootoday/theme-color.html +19 -0
  116. data/_includes/modootoday/toc.html +17 -0
  117. data/_includes/modootoday/tokens.html +11 -0
  118. data/_includes/sections/showcase.html +18 -0
  119. data/_layouts/archive.html +6 -0
  120. data/_layouts/author.html +19 -0
  121. data/_layouts/blog.html +28 -0
  122. data/_layouts/book.html +21 -0
  123. data/_layouts/brand.html +21 -0
  124. data/_layouts/case-study.html +5 -0
  125. data/_layouts/changelog.html +24 -0
  126. data/_layouts/contact.html +27 -0
  127. data/_layouts/course.html +21 -0
  128. data/_layouts/default.html +87 -0
  129. data/_layouts/detail.html +31 -0
  130. data/_layouts/directory.html +12 -0
  131. data/_layouts/doc.html +27 -0
  132. data/_layouts/download.html +21 -0
  133. data/_layouts/episode.html +5 -0
  134. data/_layouts/error.html +7 -0
  135. data/_layouts/event.html +5 -0
  136. data/_layouts/events.html +6 -0
  137. data/_layouts/feature.html +18 -0
  138. data/_layouts/feed.html +35 -0
  139. data/_layouts/glossary.html +24 -0
  140. data/_layouts/home.html +7 -0
  141. data/_layouts/hub.html +16 -0
  142. data/_layouts/integrations.html +7 -0
  143. data/_layouts/job.html +5 -0
  144. data/_layouts/jobs.html +7 -0
  145. data/_layouts/legal.html +21 -0
  146. data/_layouts/links.html +18 -0
  147. data/_layouts/llms-full.html +8 -0
  148. data/_layouts/llms.html +32 -0
  149. data/_layouts/minimal.html +8 -0
  150. data/_layouts/news-article.html +6 -0
  151. data/_layouts/news.html +22 -0
  152. data/_layouts/portfolio.html +7 -0
  153. data/_layouts/post.html +54 -0
  154. data/_layouts/pricing.html +32 -0
  155. data/_layouts/project.html +5 -0
  156. data/_layouts/redirect.html +21 -0
  157. data/_layouts/reference.html +24 -0
  158. data/_layouts/resume.html +50 -0
  159. data/_layouts/roadmap.html +22 -0
  160. data/_layouts/search.html +8 -0
  161. data/_layouts/sponsors.html +7 -0
  162. data/_layouts/team.html +7 -0
  163. data/_layouts/twin.html +2 -0
  164. data/_layouts/wide.html +7 -0
  165. data/_sass/modootoday/_access.scss +12 -0
  166. data/_sass/modootoday/_core.scss +18 -0
  167. data/_sass/modootoday/_settings.scss +5 -0
  168. data/_sass/modootoday/_sheet.scss +6 -0
  169. data/_sass/modootoday/base/_bidi.scss +8 -0
  170. data/_sass/modootoday/base/_code.scss +18 -0
  171. data/_sass/modootoday/base/_interaction.scss +6 -0
  172. data/_sass/modootoday/base/_prose.scss +32 -0
  173. data/_sass/modootoday/base/_reset.scss +14 -0
  174. data/_sass/modootoday/base/_table.scss +8 -0
  175. data/_sass/modootoday/blog/_article.scss +27 -0
  176. data/_sass/modootoday/blog/_cards.scss +36 -0
  177. data/_sass/modootoday/blog/_index.scss +10 -0
  178. data/_sass/modootoday/blog/_news.scss +26 -0
  179. data/_sass/modootoday/blog/_post.scss +9 -0
  180. data/_sass/modootoday/components/core/chrome/_banner.scss +7 -0
  181. data/_sass/modootoday/components/core/chrome/_breadcrumb.scss +10 -0
  182. data/_sass/modootoday/components/core/chrome/_nav.scss +39 -0
  183. data/_sass/modootoday/components/core/chrome/_pagination.scss +10 -0
  184. data/_sass/modootoday/components/core/chrome/_theme-toggle.scss +16 -0
  185. data/_sass/modootoday/components/core/inline/_badge.scss +15 -0
  186. data/_sass/modootoday/components/core/inline/_callout.scss +14 -0
  187. data/_sass/modootoday/components/core/inline/_code-copy.scss +11 -0
  188. data/_sass/modootoday/components/core/inline/_code.scss +11 -0
  189. data/_sass/modootoday/components/core/inline/_cta.scss +11 -0
  190. data/_sass/modootoday/components/core/inline/_embed.scss +6 -0
  191. data/_sass/modootoday/components/core/inline/_figure.scss +9 -0
  192. data/_sass/modootoday/components/core/inline/_footnotes.scss +6 -0
  193. data/_sass/modootoday/components/core/inline/_hero.scss +8 -0
  194. data/_sass/modootoday/components/core/inline/_kbd.scss +6 -0
  195. data/_sass/modootoday/components/core/inline/_progress.scss +8 -0
  196. data/_sass/modootoday/components/doc/_page-actions.scss +10 -0
  197. data/_sass/modootoday/components/doc/_sidebar.scss +31 -0
  198. data/_sass/modootoday/components/doc/_toc.scss +19 -0
  199. data/_sass/modootoday/components/loading/_loading.scss +33 -0
  200. data/_sass/modootoday/components/search/_search.scss +36 -0
  201. data/_sass/modootoday/components/section/showcase/_card.scss +15 -0
  202. data/_sass/modootoday/components/section/showcase/_cta.scss +7 -0
  203. data/_sass/modootoday/components/section/showcase/_gallery.scss +15 -0
  204. data/_sass/modootoday/components/section/showcase/_linkcards.scss +12 -0
  205. data/_sass/modootoday/components/section/showcase/_pricing.scss +12 -0
  206. data/_sass/modootoday/components/section/showcase/_quote.scss +7 -0
  207. data/_sass/modootoday/components/section/showcase/_subscribe.scss +9 -0
  208. data/_sass/modootoday/components/section/structure/_accordion.scss +15 -0
  209. data/_sass/modootoday/components/section/structure/_details.scss +15 -0
  210. data/_sass/modootoday/components/section/structure/_faq.scss +5 -0
  211. data/_sass/modootoday/components/section/structure/_filetree.scss +13 -0
  212. data/_sass/modootoday/components/section/structure/_params.scss +8 -0
  213. data/_sass/modootoday/components/section/structure/_steps.scss +13 -0
  214. data/_sass/modootoday/components/section/structure/_tabs.scss +21 -0
  215. data/_sass/modootoday/components/section/structure/_timeline.scss +10 -0
  216. data/_sass/modootoday/detail/_index.scss +8 -0
  217. data/_sass/modootoday/detail/_page.scss +25 -0
  218. data/_sass/modootoday/directory/_filter.scss +12 -0
  219. data/_sass/modootoday/directory/_grid.scss +18 -0
  220. data/_sass/modootoday/directory/_index.scss +11 -0
  221. data/_sass/modootoday/directory/_presets.scss +16 -0
  222. data/_sass/modootoday/docs-extra/_index.scss +7 -0
  223. data/_sass/modootoday/docs-extra/_page.scss +37 -0
  224. data/_sass/modootoday/hero/_index.scss +6 -0
  225. data/_sass/modootoday/hero/_layout.scss +28 -0
  226. data/_sass/modootoday/hero/_scene.scss +20 -0
  227. data/_sass/modootoday/kit/_button.scss +8 -0
  228. data/_sass/modootoday/layout/_page-meta.scss +9 -0
  229. data/_sass/modootoday/layout/_safe-area.scss +9 -0
  230. data/_sass/modootoday/layout/_shell.scss +56 -0
  231. data/_sass/modootoday/marketing/_forms.scss +11 -0
  232. data/_sass/modootoday/marketing/_index.scss +10 -0
  233. data/_sass/modootoday/marketing/_page.scss +31 -0
  234. data/_sass/modootoday/marketing/_pricing.scss +15 -0
  235. data/_sass/modootoday/pages/_actions.scss +11 -0
  236. data/_sass/modootoday/pages/_error.scss +7 -0
  237. data/_sass/modootoday/pages/_index.scss +11 -0
  238. data/_sass/modootoday/pages/_legal.scss +17 -0
  239. data/_sass/modootoday/pages/_search.scss +9 -0
  240. data/_sass/modootoday/pages/_shells.scss +11 -0
  241. data/_sass/modootoday/resume/_index.scss +5 -0
  242. data/_sass/modootoday/resume/_page.scss +27 -0
  243. data/_sass/modootoday/tokens/_emit.scss +15 -0
  244. data/_sass/modootoday/tokens/_primitive.scss +18 -0
  245. data/_sass/modootoday/tokens/_semantic.scss +37 -0
  246. data/_sass/modootoday/tools/_functions.scss +2 -0
  247. data/_sass/modootoday/tools/_img-dark.scss +12 -0
  248. data/_sass/modootoday/tools/_mixins.scss +8 -0
  249. data/_sass/modootoday.scss +40 -0
  250. data/assets/css/modootoday/doc.scss +10 -0
  251. data/assets/css/modootoday/loading.scss +8 -0
  252. data/assets/css/modootoday/search.scss +8 -0
  253. data/assets/css/modootoday/showcase/card.scss +7 -0
  254. data/assets/css/modootoday/showcase/cta.scss +7 -0
  255. data/assets/css/modootoday/showcase/gallery.scss +7 -0
  256. data/assets/css/modootoday/showcase/linkcards.scss +7 -0
  257. data/assets/css/modootoday/showcase/pricing.scss +7 -0
  258. data/assets/css/modootoday/showcase/quote.scss +7 -0
  259. data/assets/css/modootoday/showcase/subscribe.scss +7 -0
  260. data/assets/css/modootoday/structure/accordion.scss +7 -0
  261. data/assets/css/modootoday/structure/details.scss +7 -0
  262. data/assets/css/modootoday/structure/faq.scss +7 -0
  263. data/assets/css/modootoday/structure/filetree.scss +7 -0
  264. data/assets/css/modootoday/structure/params.scss +7 -0
  265. data/assets/css/modootoday/structure/steps.scss +7 -0
  266. data/assets/css/modootoday/structure/tabs.scss +7 -0
  267. data/assets/css/modootoday/structure/timeline.scss +7 -0
  268. data/assets/css/modootoday-blog.scss +7 -0
  269. data/assets/css/modootoday-detail.scss +7 -0
  270. data/assets/css/modootoday-directory.scss +7 -0
  271. data/assets/css/modootoday-docs-extra.scss +7 -0
  272. data/assets/css/modootoday-hero.scss +7 -0
  273. data/assets/css/modootoday-marketing.scss +7 -0
  274. data/assets/css/modootoday-pages.scss +7 -0
  275. data/assets/css/modootoday-resume.scss +7 -0
  276. data/assets/css/modootoday.scss +17 -0
  277. data/assets/js/modootoday/accordion.js +13 -0
  278. data/assets/js/modootoday/code-copy.js +29 -0
  279. data/assets/js/modootoday/menu.js +19 -0
  280. data/assets/js/modootoday/scroll-region.js +14 -0
  281. data/assets/js/modootoday/search-keys.js +18 -0
  282. data/assets/js/modootoday/search.js +31 -0
  283. data/assets/js/modootoday/share.js +16 -0
  284. data/assets/js/modootoday/tabs.js +26 -0
  285. data/assets/js/modootoday/theme-init.js +2 -0
  286. data/assets/js/modootoday/theme-toggle.js +17 -0
  287. data/assets/modootoday/apple-touch-icon.png +0 -0
  288. data/assets/modootoday/favicon-32.png +0 -0
  289. data/assets/modootoday/favicon.svg +1 -0
  290. data/assets/modootoday/hero/dawn-1280.webp +0 -0
  291. data/assets/modootoday/hero/dawn-640.webp +0 -0
  292. data/assets/modootoday/hero/dawn-960.webp +0 -0
  293. data/exe/modootoday-check +7 -0
  294. data/i18n/ar.yml +130 -0
  295. data/i18n/de.yml +130 -0
  296. data/i18n/el.yml +130 -0
  297. data/i18n/en.yml +131 -0
  298. data/i18n/es.yml +130 -0
  299. data/i18n/fa.yml +130 -0
  300. data/i18n/fr.yml +130 -0
  301. data/i18n/glossary.yml +61 -0
  302. data/i18n/id.yml +130 -0
  303. data/i18n/it.yml +130 -0
  304. data/i18n/ja.yml +130 -0
  305. data/i18n/ko.yml +130 -0
  306. data/i18n/languages.yml +25 -0
  307. data/i18n/ml.yml +130 -0
  308. data/i18n/pl.yml +130 -0
  309. data/i18n/pt-BR.yml +130 -0
  310. data/i18n/ro.yml +130 -0
  311. data/i18n/ru.yml +130 -0
  312. data/i18n/th.yml +130 -0
  313. data/i18n/tr.yml +130 -0
  314. data/i18n/uk.yml +130 -0
  315. data/i18n/ur-Latn.yml +130 -0
  316. data/i18n/vi.yml +130 -0
  317. data/i18n/zh-CN.yml +130 -0
  318. data/i18n/zh-TW.yml +130 -0
  319. data/lib/jekyll-theme-modootoday.rb +59 -0
  320. data/lib/modootoday/check/a11y/content.rb +57 -0
  321. data/lib/modootoday/check/a11y/new_window.rb +64 -0
  322. data/lib/modootoday/check/blog.rb +72 -0
  323. data/lib/modootoday/check/build_output.rb +39 -0
  324. data/lib/modootoday/check/built_parts.rb +40 -0
  325. data/lib/modootoday/check/cli.rb +61 -0
  326. data/lib/modootoday/check/component_rules.rb +54 -0
  327. data/lib/modootoday/check/context.rb +41 -0
  328. data/lib/modootoday/check/gh_render.rb +21 -0
  329. data/lib/modootoday/check/github_pages.rb +23 -0
  330. data/lib/modootoday/check/i18n.rb +92 -0
  331. data/lib/modootoday/check/layouts/detail.rb +52 -0
  332. data/lib/modootoday/check/layouts/directory.rb +36 -0
  333. data/lib/modootoday/check/layouts/docs_extra.rb +84 -0
  334. data/lib/modootoday/check/layouts/marketing.rb +64 -0
  335. data/lib/modootoday/check/layouts/shape.rb +38 -0
  336. data/lib/modootoday/check/layouts/shells.rb +47 -0
  337. data/lib/modootoday/check/layouts.rb +37 -0
  338. data/lib/modootoday/check/markdown.rb +39 -0
  339. data/lib/modootoday/check/page_sections.rb +72 -0
  340. data/lib/modootoday/check/page_syntax.rb +104 -0
  341. data/lib/modootoday/check/page_values.rb +30 -0
  342. data/lib/modootoday/check/pages.rb +31 -0
  343. data/lib/modootoday/check/registry.rb +69 -0
  344. data/lib/modootoday/check/site_config.rb +74 -0
  345. data/lib/modootoday/check/slots.rb +50 -0
  346. data/lib/modootoday/check/structured_data.rb +97 -0
  347. data/lib/modootoday/check/stylesheets.rb +28 -0
  348. data/lib/modootoday/check/twins.rb +69 -0
  349. data/lib/modootoday/check.rb +22 -0
  350. data/lib/modootoday/schema/rules.rb +49 -0
  351. data/lib/modootoday/schema/vocab.txt +2478 -0
  352. metadata +420 -0
data/i18n/zh-TW.yml ADDED
@@ -0,0 +1,130 @@
1
+ # jekyll-theme-modootoday interface strings (Traditional Chinese). Copy to _data/i18n/zh-TW.yml; keys are fixed, values are yours.
2
+ language: 繁體中文
3
+ direction: ltr
4
+ skip: 跳至主要內容
5
+ new_window: {hint: "在新視窗中開啟", open: "在新視窗中開啟"}
6
+ callout: {note: 注意, tip: 提示, important: 重要, warning: 警告, caution: 小心}
7
+ status: {available: 可用, partial: 部分支援, planned: 規劃中}
8
+ task: {done: 已完成, open: 未完成}
9
+ tabs: {label: 選項}
10
+ toc: {label: 本頁內容}
11
+ theme: {toggle: 深色模式}
12
+ sidebar: {label: "文件"}
13
+ page_actions: {label: "頁面操作", copy: "複製頁面", copy_ai: "複製給 AI", view: "以 Markdown 檢視", source: "來源"}
14
+ breadcrumb: {label: 導覽路徑}
15
+ nav: {label: 主選單, back_to_top: 回到頂端}
16
+ pagination: {label: 換頁, prev: 上一頁, next: 下一頁}
17
+ code: {copy: 複製, copied: 已複製, label: 程式碼, file: 檔案}
18
+ table: {label: 表格}
19
+ search: {label: 搜尋}
20
+ page: {edit: 編輯此頁, updated: 最後更新}
21
+ languages: {label: 語言}
22
+ post: {date_format: "%Y年%-m月%-d日", author: 關於作者, related: 相關文章, share: 分享}
23
+ fallback: {notice: 本頁尚未翻譯,以下顯示英文原文。}
24
+ archive:
25
+ years: "年份"
26
+ tags: "標籤"
27
+ categories: "分類"
28
+ posts: "篇文章"
29
+ empty: "目前還沒有文章。"
30
+ blog:
31
+ featured: "精選"
32
+ read_more: "閱讀全文"
33
+ reading_time: "閱讀約 [n] 分鐘"
34
+ by: "作者"
35
+ updated: "更新於"
36
+ newer: "較新文章"
37
+ older: "較舊文章"
38
+ related: "相關文章"
39
+ series: "系列"
40
+ series_part: "第 [n] 篇,共 [total] 篇"
41
+ share: "複製連結"
42
+ copied: "已複製連結"
43
+ feed: "訂閱源"
44
+ latest: "最新"
45
+ breaking: "快訊"
46
+ live: "直播"
47
+ correction: "更正"
48
+ sources: "來源"
49
+ more: "更多[section]"
50
+ authors: "作者"
51
+ filetree: {label: 檔案結構}
52
+ gallery: {close: 關閉}
53
+ embed: {title: 嵌入內容}
54
+ pricing: {recommended: 推薦}
55
+ banner: {label: 公告}
56
+ footnote: {back: 返回正文}
57
+ progress: {label: 進度}
58
+ subscribe: {email: 電子郵件地址, submit: 訂閱}
59
+ params: {required: 必填}
60
+ loading: {label: 載入中…}
61
+ pages:
62
+ not_found: "找不到頁面"
63
+ not_found_text: "您要求的頁面不存在或已移動。"
64
+ gone: "頁面已移除"
65
+ gone_text: "此頁面已被刻意移除,不會再恢復。"
66
+ error: "發生問題"
67
+ error_text: "伺服器無法顯示此頁面。請稍後再試。"
68
+ home: "返回首頁"
69
+ moved: "此頁面已移動。"
70
+ moved_link: "前往新頁面"
71
+ search_hint: "輸入至少兩個字元開始搜尋。"
72
+ layouts:
73
+ all: "全部"
74
+ filter: "篩選"
75
+ role: "角色"
76
+ client: "客戶"
77
+ year: "年份"
78
+ stack: "工具"
79
+ links: "連結"
80
+ location: "地點"
81
+ employment: "僱用類型"
82
+ salary: "薪資"
83
+ posted: "發布日期"
84
+ deadline: "截止日期"
85
+ apply: "應徵"
86
+ starts: "開始"
87
+ ends: "結束"
88
+ venue: "地點"
89
+ online: "線上"
90
+ agenda: "議程"
91
+ tickets: "取得門票"
92
+ calendar: "加入行事曆"
93
+ episode: "第 [n] 集"
94
+ season: "第 [n] 季"
95
+ duration: "長度"
96
+ listen: "收聽"
97
+ transcript: "逐字稿"
98
+ monthly: "月繳"
99
+ yearly: "年繳"
100
+ billing: "計費週期"
101
+ popular: "最受歡迎"
102
+ download: "下載"
103
+ version: "版本"
104
+ colors: "顏色"
105
+ assets: "素材"
106
+ send: "送出"
107
+ name: "姓名"
108
+ email: "電子郵件"
109
+ message: "訊息"
110
+ contact: "聯絡方式"
111
+ added: "新增"
112
+ changed: "變更"
113
+ fixed: "修正"
114
+ removed: "移除"
115
+ security: "安全性"
116
+ now: "現在"
117
+ next: "下一步"
118
+ later: "之後"
119
+ letters: "跳到字母"
120
+ lessons: "課程"
121
+ minutes: "[n] 分鐘"
122
+ module: "單元 [n]"
123
+ chapter: "第 [n] 章"
124
+ effective: "生效日期"
125
+ experience: "工作經歷"
126
+ education: "學歷"
127
+ skills: "技能"
128
+ present: "至今"
129
+ loading: "載入中…"
130
+ contents: "目錄"
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # md-components v1 — optional generator for Jekyll builds that can load plugins
4
+ # (Jekyll 4 / GitHub Actions; NOT the classic GitHub Pages build, which only runs whitelisted plugins).
5
+ # Enable with `plugins: [jekyll-theme-modootoday]`. For every Markdown page without a stub it adds the
6
+ # same virtual page a hand-written stub would be:
7
+ # <name>.md.txt {layout: twin, twin_of: <name>.md, permalink: /<name>/index.md}
8
+ # llms.txt / llms-full.txt at the root and in each /<lang>/ directory (layouts llms / llms-full)
9
+ # so output is identical to the stub-based (GitHub Pages) setup. Existing pages/stubs always win.
10
+ require "jekyll"
11
+
12
+ module JekyllThemeModootoday
13
+ class Stub < Jekyll::PageWithoutAFile
14
+ def initialize(site, dir, name, data)
15
+ super(site, site.source, dir, name)
16
+ self.content = ""
17
+ self.data.merge!(data)
18
+ self.data["sitemap"] = false
19
+ end
20
+ end
21
+
22
+ class Generator < Jekyll::Generator
23
+ safe true
24
+ priority :lowest
25
+
26
+ def generate(site)
27
+ cfg = site.config["modootoday"] || {}
28
+ urls = site.pages.map(&:url).to_h { |u| [u, true] }
29
+ add = lambda do |dir, name, data|
30
+ page = Stub.new(site, dir, name, data)
31
+ next if urls[page.url]
32
+
33
+ urls[page.url] = true
34
+ site.pages << page
35
+ end
36
+ md = site.pages.select { |p| p.is_a?(Jekyll::Page) && !p.is_a?(Stub) && p.ext == ".md" && p.name != "README.md" }
37
+ raw = site.static_files.to_h { |f| [f.relative_path.sub(%r{\A/}, ""), true] }
38
+ if cfg.fetch("twins", true)
39
+ md.each do |p|
40
+ next if p.data["twin"] == false
41
+ next if raw[p.relative_path.sub(%r{\A/}, "")] # front-matter-less mode: the source itself is the twin
42
+
43
+ url = p.url.end_with?("/") ? "#{p.url}index.md" : p.url.sub(/\.html\z/, ".md")
44
+ d = File.dirname(p.path)
45
+ add.call(d == "." ? "" : d, "#{p.name}.txt", "layout" => "twin", "twin_of" => p.name, "permalink" => url)
46
+ end
47
+ end
48
+ return unless cfg.fetch("llms", true)
49
+
50
+ langs = (site.data["i18n"] || {}).keys.select { |l| Dir.exist?(File.join(site.source, l)) }
51
+ ([""] + langs).each do |l|
52
+ url = l.empty? ? "/" : "/#{l}/"
53
+ extra = l.empty? ? {} : { "lang" => l }
54
+ add.call(l, "llms.txt", { "layout" => "llms", "permalink" => "#{url}llms.txt" }.merge(extra))
55
+ add.call(l, "llms-full.txt", { "layout" => "llms-full", "permalink" => "#{url}llms-full.txt" }.merge(extra))
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # Authoring-side screen-reader rules (WCAG 1.1.1, 1.3.1, 2.4.4, 4.1.1-style ids) on one page's Markdown,
6
+ # code already blanked. Errors: images with no alt, empty links, duplicate ids. Warnings: heading skips,
7
+ # vague link text, empty Markdown alt (decorative is allowed but should be deliberate).
8
+ module A11yContent
9
+ # English list mirrors Lighthouse SEO "link-text" (a hit there costs the SEO score).
10
+ VAGUE = /\A(click here|click this|here|right here|more|read more|learn more|see more|more info|more information|information|go|start|this|continue|link|여기|여기를 클릭|더 보기|こちら|ここ|詳しくはこちら)\z/i
11
+
12
+ module_function
13
+
14
+ def call(ctx, f, code)
15
+ r = ctx.rel(f)
16
+ images(ctx, r, code)
17
+ links(ctx, r, code)
18
+ headings(ctx, r, code)
19
+ ids(ctx, r, code)
20
+ end
21
+
22
+ def each_line(code, re)
23
+ code.each_line.with_index(1) { |l, n| l.scan(re) { yield n, Regexp.last_match } }
24
+ end
25
+
26
+ def images(ctx, r, code)
27
+ each_line(code, /<img\b(?![^>]*\balt=)[^>]*>/i) { |n, _| ctx.errs << "#{r}:#{n}: <img> without alt (screen readers read the file name); add alt=\"…\" or alt=\"\" if decorative" }
28
+ each_line(code, /!\[\s*\]\(/) { |n, _| ctx.warns << "#{r}:#{n}: image with empty alt ![](…) is treated as decorative; describe it unless it adds no information" }
29
+ end
30
+
31
+ def links(ctx, r, code)
32
+ each_line(code, /(?<!!)\[([^\]]*)\]\([^)]*\)/) do |n, m|
33
+ t = m[1].gsub(/[*_`]/, "").strip
34
+ next ctx.errs << "#{r}:#{n}: empty link text [](…) (announced as just \"link\"); write what it opens" if t.empty?
35
+ ctx.warns << "#{r}:#{n}: vague link text \"#{t}\"; name the destination so it makes sense out of context" if t.match?(VAGUE)
36
+ end
37
+ each_line(code, %r{<a\b[^>]*>\s*</a>}i) { |n, _| ctx.errs << "#{r}:#{n}: empty <a></a> link" }
38
+ end
39
+
40
+ # The layout supplies the page <h1> (title); content starts at ## and must not skip levels.
41
+ def headings(ctx, r, code)
42
+ prev = 1
43
+ code.each_line.with_index(1) do |l, n|
44
+ next unless (m = l.match(/^ {0,3}(#{"#"}{1,6})\s+\S/))
45
+ lv = m[1].size
46
+ ctx.warns << "#{r}:#{n}: heading jumps from h#{prev} to h#{lv}; screen-reader users navigate by level, do not skip" if lv > prev + 1
47
+ prev = lv
48
+ end
49
+ end
50
+
51
+ def ids(ctx, r, code)
52
+ seen = Hash.new(0)
53
+ each_line(code, /\bid=["']([^"']+)["']/) { |n, m| ctx.errs << "#{r}:#{n}: duplicate id=\"#{m[1]}\" (breaks labels, skip links and anchors)" if (seen[m[1]] += 1) == 2 }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "date"
5
+
6
+ module Modootoday
7
+ module Check
8
+ # Links that open a new window, and sample links in demo pages.
9
+ # Source: raw <a target="_blank"> without rel="noopener" (error) or without the theme hint (warning; use
10
+ # {: .mt-new-window} or new_window: true); a non-boolean new_window: (error); in a demo page (front matter
11
+ # noindex: true) a link or url: that leaves the sample (root path, other site) or is empty (warning).
12
+ # Built site: target="_blank" without rel="noopener" (error) or without aria-describedby (warning); a demo page
13
+ # link that resolves to the site root (warning: write "#").
14
+ module NewWindow
15
+ BLANK = /<a\b[^>]*\btarget=["']_blank["'][^>]*>/i
16
+
17
+ module_function
18
+
19
+ def call(ctx, f, text, code)
20
+ r = ctx.rel(f)
21
+ code.each_line.with_index(1) do |l, n|
22
+ l.scan(BLANK) do |a|
23
+ next if a.include?("mt-new-window") || a.include?("data-mt-new-window")
24
+ next ctx.errs << "#{r}:#{n}: target=\"_blank\" without rel=\"noopener\"; add it or use [text](url \"new-window\")" unless a =~ /\brel=["'][^"']*noopener/
25
+ ctx.warns << "#{r}:#{n}: new-window link without the spoken hint; use [text](url \"new-window\") or new_window: true"
26
+ end
27
+ end
28
+ fm = front(text)
29
+ text[/\A---\s*\n.*?\n---/m].to_s.scan(/\bnew_window:\s*([^,}\s]+)/) { ctx.errs << "#{r}: new_window: must be true or false (got #{$1})" unless %w[true false].include?($1) }
30
+ demo(ctx, r, text, code) if fm["noindex"] == true
31
+ end
32
+
33
+ def front(text)
34
+ m = text.match(/\A---\s*\n(.*?)\n---/m)
35
+ m ? (YAML.safe_load(m[1], permitted_classes: [Date, Time]) || {}) : {}
36
+ rescue Psych::Exception
37
+ {}
38
+ end
39
+
40
+ def demo(ctx, r, text, code)
41
+ urls = code.scan(/(?<!!)\[[^\]]*\]\(([^)]*)\)/).flatten + text.scan(/"url":\s*"([^"]*)"|\burl:\s*"?([^",}\s]*)/).flatten.compact
42
+ urls.each do |u|
43
+ next ctx.warns << "#{r}: demo link with no target; write \"#\" for a sample link" if u.strip.empty?
44
+ next unless u.start_with?("/") || u =~ %r{\A\w+://}
45
+ ctx.warns << "#{r}: demo link #{u} leaves the sample; write \"#\" unless it is the real destination"
46
+ end
47
+ end
48
+
49
+ def built(ctx)
50
+ Dir.glob("#{ctx.site}/**/*.html").each do |f|
51
+ t = File.read(f).gsub(%r{<pre\b.*?</pre>}m, "")
52
+ t.scan(BLANK) do |a|
53
+ next ctx.errs << "#{f}: target=\"_blank\" without rel=\"noopener\"" unless a =~ /\brel=["'][^"']*noopener/
54
+ ctx.warns << "#{f}: new-window link without aria-describedby hint" unless a.include?("aria-describedby")
55
+ end
56
+ next unless t.include?('name="robots" content="noindex"') && !t.include?('http-equiv="refresh"')
57
+ t.scan(/<main\b.*<\/main>/m).join.gsub(%r{<nav[^>]*breadcrumb.*?</nav>}m, "").scan(/<a\b[^>]*\bhref="([^"]*)"/) do |(h)|
58
+ ctx.warns << "#{f}: demo link #{h} resolves to the site root; write \"#\" for a sample link" if h =~ %r{\A(/[\w.-]+)?/#?\z}
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # Blog and news: modootoday.blog / modootoday.news settings, _data/authors.yml and post or listing front matter.
6
+ module Blog
7
+ VARIANTS = %w[list grid magazine].freeze
8
+ LISTING = %w[blog news author].freeze
9
+ POST = %w[post news-article].freeze
10
+
11
+ module_function
12
+
13
+ def call(ctx)
14
+ settings(ctx)
15
+ authors = load_authors(ctx)
16
+ ctx.mds.each { |f| page(ctx, f, authors) }
17
+ end
18
+
19
+ def settings(ctx)
20
+ mtc, errs = ctx.mtc, ctx.errs
21
+ return unless mtc.is_a?(Hash)
22
+ if mtc.key?("blog")
23
+ b = mtc["blog"]
24
+ k0 = "_config.yml: modootoday.blog"
25
+ return errs << "#{k0} must be a map" unless b.is_a?(Hash)
26
+ (b.keys - %w[variant per_page excerpt reading_time related share]).each { |k| errs << "#{k0}: unknown key '#{k}'" }
27
+ errs << "#{k0}.variant must be one of #{VARIANTS.join(', ')}" if b.key?("variant") && !VARIANTS.include?(b["variant"].to_s)
28
+ %w[per_page related].each { |k| errs << "#{k0}.#{k} must be a whole number" if b.key?(k) && !(b[k].is_a?(Integer) && b[k] >= 0) }
29
+ %w[excerpt reading_time share].each { |k| errs << "#{k0}.#{k} must be true or false" if b.key?(k) && ![true, false].include?(b[k]) }
30
+ end
31
+ return unless mtc.key?("news")
32
+ n = mtc["news"]
33
+ ok = n.is_a?(Hash) && (n.keys - %w[sections]).empty? && (!n.key?("sections") || n["sections"].is_a?(Array))
34
+ errs << "_config.yml: modootoday.news must be a map with sections: [category names]" unless ok
35
+ end
36
+
37
+ def load_authors(ctx)
38
+ f = "#{ctx.src}/_data/authors.yml"
39
+ return nil unless File.exist?(f)
40
+ data = (YAML.safe_load(File.read(f)) rescue (ctx.errs << "_data/authors.yml: YAML error"; nil))
41
+ return nil unless data
42
+ return (ctx.errs << "_data/authors.yml must map author ids to {name:, url:, page:, bio:}"; nil) unless data.is_a?(Hash)
43
+ data.each do |id, a|
44
+ next ctx.errs << "_data/authors.yml: #{id} must be a map with name:" unless a.is_a?(Hash) && a["name"].to_s != ""
45
+ ctx.errs << "_data/authors.yml: #{id}.url must be an http(s) URL" if a["url"] && a["url"].to_s !~ %r{\Ahttps?://\S+\z}
46
+ ctx.errs << "_data/authors.yml: #{id}.page must be a site path starting with /" if a["page"] && !a["page"].to_s.start_with?("/")
47
+ end
48
+ data
49
+ end
50
+
51
+ def page(ctx, f, authors)
52
+ fm = (Markdown.fm(File.read(f)) rescue nil)
53
+ return unless fm.is_a?(Hash)
54
+ r = ctx.rel(f)
55
+ layout = fm["layout"].to_s
56
+ post = r.split("/").include?("_posts") || POST.include?(layout)
57
+ if LISTING.include?(layout)
58
+ ctx.errs << "#{r}: variant must be one of #{VARIANTS.join(', ')}" if fm.key?("variant") && !VARIANTS.include?(fm["variant"].to_s)
59
+ ctx.errs << "#{r}: news_sections must be a list of category names" if fm.key?("news_sections") && !fm["news_sections"].is_a?(Array)
60
+ end
61
+ ctx.errs << "#{r}: layout: author needs author: <id from _data/authors.yml>" if layout == "author" && fm["author"].to_s.empty?
62
+ ids = Array(fm["authors"] || fm["author"]).map(&:to_s)
63
+ if authors && (post || layout == "author")
64
+ ids.each { |id| ctx.warns << "#{r}: author '#{id}' is not in _data/authors.yml (shown as plain text)" unless authors.key?(id) }
65
+ end
66
+ return unless post
67
+ %w[breaking live featured].each { |k| ctx.errs << "#{r}: #{k} must be true or false" if fm.key?(k) && ![true, false].include?(fm[k]) }
68
+ ctx.errs << "#{r}: sources must be a list" if fm.key?("sources") && !fm["sources"].is_a?(Array)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # 5. Built site: unconverted Markdown markers in HTML, and CSS that GitHub Pages' Ruby Sass
6
+ # passes through or leaves undefined.
7
+ module BuildOutput
8
+ MARKS = /\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]/i
9
+
10
+ module_function
11
+
12
+ def residue(ctx)
13
+ Dir.glob("#{ctx.site}/**/*.html").each do |f|
14
+ t = File.read(f)
15
+ t = t.gsub(%r{<pre\b.*?</pre>}m, "").gsub(%r{<code\b.*?</code>}m) { |c| c.include?("status:") ? c : "" }
16
+ t.scan(MARKS) { ctx.errs << "#{f}: unconverted alert marker [!#{$1}]" }
17
+ ctx.errs << "#{f}: unconverted status badge (<code>status:)" if t =~ /<code[^>]*>status:/
18
+ end
19
+ end
20
+
21
+ def css(ctx)
22
+ errs = ctx.errs
23
+ files = Dir.glob("#{ctx.site}/**/*.css").sort
24
+ defined = {}
25
+ files.each do |f|
26
+ t = File.read(f)
27
+ errs << "#{f}: @use/@forward left in built CSS (Ruby Sass passes them through); use @import" if t =~ /@(use|forward)\s/
28
+ errs << "#{f}: @supports selector() in built CSS" if t =~ /@supports\s+(not\s+)?selector\(/
29
+ errs << "#{f}: custom property with a literal Sass variable (write --x: \#{$var})" if t =~ /--[\w-]+:\s*\$/
30
+ t.scan(/(--mt-[\w-]+)\s*:/) { defined[$1] = true }
31
+ end
32
+ Dir.glob("#{ctx.site}/**/*.html").each { |f| File.read(f).scan(%r{<style[^>]*>(.*?)</style>}m) { $1.scan(/(--mt-[\w-]+)\s*:/) { defined[$1] = true } } }
33
+ files.each do |f|
34
+ File.read(f).scan(/var\((--mt-[\w-]+)/).flatten.uniq.each { |v| errs << "#{f}: var(#{v}) is never defined" unless defined[v] }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # 4b. Registered parts exist in the built HTML of the pages that use the component (needs
6
+ # Nokogiri; skipped without it). Attributes that component scripts add at runtime (role=, aria-*)
7
+ # are checked after scripts run by docs QA.
8
+ module BuiltParts
9
+ RUNTIME_PART = /\[(role|aria-)/
10
+
11
+ module_function
12
+
13
+ def nokogiri?
14
+ require "nokogiri"
15
+ true
16
+ rescue LoadError
17
+ false
18
+ end
19
+
20
+ def call(ctx)
21
+ return unless ctx.comps.is_a?(Hash) && nokogiri?
22
+ docs = {}
23
+ ctx.part_uses.each do |c, pages|
24
+ secs = pages.uniq.flat_map do |pg|
25
+ r = pg.delete_suffix(".md")
26
+ cand = File.basename(r) == "index" ? ["#{ctx.site}/#{r}.html"] : ["#{ctx.site}/#{r}.html", "#{ctx.site}/#{r}/index.html"]
27
+ h = cand.find { |x| File.exist?(x) }
28
+ h ? (docs[h] ||= Nokogiri::HTML(File.read(h))).css(%(section[data-c="#{c}"])).to_a : []
29
+ end
30
+ next if secs.empty? # page not built under its source path (custom permalink): nothing to compare
31
+ ctx.comps[c]["parts"].each do |sel|
32
+ next unless sel.is_a?(String) && !sel.match?(RUNTIME_PART)
33
+ found = secs.any? { |sec| (sec.matches?(sel) rescue false) || !(sec.css(sel) rescue []).empty? }
34
+ ctx.errs << "_data/components.yml: #{c}.parts: '#{sel}' not found in the built HTML of #{pages.uniq.sort.join(', ')}" unless found
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # Command line: usage, --help/--version, running the rule groups in order, reporting.
6
+ module CLI
7
+ USAGE = "usage: modootoday-check SRC SITE [--twins] [--github-pages[=VERSION]] [--gh] [--strict] [--schema-recommended] [--i18n] [--i18n-init=LANG,...]"
8
+ # Single source for --help and the README "Commands" table (script/readme-commands, test/project/readme_test.rb).
9
+ OPTIONS = [
10
+ ["SRC SITE", "always: front matter, forbidden syntax, alerts, fence info strings, `status:`/`tag:`/`kbd:` values (`since:` is rejected), `sections:` count, `_data/i18n/<lang>.yml` keys against the default language and translated pages against default-language pages, `_data/components.yml` (keys, slot contract counts per section, `parts` against `api.md` and the built HTML), Sass/CSS rules, unconverted markers in the build, schema.org JSON-LD in the build (valid JSON, vocabulary, required properties per type, absolute URLs, ISO 8601 dates, one graph per page, agreement with Open Graph)"],
11
+ ["--twins", "every page has a byte-identical Markdown twin, per-language `llms.txt` and `llms-full.txt`"],
12
+ ["--github-pages[=VERSION]", "`Gemfile.lock` pins `github-pages` VERSION (default 232)"],
13
+ ["--schema-recommended", "also warn about schema.org properties Google Search recommends (author, image, dates, ...) that a JSON-LD node lacks"],
14
+ ["--strict", "warnings fail the run"],
15
+ ["--i18n", "`_data/i18n/<lang>.yml` against the theme registry (`i18n/en.yml` in the gem): unknown keys, `%{name}` placeholder mismatches, and pages marked `translation: pending`"],
16
+ ["--i18n-init=LANG,...", "copy the theme's catalogs (`i18n/<lang>.yml`, 23 languages) into `SRC/_data/i18n/` as a starting point for overrides (optional; the theme already uses them), and list keys an existing catalog lacks"],
17
+ ["--gh", "also render each page with `gh api markdown` (network, `gh` login)"],
18
+ ["--help, -h", "print usage and options (`--help=markdown` prints the README table)"],
19
+ ["--version", "print the rules version"],
20
+ ].freeze
21
+ # Rule groups in report order.
22
+ RULES = [Registry, SiteConfig.method(:source_mode), Pages, Layouts, I18n, Registry.method(:i18n_keys), BuiltParts,
23
+ BuildOutput.method(:residue), NewWindow.method(:built), StructuredData, Stylesheets, BuildOutput.method(:css), SiteConfig, Blog, GithubPages, Twins, GhRender].freeze
24
+
25
+ module_function
26
+
27
+ def run(argv)
28
+ return help(argv.find { |a| a == "-h" || a.start_with?("--help") }) if argv.any? { |a| a == "-h" || a.start_with?("--help") }
29
+ return (puts "modootoday-check (md-components v1)"; 0) if argv.include?("--version")
30
+
31
+ pos = argv.reject { |a| a.start_with?("--") }
32
+ src, site = pos[0]&.chomp("/"), pos[1]&.chomp("/")
33
+ abort USAGE unless src && site
34
+ ctx = Context.new(src, site, argv)
35
+ RULES.each { |r| r.call(ctx) }
36
+ report(ctx)
37
+ end
38
+
39
+ def help(h)
40
+ if h == "--help=markdown"
41
+ puts "| Option | Checks |", "|---|---|"
42
+ OPTIONS.each { |o, d| puts "| `#{o}` | #{d.gsub("|", "\\|")} |" }
43
+ else
44
+ puts USAGE, "", "options:"
45
+ OPTIONS.each { |o, d| puts format(" %-26s %s", o, d.delete("`")) }
46
+ puts "", "exit status: 0 no problems, 1 problems found or bad usage"
47
+ end
48
+ 0
49
+ end
50
+
51
+ def report(ctx)
52
+ errs, warns = ctx.errs, ctx.warns
53
+ errs.concat(warns.map { |w| "#{w} [strict]" }) if ctx.option?("--strict")
54
+ warns.each { |w| warn "warning: #{w}" } unless ctx.option?("--strict")
55
+ puts errs
56
+ puts errs.empty? ? "md-components check: OK (#{ctx.mds.size} pages)" : "md-components check: #{errs.size} problem(s)"
57
+ errs.empty? ? 0 : 1
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # Values the transforms need (Rule 2b): inline `file:`, `progress:`, `embed:` code spans, and per-section
6
+ # content rules for c: gallery, quote, pricing and subscribe that slot counts cannot express.
7
+ module ComponentRules
8
+ SKELETONS = %w[text card image avatar row].freeze
9
+
10
+ module_function
11
+
12
+ def inline(ctx, r, b)
13
+ errs, lines = ctx.errs, Markdown.strip_fences(b).lines
14
+ text = lines.join
15
+ text.scan(/`progress:([^`]*)`/) do |(v)|
16
+ errs << "#{r}: `progress:#{v}` must be a whole number from 0 to 100" unless v =~ /\A(100|[1-9]?\d)\z/
17
+ end
18
+ text.scan(/`skeleton:([^`]*)`/) do |(v)|
19
+ errs << "#{r}: `skeleton:#{v}` must be one of #{SKELETONS.join(', ')}" unless SKELETONS.include?(v)
20
+ end
21
+ text.scan(/`embed:([^`]*)`/) do |(v)|
22
+ errs << "#{r}: `embed:#{v}` must be an https:// URL, optionally followed by a title" unless v =~ %r{\Ahttps://[^\s"<>]+( [^"<>]+)?\z}
23
+ end
24
+ rl = b.lines
25
+ rl.each_with_index do |l, i|
26
+ next unless (fm = l.strip.match(/\A`file:([^`]*)`\z/))
27
+ nxt = rl[(i + 1)..].to_a.find { |x| x.strip != "" }.to_s
28
+ errs << "#{r}: `file:#{fm[1]}` must be alone in a paragraph right before a fenced code block" unless nxt =~ /\A\s*(```|~~~)/ && fm[1] !~ /\s/
29
+ end
30
+ end
31
+
32
+ def section(ctx, r, i, s, md)
33
+ errs, (m, _raw) = ctx.errs, md || ["", ""]
34
+ w = "#{r}: sections[#{i}] #{s['c']}"
35
+ case s["c"]
36
+ when "gallery"
37
+ imgs = m.scan(/^[-*+]\s+(.*)$/).flatten
38
+ errs << "#{w}: each list item must be one image ![alt](src) with alt text" unless imgs.all? { |x| x.strip =~ /\A!\[[^\]\n]*\S[^\]\n]*\]\([^)\s]+\)\z/ }
39
+ errs << "#{w}: write the image list without blank lines between items" if m =~ /^[-*+]\s.*\n\s*\n[-*+]\s/
40
+ when "quote"
41
+ errs << "#{w}: needs a > quote whose last line is the attribution (> — Name)" unless m =~ /^>\s*— \S/
42
+ when "pricing"
43
+ n = m.scan(/^###(?: |$)/).size
44
+ rec = s["recommended"]
45
+ errs << "#{w}: recommended: must be a plan number from 1 to #{n}" if s.key?("recommended") && !(rec.is_a?(Integer) && rec.between?(1, n))
46
+ when "subscribe"
47
+ act = ctx.mtc.is_a?(Hash) && ctx.mtc.dig("subscribe", "action")
48
+ ctx.warns << "#{w}: no form is shown until _config.yml sets modootoday.subscribe.action" unless act
49
+ errs << "#{w}: modootoday.subscribe.action must be an https:// URL" if act && act.to_s !~ %r{\Ahttps://\S+\z}
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modootoday
4
+ module Check
5
+ # What every rule reads (source and site paths, options, parsed config and data) and the
6
+ # error/warning lists every rule writes to. Loaded once per run.
7
+ class Context
8
+ SKIP = %r{(^|/)(_site|_includes|_layouts|_data|vendor|node_modules)/}
9
+
10
+ attr_reader :src, :site, :argv, :errs, :warns, :mds, :comps, :i18n, :site_cfg, :mtc
11
+ attr_accessor :part_uses
12
+
13
+ def initialize(src, site, argv)
14
+ @src, @site, @argv = src, site, argv
15
+ @errs, @warns = [], []
16
+ @part_uses = {} # component -> source pages that use it (parts are looked up in their built HTML)
17
+ comps_file = "#{src}/_data/components.yml"
18
+ @comps = File.exist?(comps_file) ? (YAML.safe_load(File.read(comps_file)) || {}) : nil
19
+ @i18n = Dir.glob("#{src}/_data/i18n/*.yml").to_h { |f| [File.basename(f, ".yml"), YAML.safe_load(File.read(f)) || {}] }
20
+ cfg_file = "#{src}/_config.yml"
21
+ @site_cfg = (File.exist?(cfg_file) && ((YAML.safe_load(File.read(cfg_file), permitted_classes: [Date, Time]) rescue nil) || {})) || {}
22
+ @mtc = @site_cfg["modootoday"] || {}
23
+ # directories listed in the site's exclude: are not pages (e.g. a nested example site)
24
+ excl = Array(@site_cfg["exclude"]).map { |e| e.to_s.chomp("/") }.select { |e| File.directory?("#{src}/#{e}") }
25
+ @mds = Dir.glob("#{src}/**/*.md").reject { |f| f.delete_prefix(src) =~ SKIP }.sort
26
+ @mds.reject! { |f| excl.any? { |e| f.start_with?("#{src}/#{e}/") } }
27
+ end
28
+
29
+ def option?(name) = argv.include?(name)
30
+ def rel(f) = f.delete_prefix("#{src}/")
31
+ def skipped?(f) = f.delete_prefix(src) =~ SKIP
32
+
33
+ # modootoday.twins: source = front-matter-less mode: a page without front matter is rendered by
34
+ # jekyll-optional-front-matter AND copied raw to its own path, which then is its twin.
35
+ def source_mode? = mtc.is_a?(Hash) && mtc["twins"] == "source"
36
+
37
+ # The language of a page path ("ko/guide" -> "ko") when that language has an i18n file.
38
+ def lang_of(r) = (r.split("/").first if r.include?("/") && i18n.key?(r.split("/").first))
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Modootoday
6
+ module Check
7
+ # --gh: render each page with `gh api markdown` (needs gh auth and network).
8
+ module GhRender
9
+ module_function
10
+
11
+ def call(ctx)
12
+ return unless ctx.option?("--gh")
13
+ ctx.mds.each do |f|
14
+ out, st = Open3.capture2("gh", "api", "markdown", "-f", "mode=gfm", "-f", "text=#{Markdown.body(File.read(f))}")
15
+ next ctx.errs << "#{ctx.rel(f)}: gh api failed" unless st.success?
16
+ ctx.errs << "#{ctx.rel(f)}: GitHub shows ': ' definition marker" if out =~ /<p>: /
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end