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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58a5e825dc2b90b41ca5409f379a5ef6257b6d05de4cec3e7dc517d52cd045fd
4
- data.tar.gz: ffae44f7bc9a560d21a8d910b16ff7e4bc7aac14819ebe01b9e28f101b2012da
3
+ metadata.gz: f12e20da143bbf828f95af42951dcfc1bbce4e9152dd47630ea8a668b73b8a6b
4
+ data.tar.gz: 8ae19423f07e68ec8fef6ba7a4014751b25b658e218a939bf802157247b96c05
5
5
  SHA512:
6
- metadata.gz: 6ea72f1636e28368c812810ce70784a4a0067477596b01d7655532d667df81b2016186a6bea73fc639cd3a07bbaa376f0a2f4ba211bba02cbd09f12fa60594c0
7
- data.tar.gz: 5f8cf9da3f29af73e5ab9e8d612762ee16220f9acda35eac2bf8386ff5a08b68374812d682a5fb68e2af220840fa60a0ef12993c78f03f448d5d4d4115c76702
6
+ metadata.gz: c40643ed0fd03ec5bc35a717c08a50a7942f9b64df50e397ab4de7217a46090c60d3ae2f25684fb8c5629bb264a1fb4ca87ab6119a819512db90eb84d0f6b2ff
7
+ data.tar.gz: af8cd76fde91eaad7ac5c175721ca1b7b5a26a80caad882fcfe404ce5bd486252a57dd3681ed108fa51d90d8c500c969e05863b234c4034804502937971186db
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to PostnHost are documented in this file.
4
+
5
+ ## [0.1.0] - 2026-08-29
6
+
7
+ First open source release of PostnHost.
8
+
9
+ ### Added
10
+
11
+ - Mountable Rails CMS engine with a built-in dashboard, onboarding, session-based authentication, and user management.
12
+ - Draft and snapshot-based publishing for articles, pages, and translated variants, including scheduled publishing, unpublishing, bulk transitions, and version restoration.
13
+ - Multilingual content, localized routes, language switching, locale-aware metadata, and optional OpenAI-assisted translations.
14
+ - Rich text editing, image uploads and processing, categories, multiple authors, article suggestions, navigation management, static pages, and site-wide settings.
15
+ - Public search, sitemaps, canonical URLs, hreflang tags, structured metadata, and HTTP and fragment caching with publication-aware invalidation.
16
+ - Three packaged public templates: Default, Swiss Editorial, and Workspace Journal.
17
+ - Generators for installation, CMS users, public view overrides, locales, static pages, and host Tailwind integration.
18
+ - Precompiled JavaScript and CSS assets with Propshaft and Sprockets support, requiring no frontend toolchain in host applications.
19
+ - Support for Ruby 3.4 and 4.0 with Rails 7.2, 8.0, and 8.1.
20
+
21
+ ### Credits
22
+
23
+ Authored by Kirill Shevchenko (Ruby/Rails engineering) and Maxim Sova (technical SEO expertise).
24
+
25
+ [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,365 @@
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
+ - [Manual configuration](#manual-configuration)
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
10
109
 
11
- Install the gem and add to the application's Gemfile by executing:
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:
12
163
 
13
164
  ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
165
+ bin/rails g postnhost:views --views-scope=minimal
166
+ # or
167
+ bin/rails g postnhost:views --views-scope=full
15
168
  ```
16
169
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
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:
18
189
 
19
190
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
191
+ bin/rails g postnhost:tailwindcss:install
192
+ bin/dev
21
193
  ```
22
194
 
23
- ## Usage
195
+ This generator creates:
196
+
197
+ - `postnhost.tailwind.config.js`
198
+ - `app/assets/stylesheets/postnhost/host.tailwind.css`
199
+ - `Procfile.dev` watcher entry:
200
+ - `postnhost_css: bundle exec rails postnhost:tailwindcss:watch`
201
+ - optional `package.json` scripts (if `package.json` exists):
202
+ - `postnhost:tailwindcss`
203
+ - `postnhost:tailwindcss:watch`
204
+
205
+ When host-tailwind mode files are present, PostnHost layouts automatically include `postnhost/host.css` after `postnhost/application.css`.
206
+ You do not need to edit layout tags.
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
+ ```
24
219
 
25
- TODO: Write usage instructions here
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).
265
+
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:
267
+
268
+ ```bash
269
+ bin/rails g postnhost:locale it
270
+ ```
271
+
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:
281
+
282
+ ```bash
283
+ bin/rails g postnhost:static_pages terms privacy about
284
+ ```
285
+
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
+ ## Manual configuration
323
+
324
+ Some parts must still be configured manually in code:
325
+
326
+ - Favicons
327
+ - Robots.txt
328
+ - Error pages 404/500 etc.
26
329
 
27
330
  ## Development
28
331
 
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.
332
+ 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:
333
+
334
+ ```bash
335
+ cd postnhost
336
+ bundle install
337
+ yarn install
338
+ ```
339
+
340
+ Run both JS and CSS watchers in one command:
341
+
342
+ ```bash
343
+ yarn dev
344
+ ```
345
+
346
+ ### Running tests
30
347
 
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).
348
+ From the engine root:
32
349
 
33
- ## Contributing
350
+ ```bash
351
+ bundle install
352
+ yarn install
353
+ bundle exec rake prepare_test_db
354
+ bundle exec rspec
355
+ ```
34
356
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/postnhost.
357
+ System specs run with Selenium headless by default. Use visible Chrome when needed:
358
+
359
+ ```bash
360
+ SYSTEM_TESTS_BROWSER=1 bundle exec rspec spec/system
361
+ ```
36
362
 
37
363
  ## License
38
364
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
365
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
data/Rakefile CHANGED
@@ -1,12 +1,44 @@
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
5
 
6
- RSpec::Core::RakeTask.new(:spec)
6
+ # Load dummy app so the Rails `environment` task exists (required by prepare_test_db).
7
+ require_relative "spec/dummy/config/application"
8
+ Rails.application.load_tasks
7
9
 
8
- require "rubocop/rake_task"
10
+ desc "Prepare dummy app test database"
11
+ task prepare_test_db: :environment do
12
+ dummy_dir = File.expand_path("spec/dummy", __dir__)
13
+ engine_migrations_dir = File.expand_path("db/migrate", __dir__)
14
+ dummy_migrations_dir = File.join(dummy_dir, "db", "migrate")
15
+ dummy_test_db = File.join(dummy_dir, "storage", "test.sqlite3")
16
+ dummy_schema = File.join(dummy_dir, "db", "schema.rb")
9
17
 
10
- RuboCop::RakeTask.new
18
+ FileUtils.mkdir_p(dummy_migrations_dir)
19
+ FileUtils.rm_f(Dir[File.join(dummy_migrations_dir, "*.rb")])
20
+ Dir[File.join(engine_migrations_dir, "*.rb")].each do |migration_file|
21
+ FileUtils.cp(migration_file, dummy_migrations_dir)
22
+ end
23
+ FileUtils.rm_f(dummy_test_db)
24
+ FileUtils.rm_f(dummy_schema)
11
25
 
12
- task default: %i[spec rubocop]
26
+ system(
27
+ { "RAILS_ENV" => "test" },
28
+ "cd #{dummy_dir} && bundle exec rails db:create db:migrate",
29
+ out: $stdout,
30
+ err: $stderr
31
+ ) ||
32
+ raise("Failed to prepare test database")
33
+ end
34
+
35
+ begin
36
+ require "rspec/core/rake_task"
37
+
38
+ desc "Run engine specs"
39
+ RSpec::Core::RakeTask.new(:spec)
40
+ task spec: :prepare_test_db
41
+ task default: :spec
42
+ rescue LoadError
43
+ # rspec-rails is not available in minimal environments.
44
+ end