postnhost 0.0.0 → 0.1.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 (497) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/{LICENSE.txt → LICENSE} +6 -6
  4. data/README.md +341 -15
  5. data/Rakefile +38 -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 +343 -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 +247 -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 +56 -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 +97 -0
  476. data/lib/generators/postnhost/tailwindcss/templates/host.tailwind.css +7 -0
  477. data/lib/generators/postnhost/tailwindcss/templates/tailwind.config.js +8 -0
  478. data/lib/generators/postnhost/user/user_generator.rb +36 -0
  479. data/lib/generators/postnhost/views/views_generator.rb +142 -0
  480. data/lib/postnhost/configuration.rb +64 -0
  481. data/lib/postnhost/engine.rb +57 -0
  482. data/lib/postnhost/sample_data.rb +865 -0
  483. data/lib/postnhost/settings/i18n_overrides.rb +137 -0
  484. data/lib/postnhost/settings/i18n_patch.rb +18 -0
  485. data/lib/postnhost/version.rb +1 -3
  486. data/lib/postnhost.rb +26 -3
  487. data/lib/tasks/postnhost_snapshots.rake +77 -0
  488. data/lib/tasks/postnhost_tasks.rake +96 -0
  489. metadata +662 -21
  490. data/.github/workflows/main.yml +0 -27
  491. data/.gitignore +0 -11
  492. data/.rspec +0 -3
  493. data/.rubocop.yml +0 -8
  494. data/Gemfile +0 -13
  495. data/bin/console +0 -11
  496. data/bin/setup +0 -8
  497. data/postnhost.gemspec +0 -31
@@ -0,0 +1,37 @@
1
+ <article class="bg-white rounded-2xl border border-gray-200 hover:border-gray-300 transition-all duration-200 overflow-hidden flex flex-col break-words">
2
+ <%= link_to category_article_url(item), class: "block", "aria-label": t('postnhost.public.blog.read_article_aria_label', title: item.title) do %>
3
+ <%= render "postnhost/public/templates/default/shared/thumbnail_cover_image", publication: category_article(item), index: local_assigns.fetch(:index) { item_counter + local_assigns.fetch(:index_offset, 0) } %>
4
+ <% end %>
5
+
6
+ <div class="p-8 flex-1 flex flex-col">
7
+ <div class="flex items-center space-x-3 mb-4">
8
+ <% if category_article(item).categories.any? %>
9
+ <%= link_to localized_category_name(category_article(item).categories.first, @current_language), language_category_path(category_article(item).categories.first.slug, @current_language),
10
+ class: 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 transition-colors' %>
11
+ <% end %>
12
+ <span class="text-sm text-gray-500">
13
+ <%= l category_article(item).published_at.to_date, format: :long %>
14
+ </span>
15
+ </div>
16
+
17
+ <%= link_to category_article_url(item), class: "block group flex-1" do %>
18
+ <h2 class="text-xl font-semibold text-gray-900 group-hover:text-gray-700 mb-3 line-clamp-2">
19
+ <%= item.title %>
20
+ </h2>
21
+ <% if article_excerpt(item).present? %>
22
+ <p class="text-sm text-gray-600 mb-4 line-clamp-3">
23
+ <%= article_excerpt(item) %>
24
+ </p>
25
+ <% end %>
26
+ <% end %>
27
+
28
+ <% if author_name(category_article_author_user(item)).present? %>
29
+ <div class="flex items-center text-sm text-gray-500 mt-auto">
30
+ <div class="flex items-center space-x-2">
31
+ <%= render 'postnhost/public/templates/default/shared/author_avatar', user: category_article_author_user(item), size_class: 'w-6 h-6', text_size_class: 'text-xs' %>
32
+ <span><%= author_name(category_article_author_user(item)) %></span>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+ </div>
37
+ </article>
@@ -0,0 +1,12 @@
1
+ <h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4 tracking-tight">
2
+ <%= public_search_page? ? public_articles_index_heading : t("postnhost.public.search.submit") %>
3
+ </h1>
4
+ <%= form_with url: language_search_path(@current_language), method: :get, local: true, class: "max-w-3xl mx-auto" do %>
5
+ <label for="public-blog-search" class="sr-only"><%= t("postnhost.public.search.submit") %></label>
6
+ <div class="relative">
7
+ <%= text_field_tag :s, public_search_query, id: "public-blog-search", placeholder: t("postnhost.public.search.input_placeholder"), class: "w-full rounded-2xl border border-gray-200 bg-white px-5 py-3 pr-12 text-base text-gray-900 placeholder:text-gray-400 focus:border-gray-900 focus:outline-none focus:ring-2 focus:ring-gray-900" %>
8
+ <button type="submit" class="absolute inset-y-1 right-1 inline-flex items-center justify-center rounded-xl px-3 text-gray-500 hover:text-gray-900 cursor-pointer" aria-label="<%= t("postnhost.public.search.submit") %>">
9
+ <%= icon("magnifying-glass", css_class: "size-5 shrink-0") %>
10
+ </button>
11
+ </div>
12
+ <% end %>
@@ -0,0 +1,44 @@
1
+ <% cache [public_site_revision, @article, @actual_language, "default_suggestions"] do %>
2
+ <% if @article.suggestions_for(language: @actual_language).any? %>
3
+ <div class="bg-white border-t border-gray-200 py-10">
4
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
5
+ <h2 class="text-xl font-semibold text-gray-900 mb-8"><%= t('postnhost.public.blog.read_more') %></h2>
6
+
7
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
8
+ <% @article.suggestions_for(language: @actual_language).each_with_index do |suggestion, index| %>
9
+ <% cache [public_site_revision, suggestion, article_authors_cache_key(suggestion), "default_suggestion"] do %>
10
+ <article class="bg-white rounded-2xl border border-gray-200 hover:border-gray-300 transition-all duration-200 overflow-hidden flex flex-col break-words">
11
+ <%= link_to public_article_path(suggestion.slug), class: "block", "aria-label": t('postnhost.public.blog.read_article_aria_label', title: suggestion.title) do %>
12
+ <%= render "postnhost/public/templates/default/shared/thumbnail_cover_image", publication: suggestion, index: index %>
13
+ <% end %>
14
+
15
+ <div class="p-8 flex-1 flex flex-col">
16
+ <div class="flex items-center space-x-3 mb-4">
17
+ <% if suggestion.categories.any? %>
18
+ <%= link_to localized_category_name(suggestion.categories.first, @current_language), language_category_path(suggestion.categories.first.slug, @current_language),
19
+ class: 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 transition-colors' %>
20
+ <% end %>
21
+ <span class="text-sm text-gray-500">
22
+ <%= l suggestion.published_at.to_date, format: :long %>
23
+ </span>
24
+ </div>
25
+
26
+ <%= link_to public_article_path(suggestion.slug), class: "block group flex-1" do %>
27
+ <h2 class="text-xl font-semibold text-gray-900 group-hover:text-gray-700 mb-3 line-clamp-2">
28
+ <%= suggestion.title %>
29
+ </h2>
30
+ <% if article_excerpt(suggestion).present? %>
31
+ <p class="text-gray-600 mb-4 line-clamp-3">
32
+ <%= article_excerpt(suggestion) %>
33
+ </p>
34
+ <% end %>
35
+ <% end %>
36
+ </div>
37
+ </article>
38
+ <% end %>
39
+ <% end %>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <% end %>
44
+ <% end %>
@@ -0,0 +1,42 @@
1
+ <% if top_pick_articles.any? && !public_search_endpoint? && !public_search_page? && !paginated_public_page? %>
2
+ <section class="bg-white border-b border-gray-200 py-10">
3
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
4
+ <div class="mb-8 border-b border-gray-200 pb-4">
5
+ <h2 class="text-2xl font-semibold text-gray-900"><%= t("postnhost.public.blog.top_picks") %></h2>
6
+ </div>
7
+
8
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
9
+ <% top_pick_articles.each_with_index do |item, index| %>
10
+ <% cache [public_site_revision, item, article_authors_cache_key(item), "default_editors_pick"] do %>
11
+ <article class="border-b border-gray-200 pb-6">
12
+ <% if category_article(item).categories.any? %>
13
+ <span class="text-xs font-semibold uppercase tracking-wide text-gray-500">
14
+ <%= localized_category_name(category_article(item).categories.first, @current_language) %>
15
+ </span>
16
+ <% end %>
17
+
18
+ <%= link_to category_article_url(item), class: "group mt-2 block cursor-pointer" do %>
19
+ <h3 class="text-xl font-semibold leading-tight text-gray-900 group-hover:text-gray-700">
20
+ <%= item.title %>
21
+ </h3>
22
+ <% end %>
23
+
24
+ <% if article_excerpt(item).present? %>
25
+ <p class="mt-2 text-sm text-gray-600">
26
+ <%= article_excerpt(item) %>
27
+ </p>
28
+ <% end %>
29
+
30
+ <p class="mt-3 text-xs font-medium uppercase tracking-wide text-gray-500">
31
+ <% if author_name(category_article_author_user(item)).present? %>
32
+ <%= author_name(category_article_author_user(item)) %> ·
33
+ <% end %>
34
+ <%= l category_article(item).published_at.to_date, format: :long %>
35
+ </p>
36
+ </article>
37
+ <% end %>
38
+ <% end %>
39
+ </div>
40
+ </div>
41
+ </section>
42
+ <% end %>
@@ -0,0 +1,53 @@
1
+ <%= render "postnhost/public/articles/content_for/index" %>
2
+
3
+ <!-- Blog Header -->
4
+ <div class="bg-white border-b border-gray-200">
5
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-12 pb-12">
6
+ <div class="text-center">
7
+ <% if public_search_endpoint? %>
8
+ <%= render "postnhost/public/templates/default/articles/search_form" %>
9
+ <% else %>
10
+ <h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4 tracking-tight">
11
+ <%= public_blog_heading %>
12
+ </h1>
13
+ <% if show_public_blog_subtitle? %>
14
+ <p class="text-lg text-gray-600 max-w-3xl mx-auto leading-relaxed">
15
+ <%= t('postnhost.public.site.blog_subtitle') %>
16
+ </p>
17
+ <% end %>
18
+ <% end %>
19
+ </div>
20
+ </div>
21
+ </div>
22
+
23
+ <%= render "postnhost/public/templates/default/articles/top_picks" %>
24
+
25
+ <!-- Articles Section -->
26
+ <% if category_articles.any? %>
27
+ <div class="bg-white py-8">
28
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
29
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
30
+ <%= render partial: "postnhost/public/templates/default/articles/article_card",
31
+ collection: category_articles,
32
+ as: :item,
33
+ cached: ->(item) { [public_site_revision, item, article_authors_cache_key(item), "card"] } %>
34
+ </div>
35
+
36
+ <!-- Pagination -->
37
+ <div class="flex justify-center">
38
+ <%= render "postnhost/public/templates/default/shared/pagination", pagy: @pagy %>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <% else %>
43
+ <!-- Empty State -->
44
+ <div class="bg-white py-24">
45
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
46
+ <div class="mx-auto h-12 w-12 text-gray-400">
47
+ <%= icon("document-text", css_class: "w-full h-full") %>
48
+ </div>
49
+ <h2 class="mt-2 text-2xl font-semibold text-gray-900 mb-4"><%= t('postnhost.public.blog.no_articles') %></h2>
50
+ <p class="text-lg text-gray-600"><%= t('postnhost.public.blog.no_articles_description') %></p>
51
+ </div>
52
+ </div>
53
+ <% end %>
@@ -0,0 +1,102 @@
1
+ <%= render "postnhost/public/articles/content_for/preview" %>
2
+
3
+ <!-- Preview Banner -->
4
+ <div class="bg-yellow-50 border-b border-t border-yellow-200 mt-8">
5
+ <div class="flex items-center justify-center py-2">
6
+ <span class="text-xs font-medium text-yellow-800">Preview Mode</span>
7
+ </div>
8
+ </div>
9
+
10
+ <!-- Cover Image -->
11
+ <% if @content.cover_image? %>
12
+ <div class="bg-white border-b border-gray-200">
13
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
14
+ <%= image_tag @content.cover_image.url,
15
+ loading: 'eager',
16
+ fetchpriority: 'high',
17
+ alt: @content.cover_image_alt.presence || @content.title,
18
+ width: 1200,
19
+ height: 630,
20
+ srcset: "#{@content.cover_image.thumbnail.url} 520w, #{@content.cover_image.medium.url} 1024w, #{@content.cover_image.url} 1200w",
21
+ sizes: "(max-width: 640px) 173px, (max-width: 1279px) 342px, 1200px",
22
+ class: 'w-full object-cover rounded-lg' %>
23
+ </div>
24
+ </div>
25
+ <% end %>
26
+
27
+ <!-- Article Header -->
28
+ <div class="bg-white border-b border-gray-200">
29
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
30
+ <div class="text-center">
31
+ <div class="flex flex-wrap items-center justify-center gap-2 mb-6">
32
+ <% if @article.categories.any? %>
33
+ <% @article.categories.each do |category| %>
34
+ <%= link_to localized_category_name(category, @current_language), language_category_path(category.slug, @current_language),
35
+ class: 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 transition-colors' %>
36
+ <% end %>
37
+ <% end %>
38
+ </div>
39
+
40
+ <h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-6 leading-tight">
41
+ <%= @content.title %>
42
+ </h1>
43
+
44
+ <%= render "postnhost/public/templates/default/shared/article_authors", authors: article_authors(@article), current_language: @current_language %>
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <!-- Article Content -->
50
+ <div class="bg-white">
51
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
52
+ <div class="prose prose-gray max-w-none py-8">
53
+ <%= render_article_content(@content.content) %>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <!-- Language Variants -->
59
+ <% if @available_variants.count > 1 || (@article.published? && @available_variants.any?) %>
60
+ <div class="bg-white py-8 border-t border-gray-200">
61
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
62
+ <p class="text-lg font-semibold text-gray-900 mb-6"><%= t('postnhost.public.blog.available_in_languages') %></p>
63
+ <div class="flex flex-wrap gap-3">
64
+ <% if @article.published? %>
65
+ <%= link_to public_article_path(@article.slug),
66
+ class: "inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-colors #{'bg-gray-900 text-white' if @content == @article} #{'bg-white border border-gray-200 text-gray-700 hover:bg-gray-50' if @content != @article}" do %>
67
+ <%= @article.language&.name %>
68
+ <% end %>
69
+ <% else %>
70
+ <span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium bg-gray-900 text-white">
71
+ <%= @article.language&.name %>
72
+ </span>
73
+ <% end %>
74
+
75
+ <% @available_variants.each do |variant| %>
76
+ <% if variant.published? %>
77
+ <%= link_to localized_public_article_path(variant.language.html_lang, @article.slug),
78
+ class: "inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium bg-white border border-gray-200 text-gray-700 hover:bg-gray-50" do %>
79
+ <%= variant.language.name %>
80
+ <% end %>
81
+ <% else %>
82
+ <span class="inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium bg-white border border-gray-200 text-gray-700">
83
+ <%= variant.language.name %>
84
+ </span>
85
+ <% end %>
86
+ <% end %>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ <% end %>
91
+
92
+ <!-- Back to Editor -->
93
+ <div class="bg-white border-t border-gray-200 py-8">
94
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
95
+ <div class="flex items-center justify-center">
96
+ <%= link_to edit_article_path(@article), class: 'inline-flex items-center text-gray-600 hover:text-gray-900 transition-colors' do %>
97
+ <%= icon("arrow-left-long", css_class: "w-4 h-4 mr-2") %>
98
+ Back to Editor
99
+ <% end %>
100
+ </div>
101
+ </div>
102
+ </div>
@@ -0,0 +1,111 @@
1
+ <%= render "postnhost/public/articles/content_for/show" %>
2
+
3
+ <!-- Cover Image -->
4
+ <% if @article.cover_image? %>
5
+ <div class="bg-white border-b border-gray-200">
6
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
7
+ <%= render "postnhost/public/templates/default/shared/hero_cover_image", publication: @article, alt_text: @content.title %>
8
+ </div>
9
+ </div>
10
+ <% end %>
11
+
12
+ <% if user_signed_in? %>
13
+ <div class="bg-white">
14
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pt-6">
15
+ <div class="flex justify-center">
16
+ <%= link_to(@article_variant.present? ? edit_article_variant_path(@article.article_id, @article_variant.article_variant_id) : edit_article_path(@article.article_id),
17
+ data: { turbo_prefetch: false },
18
+ class: "inline-flex items-center justify-center font-medium rounded-xl transition-all duration-200 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 px-4 py-2 bg-white text-gray-700 hover:bg-gray-50 border border-gray-200 focus:ring-gray-500 cursor-pointer") do %>
19
+ <%= icon("edit", css_class: "w-4 h-4 mr-2") %>
20
+ Edit
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <% end %>
26
+
27
+ <!-- Article Header -->
28
+ <% cache [public_site_revision, @article, @article_variant, article_authors_cache_key(@article), "header"].compact do %>
29
+ <div class="bg-white border-b border-gray-200">
30
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
31
+ <div class="text-center">
32
+ <div class="flex flex-wrap items-center justify-center gap-2 mb-6">
33
+ <% if @article.categories.any? %>
34
+ <% @article.categories.each do |category| %>
35
+ <%= link_to localized_category_name(category, @current_language), language_category_path(category.slug, @current_language),
36
+ class: 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 transition-colors' %>
37
+ <% end %>
38
+ <% end %>
39
+ <span class="text-sm text-gray-500">
40
+ <%= l @article.published_at.to_date, format: :long %>
41
+ </span>
42
+ </div>
43
+
44
+ <h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-6 leading-tight">
45
+ <%= @content.title %>
46
+ </h1>
47
+
48
+ <%= render "postnhost/public/templates/default/shared/article_authors", authors: article_authors(@article), current_language: @current_language %>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <% end %>
53
+
54
+ <!-- Article Content -->
55
+ <% cache [public_site_revision, @article, @article_variant, "content"].compact do %>
56
+ <article class="bg-white">
57
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
58
+ <div class="prose prose-gray max-w-none py-8">
59
+ <%= render_article_content(@content.content) %>
60
+ </div>
61
+ </div>
62
+ </article>
63
+ <% end %>
64
+
65
+ <!-- Language Variants -->
66
+ <% if @available_variants.count > 1 || (@article.published? && @available_variants.any?) %>
67
+ <div class="bg-white py-8 border-t border-gray-200">
68
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
69
+ <p class="text-lg font-semibold text-gray-900 mb-6"><%= t('postnhost.public.blog.available_in_languages') %></p>
70
+ <div class="flex flex-wrap gap-3">
71
+ <% if @article.published? %>
72
+ <%= link_to public_article_path(@article.slug),
73
+ class: "inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-colors #{'bg-gray-900 text-white' if @content == @article} #{'bg-white border border-gray-200 text-gray-700 hover:bg-gray-50' if @content != @article}" do %>
74
+ <%= @primary_language.name %>
75
+ <% end %>
76
+ <% end %>
77
+
78
+ <% @available_variants.each do |variant| %>
79
+ <%= link_to localized_public_article_path(variant.language.html_lang, @article.slug),
80
+ class: "inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-colors #{'bg-gray-900 text-white' if @content == variant} #{'bg-white border border-gray-200 text-gray-700 hover:bg-gray-50' if @content != variant}" do %>
81
+ <%= variant.language.name %>
82
+ <% end %>
83
+ <% end %>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <% end %>
88
+
89
+ <!-- Read More Suggestions -->
90
+ <%= render "postnhost/public/templates/default/articles/suggestions" %>
91
+
92
+ <!-- Back to Articles -->
93
+ <div class="bg-white border-t border-gray-200 py-8">
94
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
95
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
96
+ <%= link_to language_root_path(@current_language), class: 'inline-flex items-center text-gray-600 hover:text-gray-900 transition-colors' do %>
97
+ <%= icon("arrow-left-long", css_class: "w-4 h-4 mr-2") %>
98
+ <%= t('postnhost.public.blog.back_to_articles') %>
99
+ <% end %>
100
+
101
+ <div class="flex flex-wrap items-center gap-4 sm:justify-end">
102
+ <% if @article.categories.any? %>
103
+ <%= link_to language_category_path(@article.categories.first.slug, @current_language), class: 'inline-flex items-center text-gray-600 hover:text-gray-900 transition-colors cursor-pointer' do %>
104
+ <%= t('postnhost.public.blog.more_in_category', category: localized_category_name(@article.categories.first, @current_language)) %>
105
+ <%= icon("arrow-right-long", css_class: "w-4 h-4 ml-2") %>
106
+ <% end %>
107
+ <% end %>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
@@ -0,0 +1,28 @@
1
+ <article class="bg-white rounded-2xl border border-gray-200 hover:border-gray-300 transition-all duration-200 overflow-hidden flex flex-col break-words">
2
+ <%= link_to category_article_url(item), class: "block", "aria-label": t('postnhost.public.blog.read_article_aria_label', title: item.title) do %>
3
+ <%= render "postnhost/public/templates/default/shared/thumbnail_cover_image", publication: category_article(item), index: local_assigns.fetch(:index) { item_counter + local_assigns.fetch(:index_offset, 0) } %>
4
+ <% end %>
5
+
6
+ <div class="p-8 flex-1 flex flex-col">
7
+ <div class="flex items-center space-x-3 mb-4">
8
+ <% if category_article(item).categories.any? %>
9
+ <%= link_to localized_category_name(category_article(item).categories.first, @current_language), language_category_path(category_article(item).categories.first.slug, @current_language),
10
+ class: "inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800 hover:bg-gray-200 transition-colors" %>
11
+ <% end %>
12
+ <span class="text-sm text-gray-500">
13
+ <%= l category_article(item).published_at.to_date, format: :long %>
14
+ </span>
15
+ </div>
16
+
17
+ <%= link_to category_article_url(item), class: "block group flex-1", "aria-label": t("postnhost.public.blog.read_article_aria_label", title: item.title) do %>
18
+ <h3 class="text-xl font-semibold text-gray-900 group-hover:text-gray-700 mb-3 line-clamp-2">
19
+ <%= item.title %>
20
+ </h3>
21
+ <% if article_excerpt(item).present? %>
22
+ <p class="text-sm text-gray-600 line-clamp-3">
23
+ <%= article_excerpt(item) %>
24
+ </p>
25
+ <% end %>
26
+ <% end %>
27
+ </div>
28
+ </article>
@@ -0,0 +1,29 @@
1
+ <div class="bg-white border border-gray-200 rounded-2xl p-6 md:p-8">
2
+ <div class="flex flex-col sm:flex-row sm:items-start gap-5 sm:gap-6">
3
+ <div class="shrink-0">
4
+ <%= render "postnhost/public/templates/default/shared/author_avatar", user: author, size_class: "w-20 h-20", text_size_class: "text-xl" %>
5
+ </div>
6
+
7
+ <div class="min-w-0 flex-1">
8
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 tracking-tight">
9
+ <%= author_name(author) %>
10
+ </h1>
11
+ <% if author.position.present? %>
12
+ <p class="mt-2 text-sm font-medium uppercase tracking-wide text-gray-500"><%= author.position %></p>
13
+ <% end %>
14
+ <% if author.bio.present? %>
15
+ <p class="mt-3 text-base text-gray-600 leading-relaxed"><%= author.bio %></p>
16
+ <% end %>
17
+
18
+ <% if author_social_links(author).any? %>
19
+ <div class="mt-5 flex flex-wrap items-center gap-4">
20
+ <% author_social_links(author).each do |social| %>
21
+ <%= link_to social[:url], target: "_blank", rel: "noopener noreferrer", class: "inline-flex items-center text-gray-600 hover:text-gray-900 transition-colors duration-200 cursor-pointer", aria: { label: social[:label] }, title: social[:label] do %>
22
+ <%= icon(social[:icon], css_class: "w-5 h-5") %>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+ </div>
@@ -0,0 +1,36 @@
1
+ <%= render "postnhost/public/authors/content_for/show" %>
2
+
3
+ <div class="bg-white border-b border-gray-200">
4
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pt-12 pb-12">
5
+ <%= render "postnhost/public/templates/default/authors/profile_block", author: @author %>
6
+ </div>
7
+ </div>
8
+
9
+ <div class="bg-white py-8">
10
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
11
+ <h2 class="text-2xl md:text-3xl font-semibold text-gray-900 mb-8">
12
+ <%= t("postnhost.public.authors.latest_from_author") %>
13
+ </h2>
14
+
15
+ <% if category_articles.any? %>
16
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
17
+ <%= render partial: "postnhost/public/templates/default/authors/article_card",
18
+ collection: category_articles,
19
+ as: :item,
20
+ cached: ->(item) { [public_site_revision, item, "author_card"] } %>
21
+ </div>
22
+
23
+ <div class="flex justify-center">
24
+ <%= render "postnhost/public/templates/default/shared/pagination", pagy: @pagy %>
25
+ </div>
26
+ <% else %>
27
+ <div class="text-center py-24">
28
+ <div class="mx-auto h-12 w-12 text-gray-400">
29
+ <%= icon("document-text", css_class: "w-full h-full") %>
30
+ </div>
31
+ <h3 class="mt-2 text-2xl font-semibold text-gray-900 mb-4"><%= t("postnhost.public.authors.no_articles") %></h3>
32
+ <p class="text-lg text-gray-600"><%= t("postnhost.public.authors.no_articles_description") %></p>
33
+ </div>
34
+ <% end %>
35
+ </div>
36
+ </div>
@@ -0,0 +1,34 @@
1
+ <%# Article card without category badge - for category index %>
2
+ <article class="bg-white rounded-2xl border border-gray-200 hover:border-gray-300 transition-all duration-200 overflow-hidden flex flex-col break-words">
3
+ <%= link_to category_article_url(item), class: "block", "aria-label": t('postnhost.public.blog.read_article_aria_label', title: item.title) do %>
4
+ <%= render "postnhost/public/templates/default/shared/thumbnail_cover_image", publication: category_article(item), index: local_assigns.fetch(:index) { item_counter + local_assigns.fetch(:index_offset, 0) } %>
5
+ <% end %>
6
+
7
+ <div class="p-8 flex-1 flex flex-col">
8
+ <div class="flex items-center space-x-3 mb-4">
9
+ <span class="text-sm text-gray-500">
10
+ <%= l category_article(item).published_at.to_date, format: :long %>
11
+ </span>
12
+ </div>
13
+
14
+ <%= link_to category_article_url(item), class: "block group flex-1" do %>
15
+ <h2 class="text-xl font-semibold text-gray-900 group-hover:text-gray-700 mb-3 line-clamp-2">
16
+ <%= item.title %>
17
+ </h2>
18
+ <% if article_excerpt(item).present? %>
19
+ <p class="text-sm text-gray-600 mb-4 line-clamp-3">
20
+ <%= article_excerpt(item) %>
21
+ </p>
22
+ <% end %>
23
+ <% end %>
24
+
25
+ <% if author_name(category_article_author_user(item)).present? %>
26
+ <div class="flex items-center text-sm text-gray-500 mt-auto">
27
+ <div class="flex items-center space-x-2">
28
+ <%= render 'postnhost/public/templates/default/shared/author_avatar', user: category_article_author_user(item), size_class: 'w-6 h-6', text_size_class: 'text-xs' %>
29
+ <span><%= author_name(category_article_author_user(item)) %></span>
30
+ </div>
31
+ </div>
32
+ <% end %>
33
+ </div>
34
+ </article>
@@ -0,0 +1,48 @@
1
+ <%= render "postnhost/public/categories/content_for/index" %>
2
+
3
+ <!-- Category Header -->
4
+ <div class="bg-white border-b border-gray-200">
5
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-12 pb-12">
6
+ <div class="text-center">
7
+ <h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4 tracking-tight">
8
+ <%= public_category_heading(@category) %>
9
+ </h1>
10
+ <% if show_public_category_description?(@category) %>
11
+ <p class="text-lg text-gray-600 max-w-3xl mx-auto leading-relaxed">
12
+ <%= public_category_description(@category) %>
13
+ </p>
14
+ <% end %>
15
+ </div>
16
+ </div>
17
+ </div>
18
+
19
+ <!-- Articles Grid -->
20
+ <% if category_articles.any? %>
21
+ <div class="bg-white py-8">
22
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
23
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
24
+ <%= render partial: "postnhost/public/templates/default/categories/article_card",
25
+ collection: category_articles,
26
+ as: :item,
27
+ cached: ->(item) { [public_site_revision, item, article_authors_cache_key(item), "category_card"] } %>
28
+ </div>
29
+
30
+ <!-- Pagination -->
31
+ <div class="flex justify-center">
32
+ <%= render "postnhost/public/templates/default/shared/pagination", pagy: @pagy %>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ <% else %>
37
+ <!-- Empty State -->
38
+ <div class="bg-white py-24">
39
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
40
+ <div class="mx-auto h-12 w-12 text-gray-400">
41
+ <%= icon("document-text", css_class: "w-full h-full") %>
42
+ </div>
43
+ <h2 class="mt-2 text-2xl font-semibold text-gray-900 mb-4"><%= t('postnhost.public.category.no_articles') %></h2>
44
+ <p class="text-lg text-gray-600 mb-8"><%= t('postnhost.public.category.no_articles_description') %></p>
45
+ <%= link_to t('postnhost.public.blog.browse_all_articles'), language_root_path(@current_language), class: 'bg-gray-900 text-white px-6 py-3 rounded-2xl font-semibold hover:bg-gray-800 transition-all duration-200' %>
46
+ </div>
47
+ </div>
48
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <% if authors.any? %>
2
+ <div class="mt-8 flex max-w-full flex-wrap items-center justify-center gap-x-6 gap-y-3 px-2">
3
+ <% authors.each do |author| %>
4
+ <% if author_pages_enabled? %>
5
+ <%= link_to language_author_path(author.slug, current_language),
6
+ class: "group inline-flex max-w-full shrink-0 items-center space-x-3 rounded-xl -m-1 p-1 text-left hover:bg-gray-50 transition-colors cursor-pointer no-underline" do %>
7
+ <%= render "postnhost/public/templates/default/shared/author_avatar", user: author, size_class: "w-10 h-10", text_size_class: "text-sm" %>
8
+ <div class="text-left min-w-0">
9
+ <p class="text-sm font-medium text-gray-900 group-hover:text-gray-700"><%= author_name(author) %></p>
10
+ <p class="text-sm text-gray-500"><%= article_author_label(author) %></p>
11
+ </div>
12
+ <% end %>
13
+ <% else %>
14
+ <div class="inline-flex max-w-full shrink-0 items-center space-x-3 rounded-xl -m-1 p-1 text-left no-underline">
15
+ <%= render "postnhost/public/templates/default/shared/author_avatar", user: author, size_class: "w-10 h-10", text_size_class: "text-sm" %>
16
+ <div class="text-left min-w-0">
17
+ <p class="text-sm font-medium text-gray-900"><%= author_name(author) %></p>
18
+ <p class="text-sm text-gray-500"><%= article_author_label(author) %></p>
19
+ </div>
20
+ </div>
21
+ <% end %>
22
+ <% end %>
23
+ </div>
24
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <div class="<%= size_class %> bg-gray-100 rounded-full flex items-center justify-center overflow-hidden">
2
+ <% if author_avatar(user).present? %>
3
+ <%= image_tag author_avatar(user), class: "w-full h-full object-cover", alt: "Author Image" %>
4
+ <% elsif author_initials(user).present? %>
5
+ <span class="<%= text_size_class %> font-medium text-gray-600">
6
+ <%= author_initials(user) %>
7
+ </span>
8
+ <% end %>
9
+ </div>
@@ -0,0 +1,11 @@
1
+ <% if publication.cover_image? %>
2
+ <%= image_tag publication.cover_image.thumbnail.url,
3
+ loading: "eager",
4
+ fetchpriority: "high",
5
+ alt: publication.cover_image_alt.presence || local_assigns[:alt_text] || publication.title,
6
+ width: 520,
7
+ height: 273,
8
+ class: local_assigns.fetch(:image_class, "w-full"),
9
+ srcset: "#{publication.cover_image.thumbnail.url} 520w, #{publication.cover_image.medium.url} 1024w, #{publication.cover_image.url} 1200w",
10
+ sizes: "(max-width: 639px) 173px, (max-width: 1279px) 342px, 1200px" %>
11
+ <% end %>