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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58a5e825dc2b90b41ca5409f379a5ef6257b6d05de4cec3e7dc517d52cd045fd
4
- data.tar.gz: ffae44f7bc9a560d21a8d910b16ff7e4bc7aac14819ebe01b9e28f101b2012da
3
+ metadata.gz: 1558c66e1ac90ab08507a78ffea0bac3294003f1ac89107090a2e0dd27e234d8
4
+ data.tar.gz: 95d7eaa5879b8b964c21a50b73ee87a941b577341b6a0a55f824fb7bdbedc1eb
5
5
  SHA512:
6
- metadata.gz: 6ea72f1636e28368c812810ce70784a4a0067477596b01d7655532d667df81b2016186a6bea73fc639cd3a07bbaa376f0a2f4ba211bba02cbd09f12fa60594c0
7
- data.tar.gz: 5f8cf9da3f29af73e5ab9e8d612762ee16220f9acda35eac2bf8386ff5a08b68374812d682a5fb68e2af220840fa60a0ef12993c78f03f448d5d4d4115c76702
6
+ metadata.gz: 54a365175843db9416fa0be1725e0acd6cc86574b81f8b8e6dccec2c4dc029161998bbd9901d1ed0c62768f30a46234b622b533395211f63d23592cd4910267c
7
+ data.tar.gz: 759fec551c44bd9060c87d183468aed04875881733e163262bf01100562837f2b6c1999379a80204ce1e5b603d26a0f1e36f74510173349d9a0596ffed0e8cfb
data/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to PostnHost are documented in this file.
4
+
5
+ ## [0.1.1] - 2026-08-31
6
+
7
+ ### Fixed
8
+
9
+ - Scoped packaged and host-generated CSS to PostnHost layouts and replaced competing stylesheets with one combined host build that transparently shadows the packaged logical asset.
10
+ - Corrected installer, template customization, Tailwind setup, and production checklist instructions.
11
+
12
+ ## [0.1.0] - 2026-08-29
13
+
14
+ First open source release of PostnHost.
15
+
16
+ ### Added
17
+
18
+ - Mountable Rails CMS engine with a built-in dashboard, onboarding, session-based authentication, and user management.
19
+ - Draft and snapshot-based publishing for articles, pages, and translated variants, including scheduled publishing, unpublishing, bulk transitions, and version restoration.
20
+ - Multilingual content, localized routes, language switching, locale-aware metadata, and optional OpenAI-assisted translations.
21
+ - Rich text editing, image uploads and processing, categories, multiple authors, article suggestions, navigation management, static pages, and site-wide settings.
22
+ - Public search, sitemaps, canonical URLs, hreflang tags, structured metadata, and HTTP and fragment caching with publication-aware invalidation.
23
+ - Three packaged public templates: Default, Swiss Editorial, and Workspace Journal.
24
+ - Generators for installation, CMS users, public view overrides, locales, static pages, and host Tailwind integration.
25
+ - Precompiled JavaScript and CSS assets with Propshaft and Sprockets support, requiring no frontend toolchain in host applications.
26
+ - Support for Ruby 3.4 and 4.0 with Rails 7.2, 8.0, and 8.1.
27
+
28
+ ### Credits
29
+
30
+ Authored by Kirill Shevchenko (Ruby/Rails engineering) and Maxim Sova (technical SEO expertise).
31
+
32
+ [0.1.1]: https://github.com/postnhost/postnhost/compare/v0.1.0...v0.1.1
33
+ [0.1.0]: https://github.com/postnhost/postnhost/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
- The MIT License (MIT)
1
+ MIT License
2
2
 
3
- Copyright (c) 2025 Kirill Shevchenko
3
+ Copyright (c) 2025-2026 Kirill Shevchenko and Maxim Sova
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  copies of the Software, and to permit persons to whom the Software is
10
10
  furnished to do so, subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,39 +1,367 @@
1
- # Postnhost
1
+ <h1 align="center" style="border-bottom: none">
2
+ <div>
3
+ <a href="https://postnhost.com">
4
+ <img alt="PostnHost" src="app/assets/images/postnhost/logo.webp" width="200" />
5
+ <br>
6
+ </a>
7
+ </div>
8
+ </h1>
9
+ <h3 align="center">
10
+ SEO-ready multilanguage content engine for Rails.
11
+ </h3>
12
+ <p align="left">
13
+ PostnHost is an open source CMS engine built with Rails, Hotwire, and TailwindCSS. Write, translate, and publish articles with a rich text editor, full version history, and proper SEO that works out of the box.
14
+ </p>
15
+ <p align="left">
16
+ Mount this engine into an existing Rails app, or use the <a href="https://github.com/postnhost/postnhost-app">postnhost-app</a> repository for a pre-configured, self-hosted Rails application.
17
+ </p>
2
18
 
3
- TODO: Delete this and the text below, and describe your gem
4
19
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/postnhost`. To experiment with that code, run `bin/console` for an interactive prompt.
20
+ ![PostnHost Demo](app/assets/images/postnhost/demo.webp)
21
+
22
+ ## Features
23
+
24
+ - 📝 **Rich Text Editor** - TipTap-based WYSIWYG editor
25
+ - 🌍 **Multilingual** - Translatable articles, i18n support, and locale-aware SEO metadata
26
+ - 🖼️ **Image Management** - Optimized multisize WebP everywhere
27
+ - 📊 **Version History** - Published version history and rollback options
28
+ - 🤖 **Suggested Articles** - Manual picks plus automatic suggestions from related categories
29
+ - ⏰ **Post Scheduling** - Schedule publication in the chosen timezone
30
+ - 🌐 **Localized SEO** - Localized routes, language switcher, sitemap, and hreflang tags
31
+ - ⚙️ **Settings** - Manage the canonical site URL, pagination, key copy, and assets from the dashboard
32
+ - 🎨 **Customizable Templates** - Override any public view in your host application
33
+ - ⚡ **Hotwire-powered** - Fast, modern UI with Turbo Frames/Streams and Stimulus
34
+ - 👥 **Authors** - Multiple CMS users, author profiles, and per-article co-author bylines
35
+
36
+ ## Table of contents
37
+
38
+ - [Installation](#installation)
39
+ - [Asset compatibility](#asset-compatibility)
40
+ - [Configuration](#configuration)
41
+ - [Customizing templates](#customizing-templates)
42
+ - [Replacing favicon](#replacing-favicon)
43
+ - [Host app i18n](#host-app-i18n-optional)
44
+ - [Static pages](#static-pages-terms-privacy-etc)
45
+ - [Routes](#routes)
46
+ - [Production checklist](#production-checklist)
47
+ - [Development](#development)
48
+ - [License](#license)
6
49
 
7
50
  ## Installation
8
51
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
52
+ ### Requirements
53
+
54
+ - Ruby 3.4+
55
+ - libvips for image processing
56
+
57
+ Add PostnHost to your host application's Gemfile:
58
+
59
+ ```ruby
60
+ gem "postnhost"
61
+ ```
62
+
63
+ Run the installer:
64
+
65
+ ```bash
66
+ bundle install
67
+ bin/rails g postnhost:install
68
+ ```
69
+
70
+ This will:
71
+ - Create `config/initializers/postnhost.rb` with configuration options
72
+ - Create `config/initializers/carrierwave.rb` (if not exists)
73
+ - Mount the engine in your routes
74
+
75
+ Run migrations:
76
+
77
+ ```bash
78
+ bin/rails postnhost:install:migrations
79
+ bin/rails db:migrate
80
+ ```
81
+
82
+ ### First-time CMS setup
83
+
84
+ If there are no users yet, visit **`/blog/onboarding`** to create the first CMS administrator with credentials you choose. This is the default installer path; if you mount the engine somewhere else, open `<mount-path>/onboarding`. The setup flow can add optional sample content after the account is created.
85
+
86
+ Alternatively, create a CMS user interactively from the terminal:
87
+
88
+ ```bash
89
+ bin/rails g postnhost:user
90
+ ```
91
+
92
+ The generator prompts for the name, email, password, and password confirmation.
93
+
94
+ ## Asset compatibility
95
+
96
+ PostnHost ships precompiled CSS and a prebundled JavaScript ES module. The engine serves these files through Rails asset helpers, so the host application does not need to compile PostnHost's JavaScript or install its frontend dependencies.
97
+
98
+ | Host setup | Compatible | How PostnHost integrates |
99
+ | --- | --- | --- |
100
+ | Propshaft | ✅ | The engine registers its packaged builds and images with the host asset pipeline. |
101
+ | Sprockets | ✅ | The engine registers its packaged builds and images for precompilation. |
102
+ | `importmap-rails` | ✅ | The engine bundle is loaded separately from the host import map. No pins are required. |
103
+ | `jsbundling-rails` with esbuild | ✅ | The engine uses its packaged bundle and does not need to be added to the host esbuild entry point. |
104
+ | `cssbundling-rails` or host Tailwind CSS | ✅ | The packaged engine CSS works independently. Use PostnHost host-tailwind mode only when copied views introduce new Tailwind classes. |
105
+
106
+ A host application must use Propshaft or Sprockets to serve the packaged engine assets, and either pipeline can be paired with import maps or esbuild.
107
+
108
+ ## Configuration
109
+
110
+ ### Initializer (`Postnhost.configure`)
111
+
112
+ Edit `config/initializers/postnhost.rb`:
113
+
114
+ ```ruby
115
+ Postnhost.configure do |config|
116
+ # Site defaults used when the matching Dashboard → Settings field is blank
117
+ # config.site_url = 'https://example.com'
118
+ # config.public_page_size = 12
119
+ # config.default_timezone = 'UTC'
120
+
121
+ # Optional: required only for AI-assisted translations
122
+ # config.openai_api_key = 'sk-...'
123
+ # config.openai_gpt_model = 'gpt-5.6-luna'
124
+
125
+ # Required by the generated CarrierWave initializer in production
126
+ # config.aws_access_key_id = 'AKIA...'
127
+ # config.aws_secret_access_key = '...'
128
+ # config.aws_region = 'us-east-1'
129
+ # config.aws_bucket = 'my-bucket'
130
+ # config.aws_endpoint_url_s3 = 'https://fly.storage.tigris.dev'
131
+ end
132
+ ```
133
+
134
+ Site URL, pagination, and timezone values are optional defaults; values saved under **Dashboard → Settings** take priority. The OpenAI settings are optional and needed only for AI translations.
135
+
136
+ The generated CarrierWave initializer uses S3-compatible storage in production. Configure the relevant AWS/S3 values above before deploying; `aws_endpoint_url_s3` is needed only for providers that use a custom endpoint. Alternatively, edit `config/initializers/carrierwave.rb` to use a different production storage backend.
137
+
138
+ Set dashboard overrides for the canonical site URL and public articles per page under **Dashboard → Settings → Site**. When both site URL sources are blank, public URLs use the incoming request origin.
139
+
140
+ ### Rails credentials (alternative)
141
+
142
+ Instead of defining values in the initializer, you can add them under `postnhost:` in Rails credentials (`bin/rails credentials:edit`).
143
+
144
+ ```yaml
145
+ postnhost:
146
+ # For AI translations
147
+ openai_access_token: ...
148
+ openai_gpt_model: gpt-5.6-luna
149
+
150
+ # For image uploads (S3-compatible)
151
+ aws_access_key_id: AKIA...
152
+ aws_secret_access_key: ...
153
+ aws_region: us-east-1
154
+ aws_bucket_name: my-bucket
155
+ aws_endpoint_url_s3: https://s3.us-east-1.amazonaws.com # or Tigris/other S3-compatible
156
+ ```
157
+
158
+ ## Customizing templates
159
+
160
+ PostnHost includes three public templates: **Default**, **Swiss Editorial**, and **Workspace Journal**. Open **Template** in the CMS, choose a template, preview it, and save the selection.
161
+
162
+ Copy views to your application for manual/vibecoding customization:
163
+
164
+ ```bash
165
+ bin/rails g postnhost:views --views-scope=minimal
166
+ # or
167
+ bin/rails g postnhost:views --views-scope=full
168
+ ```
169
+
170
+ Host-application copies override the packaged engine views. The CMS selection still controls which template is active.
171
+
172
+ By default, PostnHost serves packaged engine CSS (`postnhost/application.css`) for zero-setup installs.
173
+ If your copied views introduce new Tailwind utility classes, enable host-tailwind mode so those classes are compiled in the host app.
174
+
175
+ `--views-scope=minimal`:
176
+ - Copies the Default public content templates under `app/views/postnhost/public/templates/default/`
177
+ - Copies shared public layout partials such as the footer, favicon, logo, and language switcher
178
+ - Excludes internal metadata partials under `content_for/`
179
+ - Keeps SEO/meta internals in the engine by default
180
+
181
+ `--views-scope=full`:
182
+ - Copies all three public content template sets under `app/views/postnhost/public/templates/`
183
+ - Copies their layouts under `app/views/layouts/postnhost/public/templates/`
184
+ - Copies metadata, structured-data, and SEO partials
185
+
186
+ ### Host Tailwind mode (for custom classes in overridden views)
187
+
188
+ Enable host-tailwind mode in your host app:
189
+
190
+ ```bash
191
+ bin/rails g postnhost:tailwindcss:install
192
+ bin/dev
193
+ ```
194
+
195
+ This generator creates:
196
+
197
+ - `app/assets/stylesheets/postnhost/host.tailwind.css`
198
+ - `Procfile.dev` watcher entry:
199
+ - `postnhost_css: bundle exec rails postnhost:tailwindcss:watch`
200
+ - optional `package.json` scripts (if `package.json` exists):
201
+ - `postnhost:tailwindcss`
202
+ - `postnhost:tailwindcss:watch`
203
+
204
+ Host-tailwind mode builds one combined stylesheet from the engine and host view sources at `app/assets/builds/postnhost/application.css`. That host-owned file transparently overrides the packaged asset with the same logical path; the engine always requests `postnhost/application`. This avoids competing Tailwind builds and lets copied views use ordinary classes such as `bg-red-500` or `md:grid`.
205
+
206
+ If the host build is absent, Rails uses the packaged engine asset automatically. Both packaged and combined stylesheets are limited to PostnHost layouts through the `data-postnhost` root attribute. You do not need to edit layout tags or prefix Tailwind classes. Host customization requires Tailwind CSS 4.
207
+
208
+ ### Robots.txt sitemap URL (required)
209
+
210
+ Update the host app `public/robots.txt` so it points to the correct PostnHost sitemap URL.
211
+
212
+ PostnHost generates `sitemap.xml` automatically from live public articles and pages.
213
+
214
+ If the engine is mounted at `/`:
215
+
216
+ ```txt
217
+ Sitemap: https://your-domain.com/sitemap.xml
218
+ ```
219
+
220
+ If the engine is mounted at `/blog`:
221
+
222
+ ```txt
223
+ Sitemap: https://your-domain.com/blog/sitemap.xml
224
+ ```
225
+
226
+ Set the canonical production origin under **Dashboard → Settings → Site** or with `config.site_url` so sitemap URLs are generated with the correct host.
227
+
228
+ ### Replacing favicon
229
+
230
+ PostnHost renders favicon tags from `app/views/layouts/postnhost/_favicon.html.erb` and points to root-level public paths:
231
+
232
+ - `/favicon.ico`
233
+ - `/icon.svg`
234
+ - `/apple-touch-icon.png`
235
+ - `/site.webmanifest`
236
+
237
+ Because these are absolute paths, they are served from the **host app** `public/` directory.
238
+ Replace the files in your host app:
239
+
240
+ ```bash
241
+ # From host app root
242
+ cp /path/to/your/favicon.ico public/favicon.ico
243
+ cp /path/to/your/icon.svg public/icon.svg
244
+ cp /path/to/your/apple-touch-icon.png public/apple-touch-icon.png
245
+ cp /path/to/your/site.webmanifest public/site.webmanifest
246
+ ```
247
+
248
+ If you need different markup or paths, run either `postnhost:views` command from the template-customization section, then edit:
249
+
250
+ - `app/views/layouts/postnhost/_favicon.html.erb`
251
+
252
+ ### Host app i18n (optional)
253
+
254
+ PostnHost ships with locale files for: `en`, `fr`, `de`, `ja`, `ko`, `pt`, `pl`, `es`, `ru`.
255
+
256
+ You can use PostnHost without extra i18n setup for a single-locale site. **If you use multiple locales** (localized routes, language switcher, or custom `config/locales/*.yml` files), add this to `config/application.rb`:
257
+
258
+ ```ruby
259
+ config.i18n.default_locale = :en
260
+ config.i18n.available_locales = %i[en fr de ja ko pt pl es ru]
261
+ config.i18n.fallbacks = [:en]
262
+ ```
263
+
264
+ **Required:** `config.i18n.fallbacks = [:en]` (or a map that ultimately falls back to a default language).
10
265
 
11
- Install the gem and add to the application's Gemfile by executing:
266
+ If you want to add more languages, add the locale code to `available_locales` and add `config/locales/<code>.yml`. To scaffold English strings into your host app:
12
267
 
13
268
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
269
+ bin/rails g postnhost:locale it
15
270
  ```
16
271
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
272
+ Then translate the values under `postnhost.public` as needed.
273
+
274
+ ### Static pages (Terms, Privacy, etc.)
275
+
276
+ You can define static public pages directly in your host app under:
277
+
278
+ - `app/views/postnhost/static_pages/*.html.erb`
279
+
280
+ You can generate starter templates:
18
281
 
19
282
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
283
+ bin/rails g postnhost:static_pages terms privacy about
21
284
  ```
22
285
 
23
- ## Usage
286
+ Examples:
287
+
288
+ - `app/views/postnhost/static_pages/terms.html.erb` → `/blog/terms`
289
+ - `app/views/postnhost/static_pages/privacy.html.erb` → `/blog/privacy`
290
+ - `app/views/postnhost/static_pages/about.html.erb` → `/blog/about`
291
+
292
+ Link helpers:
293
+
294
+ ```erb
295
+ <%= link_to "Terms", postnhost.public_static_page_path(slug: "terms") %>
296
+ ```
297
+
298
+ ## Routes
299
+
300
+ The engine mounts at `/blog` by default. You can change this in `config/routes.rb`:
301
+
302
+ ```ruby
303
+ mount Postnhost::Engine, at: "/blog"
304
+ ```
305
+
306
+ ### Available Public Routes
307
+
308
+ - `GET /` - Blog index
309
+ - `GET /:locale` - Blog index (localized)
310
+ - `GET /search` - Article search
311
+ - `GET /:locale/search` - Article search (localized)
312
+ - `GET /authors/:slug` - Author show (when author pages are enabled)
313
+ - `GET /:locale/authors/:slug` - Author show (localized, when author pages are enabled)
314
+ - `GET /:category_slug` - Category articles
315
+ - `GET /:locale/:category_slug` - Category articles (localized)
316
+ - `GET /:slug` - Article show or static page
317
+ - `GET /:locale/:slug` - Article show or static page (localized)
318
+ - `GET /preview/:id` - Article preview
319
+ - `GET /sitemap.xml` - XML sitemap
320
+ - `GET /sitemap.xsl` - Sitemap stylesheet
321
+
322
+ ## Production checklist
24
323
 
25
- TODO: Write usage instructions here
324
+ Review these host-application settings before deployment:
325
+
326
+ - Replace the host app’s favicon and related icon files if needed.
327
+ - Update `public/robots.txt` so its sitemap URL matches the production domain and engine mount path.
328
+ - Review the host app’s static `public/404.html` and `public/500.html` pages.
329
+ - Configure the generated CarrierWave initializer for S3-compatible production storage, or replace it with another storage configuration.
330
+ - If you customize the public templates, copy the required views and enable host Tailwind support when your changes introduce new utility classes.
26
331
 
27
332
  ## Development
28
333
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
334
+ For engine development, use Ruby 4.0.6 and Node.js 24+ as listed in `mise.toml`. Install them with mise, or another version manager, then run:
335
+
336
+ ```bash
337
+ cd postnhost
338
+ bundle install
339
+ yarn install
340
+ ```
341
+
342
+ Run both JS and CSS watchers in one command:
343
+
344
+ ```bash
345
+ yarn dev
346
+ ```
347
+
348
+ ### Running tests
30
349
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
350
+ From the engine root:
32
351
 
33
- ## Contributing
352
+ ```bash
353
+ bundle install
354
+ yarn install
355
+ bundle exec rake prepare_test_db
356
+ bundle exec rspec
357
+ ```
34
358
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/postnhost.
359
+ System specs run with Selenium headless by default. Use visible Chrome when needed:
360
+
361
+ ```bash
362
+ SYSTEM_TESTS_BROWSER=1 bundle exec rspec spec/system
363
+ ```
36
364
 
37
365
  ## License
38
366
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
367
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
data/Rakefile CHANGED
@@ -1,12 +1,72 @@
1
- # frozen_string_literal: true
1
+ require "bundler/setup"
2
+ require "fileutils"
2
3
 
3
4
  require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
+ require_relative "lib/postnhost/css_build"
5
6
 
6
- RSpec::Core::RakeTask.new(:spec)
7
+ css_build = lambda do |watch:|
8
+ root = __dir__
9
+ unscoped_path = File.join(root, "app/assets/builds/postnhost/application.unscoped.css")
10
+ output_path = File.join(root, "app/assets/builds/postnhost/application.css")
11
+ command = [
12
+ File.join(root, "node_modules/.bin/tailwindcss"),
13
+ "-i", File.join(root, "app/assets/stylesheets/application.tailwind.css"),
14
+ "-o", unscoped_path,
15
+ "--minify"
16
+ ]
17
+ command << "--watch" if watch
7
18
 
8
- require "rubocop/rake_task"
19
+ Postnhost::CssBuild.run(command:, unscoped_path:, output_path:, watch:)
20
+ end
9
21
 
10
- RuboCop::RakeTask.new
22
+ namespace :css do
23
+ desc "Build the engine stylesheet"
24
+ task build: :environment do
25
+ css_build.call(watch: false)
26
+ end
11
27
 
12
- task default: %i[spec rubocop]
28
+ desc "Build the engine stylesheet and watch for changes"
29
+ task watch: :environment do
30
+ css_build.call(watch: true)
31
+ end
32
+ end
33
+
34
+ # Load dummy app so the Rails `environment` task exists (required by prepare_test_db).
35
+ require_relative "spec/dummy/config/application"
36
+ Rails.application.load_tasks
37
+
38
+ desc "Prepare dummy app test database"
39
+ task prepare_test_db: :environment do
40
+ dummy_dir = File.expand_path("spec/dummy", __dir__)
41
+ engine_migrations_dir = File.expand_path("db/migrate", __dir__)
42
+ dummy_migrations_dir = File.join(dummy_dir, "db", "migrate")
43
+ dummy_test_db = File.join(dummy_dir, "storage", "test.sqlite3")
44
+ dummy_schema = File.join(dummy_dir, "db", "schema.rb")
45
+
46
+ FileUtils.mkdir_p(dummy_migrations_dir)
47
+ FileUtils.rm_f(Dir[File.join(dummy_migrations_dir, "*.rb")])
48
+ Dir[File.join(engine_migrations_dir, "*.rb")].each do |migration_file|
49
+ FileUtils.cp(migration_file, dummy_migrations_dir)
50
+ end
51
+ FileUtils.rm_f(dummy_test_db)
52
+ FileUtils.rm_f(dummy_schema)
53
+
54
+ system(
55
+ { "RAILS_ENV" => "test" },
56
+ "cd #{dummy_dir} && bundle exec rails db:create db:migrate",
57
+ out: $stdout,
58
+ err: $stderr
59
+ ) ||
60
+ raise("Failed to prepare test database")
61
+ end
62
+
63
+ begin
64
+ require "rspec/core/rake_task"
65
+
66
+ desc "Run engine specs"
67
+ RSpec::Core::RakeTask.new(:spec)
68
+ task spec: :prepare_test_db
69
+ task default: :spec
70
+ rescue LoadError
71
+ # rspec-rails is not available in minimal environments.
72
+ end