postnhost 0.0.0 → 0.1.1

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 (500) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/{LICENSE.txt → LICENSE} +6 -6
  4. data/README.md +343 -15
  5. data/Rakefile +66 -6
  6. data/app/assets/builds/postnhost/application.css +2 -0
  7. data/app/assets/builds/postnhost/application.js +50190 -0
  8. data/app/assets/config/postnhost_manifest.js +2 -0
  9. data/app/assets/images/postnhost/demo.webp +0 -0
  10. data/app/assets/images/postnhost/icons/alert-circle.svg +3 -0
  11. data/app/assets/images/postnhost/icons/arrow-left-long.svg +3 -0
  12. data/app/assets/images/postnhost/icons/arrow-left.svg +3 -0
  13. data/app/assets/images/postnhost/icons/arrow-right-end-on-rectangle.svg +3 -0
  14. data/app/assets/images/postnhost/icons/arrow-right-long.svg +3 -0
  15. data/app/assets/images/postnhost/icons/arrow-right.svg +3 -0
  16. data/app/assets/images/postnhost/icons/bars.svg +3 -0
  17. data/app/assets/images/postnhost/icons/bluesky.svg +3 -0
  18. data/app/assets/images/postnhost/icons/check-circle.svg +3 -0
  19. data/app/assets/images/postnhost/icons/chevron-down-small.svg +3 -0
  20. data/app/assets/images/postnhost/icons/chevron-down.svg +3 -0
  21. data/app/assets/images/postnhost/icons/chevron-left.svg +3 -0
  22. data/app/assets/images/postnhost/icons/chevron-right.svg +3 -0
  23. data/app/assets/images/postnhost/icons/clock.svg +3 -0
  24. data/app/assets/images/postnhost/icons/close.svg +3 -0
  25. data/app/assets/images/postnhost/icons/code-bracket.svg +3 -0
  26. data/app/assets/images/postnhost/icons/document-text.svg +3 -0
  27. data/app/assets/images/postnhost/icons/drag-handle.svg +8 -0
  28. data/app/assets/images/postnhost/icons/edit.svg +3 -0
  29. data/app/assets/images/postnhost/icons/external-link.svg +3 -0
  30. data/app/assets/images/postnhost/icons/eye.svg +4 -0
  31. data/app/assets/images/postnhost/icons/facebook.svg +3 -0
  32. data/app/assets/images/postnhost/icons/folder.svg +3 -0
  33. data/app/assets/images/postnhost/icons/image.svg +3 -0
  34. data/app/assets/images/postnhost/icons/info-circle.svg +3 -0
  35. data/app/assets/images/postnhost/icons/instagram.svg +3 -0
  36. data/app/assets/images/postnhost/icons/language.svg +3 -0
  37. data/app/assets/images/postnhost/icons/layers.svg +3 -0
  38. data/app/assets/images/postnhost/icons/lightning.svg +3 -0
  39. data/app/assets/images/postnhost/icons/linkedin.svg +3 -0
  40. data/app/assets/images/postnhost/icons/logout.svg +3 -0
  41. data/app/assets/images/postnhost/icons/magnifying-glass.svg +3 -0
  42. data/app/assets/images/postnhost/icons/mastodon.svg +3 -0
  43. data/app/assets/images/postnhost/icons/menu.svg +3 -0
  44. data/app/assets/images/postnhost/icons/plus.svg +3 -0
  45. data/app/assets/images/postnhost/icons/schedule.svg +3 -0
  46. data/app/assets/images/postnhost/icons/settings.svg +4 -0
  47. data/app/assets/images/postnhost/icons/sparkles.svg +3 -0
  48. data/app/assets/images/postnhost/icons/spinner.svg +4 -0
  49. data/app/assets/images/postnhost/icons/threads.svg +3 -0
  50. data/app/assets/images/postnhost/icons/tiktok.svg +3 -0
  51. data/app/assets/images/postnhost/icons/tiptap/align-center.svg +3 -0
  52. data/app/assets/images/postnhost/icons/tiptap/align-left.svg +3 -0
  53. data/app/assets/images/postnhost/icons/tiptap/align-right.svg +3 -0
  54. data/app/assets/images/postnhost/icons/tiptap/apply-link.svg +3 -0
  55. data/app/assets/images/postnhost/icons/tiptap/blockquote.svg +3 -0
  56. data/app/assets/images/postnhost/icons/tiptap/bold.svg +3 -0
  57. data/app/assets/images/postnhost/icons/tiptap/bullet-list.svg +3 -0
  58. data/app/assets/images/postnhost/icons/tiptap/code-block.svg +3 -0
  59. data/app/assets/images/postnhost/icons/tiptap/code.svg +3 -0
  60. data/app/assets/images/postnhost/icons/tiptap/heading-2.svg +3 -0
  61. data/app/assets/images/postnhost/icons/tiptap/heading-3.svg +3 -0
  62. data/app/assets/images/postnhost/icons/tiptap/image-add.svg +3 -0
  63. data/app/assets/images/postnhost/icons/tiptap/italic.svg +3 -0
  64. data/app/assets/images/postnhost/icons/tiptap/link.svg +3 -0
  65. data/app/assets/images/postnhost/icons/tiptap/ordered-list.svg +3 -0
  66. data/app/assets/images/postnhost/icons/tiptap/redo.svg +3 -0
  67. data/app/assets/images/postnhost/icons/tiptap/remove-link.svg +3 -0
  68. data/app/assets/images/postnhost/icons/tiptap/strike.svg +3 -0
  69. data/app/assets/images/postnhost/icons/tiptap/table.svg +3 -0
  70. data/app/assets/images/postnhost/icons/tiptap/underline.svg +3 -0
  71. data/app/assets/images/postnhost/icons/tiptap/undo.svg +3 -0
  72. data/app/assets/images/postnhost/icons/tiptap/youtube.svg +4 -0
  73. data/app/assets/images/postnhost/icons/translate.svg +3 -0
  74. data/app/assets/images/postnhost/icons/trash.svg +3 -0
  75. data/app/assets/images/postnhost/icons/user.svg +4 -0
  76. data/app/assets/images/postnhost/icons/users.svg +3 -0
  77. data/app/assets/images/postnhost/icons/warning.svg +3 -0
  78. data/app/assets/images/postnhost/icons/x.svg +3 -0
  79. data/app/assets/images/postnhost/icons/youtube.svg +3 -0
  80. data/app/assets/images/postnhost/logo.webp +0 -0
  81. data/app/assets/images/postnhost/og-image.webp +0 -0
  82. data/app/assets/images/postnhost/sample-docs/08-how-to-control-suggested-articles-and-top-picks-01-discovery-controls.webp +0 -0
  83. data/app/assets/images/postnhost/sample-docs/09-all-about-authors-in-postnhost-01-author-profile.webp +0 -0
  84. data/app/assets/images/postnhost/sample-docs/09-all-about-authors-in-postnhost-02-multiple-authors.webp +0 -0
  85. data/app/assets/images/postnhost/sample-docs/11-how-to-schedule-an-article-for-publication-01-timezone.webp +0 -0
  86. data/app/assets/images/postnhost/sample-docs/11-how-to-schedule-an-article-for-publication-02-scheduled-article.webp +0 -0
  87. data/app/assets/images/postnhost/sample-docs/12-how-article-version-history-and-restores-work-01-version-history.webp +0 -0
  88. data/app/assets/images/postnhost/sample-docs/13-how-to-add-and-manage-languages-01-language-management.webp +0 -0
  89. data/app/assets/images/postnhost/sample-docs/14-how-to-translate-articles-pages-and-categories-01-translation-options.webp +0 -0
  90. data/app/assets/images/postnhost/sample-docs/14-how-to-translate-articles-pages-and-categories-02-ai-language-selection.webp +0 -0
  91. data/app/assets/images/postnhost/sample-docs/16-how-to-add-navigation-links-01-navigation-builder.webp +0 -0
  92. data/app/assets/images/postnhost/sample-docs/16-how-to-add-navigation-links-02-external-link-settings.webp +0 -0
  93. data/app/assets/images/postnhost/sample-docs/17-how-to-add-a-logo-and-default-og-image-01-assets-settings.webp +0 -0
  94. data/app/assets/images/postnhost/sample-docs/19-how-to-customize-public-text-for-each-language-01-i18n-overrides.webp +0 -0
  95. data/app/assets/images/postnhost/sample-docs/20-how-to-configure-structured-data-and-sitemap-seo-01-site-schema.webp +0 -0
  96. data/app/assets/images/postnhost/sample-docs/20-how-to-configure-structured-data-and-sitemap-seo-02-author-schema-defaults.webp +0 -0
  97. data/app/assets/images/postnhost/sample-docs/20-how-to-configure-structured-data-and-sitemap-seo-03-author-schema-advanced-fields.webp +0 -0
  98. data/app/assets/images/postnhost/sample-docs/covers/08-suggested-articles-and-top-picks.webp +0 -0
  99. data/app/assets/images/postnhost/sample-docs/covers/09-authors.webp +0 -0
  100. data/app/assets/images/postnhost/sample-docs/covers/11-scheduling.webp +0 -0
  101. data/app/assets/images/postnhost/sample-docs/covers/12-version-history.webp +0 -0
  102. data/app/assets/images/postnhost/sample-docs/covers/13-languages.webp +0 -0
  103. data/app/assets/images/postnhost/sample-docs/covers/14-translations.webp +0 -0
  104. data/app/assets/images/postnhost/sample-docs/covers/16-navigation-links.webp +0 -0
  105. data/app/assets/images/postnhost/sample-docs/covers/17-logo-and-default-og-image.webp +0 -0
  106. data/app/assets/images/postnhost/sample-docs/covers/19-public-text-for-each-language.webp +0 -0
  107. data/app/assets/images/postnhost/sample-docs/covers/20-structured-data-and-sitemap-seo.webp +0 -0
  108. data/app/assets/stylesheets/application.tailwind.css +347 -0
  109. data/app/assets/stylesheets/postnhost/application.css +15 -0
  110. data/app/assets/xsl/sitemap.xsl +64 -0
  111. data/app/controllers/concerns/postnhost/authentication.rb +59 -0
  112. data/app/controllers/concerns/postnhost/canonical_first_page_redirect.rb +14 -0
  113. data/app/controllers/concerns/postnhost/language_switcher.rb +42 -0
  114. data/app/controllers/concerns/postnhost/onboarding/access_control.rb +57 -0
  115. data/app/controllers/concerns/postnhost/onboarding/step_assignment.rb +55 -0
  116. data/app/controllers/concerns/postnhost/public_footer_data.rb +103 -0
  117. data/app/controllers/concerns/postnhost/public_http_caching.rb +35 -0
  118. data/app/controllers/concerns/postnhost/public_page_size.rb +17 -0
  119. data/app/controllers/concerns/postnhost/public_template_resolvable.rb +44 -0
  120. data/app/controllers/concerns/postnhost/settings/i18n_manageable.rb +61 -0
  121. data/app/controllers/concerns/postnhost/settings/navigation_manageable.rb +118 -0
  122. data/app/controllers/concerns/postnhost/settings/schema_manageable.rb +140 -0
  123. data/app/controllers/concerns/postnhost/users/schema_manageable.rb +119 -0
  124. data/app/controllers/postnhost/application_controller.rb +37 -0
  125. data/app/controllers/postnhost/article_variants_controller.rb +138 -0
  126. data/app/controllers/postnhost/articles/cover_images_controller.rb +35 -0
  127. data/app/controllers/postnhost/articles/images_controller.rb +28 -0
  128. data/app/controllers/postnhost/articles/translations_controller.rb +26 -0
  129. data/app/controllers/postnhost/articles_controller.rb +130 -0
  130. data/app/controllers/postnhost/categories/translations_controller.rb +24 -0
  131. data/app/controllers/postnhost/categories_controller.rb +59 -0
  132. data/app/controllers/postnhost/category_variants_controller.rb +69 -0
  133. data/app/controllers/postnhost/languages_controller.rb +66 -0
  134. data/app/controllers/postnhost/onboarding_controller.rb +70 -0
  135. data/app/controllers/postnhost/page_variants_controller.rb +136 -0
  136. data/app/controllers/postnhost/pages/images_controller.rb +28 -0
  137. data/app/controllers/postnhost/pages/translations_controller.rb +26 -0
  138. data/app/controllers/postnhost/pages_controller.rb +95 -0
  139. data/app/controllers/postnhost/public/articles_controller.rb +135 -0
  140. data/app/controllers/postnhost/public/authors_controller.rb +57 -0
  141. data/app/controllers/postnhost/public/categories_controller.rb +61 -0
  142. data/app/controllers/postnhost/public/static_pages_controller.rb +77 -0
  143. data/app/controllers/postnhost/public_controller.rb +32 -0
  144. data/app/controllers/postnhost/sessions_controller.rb +37 -0
  145. data/app/controllers/postnhost/settings/site_scripts_controller.rb +61 -0
  146. data/app/controllers/postnhost/settings_controller.rb +63 -0
  147. data/app/controllers/postnhost/sitemap_controller.rb +17 -0
  148. data/app/controllers/postnhost/templates_controller.rb +26 -0
  149. data/app/controllers/postnhost/users_controller.rb +112 -0
  150. data/app/helpers/postnhost/application_helper.rb +236 -0
  151. data/app/helpers/postnhost/article_helper.rb +41 -0
  152. data/app/helpers/postnhost/category_helper.rb +52 -0
  153. data/app/helpers/postnhost/common_css_helper.rb +97 -0
  154. data/app/helpers/postnhost/navigation_helper.rb +148 -0
  155. data/app/helpers/postnhost/pagy_helper.rb +103 -0
  156. data/app/helpers/postnhost/public_pages_helper.rb +287 -0
  157. data/app/helpers/postnhost/schema_helper.rb +102 -0
  158. data/app/helpers/postnhost/seo_helper.rb +428 -0
  159. data/app/helpers/postnhost/settings_helper.rb +79 -0
  160. data/app/javascript/application.js +3 -0
  161. data/app/javascript/controllers/ai_translation_controller.js +42 -0
  162. data/app/javascript/controllers/application.js +9 -0
  163. data/app/javascript/controllers/author_manager_controller.js +96 -0
  164. data/app/javascript/controllers/auto_save_controller.js +244 -0
  165. data/app/javascript/controllers/bulk_selection_controller.js +74 -0
  166. data/app/javascript/controllers/burger_controller.js +20 -0
  167. data/app/javascript/controllers/category_manager_controller.js +99 -0
  168. data/app/javascript/controllers/collapsible_sidebar_controller.js +87 -0
  169. data/app/javascript/controllers/cover_image_upload_controller.js +164 -0
  170. data/app/javascript/controllers/dropdown_controller.js +38 -0
  171. data/app/javascript/controllers/editor_mobile_sidebars_controller.js +36 -0
  172. data/app/javascript/controllers/file_auto_submit_controller.js +23 -0
  173. data/app/javascript/controllers/flash_message_controller.js +40 -0
  174. data/app/javascript/controllers/index.js +71 -0
  175. data/app/javascript/controllers/navigation_builder_controller.js +463 -0
  176. data/app/javascript/controllers/public_mobile_nav_controller.js +26 -0
  177. data/app/javascript/controllers/publish_guard_controller.js +47 -0
  178. data/app/javascript/controllers/reading_progress_controller.js +38 -0
  179. data/app/javascript/controllers/schedule_manager_controller.js +87 -0
  180. data/app/javascript/controllers/suggestion_manager_controller.js +99 -0
  181. data/app/javascript/controllers/template_preview_controller.js +34 -0
  182. data/app/javascript/controllers/tiptap_editor_controller.js +812 -0
  183. data/app/javascript/controllers/title_field_controller.js +17 -0
  184. data/app/javascript/helpers/auto_save_state.js +47 -0
  185. data/app/javascript/helpers/code_highlighting.js +48 -0
  186. data/app/javascript/helpers/dom_helpers.js +109 -0
  187. data/app/javascript/helpers/style_helpers.js +168 -0
  188. data/app/jobs/postnhost/application_job.rb +9 -0
  189. data/app/jobs/postnhost/scheduled_article_job.rb +13 -0
  190. data/app/jobs/postnhost/translation/article_variant_job.rb +17 -0
  191. data/app/jobs/postnhost/translation/category_variant_job.rb +17 -0
  192. data/app/jobs/postnhost/translation/page_variant_job.rb +17 -0
  193. data/app/models/concerns/postnhost/article/image_uploadable.rb +11 -0
  194. data/app/models/concerns/postnhost/excerpt_optimizable.rb +57 -0
  195. data/app/models/concerns/postnhost/inline_image_uploadable.rb +40 -0
  196. data/app/models/concerns/postnhost/page/image_uploadable.rb +11 -0
  197. data/app/models/concerns/postnhost/primary_language_guard.rb +18 -0
  198. data/app/models/concerns/postnhost/public_revision_touch.rb +15 -0
  199. data/app/models/concerns/postnhost/schema_localizable.rb +28 -0
  200. data/app/models/postnhost/application_record.rb +5 -0
  201. data/app/models/postnhost/article.rb +142 -0
  202. data/app/models/postnhost/article_author.rb +20 -0
  203. data/app/models/postnhost/article_category.rb +8 -0
  204. data/app/models/postnhost/article_suggestion.rb +19 -0
  205. data/app/models/postnhost/article_variant.rb +28 -0
  206. data/app/models/postnhost/category.rb +34 -0
  207. data/app/models/postnhost/category_variant.rb +24 -0
  208. data/app/models/postnhost/language.rb +38 -0
  209. data/app/models/postnhost/navigation.rb +76 -0
  210. data/app/models/postnhost/navigation_item.rb +100 -0
  211. data/app/models/postnhost/page.rb +63 -0
  212. data/app/models/postnhost/page_variant.rb +26 -0
  213. data/app/models/postnhost/public_site_revision.rb +24 -0
  214. data/app/models/postnhost/published_cover_image.rb +28 -0
  215. data/app/models/postnhost/setting.rb +116 -0
  216. data/app/models/postnhost/site_script.rb +11 -0
  217. data/app/models/postnhost/snapshot/article.rb +142 -0
  218. data/app/models/postnhost/snapshot/article_author.rb +11 -0
  219. data/app/models/postnhost/snapshot/article_category.rb +10 -0
  220. data/app/models/postnhost/snapshot/article_suggestion.rb +11 -0
  221. data/app/models/postnhost/snapshot/article_variant.rb +60 -0
  222. data/app/models/postnhost/snapshot/page.rb +33 -0
  223. data/app/models/postnhost/snapshot/page_variant.rb +41 -0
  224. data/app/models/postnhost/template.rb +20 -0
  225. data/app/models/postnhost/user.rb +63 -0
  226. data/app/models/postnhost/version.rb +7 -0
  227. data/app/services/postnhost/base_service.rb +17 -0
  228. data/app/services/postnhost/constraints/public_locale_constraint.rb +9 -0
  229. data/app/services/postnhost/constraints/public_route_constraint.rb +19 -0
  230. data/app/services/postnhost/onboarding/step_processor.rb +27 -0
  231. data/app/services/postnhost/onboarding/steps/base.rb +52 -0
  232. data/app/services/postnhost/onboarding/steps/choose_languages.rb +66 -0
  233. data/app/services/postnhost/onboarding/steps/complete_post_install_checklist.rb +12 -0
  234. data/app/services/postnhost/onboarding/steps/create_admin_account.rb +28 -0
  235. data/app/services/postnhost/onboarding/steps/finish_setup.rb +27 -0
  236. data/app/services/postnhost/onboarding/steps/set_site_metadata.rb +70 -0
  237. data/app/services/postnhost/public_navigation_targets.rb +67 -0
  238. data/app/services/postnhost/public_request_context.rb +101 -0
  239. data/app/services/postnhost/publishing/article_variants/publish.rb +26 -0
  240. data/app/services/postnhost/publishing/article_variants/publish_many.rb +23 -0
  241. data/app/services/postnhost/publishing/article_variants/snapshot_writer.rb +44 -0
  242. data/app/services/postnhost/publishing/article_variants/unpublish.rb +27 -0
  243. data/app/services/postnhost/publishing/article_variants/unpublish_many.rb +23 -0
  244. data/app/services/postnhost/publishing/articles/publish.rb +25 -0
  245. data/app/services/postnhost/publishing/articles/publish_scheduled.rb +42 -0
  246. data/app/services/postnhost/publishing/articles/snapshot_writer.rb +84 -0
  247. data/app/services/postnhost/publishing/articles/unpublish.rb +26 -0
  248. data/app/services/postnhost/publishing/bulk_transition.rb +63 -0
  249. data/app/services/postnhost/publishing/error.rb +12 -0
  250. data/app/services/postnhost/publishing/page_variants/publish.rb +26 -0
  251. data/app/services/postnhost/publishing/page_variants/publish_many.rb +23 -0
  252. data/app/services/postnhost/publishing/page_variants/snapshot_writer.rb +41 -0
  253. data/app/services/postnhost/publishing/page_variants/unpublish.rb +27 -0
  254. data/app/services/postnhost/publishing/page_variants/unpublish_many.rb +23 -0
  255. data/app/services/postnhost/publishing/pages/publish.rb +25 -0
  256. data/app/services/postnhost/publishing/pages/snapshot_writer.rb +38 -0
  257. data/app/services/postnhost/publishing/pages/unpublish.rb +26 -0
  258. data/app/services/postnhost/publishing/revision.rb +37 -0
  259. data/app/services/postnhost/publishing/route_validator.rb +41 -0
  260. data/app/services/postnhost/publishing/versions/restore_draft.rb +64 -0
  261. data/app/services/postnhost/service_result.rb +7 -0
  262. data/app/services/postnhost/sitemap_builder.rb +395 -0
  263. data/app/services/postnhost/translation/article_variant_translator.rb +102 -0
  264. data/app/services/postnhost/translation/category_variant_translator.rb +92 -0
  265. data/app/services/postnhost/translation/content_translator.rb +119 -0
  266. data/app/services/postnhost/translation/openai_llm_client.rb +30 -0
  267. data/app/services/postnhost/translation/page_variant_translator.rb +55 -0
  268. data/app/uploaders/base_uploader.rb +26 -0
  269. data/app/uploaders/cover_image_uploader.rb +31 -0
  270. data/app/uploaders/postnhost/avatar_uploader.rb +17 -0
  271. data/app/uploaders/postnhost/inline_article_image_uploader.rb +38 -0
  272. data/app/uploaders/postnhost/inline_page_image_uploader.rb +38 -0
  273. data/app/uploaders/postnhost/setting_asset_uploader.rb +18 -0
  274. data/app/views/layouts/postnhost/_favicon.html.erb +6 -0
  275. data/app/views/layouts/postnhost/_footer.html.erb +67 -0
  276. data/app/views/layouts/postnhost/_header.html.erb +89 -0
  277. data/app/views/layouts/postnhost/_sidebar.html.erb +72 -0
  278. data/app/views/layouts/postnhost/application.html.erb +23 -0
  279. data/app/views/layouts/postnhost/articles.html.erb +34 -0
  280. data/app/views/layouts/postnhost/product.html.erb +36 -0
  281. data/app/views/layouts/postnhost/public/templates/default.html.erb +110 -0
  282. data/app/views/layouts/postnhost/public/templates/swiss-editorial.html.erb +213 -0
  283. data/app/views/layouts/postnhost/public/templates/workspace-journal.html.erb +231 -0
  284. data/app/views/layouts/postnhost/public_header/_dashboard_sign_in.html.erb +14 -0
  285. data/app/views/layouts/postnhost/public_header/_language_switcher.html.erb +43 -0
  286. data/app/views/layouts/postnhost/public_header/_logo.html.erb +9 -0
  287. data/app/views/postnhost/article_variants/_variants_list.html.erb +82 -0
  288. data/app/views/postnhost/article_variants/edit.html.erb +48 -0
  289. data/app/views/postnhost/article_variants/index.html.erb +128 -0
  290. data/app/views/postnhost/article_variants/publish.turbo_stream.erb +3 -0
  291. data/app/views/postnhost/article_variants/unpublish.turbo_stream.erb +3 -0
  292. data/app/views/postnhost/article_variants/update.turbo_stream.erb +3 -0
  293. data/app/views/postnhost/articles/_author_manager.html.erb +38 -0
  294. data/app/views/postnhost/articles/_category_manager.html.erb +42 -0
  295. data/app/views/postnhost/articles/_cover_image_upload.html.erb +82 -0
  296. data/app/views/postnhost/articles/_suggestion_manager.html.erb +43 -0
  297. data/app/views/postnhost/articles/_version_snapshot.html.erb +10 -0
  298. data/app/views/postnhost/articles/edit.html.erb +51 -0
  299. data/app/views/postnhost/articles/index.html.erb +132 -0
  300. data/app/views/postnhost/articles/publish.turbo_stream.erb +3 -0
  301. data/app/views/postnhost/articles/unpublish.turbo_stream.erb +3 -0
  302. data/app/views/postnhost/articles/update.turbo_stream.erb +3 -0
  303. data/app/views/postnhost/articles/version_preview.html.erb +17 -0
  304. data/app/views/postnhost/articles/versions.html.erb +68 -0
  305. data/app/views/postnhost/categories/edit.html.erb +77 -0
  306. data/app/views/postnhost/categories/index.html.erb +80 -0
  307. data/app/views/postnhost/categories/new.html.erb +50 -0
  308. data/app/views/postnhost/categories/show.html.erb +99 -0
  309. data/app/views/postnhost/category_variants/_category_variants_list.html.erb +67 -0
  310. data/app/views/postnhost/category_variants/_form.html.erb +29 -0
  311. data/app/views/postnhost/category_variants/edit.html.erb +21 -0
  312. data/app/views/postnhost/category_variants/index.html.erb +82 -0
  313. data/app/views/postnhost/category_variants/new.html.erb +21 -0
  314. data/app/views/postnhost/languages/edit.html.erb +45 -0
  315. data/app/views/postnhost/languages/index.html.erb +86 -0
  316. data/app/views/postnhost/languages/new.html.erb +45 -0
  317. data/app/views/postnhost/languages/show.html.erb +156 -0
  318. data/app/views/postnhost/onboarding/_post_install_checklist.html.erb +40 -0
  319. data/app/views/postnhost/onboarding/_step_1.html.erb +42 -0
  320. data/app/views/postnhost/onboarding/_step_2.html.erb +38 -0
  321. data/app/views/postnhost/onboarding/_step_3.html.erb +62 -0
  322. data/app/views/postnhost/onboarding/_step_4.html.erb +26 -0
  323. data/app/views/postnhost/onboarding/show.html.erb +51 -0
  324. data/app/views/postnhost/page_variants/_variants_list.html.erb +82 -0
  325. data/app/views/postnhost/page_variants/edit.html.erb +50 -0
  326. data/app/views/postnhost/page_variants/index.html.erb +128 -0
  327. data/app/views/postnhost/page_variants/publish.turbo_stream.erb +3 -0
  328. data/app/views/postnhost/page_variants/unpublish.turbo_stream.erb +3 -0
  329. data/app/views/postnhost/page_variants/update.turbo_stream.erb +3 -0
  330. data/app/views/postnhost/pages/edit.html.erb +50 -0
  331. data/app/views/postnhost/pages/index.html.erb +110 -0
  332. data/app/views/postnhost/pages/publish.turbo_stream.erb +3 -0
  333. data/app/views/postnhost/pages/unpublish.turbo_stream.erb +3 -0
  334. data/app/views/postnhost/public/articles/content_for/_index.html.erb +30 -0
  335. data/app/views/postnhost/public/articles/content_for/_preview.html.erb +9 -0
  336. data/app/views/postnhost/public/articles/content_for/_show.html.erb +24 -0
  337. data/app/views/postnhost/public/authors/content_for/_show.html.erb +24 -0
  338. data/app/views/postnhost/public/categories/content_for/_index.html.erb +29 -0
  339. data/app/views/postnhost/public/static_pages/content_for/_show.html.erb +19 -0
  340. data/app/views/postnhost/public/templates/default/articles/_article_card.html.erb +37 -0
  341. data/app/views/postnhost/public/templates/default/articles/_search_form.html.erb +12 -0
  342. data/app/views/postnhost/public/templates/default/articles/_suggestions.html.erb +44 -0
  343. data/app/views/postnhost/public/templates/default/articles/_top_picks.html.erb +42 -0
  344. data/app/views/postnhost/public/templates/default/articles/index.html.erb +53 -0
  345. data/app/views/postnhost/public/templates/default/articles/preview.html.erb +102 -0
  346. data/app/views/postnhost/public/templates/default/articles/show.html.erb +111 -0
  347. data/app/views/postnhost/public/templates/default/authors/_article_card.html.erb +28 -0
  348. data/app/views/postnhost/public/templates/default/authors/_profile_block.html.erb +29 -0
  349. data/app/views/postnhost/public/templates/default/authors/show.html.erb +36 -0
  350. data/app/views/postnhost/public/templates/default/categories/_article_card.html.erb +34 -0
  351. data/app/views/postnhost/public/templates/default/categories/index.html.erb +48 -0
  352. data/app/views/postnhost/public/templates/default/shared/_article_authors.html.erb +24 -0
  353. data/app/views/postnhost/public/templates/default/shared/_author_avatar.html.erb +9 -0
  354. data/app/views/postnhost/public/templates/default/shared/_hero_cover_image.html.erb +11 -0
  355. data/app/views/postnhost/public/templates/default/shared/_pagination.html.erb +35 -0
  356. data/app/views/postnhost/public/templates/default/shared/_thumbnail_cover_image.html.erb +9 -0
  357. data/app/views/postnhost/public/templates/default/static_pages/show.html.erb +22 -0
  358. data/app/views/postnhost/public/templates/swiss-editorial/articles/_article_card.html.erb +28 -0
  359. data/app/views/postnhost/public/templates/swiss-editorial/articles/_search_form.html.erb +19 -0
  360. data/app/views/postnhost/public/templates/swiss-editorial/articles/_suggestions.html.erb +42 -0
  361. data/app/views/postnhost/public/templates/swiss-editorial/articles/_top_picks.html.erb +38 -0
  362. data/app/views/postnhost/public/templates/swiss-editorial/articles/index.html.erb +133 -0
  363. data/app/views/postnhost/public/templates/swiss-editorial/articles/preview.html.erb +42 -0
  364. data/app/views/postnhost/public/templates/swiss-editorial/articles/show.html.erb +100 -0
  365. data/app/views/postnhost/public/templates/swiss-editorial/authors/_article_card.html.erb +27 -0
  366. data/app/views/postnhost/public/templates/swiss-editorial/authors/_profile_block.html.erb +32 -0
  367. data/app/views/postnhost/public/templates/swiss-editorial/authors/show.html.erb +31 -0
  368. data/app/views/postnhost/public/templates/swiss-editorial/categories/_article_card.html.erb +28 -0
  369. data/app/views/postnhost/public/templates/swiss-editorial/categories/index.html.erb +88 -0
  370. data/app/views/postnhost/public/templates/swiss-editorial/shared/_article_authors.html.erb +26 -0
  371. data/app/views/postnhost/public/templates/swiss-editorial/shared/_author_avatar.html.erb +9 -0
  372. data/app/views/postnhost/public/templates/swiss-editorial/shared/_hero_cover_image.html.erb +11 -0
  373. data/app/views/postnhost/public/templates/swiss-editorial/shared/_pagination.html.erb +35 -0
  374. data/app/views/postnhost/public/templates/swiss-editorial/shared/_thumbnail_cover_image.html.erb +9 -0
  375. data/app/views/postnhost/public/templates/swiss-editorial/static_pages/show.html.erb +17 -0
  376. data/app/views/postnhost/public/templates/workspace-journal/articles/_article_card.html.erb +44 -0
  377. data/app/views/postnhost/public/templates/workspace-journal/articles/_search_form.html.erb +19 -0
  378. data/app/views/postnhost/public/templates/workspace-journal/articles/_suggestions.html.erb +49 -0
  379. data/app/views/postnhost/public/templates/workspace-journal/articles/_top_picks.html.erb +34 -0
  380. data/app/views/postnhost/public/templates/workspace-journal/articles/index.html.erb +69 -0
  381. data/app/views/postnhost/public/templates/workspace-journal/articles/preview.html.erb +113 -0
  382. data/app/views/postnhost/public/templates/workspace-journal/articles/show.html.erb +120 -0
  383. data/app/views/postnhost/public/templates/workspace-journal/authors/_article_card.html.erb +1 -0
  384. data/app/views/postnhost/public/templates/workspace-journal/authors/_profile_block.html.erb +32 -0
  385. data/app/views/postnhost/public/templates/workspace-journal/authors/show.html.erb +33 -0
  386. data/app/views/postnhost/public/templates/workspace-journal/categories/_article_card.html.erb +1 -0
  387. data/app/views/postnhost/public/templates/workspace-journal/categories/index.html.erb +58 -0
  388. data/app/views/postnhost/public/templates/workspace-journal/shared/_article_authors.html.erb +27 -0
  389. data/app/views/postnhost/public/templates/workspace-journal/shared/_article_card.html.erb +44 -0
  390. data/app/views/postnhost/public/templates/workspace-journal/shared/_author_avatar.html.erb +9 -0
  391. data/app/views/postnhost/public/templates/workspace-journal/shared/_category_navigation.html.erb +10 -0
  392. data/app/views/postnhost/public/templates/workspace-journal/shared/_hero_cover_image.html.erb +11 -0
  393. data/app/views/postnhost/public/templates/workspace-journal/shared/_pagination.html.erb +35 -0
  394. data/app/views/postnhost/public/templates/workspace-journal/shared/_thumbnail_cover_image.html.erb +9 -0
  395. data/app/views/postnhost/public/templates/workspace-journal/static_pages/show.html.erb +17 -0
  396. data/app/views/postnhost/sessions/new.html.erb +59 -0
  397. data/app/views/postnhost/settings/edit.html.erb +68 -0
  398. data/app/views/postnhost/settings/sections/_assets.html.erb +40 -0
  399. data/app/views/postnhost/settings/sections/_feature_flags.html.erb +43 -0
  400. data/app/views/postnhost/settings/sections/_i18n.html.erb +57 -0
  401. data/app/views/postnhost/settings/sections/_navigation.html.erb +98 -0
  402. data/app/views/postnhost/settings/sections/_schema.html.erb +253 -0
  403. data/app/views/postnhost/settings/sections/_scripts.html.erb +79 -0
  404. data/app/views/postnhost/settings/sections/_site.html.erb +41 -0
  405. data/app/views/postnhost/settings/sections/_timezone.html.erb +19 -0
  406. data/app/views/postnhost/shared/_ai_translation_modal.html.erb +85 -0
  407. data/app/views/postnhost/shared/_flash_messages.html.erb +27 -0
  408. data/app/views/postnhost/shared/_form_errors.html.erb +22 -0
  409. data/app/views/postnhost/shared/_seo_meta_tags.html.erb +70 -0
  410. data/app/views/postnhost/shared/_structured_data.html.erb +5 -0
  411. data/app/views/postnhost/shared/articles/_action_buttons.html.erb +87 -0
  412. data/app/views/postnhost/shared/articles/_cover_image_preview.html.erb +30 -0
  413. data/app/views/postnhost/shared/articles/_editor_column.html.erb +15 -0
  414. data/app/views/postnhost/shared/articles/_editor_column_preview.html.erb +33 -0
  415. data/app/views/postnhost/shared/articles/_error_messages.html.erb +23 -0
  416. data/app/views/postnhost/shared/articles/_meta_sidebar.html.erb +322 -0
  417. data/app/views/postnhost/shared/articles/_meta_sidebar_preview.html.erb +29 -0
  418. data/app/views/postnhost/shared/articles/_post_actions.html.erb +105 -0
  419. data/app/views/postnhost/shared/articles/_schedule_actions.html.erb +51 -0
  420. data/app/views/postnhost/shared/articles/_tiptap_editor.html.erb +225 -0
  421. data/app/views/postnhost/shared/articles/_title_field.html.erb +9 -0
  422. data/app/views/postnhost/shared/articles/_version_action_buttons.html.erb +35 -0
  423. data/app/views/postnhost/shared/elements/_button.html.erb +20 -0
  424. data/app/views/postnhost/shared/elements/_button_link.html.erb +24 -0
  425. data/app/views/postnhost/shared/elements/_empty_state.html.erb +22 -0
  426. data/app/views/postnhost/shared/elements/_error_messages.html.erb +28 -0
  427. data/app/views/postnhost/shared/elements/_form_card.html.erb +10 -0
  428. data/app/views/postnhost/shared/elements/_form_page_header.html.erb +21 -0
  429. data/app/views/postnhost/shared/elements/_icon_button.html.erb +17 -0
  430. data/app/views/postnhost/shared/elements/_page_header.html.erb +40 -0
  431. data/app/views/postnhost/shared/elements/_table_container.html.erb +23 -0
  432. data/app/views/postnhost/shared/pages/_action_buttons.html.erb +84 -0
  433. data/app/views/postnhost/shared/pages/_meta_sidebar.html.erb +132 -0
  434. data/app/views/postnhost/shared/pages/_post_actions.html.erb +69 -0
  435. data/app/views/postnhost/templates/edit.html.erb +83 -0
  436. data/app/views/postnhost/users/_avatar.html.erb +9 -0
  437. data/app/views/postnhost/users/_form.html.erb +160 -0
  438. data/app/views/postnhost/users/edit.html.erb +26 -0
  439. data/app/views/postnhost/users/index.html.erb +91 -0
  440. data/app/views/postnhost/users/new.html.erb +9 -0
  441. data/app/views/postnhost/users/schema_edit.html.erb +175 -0
  442. data/config/locales/de.yml +55 -0
  443. data/config/locales/en.yml +88 -0
  444. data/config/locales/es.yml +58 -0
  445. data/config/locales/fr.yml +55 -0
  446. data/config/locales/ja.yml +55 -0
  447. data/config/locales/ko.yml +55 -0
  448. data/config/locales/pl.yml +55 -0
  449. data/config/locales/pt.yml +55 -0
  450. data/config/locales/ru.yml +55 -0
  451. data/config/routes.rb +112 -0
  452. data/db/migrate/20250822213820_create_users.rb +29 -0
  453. data/db/migrate/20250822214046_create_categories.rb +15 -0
  454. data/db/migrate/20250822214106_create_languages.rb +16 -0
  455. data/db/migrate/20250822214148_create_articles.rb +34 -0
  456. data/db/migrate/20250822214217_create_article_variants.rb +24 -0
  457. data/db/migrate/20250929091137_create_article_categories.rb +12 -0
  458. data/db/migrate/20251013204200_create_versions.rb +40 -0
  459. data/db/migrate/20260106074532_create_article_suggestions.rb +13 -0
  460. data/db/migrate/20260108110506_create_category_variants.rb +15 -0
  461. data/db/migrate/20260308130000_create_settings.rb +23 -0
  462. data/db/migrate/20260323113000_create_pages.rb +19 -0
  463. data/db/migrate/20260414100000_create_article_authors.rb +14 -0
  464. data/db/migrate/20260512100000_create_templates.rb +9 -0
  465. data/db/migrate/20260520110001_create_navigations.rb +9 -0
  466. data/db/migrate/20260520110002_create_navigation_items.rb +22 -0
  467. data/db/migrate/20260528000002_create_page_variants.rb +20 -0
  468. data/db/migrate/20260709090000_create_snapshots.rb +154 -0
  469. data/db/migrate/20260726130000_create_site_scripts.rb +11 -0
  470. data/lib/generators/postnhost/install/install_generator.rb +55 -0
  471. data/lib/generators/postnhost/install/templates/carrierwave.rb +17 -0
  472. data/lib/generators/postnhost/install/templates/postnhost.rb +36 -0
  473. data/lib/generators/postnhost/locale/locale_generator.rb +36 -0
  474. data/lib/generators/postnhost/static_pages/static_pages_generator.rb +47 -0
  475. data/lib/generators/postnhost/tailwindcss/install_generator.rb +100 -0
  476. data/lib/generators/postnhost/tailwindcss/templates/host.tailwind.css +4 -0
  477. data/lib/generators/postnhost/user/user_generator.rb +36 -0
  478. data/lib/generators/postnhost/views/views_generator.rb +140 -0
  479. data/lib/postnhost/configuration.rb +64 -0
  480. data/lib/postnhost/css_build.rb +68 -0
  481. data/lib/postnhost/css_scope.rb +77 -0
  482. data/lib/postnhost/engine.rb +57 -0
  483. data/lib/postnhost/sample_data.rb +865 -0
  484. data/lib/postnhost/settings/i18n_overrides.rb +137 -0
  485. data/lib/postnhost/settings/i18n_patch.rb +18 -0
  486. data/lib/postnhost/tailwind_compiler.rb +75 -0
  487. data/lib/postnhost/tailwind_input.rb +32 -0
  488. data/lib/postnhost/version.rb +1 -3
  489. data/lib/postnhost.rb +26 -3
  490. data/lib/tasks/postnhost_snapshots.rake +77 -0
  491. data/lib/tasks/postnhost_tasks.rake +71 -0
  492. metadata +679 -21
  493. data/.github/workflows/main.yml +0 -27
  494. data/.gitignore +0 -11
  495. data/.rspec +0 -3
  496. data/.rubocop.yml +0 -8
  497. data/Gemfile +0 -13
  498. data/bin/console +0 -11
  499. data/bin/setup +0 -8
  500. data/postnhost.gemspec +0 -31
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= document_lang %>" data-postnhost>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+
7
+ <%= render 'postnhost/shared/seo_meta_tags' %>
8
+ <%= render 'postnhost/shared/structured_data' %>
9
+
10
+ <%= csrf_meta_tags %>
11
+ <%= csp_meta_tag %>
12
+
13
+ <%= stylesheet_link_tag "postnhost/application", "data-turbo-track": "reload" %>
14
+ <%= javascript_include_tag "postnhost/application", "data-turbo-track": "reload", type: "module" %>
15
+
16
+ <%= render 'layouts/postnhost/favicon' %>
17
+
18
+ <%= yield :head %>
19
+ <%= render_site_scripts("head") %>
20
+ </head>
21
+
22
+ <body class="min-h-dvh overflow-y-scroll">
23
+ <%= render_site_scripts("body_start") %>
24
+ <header class="bg-white border-b border-gray-200"<%= ' data-controller="reading-progress"'.html_safe if public_article_show_page? %>>
25
+ <% if public_article_show_page? %>
26
+ <div class="fixed top-0 left-0 h-0.5 bg-gray-900 z-50 transition-all duration-500 ease-out" data-reading-progress-target="bar" style="width: 0%"></div>
27
+ <% end %>
28
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"<%= ' data-controller="public-mobile-nav"'.html_safe if !@use_auto_header_navigation && @header_navigation_items.any? %>>
29
+ <div class="flex items-center justify-between gap-4">
30
+ <div class="flex min-w-0 items-center gap-6">
31
+ <%= render "layouts/postnhost/public_header/logo" %>
32
+
33
+ <% if !@use_auto_header_navigation && @header_navigation_items.any? %>
34
+ <nav class="hidden md:flex items-center gap-3 lg:gap-5 min-h-[40px]">
35
+ <% @header_navigation_items.each do |item| %>
36
+ <% if item.kind == "link" && navigation_href(item).present? %>
37
+ <%= link_to navigation_label(item), navigation_href(item), **navigation_link_options(item), class: "text-sm text-gray-600 hover:text-gray-900 transition-colors cursor-pointer" %>
38
+ <% elsif item.kind == "link" && item.target_kind == "text" && navigation_label(item).present? %>
39
+ <p class="text-xs font-semibold uppercase tracking-wide text-gray-400"><%= navigation_label(item) %></p>
40
+ <% elsif item.kind == "dropdown" && item.children.any? %>
41
+ <div class="relative group py-2">
42
+ <span class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900 cursor-pointer">
43
+ <%= navigation_label(item) %>
44
+ <%= icon("chevron-down", css_class: "h-3.5 w-3.5 transition-transform duration-200 group-hover:rotate-180") %>
45
+ </span>
46
+ <div class="invisible absolute left-0 top-full z-50 min-w-48 rounded-xl border border-gray-200 bg-white p-2 opacity-0 shadow-lg transition group-hover:visible group-hover:opacity-100">
47
+ <% item.children.each do |child| %>
48
+ <% if navigation_href(child).present? %>
49
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block px-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-50 cursor-pointer" %>
50
+ <% elsif child.target_kind == "text" && navigation_label(child).present? %>
51
+ <p class="px-3 py-2 text-xs font-semibold uppercase tracking-wide text-gray-400"><%= navigation_label(child) %></p>
52
+ <% end %>
53
+ <% end %>
54
+ </div>
55
+ </div>
56
+ <% end %>
57
+ <% end %>
58
+ </nav>
59
+ <% end %>
60
+ </div>
61
+
62
+ <div class="flex items-center space-x-2 sm:space-x-4 min-h-[40px]">
63
+ <%= render "layouts/postnhost/public_header/language_switcher" %>
64
+ <%= render "layouts/postnhost/public_header/dashboard_sign_in" %>
65
+ <% if !@use_auto_header_navigation && @header_navigation_items.any? %>
66
+ <button type="button" class="inline-flex h-9 w-9 items-center justify-center rounded-md text-gray-700 hover:bg-gray-100 md:hidden cursor-pointer" aria-label="Toggle navigation" data-action="public-mobile-nav#toggle">
67
+ <%= icon("menu", css_class: "h-5 w-5") %>
68
+ </button>
69
+ <% end %>
70
+ </div>
71
+ </div>
72
+
73
+ <% if !@use_auto_header_navigation && @header_navigation_items.any? %>
74
+ <nav class="hidden border-t border-gray-200 pt-3 mt-3 space-y-2 md:hidden" data-public-mobile-nav-target="panel">
75
+ <% @header_navigation_items.each do |item| %>
76
+ <% if item.kind == "link" && navigation_href(item).present? %>
77
+ <%= link_to navigation_label(item), navigation_href(item), **navigation_link_options(item), class: "block py-2 text-sm text-gray-700 hover:text-gray-900 cursor-pointer" %>
78
+ <% elsif item.kind == "link" && item.target_kind == "text" && navigation_label(item).present? %>
79
+ <p class="py-2 text-xs font-semibold uppercase tracking-wide text-gray-400"><%= navigation_label(item) %></p>
80
+ <% elsif item.kind == "dropdown" && item.children.any? %>
81
+ <details class="group">
82
+ <summary class="flex list-none items-center justify-between py-2 text-sm font-semibold uppercase tracking-wide text-gray-500 cursor-pointer">
83
+ <%= navigation_label(item) %>
84
+ <%= icon("chevron-down", css_class: "h-4 w-4 transition-transform group-open:rotate-180") %>
85
+ </summary>
86
+ <div class="space-y-1 pl-3">
87
+ <% item.children.each do |child| %>
88
+ <% if navigation_href(child).present? %>
89
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block py-2 text-sm text-gray-700 hover:text-gray-900 cursor-pointer" %>
90
+ <% elsif child.target_kind == "text" && navigation_label(child).present? %>
91
+ <p class="py-2 text-xs font-semibold uppercase tracking-wide text-gray-400"><%= navigation_label(child) %></p>
92
+ <% end %>
93
+ <% end %>
94
+ </div>
95
+ </details>
96
+ <% end %>
97
+ <% end %>
98
+ </nav>
99
+ <% end %>
100
+ </div>
101
+ </header>
102
+
103
+ <main>
104
+ <%= yield %>
105
+ </main>
106
+
107
+ <%= render 'layouts/postnhost/footer' %>
108
+ <%= render_site_scripts("body_end") %>
109
+ </body>
110
+ </html>
@@ -0,0 +1,213 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= document_lang %>" data-postnhost>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+
7
+ <%= render "postnhost/shared/seo_meta_tags" %>
8
+ <%= render "postnhost/shared/structured_data" %>
9
+
10
+ <%= csrf_meta_tags %>
11
+ <%= csp_meta_tag %>
12
+
13
+ <%= stylesheet_link_tag "postnhost/application", "data-turbo-track": "reload" %>
14
+ <%= javascript_include_tag "postnhost/application", "data-turbo-track": "reload", type: "module" %>
15
+
16
+ <%= render "layouts/postnhost/favicon" %>
17
+
18
+ <%= yield :head %>
19
+ <%= render_site_scripts("head") %>
20
+ </head>
21
+
22
+ <body class="min-h-dvh overflow-y-scroll bg-[#fafaf7] text-[#0a0a0a] antialiased">
23
+ <%= render_site_scripts("body_start") %>
24
+ <header class="sticky top-0 z-40 border-b border-[#d9d9d9] bg-[#fafaf7]"<%= ' data-controller="reading-progress"'.html_safe if public_article_show_page? %>>
25
+ <% if public_article_show_page? %>
26
+ <div class="fixed top-0 left-0 z-50 h-0.5 bg-[#0a0a0a] transition-all duration-500 ease-out" data-reading-progress-target="bar" style="width: 0%"></div>
27
+ <% end %>
28
+
29
+ <div class="mx-auto max-w-[1440px] px-4 sm:px-6" data-controller="public-mobile-nav">
30
+ <div class="flex items-center justify-between gap-4 py-3 lg:grid lg:grid-cols-[1fr_auto_1fr] lg:gap-6">
31
+ <nav class="hidden items-center gap-6 lg:flex" aria-label="<%= t('postnhost.public.footer.explore_categories') %>">
32
+ <% if @use_auto_header_navigation %>
33
+ <% @header_categories.first(4).each do |category| %>
34
+ <%= link_to localized_category_name(category, @current_language),
35
+ language_category_path(category.slug, @current_language),
36
+ class: "text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer" %>
37
+ <% end %>
38
+ <% else %>
39
+ <% @header_navigation_items.each do |item| %>
40
+ <% if item.kind == "link" && navigation_href(item).present? %>
41
+ <%= link_to navigation_label(item), navigation_href(item), **navigation_link_options(item), class: "text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer" %>
42
+ <% elsif item.kind == "link" && item.target_kind == "text" && navigation_label(item).present? %>
43
+ <p class="text-[11px] font-bold uppercase tracking-wide text-[#6b6b6b]"><%= navigation_label(item) %></p>
44
+ <% elsif item.kind == "dropdown" && item.children.any? %>
45
+ <div class="relative group py-2">
46
+ <span class="inline-flex items-center gap-1 text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer">
47
+ <%= navigation_label(item) %>
48
+ <%= icon("chevron-down", css_class: "h-3.5 w-3.5 transition-transform duration-200 group-hover:rotate-180") %>
49
+ </span>
50
+ <div class="invisible absolute left-0 top-full z-50 min-w-52 border-2 border-[#0a0a0a] bg-[#fafaf7] p-2 opacity-0 shadow-[6px_6px_0_#0a0a0a] transition group-hover:visible group-hover:opacity-100">
51
+ <% item.children.each do |child| %>
52
+ <% if navigation_href(child).present? %>
53
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block px-3 py-2 text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer" %>
54
+ <% elsif child.target_kind == "text" && navigation_label(child).present? %>
55
+ <p class="px-3 py-2 text-[11px] font-bold uppercase tracking-wide text-[#6b6b6b]"><%= navigation_label(child) %></p>
56
+ <% end %>
57
+ <% end %>
58
+ </div>
59
+ </div>
60
+ <% end %>
61
+ <% end %>
62
+ <% end %>
63
+ </nav>
64
+
65
+ <div class="min-w-0 lg:justify-self-center">
66
+ <%= render "layouts/postnhost/public_header/logo" %>
67
+ </div>
68
+
69
+ <div class="flex min-h-10 shrink-0 items-center justify-end gap-3 lg:justify-self-end">
70
+ <% if show_article_variant_language_switcher? %>
71
+ <select id="template-2-language-select" onchange="window.location.href=this.value" class="max-w-28 cursor-pointer appearance-none border-0 border-b border-[#0a0a0a] bg-transparent py-1 pr-4 text-[12px] font-semibold uppercase text-[#0a0a0a] focus:outline-none sm:max-w-32" aria-label="<%= @current_language.name %>">
72
+ <% localized_language_options(@article).each do |label, path| %>
73
+ <option value="<%= path %>"><%= label %></option>
74
+ <% end %>
75
+ </select>
76
+ <% elsif show_other_languages_switcher? %>
77
+ <select id="template-2-language-select" onchange="window.location.href=this.value" class="max-w-28 cursor-pointer appearance-none border-0 border-b border-[#0a0a0a] bg-transparent py-1 pr-4 text-[12px] font-semibold uppercase text-[#0a0a0a] focus:outline-none sm:max-w-32" aria-label="<%= @current_language.name %>">
78
+ <% default_language_options.each do |label, path| %>
79
+ <option value="<%= path %>"><%= label %></option>
80
+ <% end %>
81
+ </select>
82
+ <% end %>
83
+ <% if search_enabled? %>
84
+ <%= link_to public_search_trigger_url, class: "inline-flex h-9 w-9 items-center justify-center rounded-md text-[#0a0a0a] hover:bg-[#ecece8] lg:hidden cursor-pointer", aria: { label: t("postnhost.public.search.submit") }, title: t("postnhost.public.search.submit") do %>
85
+ <%= icon("magnifying-glass", css_class: "h-4 w-4") %>
86
+ <% end %>
87
+ <%= link_to public_search_trigger_url, class: "hidden text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer lg:inline-flex" do %>
88
+ <%= t("postnhost.public.search.submit") %>
89
+ <% end %>
90
+ <% end %>
91
+ <% if user_signed_in? %>
92
+ <%= link_to "Dashboard", postnhost.articles_path, data: { turbo_prefetch: false }, class: "hidden border-2 border-[#0a0a0a] bg-[#0a0a0a] px-3 py-1.5 text-[12px] font-bold uppercase text-[#fafaf7] hover:border-[#d4322c] hover:bg-[#d4322c] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#0a0a0a] sm:inline-flex cursor-pointer" %>
93
+ <% end %>
94
+ <button type="button" class="inline-flex h-9 w-9 items-center justify-center rounded-md text-[#0a0a0a] hover:bg-[#ecece8] lg:hidden cursor-pointer" aria-label="Toggle navigation" data-action="public-mobile-nav#toggle">
95
+ <%= icon("menu", css_class: "h-5 w-5") %>
96
+ </button>
97
+ </div>
98
+ </div>
99
+
100
+ <nav class="hidden border-t border-[#d9d9d9] pb-3 pt-3 space-y-2 lg:hidden" data-public-mobile-nav-target="panel">
101
+ <% if @use_auto_header_navigation %>
102
+ <% @header_categories.first(6).each do |category| %>
103
+ <%= link_to localized_category_name(category, @current_language),
104
+ language_category_path(category.slug, @current_language),
105
+ class: "block py-2 text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer" %>
106
+ <% end %>
107
+ <% else %>
108
+ <% @header_navigation_items.each do |item| %>
109
+ <% if item.kind == "link" && navigation_href(item).present? %>
110
+ <%= link_to navigation_label(item), navigation_href(item), **navigation_link_options(item), class: "block py-2 text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer" %>
111
+ <% elsif item.kind == "link" && item.target_kind == "text" && navigation_label(item).present? %>
112
+ <p class="py-2 text-[11px] font-bold uppercase tracking-wide text-[#6b6b6b]"><%= navigation_label(item) %></p>
113
+ <% elsif item.kind == "dropdown" && item.children.any? %>
114
+ <details class="group">
115
+ <summary class="flex list-none items-center justify-between py-2 text-[11px] font-bold uppercase text-[#6b6b6b] cursor-pointer">
116
+ <%= navigation_label(item) %>
117
+ <%= icon("chevron-down", css_class: "h-4 w-4 transition-transform group-open:rotate-180") %>
118
+ </summary>
119
+ <div class="space-y-1 pl-3">
120
+ <% item.children.each do |child| %>
121
+ <% if navigation_href(child).present? %>
122
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block py-2 text-[13px] font-medium uppercase text-[#0a0a0a] hover:text-[#d4322c] cursor-pointer" %>
123
+ <% elsif child.target_kind == "text" && navigation_label(child).present? %>
124
+ <p class="py-2 text-[11px] font-bold uppercase tracking-wide text-[#6b6b6b]"><%= navigation_label(child) %></p>
125
+ <% end %>
126
+ <% end %>
127
+ </div>
128
+ </details>
129
+ <% end %>
130
+ <% end %>
131
+ <% end %>
132
+ <% if user_signed_in? %>
133
+ <%= link_to "Dashboard", postnhost.articles_path, data: { turbo_prefetch: false }, class: "block py-2 text-[13px] font-bold uppercase text-[#0a0a0a] hover:text-[#d4322c] sm:hidden cursor-pointer" %>
134
+ <% end %>
135
+ </nav>
136
+ </div>
137
+ </header>
138
+
139
+ <main>
140
+ <%= yield %>
141
+ </main>
142
+
143
+ <footer aria-labelledby="footer-heading" class="mt-20 border-t-2 border-[#0a0a0a] bg-[#fafaf7]">
144
+ <h2 id="footer-heading" class="sr-only"><%= t("postnhost.public.footer.heading") %></h2>
145
+
146
+ <div class="mx-auto max-w-[1440px] px-6 py-12">
147
+ <div class="grid grid-cols-1 gap-8 md:grid-cols-12">
148
+ <div class="md:col-span-5">
149
+ <%= render "layouts/postnhost/public_header/logo" %>
150
+ <p class="mt-4 max-w-xs text-[13px] leading-6 text-[#6b6b6b]"><%= t("postnhost.public.site.blog_subtitle") %></p>
151
+ </div>
152
+
153
+ <% unless @use_auto_footer_navigation %>
154
+ <div class="md:col-span-7 md:col-start-6">
155
+ <div class="flex flex-wrap gap-x-10 gap-y-8 text-left md:justify-end">
156
+ <% @footer_navigation_columns.each do |column| %>
157
+ <div class="min-w-32">
158
+ <h3 class="mb-3 text-xs font-bold uppercase text-[#0a0a0a]"><%= navigation_label(column) %></h3>
159
+ <% column.children.sort_by(&:position).each do |child| %>
160
+ <% if child.target_kind != "external" && navigation_href(child).present? %>
161
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block py-1 text-sm text-[#1f1f1f] hover:text-[#d4322c] cursor-pointer" %>
162
+ <% end %>
163
+ <% end %>
164
+ </div>
165
+ <% end %>
166
+ </div>
167
+ </div>
168
+ <% end %>
169
+
170
+ <% if @use_auto_footer_navigation && @footer_categories.any? %>
171
+ <div class="md:col-span-3 md:col-start-9">
172
+ <h3 class="mb-3 text-xs font-bold uppercase text-[#0a0a0a]"><%= t("postnhost.public.footer.explore_categories") %></h3>
173
+ <% @footer_categories.first(4).each do |category| %>
174
+ <%= link_to localized_category_name(category, @current_language),
175
+ language_category_path(category.slug, @current_language),
176
+ class: "block py-1 text-sm text-[#1f1f1f] hover:text-[#d4322c] cursor-pointer" %>
177
+ <% end %>
178
+ </div>
179
+ <% end %>
180
+
181
+ <% if @use_auto_footer_navigation && (@footer_published_pages.any? || @footer_static_pages.any?) %>
182
+ <div class="md:col-span-1">
183
+ <h3 class="mb-3 text-xs font-bold uppercase text-[#0a0a0a]"><%= t("postnhost.public.footer.pages") %></h3>
184
+ <% @footer_published_pages.each do |page| %>
185
+ <%= link_to (page.title.presence || page.slug.humanize),
186
+ language_static_page_path(page.slug, @current_language),
187
+ class: "block py-1 text-sm text-[#1f1f1f] hover:text-[#d4322c] cursor-pointer" %>
188
+ <% end %>
189
+ <% @footer_static_pages.each do |slug| %>
190
+ <%= link_to slug.humanize,
191
+ language_static_page_path(slug, @current_language),
192
+ class: "block py-1 text-sm text-[#1f1f1f] hover:text-[#d4322c] cursor-pointer" %>
193
+ <% end %>
194
+ </div>
195
+ <% end %>
196
+
197
+ </div>
198
+
199
+ <div class="mt-10 flex flex-col gap-3 border-t border-[#d9d9d9] pt-5 text-xs text-[#6b6b6b] sm:flex-row sm:items-center sm:justify-between">
200
+ <p>&copy; <%= Date.current.year %> <%= t("postnhost.public.site.name_footer_copyright") %></p>
201
+
202
+ <% if @setting.show_powered_by? %>
203
+ <a href="https://postnhost.com" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 text-xs text-[#6b6b6b] hover:text-[#0a0a0a] transition-colors sm:justify-end cursor-pointer">
204
+ Powered by
205
+ <%= image_tag "postnhost/logo.webp", alt: "PostnHost", class: "h-4 w-auto", width: 80, height: 16 %>
206
+ </a>
207
+ <% end %>
208
+ </div>
209
+ </div>
210
+ </footer>
211
+ <%= render_site_scripts("body_end") %>
212
+ </body>
213
+ </html>
@@ -0,0 +1,231 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= document_lang %>" data-postnhost>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+
7
+ <%= render "postnhost/shared/seo_meta_tags" %>
8
+ <%= render "postnhost/shared/structured_data" %>
9
+
10
+ <%= csrf_meta_tags %>
11
+ <%= csp_meta_tag %>
12
+
13
+ <%= stylesheet_link_tag "postnhost/application", "data-turbo-track": "reload" %>
14
+ <%= javascript_include_tag "postnhost/application", "data-turbo-track": "reload", type: "module" %>
15
+
16
+ <%= render "layouts/postnhost/favicon" %>
17
+
18
+ <%= yield :head %>
19
+ <%= render_site_scripts("head") %>
20
+ </head>
21
+
22
+ <body class="min-h-dvh overflow-y-scroll bg-white text-[#1f1f1f] antialiased">
23
+ <%= render_site_scripts("body_start") %>
24
+ <header class="sticky top-0 z-40 border-b border-[#eeeeec] bg-white/95 backdrop-blur"<%= ' data-controller="reading-progress"'.html_safe if public_article_show_page? %>>
25
+ <% if public_article_show_page? %>
26
+ <div class="fixed left-0 top-0 z-50 h-0.5 w-0 bg-zinc-950" data-reading-progress-target="bar"></div>
27
+ <% end %>
28
+
29
+ <div class="mx-auto max-w-[1800px] px-6 lg:px-10" data-controller="public-mobile-nav">
30
+ <div class="flex min-h-16 items-center justify-between gap-4">
31
+ <div class="flex min-w-0 items-center gap-6">
32
+ <%= link_to language_root_path(@current_language), class: "inline-flex shrink-0 items-center cursor-pointer" do %>
33
+ <%= image_tag setting_site_logo_src,
34
+ alt: "logo",
35
+ width: 180,
36
+ height: 37,
37
+ class: "h-8 w-auto" %>
38
+ <% end %>
39
+
40
+ <% if !@use_auto_header_navigation && @header_navigation_items.any? %>
41
+ <nav class="hidden items-center gap-1 lg:flex" aria-label="<%= t("postnhost.public.footer.explore_categories") %>">
42
+ <% @header_navigation_items.each do |item| %>
43
+ <% if item.kind == "link" && navigation_href(item).present? %>
44
+ <%= link_to navigation_label(item), navigation_href(item), **navigation_link_options(item), class: "rounded-md px-2.5 py-2 text-base/6 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 sm:text-sm/6 cursor-pointer" %>
45
+ <% elsif item.kind == "link" && item.target_kind == "text" && navigation_label(item).present? %>
46
+ <p class="px-2.5 py-2 text-base/6 font-medium text-zinc-500 sm:text-sm/6"><%= navigation_label(item) %></p>
47
+ <% elsif item.kind == "dropdown" && item.children.any? %>
48
+ <div class="group relative py-2">
49
+ <span class="inline-flex cursor-pointer items-center gap-1 rounded-md px-2.5 py-2 text-base/6 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 sm:text-sm/6">
50
+ <%= navigation_label(item) %>
51
+ <%= icon("chevron-down", css_class: "size-4 shrink-0 transition-transform duration-200 group-hover:rotate-180") %>
52
+ </span>
53
+ <div class="invisible absolute left-0 top-full z-50 min-w-56 rounded-lg bg-white p-2 opacity-0 shadow-lg ring-1 ring-zinc-950/10 transition group-hover:visible group-hover:opacity-100">
54
+ <% item.children.each do |child| %>
55
+ <% if navigation_href(child).present? %>
56
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block rounded-md px-3 py-2 text-base/6 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 sm:text-sm/6 cursor-pointer" %>
57
+ <% elsif child.target_kind == "text" && navigation_label(child).present? %>
58
+ <p class="px-3 py-2 text-base/6 font-medium text-zinc-500 sm:text-sm/6"><%= navigation_label(child) %></p>
59
+ <% end %>
60
+ <% end %>
61
+ </div>
62
+ </div>
63
+ <% end %>
64
+ <% end %>
65
+ </nav>
66
+ <% end %>
67
+ </div>
68
+
69
+ <div class="flex items-center justify-end gap-2">
70
+ <% if show_article_variant_language_switcher? %>
71
+ <select id="workspace-journal-language-select" onchange="window.location.href=this.value" class="max-w-32 cursor-pointer appearance-none rounded-md border-0 bg-transparent px-2 py-2 text-base/6 font-medium text-zinc-700 hover:bg-zinc-950/5 focus:outline-2 focus:outline-offset-0 focus:outline-zinc-950 sm:text-sm/6" aria-label="<%= @current_language.name %>">
72
+ <% localized_language_options(@article).each do |label, path| %>
73
+ <option value="<%= path %>"><%= label %></option>
74
+ <% end %>
75
+ </select>
76
+ <% elsif show_page_variant_language_switcher? %>
77
+ <select id="workspace-journal-language-select" onchange="window.location.href=this.value" class="max-w-32 cursor-pointer appearance-none rounded-md border-0 bg-transparent px-2 py-2 text-base/6 font-medium text-zinc-700 hover:bg-zinc-950/5 focus:outline-2 focus:outline-offset-0 focus:outline-zinc-950 sm:text-sm/6" aria-label="<%= @current_language.name %>">
78
+ <% localized_language_options(@page).each do |label, path| %>
79
+ <option value="<%= path %>"><%= label %></option>
80
+ <% end %>
81
+ </select>
82
+ <% elsif show_other_languages_switcher? %>
83
+ <select id="workspace-journal-language-select" onchange="window.location.href=this.value" class="max-w-32 cursor-pointer appearance-none rounded-md border-0 bg-transparent px-2 py-2 text-base/6 font-medium text-zinc-700 hover:bg-zinc-950/5 focus:outline-2 focus:outline-offset-0 focus:outline-zinc-950 sm:text-sm/6" aria-label="<%= @current_language.name %>">
84
+ <% default_language_options.each do |label, path| %>
85
+ <option value="<%= path %>"><%= label %></option>
86
+ <% end %>
87
+ </select>
88
+ <% end %>
89
+
90
+ <% if search_enabled? %>
91
+ <%= link_to public_search_trigger_url, class: "relative inline-flex size-10 items-center justify-center rounded-md text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 sm:size-9 cursor-pointer", aria: { label: t("postnhost.public.search.submit") }, title: t("postnhost.public.search.submit") do %>
92
+ <span class="absolute left-1/2 top-1/2 size-[max(100%,3rem)] -translate-x-1/2 -translate-y-1/2 pointer-fine:hidden" aria-hidden="true"></span>
93
+ <%= icon("magnifying-glass", css_class: "size-5 shrink-0 sm:size-4") %>
94
+ <% end %>
95
+ <% end %>
96
+
97
+ <% if user_signed_in? %>
98
+ <%= link_to postnhost.articles_path, data: { turbo_prefetch: false }, class: "hidden rounded-md bg-zinc-950 px-3 py-2 text-sm/6 font-medium text-white hover:bg-zinc-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-950 sm:inline-flex cursor-pointer" do %>
99
+ Dashboard
100
+ <% end %>
101
+ <% end %>
102
+
103
+ <% if (!@use_auto_header_navigation && @header_navigation_items.any?) || user_signed_in? %>
104
+ <button type="button" class="relative inline-flex size-10 items-center justify-center rounded-md text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 lg:hidden cursor-pointer" aria-label="Toggle navigation" data-action="public-mobile-nav#toggle">
105
+ <span class="absolute left-1/2 top-1/2 size-[max(100%,3rem)] -translate-x-1/2 -translate-y-1/2 pointer-fine:hidden" aria-hidden="true"></span>
106
+ <%= icon("menu", css_class: "size-5 shrink-0") %>
107
+ </button>
108
+ <% end %>
109
+ </div>
110
+ </div>
111
+
112
+ <% if (!@use_auto_header_navigation && @header_navigation_items.any?) || user_signed_in? %>
113
+ <nav class="hidden border-t border-zinc-950/10 py-3 lg:hidden" data-public-mobile-nav-target="panel">
114
+ <div class="flex flex-col gap-1">
115
+ <% unless @use_auto_header_navigation %>
116
+ <% @header_navigation_items.each do |item| %>
117
+ <% if item.kind == "link" && navigation_href(item).present? %>
118
+ <%= link_to navigation_label(item), navigation_href(item), **navigation_link_options(item), class: "rounded-md px-2 py-2 text-base/7 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 cursor-pointer" %>
119
+ <% elsif item.kind == "link" && item.target_kind == "text" && navigation_label(item).present? %>
120
+ <p class="px-2 py-2 text-base/7 font-medium text-zinc-500"><%= navigation_label(item) %></p>
121
+ <% elsif item.kind == "dropdown" && item.children.any? %>
122
+ <details class="group">
123
+ <summary class="flex cursor-pointer list-none items-center justify-between rounded-md px-2 py-2 text-base/7 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950">
124
+ <%= navigation_label(item) %>
125
+ <%= icon("chevron-down", css_class: "size-4 shrink-0 transition-transform group-open:rotate-180") %>
126
+ </summary>
127
+ <div class="flex flex-col gap-1 pl-3">
128
+ <% item.children.each do |child| %>
129
+ <% if navigation_href(child).present? %>
130
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "rounded-md px-2 py-2 text-base/7 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 cursor-pointer" %>
131
+ <% elsif child.target_kind == "text" && navigation_label(child).present? %>
132
+ <p class="px-2 py-2 text-base/7 font-medium text-zinc-500"><%= navigation_label(child) %></p>
133
+ <% end %>
134
+ <% end %>
135
+ </div>
136
+ </details>
137
+ <% end %>
138
+ <% end %>
139
+ <% end %>
140
+
141
+ <% if user_signed_in? %>
142
+ <%= link_to "Dashboard", postnhost.articles_path, data: { turbo_prefetch: false }, class: "rounded-md px-2 py-2 text-base/7 font-medium text-zinc-700 hover:bg-zinc-950/5 hover:text-zinc-950 sm:hidden cursor-pointer" %>
143
+ <% end %>
144
+ </div>
145
+ </nav>
146
+ <% end %>
147
+ </div>
148
+ </header>
149
+
150
+ <main class="isolate">
151
+ <%= yield %>
152
+ </main>
153
+
154
+ <footer aria-labelledby="footer-heading" class="mt-12 border-t border-[#eeeeec] bg-white lg:mt-14">
155
+ <h2 id="footer-heading" class="sr-only"><%= t("postnhost.public.footer.heading") %></h2>
156
+
157
+ <div class="mx-auto max-w-[1800px] px-6 py-12 lg:px-10 lg:py-16">
158
+ <div class="grid grid-cols-1 gap-10 md:grid-cols-12">
159
+ <div class="md:col-span-5">
160
+ <%= link_to language_root_path(@current_language), class: "inline-flex items-center cursor-pointer" do %>
161
+ <%= image_tag setting_site_logo_src, alt: "logo", width: 180, height: 37, class: "h-6 w-auto" %>
162
+ <% end %>
163
+ <p class="mt-4 max-w-[34ch] text-base/7 text-zinc-600 sm:text-sm/6"><%= t("postnhost.public.site.blog_subtitle") %></p>
164
+ </div>
165
+
166
+ <% unless @use_auto_footer_navigation %>
167
+ <div class="md:col-span-7">
168
+ <div class="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
169
+ <% @footer_navigation_columns.each do |column| %>
170
+ <div>
171
+ <h3 class="text-sm/6 font-semibold text-zinc-950"><%= navigation_label(column) %></h3>
172
+ <div class="mt-3 flex flex-col gap-2">
173
+ <% column.children.sort_by(&:position).each do |child| %>
174
+ <% if child.target_kind != "external" && navigation_href(child).present? %>
175
+ <%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "text-base/7 font-normal text-zinc-600 hover:text-zinc-950 sm:text-sm/6 cursor-pointer" %>
176
+ <% end %>
177
+ <% end %>
178
+ </div>
179
+ </div>
180
+ <% end %>
181
+ </div>
182
+ </div>
183
+ <% end %>
184
+
185
+ <% if @use_auto_footer_navigation && @footer_categories.any? %>
186
+ <div class="md:col-span-4 md:col-start-7">
187
+ <h3 class="text-sm/6 font-semibold text-zinc-950"><%= t("postnhost.public.footer.explore_categories") %></h3>
188
+ <div class="mt-3 flex flex-col gap-2">
189
+ <% @footer_categories.first(6).each do |category| %>
190
+ <%= link_to localized_category_name(category, @current_language),
191
+ language_category_path(category.slug, @current_language),
192
+ class: "text-base/7 font-normal text-zinc-600 hover:text-zinc-950 sm:text-sm/6 cursor-pointer" %>
193
+ <% end %>
194
+ </div>
195
+ </div>
196
+ <% end %>
197
+
198
+ <% if @use_auto_footer_navigation && (@footer_published_pages.any? || @footer_static_pages.any?) %>
199
+ <div class="md:col-span-2">
200
+ <h3 class="text-sm/6 font-semibold text-zinc-950"><%= t("postnhost.public.footer.pages") %></h3>
201
+ <div class="mt-3 flex flex-col gap-2">
202
+ <% @footer_published_pages.each do |page| %>
203
+ <%= link_to (page.title.presence || page.slug.humanize),
204
+ language_static_page_path(page.slug, @current_language),
205
+ class: "text-base/7 font-normal text-zinc-600 hover:text-zinc-950 sm:text-sm/6 cursor-pointer" %>
206
+ <% end %>
207
+ <% @footer_static_pages.each do |slug| %>
208
+ <%= link_to slug.humanize,
209
+ language_static_page_path(slug, @current_language),
210
+ class: "text-base/7 font-normal text-zinc-600 hover:text-zinc-950 sm:text-sm/6 cursor-pointer" %>
211
+ <% end %>
212
+ </div>
213
+ </div>
214
+ <% end %>
215
+ </div>
216
+
217
+ <div class="mt-8 flex flex-col gap-4 border-t border-zinc-950/10 pt-5 text-base/7 text-zinc-500 sm:flex-row sm:items-center sm:justify-between sm:text-sm/6 lg:mt-10 lg:pt-6">
218
+ <p>&copy; <%= Date.current.year %> <%= t("postnhost.public.site.name_footer_copyright") %></p>
219
+
220
+ <% if @setting.show_powered_by? %>
221
+ <a href="https://postnhost.com" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 font-normal text-zinc-500 hover:text-zinc-950 sm:justify-end cursor-pointer">
222
+ Powered by
223
+ <%= image_tag "postnhost/logo.webp", alt: "PostnHost", class: "h-4 w-auto opacity-70", width: 80, height: 16 %>
224
+ </a>
225
+ <% end %>
226
+ </div>
227
+ </div>
228
+ </footer>
229
+ <%= render_site_scripts("body_end") %>
230
+ </body>
231
+ </html>
@@ -0,0 +1,14 @@
1
+ <% if search_enabled? %>
2
+ <%= link_to public_search_trigger_url, class: "inline-flex items-center justify-center bg-white border border-gray-200 rounded-xl px-3 py-1.5 sm:py-2 text-gray-700 hover:border-gray-300 hover:text-gray-900 cursor-pointer" do %>
3
+ <span class="sr-only"><%= t("postnhost.public.search.submit") %></span>
4
+ <%= icon("magnifying-glass", css_class: "w-4 h-4 sm:w-5 sm:h-5 shrink-0") %>
5
+ <% end %>
6
+ <% end %>
7
+
8
+ <% if user_signed_in? %>
9
+ <%= link_to postnhost.articles_path, class: "bg-gray-900 text-white px-3 sm:px-6 py-2 sm:py-3 rounded-2xl font-semibold hover:bg-gray-800 transition-all duration-200 text-sm cursor-pointer inline-flex items-center justify-center w-10 sm:w-auto" do %>
10
+ <span class="sm:hidden" aria-hidden="true"><%= icon("arrow-right-end-on-rectangle", css_class: "w-4 h-4") %></span>
11
+ <span class="hidden sm:inline">Dashboard</span>
12
+ <span class="sr-only sm:hidden">Dashboard</span>
13
+ <% end %>
14
+ <% end %>
@@ -0,0 +1,43 @@
1
+ <% if show_article_variant_language_switcher? %>
2
+ <div class="relative">
3
+ <label for="language-select" class="sr-only">Select Language</label>
4
+ <select id="language-select" onchange="window.location.href=this.value" class="appearance-none bg-white border border-gray-200 rounded-xl px-3 sm:px-4 py-1.5 sm:py-2 pr-7 sm:pr-8 text-xs sm:text-sm text-gray-700 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-transparent cursor-pointer w-[110px] sm:w-[140px]" aria-label="Select Language">
5
+ <% localized_language_options(@article).each do |label, path| %>
6
+ <option value="<%= path %>"><%= label %></option>
7
+ <% end %>
8
+ </select>
9
+ <div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
10
+ <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
11
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
12
+ </svg>
13
+ </div>
14
+ </div>
15
+ <% elsif show_page_variant_language_switcher? %>
16
+ <div class="relative">
17
+ <label for="language-select" class="sr-only">Select Language</label>
18
+ <select id="language-select" onchange="window.location.href=this.value" class="appearance-none bg-white border border-gray-200 rounded-xl px-3 sm:px-4 py-1.5 sm:py-2 pr-7 sm:pr-8 text-xs sm:text-sm text-gray-700 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-transparent cursor-pointer w-[110px] sm:w-[140px]" aria-label="Select Language">
19
+ <% localized_language_options(@page).each do |label, path| %>
20
+ <option value="<%= path %>"><%= label %></option>
21
+ <% end %>
22
+ </select>
23
+ <div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
24
+ <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
25
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
26
+ </svg>
27
+ </div>
28
+ </div>
29
+ <% elsif show_other_languages_switcher? %>
30
+ <div class="relative">
31
+ <label for="language-select" class="sr-only">Select Language</label>
32
+ <select id="language-select" onchange="window.location.href=this.value" class="appearance-none bg-white border border-gray-200 rounded-xl px-3 sm:px-4 py-1.5 sm:py-2 pr-7 sm:pr-8 text-xs sm:text-sm text-gray-700 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-transparent cursor-pointer w-[110px] sm:w-[140px]" aria-label="Select Language">
33
+ <% default_language_options.each do |label, path| %>
34
+ <option value="<%= path %>"><%= label %></option>
35
+ <% end %>
36
+ </select>
37
+ <div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
38
+ <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
39
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
40
+ </svg>
41
+ </div>
42
+ </div>
43
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <div class="flex items-center">
2
+ <%= link_to language_root_path(@current_language), class: "flex items-center group shrink-0" do %>
3
+ <%= image_tag setting_site_logo_src,
4
+ alt: "logo",
5
+ width: 180,
6
+ height: 37,
7
+ class: "h-7 sm:h-9 w-auto group-hover:opacity-80 transition-opacity" %>
8
+ <% end %>
9
+ </div>