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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/{LICENSE.txt → LICENSE} +6 -6
- data/README.md +343 -15
- data/Rakefile +66 -6
- data/app/assets/builds/postnhost/application.css +2 -0
- data/app/assets/builds/postnhost/application.js +50190 -0
- data/app/assets/config/postnhost_manifest.js +2 -0
- data/app/assets/images/postnhost/demo.webp +0 -0
- data/app/assets/images/postnhost/icons/alert-circle.svg +3 -0
- data/app/assets/images/postnhost/icons/arrow-left-long.svg +3 -0
- data/app/assets/images/postnhost/icons/arrow-left.svg +3 -0
- data/app/assets/images/postnhost/icons/arrow-right-end-on-rectangle.svg +3 -0
- data/app/assets/images/postnhost/icons/arrow-right-long.svg +3 -0
- data/app/assets/images/postnhost/icons/arrow-right.svg +3 -0
- data/app/assets/images/postnhost/icons/bars.svg +3 -0
- data/app/assets/images/postnhost/icons/bluesky.svg +3 -0
- data/app/assets/images/postnhost/icons/check-circle.svg +3 -0
- data/app/assets/images/postnhost/icons/chevron-down-small.svg +3 -0
- data/app/assets/images/postnhost/icons/chevron-down.svg +3 -0
- data/app/assets/images/postnhost/icons/chevron-left.svg +3 -0
- data/app/assets/images/postnhost/icons/chevron-right.svg +3 -0
- data/app/assets/images/postnhost/icons/clock.svg +3 -0
- data/app/assets/images/postnhost/icons/close.svg +3 -0
- data/app/assets/images/postnhost/icons/code-bracket.svg +3 -0
- data/app/assets/images/postnhost/icons/document-text.svg +3 -0
- data/app/assets/images/postnhost/icons/drag-handle.svg +8 -0
- data/app/assets/images/postnhost/icons/edit.svg +3 -0
- data/app/assets/images/postnhost/icons/external-link.svg +3 -0
- data/app/assets/images/postnhost/icons/eye.svg +4 -0
- data/app/assets/images/postnhost/icons/facebook.svg +3 -0
- data/app/assets/images/postnhost/icons/folder.svg +3 -0
- data/app/assets/images/postnhost/icons/image.svg +3 -0
- data/app/assets/images/postnhost/icons/info-circle.svg +3 -0
- data/app/assets/images/postnhost/icons/instagram.svg +3 -0
- data/app/assets/images/postnhost/icons/language.svg +3 -0
- data/app/assets/images/postnhost/icons/layers.svg +3 -0
- data/app/assets/images/postnhost/icons/lightning.svg +3 -0
- data/app/assets/images/postnhost/icons/linkedin.svg +3 -0
- data/app/assets/images/postnhost/icons/logout.svg +3 -0
- data/app/assets/images/postnhost/icons/magnifying-glass.svg +3 -0
- data/app/assets/images/postnhost/icons/mastodon.svg +3 -0
- data/app/assets/images/postnhost/icons/menu.svg +3 -0
- data/app/assets/images/postnhost/icons/plus.svg +3 -0
- data/app/assets/images/postnhost/icons/schedule.svg +3 -0
- data/app/assets/images/postnhost/icons/settings.svg +4 -0
- data/app/assets/images/postnhost/icons/sparkles.svg +3 -0
- data/app/assets/images/postnhost/icons/spinner.svg +4 -0
- data/app/assets/images/postnhost/icons/threads.svg +3 -0
- data/app/assets/images/postnhost/icons/tiktok.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/align-center.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/align-left.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/align-right.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/apply-link.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/blockquote.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/bold.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/bullet-list.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/code-block.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/code.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/heading-2.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/heading-3.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/image-add.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/italic.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/link.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/ordered-list.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/redo.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/remove-link.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/strike.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/table.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/underline.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/undo.svg +3 -0
- data/app/assets/images/postnhost/icons/tiptap/youtube.svg +4 -0
- data/app/assets/images/postnhost/icons/translate.svg +3 -0
- data/app/assets/images/postnhost/icons/trash.svg +3 -0
- data/app/assets/images/postnhost/icons/user.svg +4 -0
- data/app/assets/images/postnhost/icons/users.svg +3 -0
- data/app/assets/images/postnhost/icons/warning.svg +3 -0
- data/app/assets/images/postnhost/icons/x.svg +3 -0
- data/app/assets/images/postnhost/icons/youtube.svg +3 -0
- data/app/assets/images/postnhost/logo.webp +0 -0
- data/app/assets/images/postnhost/og-image.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/08-how-to-control-suggested-articles-and-top-picks-01-discovery-controls.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/09-all-about-authors-in-postnhost-01-author-profile.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/09-all-about-authors-in-postnhost-02-multiple-authors.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/11-how-to-schedule-an-article-for-publication-01-timezone.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/11-how-to-schedule-an-article-for-publication-02-scheduled-article.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/12-how-article-version-history-and-restores-work-01-version-history.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/13-how-to-add-and-manage-languages-01-language-management.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/14-how-to-translate-articles-pages-and-categories-01-translation-options.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/14-how-to-translate-articles-pages-and-categories-02-ai-language-selection.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/16-how-to-add-navigation-links-01-navigation-builder.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/16-how-to-add-navigation-links-02-external-link-settings.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/17-how-to-add-a-logo-and-default-og-image-01-assets-settings.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/19-how-to-customize-public-text-for-each-language-01-i18n-overrides.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/20-how-to-configure-structured-data-and-sitemap-seo-01-site-schema.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/20-how-to-configure-structured-data-and-sitemap-seo-02-author-schema-defaults.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/20-how-to-configure-structured-data-and-sitemap-seo-03-author-schema-advanced-fields.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/08-suggested-articles-and-top-picks.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/09-authors.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/11-scheduling.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/12-version-history.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/13-languages.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/14-translations.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/16-navigation-links.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/17-logo-and-default-og-image.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/19-public-text-for-each-language.webp +0 -0
- data/app/assets/images/postnhost/sample-docs/covers/20-structured-data-and-sitemap-seo.webp +0 -0
- data/app/assets/stylesheets/application.tailwind.css +347 -0
- data/app/assets/stylesheets/postnhost/application.css +15 -0
- data/app/assets/xsl/sitemap.xsl +64 -0
- data/app/controllers/concerns/postnhost/authentication.rb +59 -0
- data/app/controllers/concerns/postnhost/canonical_first_page_redirect.rb +14 -0
- data/app/controllers/concerns/postnhost/language_switcher.rb +42 -0
- data/app/controllers/concerns/postnhost/onboarding/access_control.rb +57 -0
- data/app/controllers/concerns/postnhost/onboarding/step_assignment.rb +55 -0
- data/app/controllers/concerns/postnhost/public_footer_data.rb +103 -0
- data/app/controllers/concerns/postnhost/public_http_caching.rb +35 -0
- data/app/controllers/concerns/postnhost/public_page_size.rb +17 -0
- data/app/controllers/concerns/postnhost/public_template_resolvable.rb +44 -0
- data/app/controllers/concerns/postnhost/settings/i18n_manageable.rb +61 -0
- data/app/controllers/concerns/postnhost/settings/navigation_manageable.rb +118 -0
- data/app/controllers/concerns/postnhost/settings/schema_manageable.rb +140 -0
- data/app/controllers/concerns/postnhost/users/schema_manageable.rb +119 -0
- data/app/controllers/postnhost/application_controller.rb +37 -0
- data/app/controllers/postnhost/article_variants_controller.rb +138 -0
- data/app/controllers/postnhost/articles/cover_images_controller.rb +35 -0
- data/app/controllers/postnhost/articles/images_controller.rb +28 -0
- data/app/controllers/postnhost/articles/translations_controller.rb +26 -0
- data/app/controllers/postnhost/articles_controller.rb +130 -0
- data/app/controllers/postnhost/categories/translations_controller.rb +24 -0
- data/app/controllers/postnhost/categories_controller.rb +59 -0
- data/app/controllers/postnhost/category_variants_controller.rb +69 -0
- data/app/controllers/postnhost/languages_controller.rb +66 -0
- data/app/controllers/postnhost/onboarding_controller.rb +70 -0
- data/app/controllers/postnhost/page_variants_controller.rb +136 -0
- data/app/controllers/postnhost/pages/images_controller.rb +28 -0
- data/app/controllers/postnhost/pages/translations_controller.rb +26 -0
- data/app/controllers/postnhost/pages_controller.rb +95 -0
- data/app/controllers/postnhost/public/articles_controller.rb +135 -0
- data/app/controllers/postnhost/public/authors_controller.rb +57 -0
- data/app/controllers/postnhost/public/categories_controller.rb +61 -0
- data/app/controllers/postnhost/public/static_pages_controller.rb +77 -0
- data/app/controllers/postnhost/public_controller.rb +32 -0
- data/app/controllers/postnhost/sessions_controller.rb +37 -0
- data/app/controllers/postnhost/settings/site_scripts_controller.rb +61 -0
- data/app/controllers/postnhost/settings_controller.rb +63 -0
- data/app/controllers/postnhost/sitemap_controller.rb +17 -0
- data/app/controllers/postnhost/templates_controller.rb +26 -0
- data/app/controllers/postnhost/users_controller.rb +112 -0
- data/app/helpers/postnhost/application_helper.rb +236 -0
- data/app/helpers/postnhost/article_helper.rb +41 -0
- data/app/helpers/postnhost/category_helper.rb +52 -0
- data/app/helpers/postnhost/common_css_helper.rb +97 -0
- data/app/helpers/postnhost/navigation_helper.rb +148 -0
- data/app/helpers/postnhost/pagy_helper.rb +103 -0
- data/app/helpers/postnhost/public_pages_helper.rb +287 -0
- data/app/helpers/postnhost/schema_helper.rb +102 -0
- data/app/helpers/postnhost/seo_helper.rb +428 -0
- data/app/helpers/postnhost/settings_helper.rb +79 -0
- data/app/javascript/application.js +3 -0
- data/app/javascript/controllers/ai_translation_controller.js +42 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/author_manager_controller.js +96 -0
- data/app/javascript/controllers/auto_save_controller.js +244 -0
- data/app/javascript/controllers/bulk_selection_controller.js +74 -0
- data/app/javascript/controllers/burger_controller.js +20 -0
- data/app/javascript/controllers/category_manager_controller.js +99 -0
- data/app/javascript/controllers/collapsible_sidebar_controller.js +87 -0
- data/app/javascript/controllers/cover_image_upload_controller.js +164 -0
- data/app/javascript/controllers/dropdown_controller.js +38 -0
- data/app/javascript/controllers/editor_mobile_sidebars_controller.js +36 -0
- data/app/javascript/controllers/file_auto_submit_controller.js +23 -0
- data/app/javascript/controllers/flash_message_controller.js +40 -0
- data/app/javascript/controllers/index.js +71 -0
- data/app/javascript/controllers/navigation_builder_controller.js +463 -0
- data/app/javascript/controllers/public_mobile_nav_controller.js +26 -0
- data/app/javascript/controllers/publish_guard_controller.js +47 -0
- data/app/javascript/controllers/reading_progress_controller.js +38 -0
- data/app/javascript/controllers/schedule_manager_controller.js +87 -0
- data/app/javascript/controllers/suggestion_manager_controller.js +99 -0
- data/app/javascript/controllers/template_preview_controller.js +34 -0
- data/app/javascript/controllers/tiptap_editor_controller.js +812 -0
- data/app/javascript/controllers/title_field_controller.js +17 -0
- data/app/javascript/helpers/auto_save_state.js +47 -0
- data/app/javascript/helpers/code_highlighting.js +48 -0
- data/app/javascript/helpers/dom_helpers.js +109 -0
- data/app/javascript/helpers/style_helpers.js +168 -0
- data/app/jobs/postnhost/application_job.rb +9 -0
- data/app/jobs/postnhost/scheduled_article_job.rb +13 -0
- data/app/jobs/postnhost/translation/article_variant_job.rb +17 -0
- data/app/jobs/postnhost/translation/category_variant_job.rb +17 -0
- data/app/jobs/postnhost/translation/page_variant_job.rb +17 -0
- data/app/models/concerns/postnhost/article/image_uploadable.rb +11 -0
- data/app/models/concerns/postnhost/excerpt_optimizable.rb +57 -0
- data/app/models/concerns/postnhost/inline_image_uploadable.rb +40 -0
- data/app/models/concerns/postnhost/page/image_uploadable.rb +11 -0
- data/app/models/concerns/postnhost/primary_language_guard.rb +18 -0
- data/app/models/concerns/postnhost/public_revision_touch.rb +15 -0
- data/app/models/concerns/postnhost/schema_localizable.rb +28 -0
- data/app/models/postnhost/application_record.rb +5 -0
- data/app/models/postnhost/article.rb +142 -0
- data/app/models/postnhost/article_author.rb +20 -0
- data/app/models/postnhost/article_category.rb +8 -0
- data/app/models/postnhost/article_suggestion.rb +19 -0
- data/app/models/postnhost/article_variant.rb +28 -0
- data/app/models/postnhost/category.rb +34 -0
- data/app/models/postnhost/category_variant.rb +24 -0
- data/app/models/postnhost/language.rb +38 -0
- data/app/models/postnhost/navigation.rb +76 -0
- data/app/models/postnhost/navigation_item.rb +100 -0
- data/app/models/postnhost/page.rb +63 -0
- data/app/models/postnhost/page_variant.rb +26 -0
- data/app/models/postnhost/public_site_revision.rb +24 -0
- data/app/models/postnhost/published_cover_image.rb +28 -0
- data/app/models/postnhost/setting.rb +116 -0
- data/app/models/postnhost/site_script.rb +11 -0
- data/app/models/postnhost/snapshot/article.rb +142 -0
- data/app/models/postnhost/snapshot/article_author.rb +11 -0
- data/app/models/postnhost/snapshot/article_category.rb +10 -0
- data/app/models/postnhost/snapshot/article_suggestion.rb +11 -0
- data/app/models/postnhost/snapshot/article_variant.rb +60 -0
- data/app/models/postnhost/snapshot/page.rb +33 -0
- data/app/models/postnhost/snapshot/page_variant.rb +41 -0
- data/app/models/postnhost/template.rb +20 -0
- data/app/models/postnhost/user.rb +63 -0
- data/app/models/postnhost/version.rb +7 -0
- data/app/services/postnhost/base_service.rb +17 -0
- data/app/services/postnhost/constraints/public_locale_constraint.rb +9 -0
- data/app/services/postnhost/constraints/public_route_constraint.rb +19 -0
- data/app/services/postnhost/onboarding/step_processor.rb +27 -0
- data/app/services/postnhost/onboarding/steps/base.rb +52 -0
- data/app/services/postnhost/onboarding/steps/choose_languages.rb +66 -0
- data/app/services/postnhost/onboarding/steps/complete_post_install_checklist.rb +12 -0
- data/app/services/postnhost/onboarding/steps/create_admin_account.rb +28 -0
- data/app/services/postnhost/onboarding/steps/finish_setup.rb +27 -0
- data/app/services/postnhost/onboarding/steps/set_site_metadata.rb +70 -0
- data/app/services/postnhost/public_navigation_targets.rb +67 -0
- data/app/services/postnhost/public_request_context.rb +101 -0
- data/app/services/postnhost/publishing/article_variants/publish.rb +26 -0
- data/app/services/postnhost/publishing/article_variants/publish_many.rb +23 -0
- data/app/services/postnhost/publishing/article_variants/snapshot_writer.rb +44 -0
- data/app/services/postnhost/publishing/article_variants/unpublish.rb +27 -0
- data/app/services/postnhost/publishing/article_variants/unpublish_many.rb +23 -0
- data/app/services/postnhost/publishing/articles/publish.rb +25 -0
- data/app/services/postnhost/publishing/articles/publish_scheduled.rb +42 -0
- data/app/services/postnhost/publishing/articles/snapshot_writer.rb +84 -0
- data/app/services/postnhost/publishing/articles/unpublish.rb +26 -0
- data/app/services/postnhost/publishing/bulk_transition.rb +63 -0
- data/app/services/postnhost/publishing/error.rb +12 -0
- data/app/services/postnhost/publishing/page_variants/publish.rb +26 -0
- data/app/services/postnhost/publishing/page_variants/publish_many.rb +23 -0
- data/app/services/postnhost/publishing/page_variants/snapshot_writer.rb +41 -0
- data/app/services/postnhost/publishing/page_variants/unpublish.rb +27 -0
- data/app/services/postnhost/publishing/page_variants/unpublish_many.rb +23 -0
- data/app/services/postnhost/publishing/pages/publish.rb +25 -0
- data/app/services/postnhost/publishing/pages/snapshot_writer.rb +38 -0
- data/app/services/postnhost/publishing/pages/unpublish.rb +26 -0
- data/app/services/postnhost/publishing/revision.rb +37 -0
- data/app/services/postnhost/publishing/route_validator.rb +41 -0
- data/app/services/postnhost/publishing/versions/restore_draft.rb +64 -0
- data/app/services/postnhost/service_result.rb +7 -0
- data/app/services/postnhost/sitemap_builder.rb +395 -0
- data/app/services/postnhost/translation/article_variant_translator.rb +102 -0
- data/app/services/postnhost/translation/category_variant_translator.rb +92 -0
- data/app/services/postnhost/translation/content_translator.rb +119 -0
- data/app/services/postnhost/translation/openai_llm_client.rb +30 -0
- data/app/services/postnhost/translation/page_variant_translator.rb +55 -0
- data/app/uploaders/base_uploader.rb +26 -0
- data/app/uploaders/cover_image_uploader.rb +31 -0
- data/app/uploaders/postnhost/avatar_uploader.rb +17 -0
- data/app/uploaders/postnhost/inline_article_image_uploader.rb +38 -0
- data/app/uploaders/postnhost/inline_page_image_uploader.rb +38 -0
- data/app/uploaders/postnhost/setting_asset_uploader.rb +18 -0
- data/app/views/layouts/postnhost/_favicon.html.erb +6 -0
- data/app/views/layouts/postnhost/_footer.html.erb +67 -0
- data/app/views/layouts/postnhost/_header.html.erb +89 -0
- data/app/views/layouts/postnhost/_sidebar.html.erb +72 -0
- data/app/views/layouts/postnhost/application.html.erb +23 -0
- data/app/views/layouts/postnhost/articles.html.erb +34 -0
- data/app/views/layouts/postnhost/product.html.erb +36 -0
- data/app/views/layouts/postnhost/public/templates/default.html.erb +110 -0
- data/app/views/layouts/postnhost/public/templates/swiss-editorial.html.erb +213 -0
- data/app/views/layouts/postnhost/public/templates/workspace-journal.html.erb +231 -0
- data/app/views/layouts/postnhost/public_header/_dashboard_sign_in.html.erb +14 -0
- data/app/views/layouts/postnhost/public_header/_language_switcher.html.erb +43 -0
- data/app/views/layouts/postnhost/public_header/_logo.html.erb +9 -0
- data/app/views/postnhost/article_variants/_variants_list.html.erb +82 -0
- data/app/views/postnhost/article_variants/edit.html.erb +48 -0
- data/app/views/postnhost/article_variants/index.html.erb +128 -0
- data/app/views/postnhost/article_variants/publish.turbo_stream.erb +3 -0
- data/app/views/postnhost/article_variants/unpublish.turbo_stream.erb +3 -0
- data/app/views/postnhost/article_variants/update.turbo_stream.erb +3 -0
- data/app/views/postnhost/articles/_author_manager.html.erb +38 -0
- data/app/views/postnhost/articles/_category_manager.html.erb +42 -0
- data/app/views/postnhost/articles/_cover_image_upload.html.erb +82 -0
- data/app/views/postnhost/articles/_suggestion_manager.html.erb +43 -0
- data/app/views/postnhost/articles/_version_snapshot.html.erb +10 -0
- data/app/views/postnhost/articles/edit.html.erb +51 -0
- data/app/views/postnhost/articles/index.html.erb +132 -0
- data/app/views/postnhost/articles/publish.turbo_stream.erb +3 -0
- data/app/views/postnhost/articles/unpublish.turbo_stream.erb +3 -0
- data/app/views/postnhost/articles/update.turbo_stream.erb +3 -0
- data/app/views/postnhost/articles/version_preview.html.erb +17 -0
- data/app/views/postnhost/articles/versions.html.erb +68 -0
- data/app/views/postnhost/categories/edit.html.erb +77 -0
- data/app/views/postnhost/categories/index.html.erb +80 -0
- data/app/views/postnhost/categories/new.html.erb +50 -0
- data/app/views/postnhost/categories/show.html.erb +99 -0
- data/app/views/postnhost/category_variants/_category_variants_list.html.erb +67 -0
- data/app/views/postnhost/category_variants/_form.html.erb +29 -0
- data/app/views/postnhost/category_variants/edit.html.erb +21 -0
- data/app/views/postnhost/category_variants/index.html.erb +82 -0
- data/app/views/postnhost/category_variants/new.html.erb +21 -0
- data/app/views/postnhost/languages/edit.html.erb +45 -0
- data/app/views/postnhost/languages/index.html.erb +86 -0
- data/app/views/postnhost/languages/new.html.erb +45 -0
- data/app/views/postnhost/languages/show.html.erb +156 -0
- data/app/views/postnhost/onboarding/_post_install_checklist.html.erb +40 -0
- data/app/views/postnhost/onboarding/_step_1.html.erb +42 -0
- data/app/views/postnhost/onboarding/_step_2.html.erb +38 -0
- data/app/views/postnhost/onboarding/_step_3.html.erb +62 -0
- data/app/views/postnhost/onboarding/_step_4.html.erb +26 -0
- data/app/views/postnhost/onboarding/show.html.erb +51 -0
- data/app/views/postnhost/page_variants/_variants_list.html.erb +82 -0
- data/app/views/postnhost/page_variants/edit.html.erb +50 -0
- data/app/views/postnhost/page_variants/index.html.erb +128 -0
- data/app/views/postnhost/page_variants/publish.turbo_stream.erb +3 -0
- data/app/views/postnhost/page_variants/unpublish.turbo_stream.erb +3 -0
- data/app/views/postnhost/page_variants/update.turbo_stream.erb +3 -0
- data/app/views/postnhost/pages/edit.html.erb +50 -0
- data/app/views/postnhost/pages/index.html.erb +110 -0
- data/app/views/postnhost/pages/publish.turbo_stream.erb +3 -0
- data/app/views/postnhost/pages/unpublish.turbo_stream.erb +3 -0
- data/app/views/postnhost/public/articles/content_for/_index.html.erb +30 -0
- data/app/views/postnhost/public/articles/content_for/_preview.html.erb +9 -0
- data/app/views/postnhost/public/articles/content_for/_show.html.erb +24 -0
- data/app/views/postnhost/public/authors/content_for/_show.html.erb +24 -0
- data/app/views/postnhost/public/categories/content_for/_index.html.erb +29 -0
- data/app/views/postnhost/public/static_pages/content_for/_show.html.erb +19 -0
- data/app/views/postnhost/public/templates/default/articles/_article_card.html.erb +37 -0
- data/app/views/postnhost/public/templates/default/articles/_search_form.html.erb +12 -0
- data/app/views/postnhost/public/templates/default/articles/_suggestions.html.erb +44 -0
- data/app/views/postnhost/public/templates/default/articles/_top_picks.html.erb +42 -0
- data/app/views/postnhost/public/templates/default/articles/index.html.erb +53 -0
- data/app/views/postnhost/public/templates/default/articles/preview.html.erb +102 -0
- data/app/views/postnhost/public/templates/default/articles/show.html.erb +111 -0
- data/app/views/postnhost/public/templates/default/authors/_article_card.html.erb +28 -0
- data/app/views/postnhost/public/templates/default/authors/_profile_block.html.erb +29 -0
- data/app/views/postnhost/public/templates/default/authors/show.html.erb +36 -0
- data/app/views/postnhost/public/templates/default/categories/_article_card.html.erb +34 -0
- data/app/views/postnhost/public/templates/default/categories/index.html.erb +48 -0
- data/app/views/postnhost/public/templates/default/shared/_article_authors.html.erb +24 -0
- data/app/views/postnhost/public/templates/default/shared/_author_avatar.html.erb +9 -0
- data/app/views/postnhost/public/templates/default/shared/_hero_cover_image.html.erb +11 -0
- data/app/views/postnhost/public/templates/default/shared/_pagination.html.erb +35 -0
- data/app/views/postnhost/public/templates/default/shared/_thumbnail_cover_image.html.erb +9 -0
- data/app/views/postnhost/public/templates/default/static_pages/show.html.erb +22 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/_article_card.html.erb +28 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/_search_form.html.erb +19 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/_suggestions.html.erb +42 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/_top_picks.html.erb +38 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/index.html.erb +133 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/preview.html.erb +42 -0
- data/app/views/postnhost/public/templates/swiss-editorial/articles/show.html.erb +100 -0
- data/app/views/postnhost/public/templates/swiss-editorial/authors/_article_card.html.erb +27 -0
- data/app/views/postnhost/public/templates/swiss-editorial/authors/_profile_block.html.erb +32 -0
- data/app/views/postnhost/public/templates/swiss-editorial/authors/show.html.erb +31 -0
- data/app/views/postnhost/public/templates/swiss-editorial/categories/_article_card.html.erb +28 -0
- data/app/views/postnhost/public/templates/swiss-editorial/categories/index.html.erb +88 -0
- data/app/views/postnhost/public/templates/swiss-editorial/shared/_article_authors.html.erb +26 -0
- data/app/views/postnhost/public/templates/swiss-editorial/shared/_author_avatar.html.erb +9 -0
- data/app/views/postnhost/public/templates/swiss-editorial/shared/_hero_cover_image.html.erb +11 -0
- data/app/views/postnhost/public/templates/swiss-editorial/shared/_pagination.html.erb +35 -0
- data/app/views/postnhost/public/templates/swiss-editorial/shared/_thumbnail_cover_image.html.erb +9 -0
- data/app/views/postnhost/public/templates/swiss-editorial/static_pages/show.html.erb +17 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/_article_card.html.erb +44 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/_search_form.html.erb +19 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/_suggestions.html.erb +49 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/_top_picks.html.erb +34 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/index.html.erb +69 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/preview.html.erb +113 -0
- data/app/views/postnhost/public/templates/workspace-journal/articles/show.html.erb +120 -0
- data/app/views/postnhost/public/templates/workspace-journal/authors/_article_card.html.erb +1 -0
- data/app/views/postnhost/public/templates/workspace-journal/authors/_profile_block.html.erb +32 -0
- data/app/views/postnhost/public/templates/workspace-journal/authors/show.html.erb +33 -0
- data/app/views/postnhost/public/templates/workspace-journal/categories/_article_card.html.erb +1 -0
- data/app/views/postnhost/public/templates/workspace-journal/categories/index.html.erb +58 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_article_authors.html.erb +27 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_article_card.html.erb +44 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_author_avatar.html.erb +9 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_category_navigation.html.erb +10 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_hero_cover_image.html.erb +11 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_pagination.html.erb +35 -0
- data/app/views/postnhost/public/templates/workspace-journal/shared/_thumbnail_cover_image.html.erb +9 -0
- data/app/views/postnhost/public/templates/workspace-journal/static_pages/show.html.erb +17 -0
- data/app/views/postnhost/sessions/new.html.erb +59 -0
- data/app/views/postnhost/settings/edit.html.erb +68 -0
- data/app/views/postnhost/settings/sections/_assets.html.erb +40 -0
- data/app/views/postnhost/settings/sections/_feature_flags.html.erb +43 -0
- data/app/views/postnhost/settings/sections/_i18n.html.erb +57 -0
- data/app/views/postnhost/settings/sections/_navigation.html.erb +98 -0
- data/app/views/postnhost/settings/sections/_schema.html.erb +253 -0
- data/app/views/postnhost/settings/sections/_scripts.html.erb +79 -0
- data/app/views/postnhost/settings/sections/_site.html.erb +41 -0
- data/app/views/postnhost/settings/sections/_timezone.html.erb +19 -0
- data/app/views/postnhost/shared/_ai_translation_modal.html.erb +85 -0
- data/app/views/postnhost/shared/_flash_messages.html.erb +27 -0
- data/app/views/postnhost/shared/_form_errors.html.erb +22 -0
- data/app/views/postnhost/shared/_seo_meta_tags.html.erb +70 -0
- data/app/views/postnhost/shared/_structured_data.html.erb +5 -0
- data/app/views/postnhost/shared/articles/_action_buttons.html.erb +87 -0
- data/app/views/postnhost/shared/articles/_cover_image_preview.html.erb +30 -0
- data/app/views/postnhost/shared/articles/_editor_column.html.erb +15 -0
- data/app/views/postnhost/shared/articles/_editor_column_preview.html.erb +33 -0
- data/app/views/postnhost/shared/articles/_error_messages.html.erb +23 -0
- data/app/views/postnhost/shared/articles/_meta_sidebar.html.erb +322 -0
- data/app/views/postnhost/shared/articles/_meta_sidebar_preview.html.erb +29 -0
- data/app/views/postnhost/shared/articles/_post_actions.html.erb +105 -0
- data/app/views/postnhost/shared/articles/_schedule_actions.html.erb +51 -0
- data/app/views/postnhost/shared/articles/_tiptap_editor.html.erb +225 -0
- data/app/views/postnhost/shared/articles/_title_field.html.erb +9 -0
- data/app/views/postnhost/shared/articles/_version_action_buttons.html.erb +35 -0
- data/app/views/postnhost/shared/elements/_button.html.erb +20 -0
- data/app/views/postnhost/shared/elements/_button_link.html.erb +24 -0
- data/app/views/postnhost/shared/elements/_empty_state.html.erb +22 -0
- data/app/views/postnhost/shared/elements/_error_messages.html.erb +28 -0
- data/app/views/postnhost/shared/elements/_form_card.html.erb +10 -0
- data/app/views/postnhost/shared/elements/_form_page_header.html.erb +21 -0
- data/app/views/postnhost/shared/elements/_icon_button.html.erb +17 -0
- data/app/views/postnhost/shared/elements/_page_header.html.erb +40 -0
- data/app/views/postnhost/shared/elements/_table_container.html.erb +23 -0
- data/app/views/postnhost/shared/pages/_action_buttons.html.erb +84 -0
- data/app/views/postnhost/shared/pages/_meta_sidebar.html.erb +132 -0
- data/app/views/postnhost/shared/pages/_post_actions.html.erb +69 -0
- data/app/views/postnhost/templates/edit.html.erb +83 -0
- data/app/views/postnhost/users/_avatar.html.erb +9 -0
- data/app/views/postnhost/users/_form.html.erb +160 -0
- data/app/views/postnhost/users/edit.html.erb +26 -0
- data/app/views/postnhost/users/index.html.erb +91 -0
- data/app/views/postnhost/users/new.html.erb +9 -0
- data/app/views/postnhost/users/schema_edit.html.erb +175 -0
- data/config/locales/de.yml +55 -0
- data/config/locales/en.yml +88 -0
- data/config/locales/es.yml +58 -0
- data/config/locales/fr.yml +55 -0
- data/config/locales/ja.yml +55 -0
- data/config/locales/ko.yml +55 -0
- data/config/locales/pl.yml +55 -0
- data/config/locales/pt.yml +55 -0
- data/config/locales/ru.yml +55 -0
- data/config/routes.rb +112 -0
- data/db/migrate/20250822213820_create_users.rb +29 -0
- data/db/migrate/20250822214046_create_categories.rb +15 -0
- data/db/migrate/20250822214106_create_languages.rb +16 -0
- data/db/migrate/20250822214148_create_articles.rb +34 -0
- data/db/migrate/20250822214217_create_article_variants.rb +24 -0
- data/db/migrate/20250929091137_create_article_categories.rb +12 -0
- data/db/migrate/20251013204200_create_versions.rb +40 -0
- data/db/migrate/20260106074532_create_article_suggestions.rb +13 -0
- data/db/migrate/20260108110506_create_category_variants.rb +15 -0
- data/db/migrate/20260308130000_create_settings.rb +23 -0
- data/db/migrate/20260323113000_create_pages.rb +19 -0
- data/db/migrate/20260414100000_create_article_authors.rb +14 -0
- data/db/migrate/20260512100000_create_templates.rb +9 -0
- data/db/migrate/20260520110001_create_navigations.rb +9 -0
- data/db/migrate/20260520110002_create_navigation_items.rb +22 -0
- data/db/migrate/20260528000002_create_page_variants.rb +20 -0
- data/db/migrate/20260709090000_create_snapshots.rb +154 -0
- data/db/migrate/20260726130000_create_site_scripts.rb +11 -0
- data/lib/generators/postnhost/install/install_generator.rb +55 -0
- data/lib/generators/postnhost/install/templates/carrierwave.rb +17 -0
- data/lib/generators/postnhost/install/templates/postnhost.rb +36 -0
- data/lib/generators/postnhost/locale/locale_generator.rb +36 -0
- data/lib/generators/postnhost/static_pages/static_pages_generator.rb +47 -0
- data/lib/generators/postnhost/tailwindcss/install_generator.rb +100 -0
- data/lib/generators/postnhost/tailwindcss/templates/host.tailwind.css +4 -0
- data/lib/generators/postnhost/user/user_generator.rb +36 -0
- data/lib/generators/postnhost/views/views_generator.rb +140 -0
- data/lib/postnhost/configuration.rb +64 -0
- data/lib/postnhost/css_build.rb +68 -0
- data/lib/postnhost/css_scope.rb +77 -0
- data/lib/postnhost/engine.rb +57 -0
- data/lib/postnhost/sample_data.rb +865 -0
- data/lib/postnhost/settings/i18n_overrides.rb +137 -0
- data/lib/postnhost/settings/i18n_patch.rb +18 -0
- data/lib/postnhost/tailwind_compiler.rb +75 -0
- data/lib/postnhost/tailwind_input.rb +32 -0
- data/lib/postnhost/version.rb +1 -3
- data/lib/postnhost.rb +26 -3
- data/lib/tasks/postnhost_snapshots.rake +77 -0
- data/lib/tasks/postnhost_tasks.rake +71 -0
- metadata +679 -21
- data/.github/workflows/main.yml +0 -27
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.rubocop.yml +0 -8
- data/Gemfile +0 -13
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/postnhost.gemspec +0 -31
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Postnhost
|
|
2
|
+
module Translation
|
|
3
|
+
class OpenaiLlmClient
|
|
4
|
+
DEFAULT_MODEL = "gpt-5.6-luna"
|
|
5
|
+
TIMEOUT = 1240
|
|
6
|
+
|
|
7
|
+
def initialize(client: nil)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def text_response(prompt, model: Postnhost.config.openai_gpt_model || DEFAULT_MODEL)
|
|
12
|
+
response = client.responses.create(
|
|
13
|
+
model: model,
|
|
14
|
+
input: prompt
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
response.output_text.strip
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def client
|
|
23
|
+
@client ||= OpenAI::Client.new(
|
|
24
|
+
api_key: Postnhost.config.openai_api_key,
|
|
25
|
+
timeout: TIMEOUT
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Postnhost
|
|
2
|
+
module Translation
|
|
3
|
+
class PageVariantTranslator < BaseService
|
|
4
|
+
def initialize(page_id:, language_id:, llm_client: OpenaiLlmClient.new)
|
|
5
|
+
@page_id = page_id
|
|
6
|
+
@language_id = language_id
|
|
7
|
+
@llm_client = llm_client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
load_records
|
|
12
|
+
return failure("Translation source or language not found") unless page && language
|
|
13
|
+
return failure("Published translation source not found") unless source
|
|
14
|
+
|
|
15
|
+
load_variant
|
|
16
|
+
return failure("Translation variant not found") unless variant
|
|
17
|
+
|
|
18
|
+
attributes = ContentTranslator.call(source:, language:, llm_client:)
|
|
19
|
+
return finish_with_failure("Translation failed") if attributes[:title].blank? || attributes[:content].blank?
|
|
20
|
+
|
|
21
|
+
variant.update!(attributes.merge(generating: false))
|
|
22
|
+
success(variant)
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
mark_generation_finished
|
|
25
|
+
failure(e.message)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :page_id, :language_id, :llm_client, :page, :language, :variant
|
|
31
|
+
|
|
32
|
+
def load_records
|
|
33
|
+
@page = Postnhost::Page.find_by(id: page_id)
|
|
34
|
+
@language = Postnhost::Language.find_by(id: language_id)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def load_variant
|
|
38
|
+
@variant = Postnhost::PageVariant.find_by(page:, language:)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def source
|
|
42
|
+
@source ||= page.page_snapshot
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def finish_with_failure(message)
|
|
46
|
+
mark_generation_finished
|
|
47
|
+
failure(message)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def mark_generation_finished
|
|
51
|
+
variant&.update!(generating: false)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class BaseUploader < CarrierWave::Uploader::Base
|
|
2
|
+
include CarrierWave::Vips
|
|
3
|
+
|
|
4
|
+
def store_dir
|
|
5
|
+
data = "#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
|
6
|
+
hash = Digest::SHA256.hexdigest(data)
|
|
7
|
+
"uploads/#{model.class.to_s.underscore}/#{hash[0..20]}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def size_range
|
|
11
|
+
0..(25.megabytes)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def filename
|
|
15
|
+
generated_filename
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def generated_filename
|
|
21
|
+
data = "#{model.id}-#{mounted_as}-#{Time.current.to_i}"
|
|
22
|
+
hash = Digest::SHA256.hexdigest(data)
|
|
23
|
+
extension = "webp"
|
|
24
|
+
@generated_filename ||= "#{hash[0..12]}.#{extension}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class CoverImageUploader < BaseUploader
|
|
2
|
+
QUALITY = 90
|
|
3
|
+
|
|
4
|
+
self.remove_previously_stored_files_after_update = false
|
|
5
|
+
|
|
6
|
+
process resize_to_fill: [1200, 630]
|
|
7
|
+
process convert: "webp"
|
|
8
|
+
process :quality
|
|
9
|
+
|
|
10
|
+
version :thumbnail do
|
|
11
|
+
process resize_to_fill: [780, 410]
|
|
12
|
+
process convert: "webp"
|
|
13
|
+
process :quality
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
version :medium do
|
|
17
|
+
process resize_to_fill: [1024, 537]
|
|
18
|
+
process convert: "webp"
|
|
19
|
+
process :quality
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def quality
|
|
23
|
+
vips! do |v|
|
|
24
|
+
v.saver(Q: QUALITY)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def extension_allowlist
|
|
29
|
+
%w[png jpg jpeg gif webp heic]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Postnhost
|
|
2
|
+
class AvatarUploader < ::BaseUploader
|
|
3
|
+
process resize_to_limit: [200, 200]
|
|
4
|
+
process convert: "webp"
|
|
5
|
+
process :quality
|
|
6
|
+
|
|
7
|
+
def quality
|
|
8
|
+
vips! do |v|
|
|
9
|
+
v.saver(Q: 90)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def extension_allowlist
|
|
14
|
+
%w[png jpg jpeg gif webp heic]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Postnhost
|
|
2
|
+
class InlineArticleImageUploader < ::BaseUploader
|
|
3
|
+
MAX_WIDTH = 1200
|
|
4
|
+
QUALITY = 90
|
|
5
|
+
|
|
6
|
+
process resize_to_limit: [MAX_WIDTH, nil]
|
|
7
|
+
process convert: "webp"
|
|
8
|
+
process :quality
|
|
9
|
+
|
|
10
|
+
def store_dir
|
|
11
|
+
"uploads/articles/#{model.user_id}/#{model.id}/inline_images"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size_range
|
|
15
|
+
0..(20.megabytes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extension_allowlist
|
|
19
|
+
%w[png jpg jpeg gif webp heic]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def quality
|
|
23
|
+
vips! do |v|
|
|
24
|
+
v.saver(Q: QUALITY)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def filename
|
|
29
|
+
generated_filename
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def generated_filename
|
|
35
|
+
@generated_filename ||= "#{SecureRandom.hex(12)}.webp"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Postnhost
|
|
2
|
+
class InlinePageImageUploader < ::BaseUploader
|
|
3
|
+
MAX_WIDTH = 1200
|
|
4
|
+
QUALITY = 90
|
|
5
|
+
|
|
6
|
+
process resize_to_limit: [MAX_WIDTH, nil]
|
|
7
|
+
process convert: "webp"
|
|
8
|
+
process :quality
|
|
9
|
+
|
|
10
|
+
def store_dir
|
|
11
|
+
"uploads/pages/#{model.user_id}/#{model.id}/inline_images"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size_range
|
|
15
|
+
0..(20.megabytes)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extension_allowlist
|
|
19
|
+
%w[png jpg jpeg gif webp heic]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def quality
|
|
23
|
+
vips! do |v|
|
|
24
|
+
v.saver(Q: QUALITY)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def filename
|
|
29
|
+
generated_filename
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def generated_filename
|
|
35
|
+
@generated_filename ||= "#{SecureRandom.hex(12)}.webp"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Postnhost
|
|
2
|
+
class SettingAssetUploader < ::BaseUploader
|
|
3
|
+
QUALITY = 100
|
|
4
|
+
|
|
5
|
+
process convert: "webp"
|
|
6
|
+
process :quality
|
|
7
|
+
|
|
8
|
+
def quality
|
|
9
|
+
vips! do |v|
|
|
10
|
+
v.saver(Q: QUALITY)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def extension_allowlist
|
|
15
|
+
%w[png jpg jpeg gif webp heic]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- Favicon -->
|
|
2
|
+
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
|
3
|
+
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
|
4
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
5
|
+
<link rel="manifest" href="/site.webmanifest">
|
|
6
|
+
<meta name="theme-color" content="#ffffff">
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<footer aria-labelledby="footer-heading" class="bg-white border-t border-gray-200">
|
|
2
|
+
<h2 id="footer-heading" class="sr-only"><%= t('postnhost.public.footer.heading') %></h2>
|
|
3
|
+
|
|
4
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
5
|
+
<%# Categories %>
|
|
6
|
+
<% if @use_auto_footer_navigation && @footer_categories.any? %>
|
|
7
|
+
<div class="text-center pb-6">
|
|
8
|
+
<p class="text-sm text-gray-500 mb-4"><%= t('postnhost.public.footer.explore_categories') %></p>
|
|
9
|
+
<div class="flex flex-wrap justify-center gap-3">
|
|
10
|
+
<% @footer_categories.each do |category| %>
|
|
11
|
+
<%= link_to localized_category_name(category, @current_language),
|
|
12
|
+
language_category_path(category.slug, @current_language),
|
|
13
|
+
class: "inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-900 transition-color cursor-pointer" %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<% elsif !@use_auto_footer_navigation && @footer_navigation_columns.any? %>
|
|
18
|
+
<div class="pb-8">
|
|
19
|
+
<div class="flex flex-col gap-y-6 sm:flex-row sm:flex-wrap sm:gap-x-10 sm:gap-y-8">
|
|
20
|
+
<% @footer_navigation_columns.each do |column| %>
|
|
21
|
+
<div class="min-w-32">
|
|
22
|
+
<p class="mb-3 text-sm font-semibold text-gray-700"><%= navigation_label(column) %></p>
|
|
23
|
+
<div class="space-y-2">
|
|
24
|
+
<% column.children.each do |child| %>
|
|
25
|
+
<% if child.target_kind != "external" && navigation_href(child).present? %>
|
|
26
|
+
<%= link_to navigation_label(child), navigation_href(child), **navigation_link_options(child), class: "block text-sm text-gray-600 hover:text-gray-900 transition-colors cursor-pointer" %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% end %>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<% end %>
|
|
35
|
+
|
|
36
|
+
<div class="pt-6 border-t border-gray-100 flex flex-col sm:flex-row items-center justify-between gap-3">
|
|
37
|
+
<p class="text-xs text-gray-400 text-center sm:text-left sm:flex-1">
|
|
38
|
+
© <%= Date.current.year %> <%= t('postnhost.public.site.name_footer_copyright') %>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<% if @use_auto_footer_navigation && (@footer_published_pages.any? || @footer_static_pages.any?) %>
|
|
42
|
+
<div class="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 sm:flex-1">
|
|
43
|
+
<% @footer_published_pages.each do |page| %>
|
|
44
|
+
<%= link_to (page.title.presence || page.slug.humanize),
|
|
45
|
+
language_static_page_path(page.slug, @current_language),
|
|
46
|
+
class: "text-xs text-gray-400 hover:text-gray-600 transition-colors cursor-pointer" %>
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
49
|
+
<% @footer_static_pages.each do |slug| %>
|
|
50
|
+
<%= link_to slug.humanize,
|
|
51
|
+
language_static_page_path(slug, @current_language),
|
|
52
|
+
class: "text-xs text-gray-400 hover:text-gray-600 transition-colors cursor-pointer" %>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
55
|
+
<% else %>
|
|
56
|
+
<div class="hidden sm:block sm:flex-1"></div>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<% if @setting.show_powered_by? %>
|
|
60
|
+
<a href="https://postnhost.com" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 text-xs text-gray-400 hover:text-gray-600 transition-colors sm:flex-1 sm:justify-end cursor-pointer">
|
|
61
|
+
Powered by
|
|
62
|
+
<%= image_tag 'postnhost/logo.webp', alt: 'PostnHost', class: 'h-4 w-auto opacity-60 hover:opacity-100 transition-opacity', width: 80, height: 16 %>
|
|
63
|
+
</a>
|
|
64
|
+
<% end %>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</footer>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<nav class="bg-white border-b border-gray-200">
|
|
2
|
+
<div class="mx-auto px-6 py-2 flex items-center justify-between relative">
|
|
3
|
+
<div class="flex items-center">
|
|
4
|
+
<%= link_to root_path, class: 'flex items-center group' do %>
|
|
5
|
+
<%= image_tag 'postnhost/logo.webp',
|
|
6
|
+
alt: 'postnhost',
|
|
7
|
+
width: 160,
|
|
8
|
+
height: 33,
|
|
9
|
+
class: 'h-8 w-auto group-hover:opacity-80 transition-opacity' %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- Flash Messages - Centered on desktop, hidden on mobile (shown below header) -->
|
|
14
|
+
<div class="hidden md:block absolute left-1/2 transform -translate-x-1/2 z-10">
|
|
15
|
+
<%= render 'postnhost/shared/flash_messages' %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="flex items-center space-x-4" data-controller="burger">
|
|
19
|
+
<% if local_assigns[:layout_name] == "postnhost/articles" %>
|
|
20
|
+
<% if controller_name == "pages" && action_name != "index" %>
|
|
21
|
+
<%= link_to pages_path, class: "flex items-center space-x-2 px-3 py-2 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-200" do %>
|
|
22
|
+
<%= icon("arrow-left", css_class: "w-4 h-4") %>
|
|
23
|
+
<span class="hidden sm:inline">Back to Pages</span>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% else %>
|
|
26
|
+
<%= link_to articles_path, class: "flex items-center space-x-2 px-3 py-2 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-200" do %>
|
|
27
|
+
<%= icon("arrow-left", css_class: "w-4 h-4") %>
|
|
28
|
+
<span class="hidden sm:inline">Back to Articles</span>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<div class="relative mr-0" data-controller="dropdown">
|
|
34
|
+
<button
|
|
35
|
+
data-action="click->dropdown#toggle"
|
|
36
|
+
class="flex items-center gap-3 rounded-xl transition-colors duration-200 cursor-pointer"
|
|
37
|
+
>
|
|
38
|
+
<div class="w-9 h-9 rounded-xl bg-gray-100 flex items-center justify-center overflow-hidden border-2 border-gray-200">
|
|
39
|
+
<% if current_user&.avatar_file? %>
|
|
40
|
+
<%= image_tag current_user.avatar_file_url, class: "w-full h-full object-cover", alt: 'Author Image' %>
|
|
41
|
+
<% else %>
|
|
42
|
+
<span class="text-gray-700 font-semibold text-sm">
|
|
43
|
+
<%= author_initials(current_user) %>
|
|
44
|
+
</span>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="hidden sm:block text-left">
|
|
48
|
+
<p class="text-sm font-medium text-gray-900">
|
|
49
|
+
<%= current_user.name %>
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
<span data-dropdown-target="chevron" class="hidden sm:block">
|
|
53
|
+
<%= icon("chevron-down-small", css_class: "h-4 w-4 text-gray-400 transition-transform duration-200") %>
|
|
54
|
+
</span>
|
|
55
|
+
</button>
|
|
56
|
+
|
|
57
|
+
<div data-dropdown-target="menu" class="absolute right-0 mt-3 w-48 bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden z-20 hidden">
|
|
58
|
+
<div class="px-4 py-3 border-b border-gray-100">
|
|
59
|
+
<p class="text-sm font-medium text-gray-900"><%= current_user.name %></p>
|
|
60
|
+
<p class="text-xs text-gray-500 truncate"><%= current_user&.email %></p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="py-2">
|
|
64
|
+
<%= link_to edit_user_path(current_user), class: "flex items-center px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors duration-150" do %>
|
|
65
|
+
<%= icon("user", css_class: "mr-3 h-4 w-4 text-gray-400") %>
|
|
66
|
+
Account
|
|
67
|
+
<% end %>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="border-t border-gray-100 py-2">
|
|
71
|
+
<%= link_to session_path, data: { turbo_method: :delete }, class: "flex items-center px-4 py-3 text-sm text-red-600 hover:bg-red-50 hover:text-red-700 transition-colors duration-150" do %>
|
|
72
|
+
<%= icon("logout", css_class: "mr-3 h-4 w-4") %>
|
|
73
|
+
Sign Out
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<% unless local_assigns[:layout_name] == "postnhost/articles" %>
|
|
80
|
+
<button
|
|
81
|
+
class="block md:hidden p-2 pr-0 md:pr-2 rounded-xl hover:bg-gray-50 text-gray-400 hover:text-gray-600 transition-colors duration-200"
|
|
82
|
+
data-action="click->burger#toggleMenu"
|
|
83
|
+
>
|
|
84
|
+
<%= icon("menu", css_class: "w-5 h-5") %>
|
|
85
|
+
</button>
|
|
86
|
+
<% end %>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</nav>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<div id="menu" class="w-full md:w-64 bg-white p-4 h-full flex-shrink-0 border-r border-gray-200 flex flex-col justify-between overflow-y-auto hidden md:flex">
|
|
2
|
+
<div class="space-y-6">
|
|
3
|
+
<!-- Create Article Button -->
|
|
4
|
+
<%= link_to new_article_path, class: "w-full bg-gray-900 text-white px-4 py-2.5 rounded-xl hover:bg-gray-800 transition duration-200 text-sm font-medium inline-flex items-center justify-center space-x-2", data: { turbo_prefetch: false } do %>
|
|
5
|
+
<%= icon("plus", css_class: "w-4 h-4") %>
|
|
6
|
+
<span>Create Article</span>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
<!-- Navigation -->
|
|
10
|
+
<div class="space-y-6">
|
|
11
|
+
<div>
|
|
12
|
+
<p class="px-3 mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400">Content</p>
|
|
13
|
+
<nav class="space-y-1">
|
|
14
|
+
<%= link_to articles_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(articles_path) || controller_name == 'articles' || controller_name == 'article_variants' ? 'bg-white text-gray-900' : ''}" do %>
|
|
15
|
+
<div class="flex-shrink-0 #{current_page?(articles_path) || controller_name == 'articles' || controller_name == 'article_variants' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
16
|
+
<%= icon("document-text", css_class: "h-5 w-5") %>
|
|
17
|
+
</div>
|
|
18
|
+
<span class="text-sm font-medium">Articles</span>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<%= link_to categories_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(categories_path) || controller_name == 'categories' ? 'bg-white text-gray-900' : ''}" do %>
|
|
22
|
+
<div class="flex-shrink-0 #{current_page?(categories_path) || controller_name == 'categories' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
23
|
+
<%= icon("folder", css_class: "h-5 w-5") %>
|
|
24
|
+
</div>
|
|
25
|
+
<span class="text-sm font-medium">Categories</span>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<%= link_to languages_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(languages_path) || controller_name == 'languages' ? 'bg-white text-gray-900' : ''}" do %>
|
|
29
|
+
<div class="flex-shrink-0 #{current_page?(languages_path) || controller_name == 'languages' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
30
|
+
<%= icon("language", css_class: "h-5 w-5") %>
|
|
31
|
+
</div>
|
|
32
|
+
<span class="text-sm font-medium">Languages</span>
|
|
33
|
+
<% end %>
|
|
34
|
+
|
|
35
|
+
<%= link_to pages_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(pages_path) || controller_name == 'pages' ? 'bg-white text-gray-900' : ''}" do %>
|
|
36
|
+
<div class="flex-shrink-0 #{current_page?(pages_path) || controller_name == 'pages' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
37
|
+
<%= icon("layers", css_class: "h-5 w-5") %>
|
|
38
|
+
</div>
|
|
39
|
+
<span class="text-sm font-medium">Pages</span>
|
|
40
|
+
<% end %>
|
|
41
|
+
</nav>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="border-t border-gray-100 pt-4">
|
|
45
|
+
<p class="px-3 mb-2 text-xs font-semibold uppercase tracking-wide text-gray-400">System</p>
|
|
46
|
+
<nav class="space-y-1">
|
|
47
|
+
<%= link_to users_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(users_path) || controller_name == 'users' ? 'bg-white text-gray-900' : ''}" do %>
|
|
48
|
+
<div class="flex-shrink-0 #{current_page?(users_path) || controller_name == 'users' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
49
|
+
<%= icon("users", css_class: "h-5 w-5") %>
|
|
50
|
+
</div>
|
|
51
|
+
<span class="text-sm font-medium">Authors</span>
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
54
|
+
<%= link_to edit_settings_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(edit_settings_path) || controller_name == 'settings' ? 'bg-white text-gray-900' : ''}" do %>
|
|
55
|
+
<div class="flex-shrink-0 #{current_page?(edit_settings_path) || controller_name == 'settings' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
56
|
+
<%= icon("settings", css_class: "h-5 w-5") %>
|
|
57
|
+
</div>
|
|
58
|
+
<span class="text-sm font-medium">Settings</span>
|
|
59
|
+
<% end %>
|
|
60
|
+
|
|
61
|
+
<%= link_to edit_template_path, class: "group flex items-center space-x-3 text-gray-700 hover:text-gray-900 hover:bg-white font-medium px-3 py-2.5 rounded-xl transition-all duration-200 #{current_page?(edit_template_path) || controller_name == 'templates' ? 'bg-white text-gray-900' : ''}" do %>
|
|
62
|
+
<div class="flex-shrink-0 #{current_page?(edit_template_path) || controller_name == 'templates' ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-700'}">
|
|
63
|
+
<%= icon("eye", css_class: "h-5 w-5") %>
|
|
64
|
+
</div>
|
|
65
|
+
<span class="text-sm font-medium">Template</span>
|
|
66
|
+
<% end %>
|
|
67
|
+
|
|
68
|
+
</nav>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= content_for?(:lang) ? yield(:lang) : 'en' %>" data-postnhost>
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= content_for?(:title) ? yield(:title) : "postnhost" %></title>
|
|
5
|
+
<meta name="description" content="<%= content_for?(:meta_description) ? yield(:meta_description) : "postnhost" %>">
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
|
+
<% if content_for?(:robots) %>
|
|
8
|
+
<meta name="robots" content="<%= yield(:robots) %>">
|
|
9
|
+
<% end %>
|
|
10
|
+
<%= csrf_meta_tags %>
|
|
11
|
+
<%= csp_meta_tag %>
|
|
12
|
+
|
|
13
|
+
<%= stylesheet_link_tag "postnhost/application", "data-turbo-track": "reload", media: "all" %>
|
|
14
|
+
<%= javascript_include_tag "postnhost/application", "data-turbo-track": "reload", type: "module" %>
|
|
15
|
+
|
|
16
|
+
<%= render "layouts/postnhost/favicon" %>
|
|
17
|
+
|
|
18
|
+
<%= yield :head %>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<%= yield %>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= content_for?(:lang) ? yield(:lang) : 'en' %>" data-postnhost>
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= content_for?(:title) ? yield(:title) : "postnhost" %></title>
|
|
5
|
+
<meta name="description" content="<%= content_for?(:meta_description) ? yield(:meta_description) : "postnhost dashboard - Manage your account and projects" %>">
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
<%= csp_meta_tag %>
|
|
9
|
+
|
|
10
|
+
<%= stylesheet_link_tag "postnhost/application", "data-turbo-track": "reload" %>
|
|
11
|
+
<%= javascript_include_tag 'postnhost/application', 'data-turbo-track': 'reload', type: "module" %>
|
|
12
|
+
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
|
|
13
|
+
|
|
14
|
+
<%= render 'layouts/postnhost/favicon' %>
|
|
15
|
+
|
|
16
|
+
<%= yield :head %>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body class="min-h-screen bg-white">
|
|
20
|
+
<%= render 'layouts/postnhost/header', layout_name: 'postnhost/articles' %>
|
|
21
|
+
|
|
22
|
+
<!-- Flash Messages for mobile - shown below header -->
|
|
23
|
+
<div class="md:hidden px-6">
|
|
24
|
+
<%= render 'postnhost/shared/flash_messages' %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Main content area -->
|
|
28
|
+
<main class="flex-grow w-full">
|
|
29
|
+
<div class="w-full">
|
|
30
|
+
<%= yield %>
|
|
31
|
+
</div>
|
|
32
|
+
</main>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= content_for?(:lang) ? yield(:lang) : 'en' %>" data-postnhost>
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= content_for?(:title) ? yield(:title) : "postnhost" %></title>
|
|
5
|
+
<meta name="description" content="<%= content_for?(:meta_description) ? yield(:meta_description) : "postnhost dashboard - Manage your account and projects" %>">
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
<%= csp_meta_tag %>
|
|
9
|
+
|
|
10
|
+
<%= stylesheet_link_tag "postnhost/application", "data-turbo-track": "reload" %>
|
|
11
|
+
<%= javascript_include_tag 'postnhost/application', 'data-turbo-track': 'reload', type: "module" %>
|
|
12
|
+
|
|
13
|
+
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
|
|
14
|
+
|
|
15
|
+
<%= render 'layouts/postnhost/favicon' %>
|
|
16
|
+
|
|
17
|
+
<%= yield :head %>
|
|
18
|
+
</head>
|
|
19
|
+
|
|
20
|
+
<body class="h-screen flex flex-col">
|
|
21
|
+
<%= render 'layouts/postnhost/header' %>
|
|
22
|
+
|
|
23
|
+
<div class="flex-grow flex overflow-hidden">
|
|
24
|
+
<%= render 'layouts/postnhost/sidebar' %>
|
|
25
|
+
|
|
26
|
+
<!-- Main content area -->
|
|
27
|
+
<div class="flex-grow overflow-y-auto">
|
|
28
|
+
<div class="min-h-screen bg-white">
|
|
29
|
+
<div class="max-w-6xl mx-auto py-6 px-6">
|
|
30
|
+
<%= yield %>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|