open_blog 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/app/assets/builds/open_blog/blog.css +2 -0
- data/app/assets/javascripts/open_blog/controllers/code_copy_controller.js +40 -0
- data/app/assets/javascripts/open_blog/controllers/reading_progress_controller.js +36 -0
- data/app/assets/javascripts/open_blog/controllers/search_controller.js +49 -0
- data/app/assets/javascripts/open_blog/controllers/share_controller.js +48 -0
- data/app/assets/javascripts/open_blog/controllers/theme_controller.js +51 -0
- data/app/assets/javascripts/open_blog/controllers/toc_controller.js +33 -0
- data/app/controllers/concerns/open_blog/reader_controller.rb +49 -0
- data/app/controllers/open_blog/api/v1/adoptions_controller.rb +27 -0
- data/app/controllers/open_blog/api/v1/approvals_controller.rb +15 -0
- data/app/controllers/open_blog/api/v1/authors_controller.rb +62 -0
- data/app/controllers/open_blog/api/v1/base_controller.rb +100 -0
- data/app/controllers/open_blog/api/v1/categories_controller.rb +33 -0
- data/app/controllers/open_blog/api/v1/connections_controller.rb +45 -0
- data/app/controllers/open_blog/api/v1/doctor_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/faq_extractions_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/findings_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/images_controller.rb +22 -0
- data/app/controllers/open_blog/api/v1/pages_controller.rb +67 -0
- data/app/controllers/open_blog/api/v1/posts_controller.rb +114 -0
- data/app/controllers/open_blog/api/v1/previews_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/records_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/redirects_controller.rb +51 -0
- data/app/controllers/open_blog/api/v1/report_controller.rb +20 -0
- data/app/controllers/open_blog/api/v1/resources_controller.rb +39 -0
- data/app/controllers/open_blog/api/v1/series_controller.rb +32 -0
- data/app/controllers/open_blog/api/v1/tags_controller.rb +12 -0
- data/app/controllers/open_blog/api/v1/views_controller.rb +19 -0
- data/app/controllers/open_blog/application_controller.rb +5 -0
- data/app/controllers/open_blog/authors_controller.rb +14 -0
- data/app/controllers/open_blog/categories_controller.rb +15 -0
- data/app/controllers/open_blog/errors_controller.rb +7 -0
- data/app/controllers/open_blog/feeds_controller.rb +22 -0
- data/app/controllers/open_blog/mcp_controller.rb +38 -0
- data/app/controllers/open_blog/media_controller.rb +20 -0
- data/app/controllers/open_blog/pages_controller.rb +12 -0
- data/app/controllers/open_blog/posts_controller.rb +57 -0
- data/app/controllers/open_blog/previews_controller.rb +35 -0
- data/app/controllers/open_blog/search_controller.rb +32 -0
- data/app/controllers/open_blog/series_controller.rb +15 -0
- data/app/controllers/open_blog/sitemaps_controller.rb +10 -0
- data/app/controllers/open_blog/tags_controller.rb +15 -0
- data/app/helpers/open_blog/content_helper.rb +79 -0
- data/app/helpers/open_blog/dates_helper.rb +18 -0
- data/app/helpers/open_blog/faq_helper.rb +59 -0
- data/app/helpers/open_blog/head_helper.rb +75 -0
- data/app/helpers/open_blog/icons_helper.rb +10 -0
- data/app/helpers/open_blog/notices_helper.rb +47 -0
- data/app/helpers/open_blog/pagination_helper.rb +18 -0
- data/app/helpers/open_blog/structured_data_helper.rb +63 -0
- data/app/helpers/open_blog/theme_helper.rb +52 -0
- data/app/helpers/open_blog/url_helper.rb +58 -0
- data/app/jobs/open_blog/publish_scheduled_post_job.rb +14 -0
- data/app/models/concerns/open_blog/immutable.rb +13 -0
- data/app/models/concerns/open_blog/immutable_image_attachment.rb +43 -0
- data/app/models/concerns/open_blog/previewable.rb +38 -0
- data/app/models/open_blog/api_token.rb +34 -0
- data/app/models/open_blog/application_record.rb +5 -0
- data/app/models/open_blog/approval.rb +19 -0
- data/app/models/open_blog/author.rb +11 -0
- data/app/models/open_blog/baseline.rb +26 -0
- data/app/models/open_blog/category.rb +10 -0
- data/app/models/open_blog/connection_declaration.rb +16 -0
- data/app/models/open_blog/faq.rb +22 -0
- data/app/models/open_blog/image.rb +45 -0
- data/app/models/open_blog/page.rb +23 -0
- data/app/models/open_blog/page_view.rb +7 -0
- data/app/models/open_blog/post.rb +132 -0
- data/app/models/open_blog/publication.rb +17 -0
- data/app/models/open_blog/redirect.rb +16 -0
- data/app/models/open_blog/revision.rb +12 -0
- data/app/models/open_blog/series.rb +10 -0
- data/app/models/open_blog/tag.rb +11 -0
- data/app/models/open_blog/tagging.rb +7 -0
- data/app/queries/open_blog/api_post_query.rb +54 -0
- data/app/serializers/open_blog/adoption_result_serializer.rb +9 -0
- data/app/serializers/open_blog/author_serializer.rb +10 -0
- data/app/serializers/open_blog/category_serializer.rb +7 -0
- data/app/serializers/open_blog/doctor_serializer.rb +7 -0
- data/app/serializers/open_blog/error_serializer.rb +7 -0
- data/app/serializers/open_blog/extraction_result_serializer.rb +7 -0
- data/app/serializers/open_blog/findings_serializer.rb +13 -0
- data/app/serializers/open_blog/image_serializer.rb +9 -0
- data/app/serializers/open_blog/page_serializer.rb +9 -0
- data/app/serializers/open_blog/post_serializer.rb +59 -0
- data/app/serializers/open_blog/preview_serializer.rb +11 -0
- data/app/serializers/open_blog/records_serializer.rb +28 -0
- data/app/serializers/open_blog/redirect_serializer.rb +7 -0
- data/app/serializers/open_blog/series_serializer.rb +7 -0
- data/app/serializers/open_blog/tag_serializer.rb +7 -0
- data/app/serializers/open_blog/write_result_serializer.rb +9 -0
- data/app/views/open_blog/attachments/_blob.html.erb +1 -0
- data/app/views/open_blog/feeds/atom.xml.builder +27 -0
- data/app/views/open_blog/sitemaps/show.xml.builder +9 -0
- data/config/locales/open_blog.en.yml +95 -0
- data/config/routes.rb +52 -0
- data/db/migrate/20261003012512_create_open_blog_authors.rb +16 -0
- data/db/migrate/20261003012513_create_open_blog_categories.rb +13 -0
- data/db/migrate/20261003012514_create_open_blog_series.rb +11 -0
- data/db/migrate/20261003012515_create_open_blog_images.rb +17 -0
- data/db/migrate/20261003012516_create_open_blog_posts.rb +47 -0
- data/db/migrate/20261003012517_create_open_blog_faqs.rb +13 -0
- data/db/migrate/20261003012518_create_open_blog_tags.rb +17 -0
- data/db/migrate/20261003012519_create_open_blog_redirects.rb +14 -0
- data/db/migrate/20261003012520_create_open_blog_revisions.rb +14 -0
- data/db/migrate/20261003012521_create_open_blog_approvals.rb +21 -0
- data/db/migrate/20261003012522_create_open_blog_publications.rb +18 -0
- data/db/migrate/20261003012523_create_open_blog_baselines.rb +24 -0
- data/db/migrate/20261003012524_create_open_blog_connection_declarations.rb +13 -0
- data/db/migrate/20261003031410_add_body_image_manifest_to_open_blog_posts.rb +7 -0
- data/db/migrate/20261003164418_create_open_blog_api_tokens.rb +15 -0
- data/db/migrate/20261003175454_create_open_blog_pages.rb +16 -0
- data/db/migrate/20261003182919_create_open_blog_page_views.rb +11 -0
- data/lib/generators/open_blog/admin_suite/admin_suite_generator.rb +27 -0
- data/lib/generators/open_blog/admin_suite/templates/author_resource.rb.tt +29 -0
- data/lib/generators/open_blog/admin_suite/templates/blog_portal.rb.tt +16 -0
- data/lib/generators/open_blog/admin_suite/templates/category_resource.rb.tt +28 -0
- data/lib/generators/open_blog/admin_suite/templates/initializer.rb.tt +19 -0
- data/lib/generators/open_blog/admin_suite/templates/page_resource.rb.tt +32 -0
- data/lib/generators/open_blog/admin_suite/templates/post_resource.rb.tt +58 -0
- data/lib/generators/open_blog/generator_support.rb +64 -0
- data/lib/generators/open_blog/install/install_generator.rb +188 -0
- data/lib/generators/open_blog/install/templates/initializer.rb.tt +16 -0
- data/lib/generators/open_blog/install/templates/sample_cover.png +0 -0
- data/lib/generators/open_blog/install/templates/sample_post.md +54 -0
- data/lib/generators/open_blog/install/templates/theme/blog.css +193 -0
- data/lib/generators/open_blog/install/templates/theme/open_blog_theme.css +82 -0
- data/lib/generators/open_blog/install/templates/theme/syntax.css +358 -0
- data/lib/generators/open_blog/install/templates/theme/theme.css +120 -0
- data/lib/generators/open_blog/install/templates/views/layouts/open_blog.html.erb +16 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/authors/show.html.erb +17 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/categories/show.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/errors/not_found.html.erb +11 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/errors/preview_expired.html.erb +5 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_check.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_copy.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_link.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_monitor.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_moon.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_share.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_sun.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/pages/show.html.erb +5 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_faq.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_highlights.html.erb +7 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_preview_bar.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_series_nav.html.erb +12 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_tags.html.erb +5 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_toc.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/index.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/show.html.erb +29 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/search/show.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/series/show.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_author_box.html.erb +6 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_breadcrumbs.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_call_to_action.html.erb +7 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_featured_card.html.erb +11 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_footer.html.erb +11 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_header.html.erb +7 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_pagination.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_post_card.html.erb +9 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_related_posts.html.erb +10 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_search.html.erb +9 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_share.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_sidebar.html.erb +38 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_theme_toggle.html.erb +10 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/tags/show.html.erb +13 -0
- data/lib/generators/open_blog/views/views_generator.rb +21 -0
- data/lib/open_blog/actor.rb +7 -0
- data/lib/open_blog/admin_suite.rb +73 -0
- data/lib/open_blog/adopt/contract.rb +152 -0
- data/lib/open_blog/adopt/snapshot.rb +35 -0
- data/lib/open_blog/adopt.rb +159 -0
- data/lib/open_blog/api_fields.rb +40 -0
- data/lib/open_blog/approve.rb +18 -0
- data/lib/open_blog/authentication.rb +18 -0
- data/lib/open_blog/build_css.rb +53 -0
- data/lib/open_blog/configuration.rb +185 -0
- data/lib/open_blog/configuration_error.rb +4 -0
- data/lib/open_blog/content_guard.rb +77 -0
- data/lib/open_blog/doctor/files.rb +61 -0
- data/lib/open_blog/doctor/javascript.rb +57 -0
- data/lib/open_blog/doctor.rb +177 -0
- data/lib/open_blog/engine.rb +45 -0
- data/lib/open_blog/errors.rb +40 -0
- data/lib/open_blog/faq_extraction.rb +184 -0
- data/lib/open_blog/findings/body_checks.rb +58 -0
- data/lib/open_blog/findings/check.rb +19 -0
- data/lib/open_blog/findings/link_check.rb +78 -0
- data/lib/open_blog/findings/record_checks.rb +94 -0
- data/lib/open_blog/findings/registry.rb +18 -0
- data/lib/open_blog/findings.rb +27 -0
- data/lib/open_blog/image_fetch.rb +115 -0
- data/lib/open_blog/image_import.rb +164 -0
- data/lib/open_blog/image_resolution.rb +171 -0
- data/lib/open_blog/instructions.rb +7 -0
- data/lib/open_blog/json_feed.rb +36 -0
- data/lib/open_blog/label_policy.rb +41 -0
- data/lib/open_blog/markdown_view.rb +57 -0
- data/lib/open_blog/mcp/definition.rb +28 -0
- data/lib/open_blog/mcp/dispatcher.rb +33 -0
- data/lib/open_blog/mcp/image_input.rb +30 -0
- data/lib/open_blog/mcp/registry.rb +139 -0
- data/lib/open_blog/mcp/schemas.rb +72 -0
- data/lib/open_blog/mcp.rb +24 -0
- data/lib/open_blog/not_found.rb +4 -0
- data/lib/open_blog/operation.rb +36 -0
- data/lib/open_blog/page_views.rb +72 -0
- data/lib/open_blog/pagination.rb +18 -0
- data/lib/open_blog/plain_text.rb +36 -0
- data/lib/open_blog/policy_url.rb +9 -0
- data/lib/open_blog/post_attributes.rb +172 -0
- data/lib/open_blog/post_identity.rb +28 -0
- data/lib/open_blog/publish.rb +7 -0
- data/lib/open_blog/reader_dates.rb +21 -0
- data/lib/open_blog/reader_page.rb +21 -0
- data/lib/open_blog/reader_queries.rb +90 -0
- data/lib/open_blog/record_release.rb +66 -0
- data/lib/open_blog/redirect_target.rb +58 -0
- data/lib/open_blog/remove.rb +43 -0
- data/lib/open_blog/renderer/highlighter.rb +44 -0
- data/lib/open_blog/renderer/markdown.rb +14 -0
- data/lib/open_blog/renderer/rich_text.rb +19 -0
- data/lib/open_blog/renderer/sanitizer.rb +72 -0
- data/lib/open_blog/renderer/scrubber.rb +35 -0
- data/lib/open_blog/renderer/transform.rb +115 -0
- data/lib/open_blog/renderer.rb +51 -0
- data/lib/open_blog/result.rb +11 -0
- data/lib/open_blog/revision_payload.rb +67 -0
- data/lib/open_blog/rich_text_guard.rb +26 -0
- data/lib/open_blog/sample.rb +56 -0
- data/lib/open_blog/save_draft.rb +7 -0
- data/lib/open_blog/search.rb +21 -0
- data/lib/open_blog/sitemap_entries.rb +66 -0
- data/lib/open_blog/surface_report/expected_content.rb +27 -0
- data/lib/open_blog/surface_report/http.rb +92 -0
- data/lib/open_blog/surface_report/page_checks.rb +303 -0
- data/lib/open_blog/surface_report/reach_checks.rb +141 -0
- data/lib/open_blog/surface_report/record_checks.rb +248 -0
- data/lib/open_blog/surface_report/robots.rb +49 -0
- data/lib/open_blog/surface_report.rb +223 -0
- data/lib/open_blog/syntax_css.rb +55 -0
- data/lib/open_blog/unpublish.rb +13 -0
- data/lib/open_blog/version.rb +3 -0
- data/lib/open_blog/write_post.rb +205 -0
- data/lib/open_blog.rb +91 -0
- data/lib/tasks/open_blog.rake +71 -0
- data/skills/open-blog-adopt/SKILL.md +12 -0
- data/skills/open-blog-install/SKILL.md +10 -0
- data/skills/open-blog-policy-pages/SKILL.md +10 -0
- data/skills/open-blog-publish/SKILL.md +14 -0
- data/skills/open-blog-report/SKILL.md +10 -0
- data/skills/open-blog-update/SKILL.md +16 -0
- metadata +515 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "bundler"
|
|
3
|
+
require_relative "../generator_support"
|
|
4
|
+
|
|
5
|
+
module OpenBlog
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
include GeneratorSupport
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
source_paths << OpenBlog::Engine.root.join("app/assets/javascripts").to_s
|
|
11
|
+
|
|
12
|
+
class_option :skip_tailwind, type: :boolean, default: false
|
|
13
|
+
class_option :body_format, type: :string, default: "markdown"
|
|
14
|
+
class_option :mount_at, type: :string, default: "/blog"
|
|
15
|
+
class_option :mount_position, type: :string, default: "last"
|
|
16
|
+
class_option :site_name, type: :string
|
|
17
|
+
class_option :author_name, type: :string
|
|
18
|
+
class_option :skip_sample, type: :boolean, default: false
|
|
19
|
+
class_option :skip_migrate, type: :boolean, default: false
|
|
20
|
+
class_option :admin_suite, type: :boolean, default: false
|
|
21
|
+
|
|
22
|
+
def detect
|
|
23
|
+
unless %w[markdown rich_text both].include?(options[:body_format])
|
|
24
|
+
raise Thor::Error, "--body-format must be markdown, rich_text or both"
|
|
25
|
+
end
|
|
26
|
+
unless %w[first last].include?(options[:mount_position])
|
|
27
|
+
raise Thor::Error, "--mount-position must be first or last"
|
|
28
|
+
end
|
|
29
|
+
unless options[:mount_at].match?(%r{\A/(?:[a-zA-Z0-9_-]+(?:/[a-zA-Z0-9_-]+)*)?\z})
|
|
30
|
+
raise Thor::Error, "--mount-at must be an absolute path without a query, fragment or trailing slash"
|
|
31
|
+
end
|
|
32
|
+
@javascript = javascript_setup
|
|
33
|
+
@css = css_setup
|
|
34
|
+
@install_stimulus = !File.file?(host_file("app/javascript/controllers/index.js"))
|
|
35
|
+
@install_tailwind = @css == :none
|
|
36
|
+
say "JavaScript: #{@javascript}; stylesheets: #{@css}"
|
|
37
|
+
say "Tailwind 3 detected; using the built blog stylesheet." if @css == :legacy
|
|
38
|
+
if @install_tailwind
|
|
39
|
+
say "Tailwind's installer changes the host layout, bin/dev and Procfile.dev. Use --skip-tailwind to keep your existing CSS setup."
|
|
40
|
+
end
|
|
41
|
+
say "The JavaScript installers may update the host entrypoint and controller manifest." if @install_stimulus || @javascript != :importmap
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def configure_blog
|
|
45
|
+
@site_name = identity(:site_name, "Site name", "Example")
|
|
46
|
+
@author_name = identity(:author_name, "Author name", "Ada Example")
|
|
47
|
+
template "initializer.rb.tt", "config/initializers/open_blog.rb", **copy_options
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def install_dependencies
|
|
51
|
+
processor = (Rails.application.config.active_storage.variant_processor || :vips).to_sym
|
|
52
|
+
backend = { vips: "ruby-vips", mini_magick: "mini_magick" }.fetch(processor) do
|
|
53
|
+
raise Thor::Error, "Unsupported Active Storage variant processor: #{processor}"
|
|
54
|
+
end
|
|
55
|
+
additions = [ "stimulus-rails", "image_processing", backend ]
|
|
56
|
+
additions << "importmap-rails" if @javascript == :none
|
|
57
|
+
additions << "tailwindcss-rails" if @install_tailwind
|
|
58
|
+
added = additions.reject { |name| host_read("Gemfile").match?(/^\s*gem\s+["']#{Regexp.escape(name)}["']/) }
|
|
59
|
+
added.each { |name| gem(name, *(name == "tailwindcss-rails" ? [ "~> 4.0" ] : [])) }
|
|
60
|
+
run_host_command("bundle", "install") if added.any?
|
|
61
|
+
if @javascript == :none
|
|
62
|
+
run_host_command("bin/rails", "importmap:install")
|
|
63
|
+
@javascript = :importmap
|
|
64
|
+
end
|
|
65
|
+
run_host_command("bin/rails", "stimulus:install") if @install_stimulus
|
|
66
|
+
if @install_tailwind
|
|
67
|
+
run_host_command("bin/rails", "tailwindcss:install")
|
|
68
|
+
@css = :rails
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def install_migrations
|
|
73
|
+
run_host_command("bin/rails", "railties:install:migrations", "FROM=active_storage,action_text,open_blog")
|
|
74
|
+
run_host_command("bin/rails", "db:migrate") unless options[:skip_migrate]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def mount_engine
|
|
78
|
+
return if host_read("config/routes.rb").match?(/^\s*mount\s+OpenBlog::Engine\b/)
|
|
79
|
+
declaration = " mount OpenBlog::Engine, at: #{options[:mount_at].dump}\n"
|
|
80
|
+
if options[:mount_position] == "first"
|
|
81
|
+
unless host_read("config/routes.rb").match?(/Rails\.application\.routes\.draw do\s*\n/)
|
|
82
|
+
raise Thor::Error, "Cannot locate the routes draw block; mount OpenBlog::Engine manually."
|
|
83
|
+
end
|
|
84
|
+
inject_into_file "config/routes.rb", declaration, after: /Rails\.application\.routes\.draw do\s*\n/
|
|
85
|
+
else
|
|
86
|
+
unless host_read("config/routes.rb").match?(/^end\s*\z/)
|
|
87
|
+
raise Thor::Error, "Cannot locate the routes draw block; mount OpenBlog::Engine manually."
|
|
88
|
+
end
|
|
89
|
+
inject_into_file "config/routes.rb", declaration, before: /^end\s*\z/
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def install_views
|
|
94
|
+
copy_reader_views(css: @css, javascript: @javascript)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def install_theme
|
|
98
|
+
if %i[rails bundler].include?(@css)
|
|
99
|
+
%w[theme.css blog.css syntax.css].each do |name|
|
|
100
|
+
copy_file "theme/#{name}", "app/assets/tailwind/open_blog/#{name}", **copy_options
|
|
101
|
+
end
|
|
102
|
+
entry, import = if @css == :rails
|
|
103
|
+
[ "app/assets/tailwind/application.css", '@import "./open_blog/theme.css";' ]
|
|
104
|
+
else
|
|
105
|
+
[ "app/assets/stylesheets/application.tailwind.css", '@import "../tailwind/open_blog/theme.css";' ]
|
|
106
|
+
end
|
|
107
|
+
append_once(entry, "\n#{import}\n")
|
|
108
|
+
else
|
|
109
|
+
copy_file "theme/open_blog_theme.css", "app/assets/stylesheets/open_blog_theme.css", **copy_options
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def install_controllers
|
|
114
|
+
directory "open_blog/controllers", "app/javascript/controllers/open_blog", **copy_options
|
|
115
|
+
if @javascript == :bundler
|
|
116
|
+
say "Regenerating the host Stimulus controller manifest."
|
|
117
|
+
run_host_command("bin/rails", "stimulus:manifest:update")
|
|
118
|
+
else
|
|
119
|
+
append_once "config/importmap.rb", "\npin_all_from \"app/javascript/controllers\", under: \"controllers\"\n"
|
|
120
|
+
index = "app/javascript/controllers/index.js"
|
|
121
|
+
unless host_read(index).match?(/(?:eager|lazy)LoadControllersFrom\s*\(/)
|
|
122
|
+
controller_names.each do |name|
|
|
123
|
+
identifier = "open-blog--#{name.tr('_', '-')}"
|
|
124
|
+
next if host_read(index).match?(/application\.register\(\s*["']#{Regexp.escape(identifier)}["']/)
|
|
125
|
+
variable = "OpenBlog#{name.camelize}Controller"
|
|
126
|
+
append_to_file index, "\nimport #{variable} from \"controllers/open_blog/#{name}_controller\"\napplication.register(#{identifier.dump}, #{variable})\n"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
say "Blog JavaScript entrypoint: application. Register these controllers there if you use another public entrypoint."
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def install_text
|
|
134
|
+
copy_file OpenBlog::Engine.root.join("config/locales/open_blog.en.yml").to_s, "config/locales/open_blog.en.yml", **copy_options
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def sample_and_report
|
|
138
|
+
if options[:skip_migrate]
|
|
139
|
+
say "Sample deferred: run db:migrate, then open_blog:sample when ready."
|
|
140
|
+
elsif !options[:skip_sample]
|
|
141
|
+
run_host_command("bin/rails", "open_blog:sample")
|
|
142
|
+
end
|
|
143
|
+
say "Blog: #{options[:mount_at]}"
|
|
144
|
+
if options[:skip_migrate]
|
|
145
|
+
say "API token deferred: run db:migrate, then open_blog:install_token."
|
|
146
|
+
else
|
|
147
|
+
run_host_command("bin/rails", "open_blog:install_token")
|
|
148
|
+
end
|
|
149
|
+
say "Publishing API: #{options[:mount_at].chomp('/')}/api/v1"
|
|
150
|
+
say "MCP: #{options[:mount_at].chomp('/')}/mcp"
|
|
151
|
+
invoke "open_blog:admin_suite", [], force: options[:force], skip: options[:skip] if options[:admin_suite]
|
|
152
|
+
say "Next: set your public base URL, review publisher details and policy links, and customize the theme."
|
|
153
|
+
run_host_command("bin/rails", "open_blog:doctor")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def run_host_command(*arguments)
|
|
159
|
+
say_status :run, arguments.join(" ")
|
|
160
|
+
return true if options[:pretend]
|
|
161
|
+
success = Bundler.with_unbundled_env { system(*arguments, chdir: destination_root) }
|
|
162
|
+
if !success && arguments == [ "bin/rails", "open_blog:doctor" ]
|
|
163
|
+
say "Doctor found setup issues; review the report above and run open_blog:doctor after completing setup."
|
|
164
|
+
elsif !success
|
|
165
|
+
raise Thor::Error, "Host command failed: #{arguments.join(' ')}"
|
|
166
|
+
end
|
|
167
|
+
success
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def identity(key, prompt, placeholder)
|
|
171
|
+
return options[key] if options[key].present?
|
|
172
|
+
return ask("#{prompt}:", default: placeholder) if $stdin.tty?
|
|
173
|
+
say "Using placeholder #{prompt.downcase} #{placeholder.inspect}; update config/initializers/open_blog.rb."
|
|
174
|
+
placeholder
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def controller_names
|
|
178
|
+
Dir[OpenBlog::Engine.root.join("app/assets/javascripts/open_blog/controllers/*_controller.js")].map do |path|
|
|
179
|
+
File.basename(path, "_controller.js")
|
|
180
|
+
end.sort
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def body_formats
|
|
184
|
+
options[:body_format] == "both" ? %i[markdown rich_text] : [ options[:body_format].to_sym ]
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
OpenBlog.configure do |config|
|
|
2
|
+
config.site_name = <%= @site_name.dump %>
|
|
3
|
+
config.public_base_url = ENV["OPEN_BLOG_PUBLIC_BASE_URL"].presence
|
|
4
|
+
config.default_author = { name: <%= @author_name.dump %>, type: :person }
|
|
5
|
+
config.publisher = { name: <%= @site_name.dump %>, url: ENV["OPEN_BLOG_PUBLIC_BASE_URL"].presence }
|
|
6
|
+
config.mount_path = <%= options[:mount_at].dump %>
|
|
7
|
+
config.body_formats = [ <%= body_formats.map { |format| ":#{format}" }.join(", ") %> ]
|
|
8
|
+
config.default_body_format = <%= ":#{body_formats.first}" %>
|
|
9
|
+
config.primary_list_type = :categories
|
|
10
|
+
config.sign_in_destinations = []
|
|
11
|
+
config.ai_label = :when_required
|
|
12
|
+
end
|
|
13
|
+
<% if @css == :bundler -%>
|
|
14
|
+
|
|
15
|
+
Rails.application.config.assets.excluded_paths << Rails.root.join("app/assets/tailwind")
|
|
16
|
+
<% end -%>
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
## Make yourself at home
|
|
2
|
+
|
|
3
|
+
This sample is a place to try **bold ideas**, *quiet emphasis* and ~~an earlier draft~~. An article can be short and still be useful.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
### A few things to try
|
|
8
|
+
|
|
9
|
+
- Give your article a clear title.
|
|
10
|
+
- Add an image with a useful description.
|
|
11
|
+
- Keep paragraphs easy to read.
|
|
12
|
+
|
|
13
|
+
1. Write a first draft.
|
|
14
|
+
2. Read it aloud.
|
|
15
|
+
3. Share it when you are ready.
|
|
16
|
+
|
|
17
|
+
- [x] Start with a sample.
|
|
18
|
+
- [ ] Write your next article.
|
|
19
|
+
|
|
20
|
+
> A small observation can be the beginning of a good story.
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> This article is sample content. You can change or remove it.
|
|
24
|
+
|
|
25
|
+
## Show an example
|
|
26
|
+
|
|
27
|
+
Inline code such as `puts "hello"` fits naturally into a sentence. Longer examples have their own space:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
def greeting(name)
|
|
31
|
+
"Hello, #{name}!"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
puts greeting("reader")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Part | Purpose |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| Heading | Introduce the next idea |
|
|
40
|
+
| Image | Show a useful detail |
|
|
41
|
+
| Table | Compare a few related items |
|
|
42
|
+
|
|
43
|
+
Visit [Ruby's website](https://www.ruby-lang.org/) for more examples. A plain address such as https://www.ruby-lang.org/ is also a link.
|
|
44
|
+
|
|
45
|
+
## Leave room for the reader
|
|
46
|
+
|
|
47
|
+
An article can include a reference without interrupting the main paragraph.[^note]
|
|
48
|
+
|
|
49
|
+
The questions below are stored separately from the article body, so they stay easy to read and update.
|
|
50
|
+
|
|
51
|
+
One line can lead to the next.
|
|
52
|
+
This line demonstrates a paragraph with a line break.
|
|
53
|
+
|
|
54
|
+
[^note]: This is an example footnote with a short explanation.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
.ob-page {
|
|
2
|
+
margin: 0;
|
|
3
|
+
background: var(--ob-surface);
|
|
4
|
+
color: var(--ob-text);
|
|
5
|
+
font-family: var(--ob-font-body);
|
|
6
|
+
line-height: 1.6;
|
|
7
|
+
overflow-wrap: anywhere;
|
|
8
|
+
}
|
|
9
|
+
.ob-page *, .ob-page *::before, .ob-page *::after { box-sizing: border-box; }
|
|
10
|
+
.ob-page [hidden] { display: none !important; }
|
|
11
|
+
.ob-page a { color: var(--ob-accent); text-underline-offset: 0.2em; }
|
|
12
|
+
.ob-page a:hover { color: var(--ob-accent-hover); }
|
|
13
|
+
.ob-page a:focus-visible, .ob-page button:focus-visible, .ob-page input:focus-visible,
|
|
14
|
+
.ob-page .ob-table:focus-visible, .ob-page .ob-prose pre:focus-visible {
|
|
15
|
+
outline: 3px solid var(--ob-accent);
|
|
16
|
+
outline-offset: 4px;
|
|
17
|
+
}
|
|
18
|
+
.ob-page h1, .ob-page h2, .ob-page h3, .ob-page h4, .ob-page h5, .ob-page h6 {
|
|
19
|
+
color: var(--ob-heading);
|
|
20
|
+
font-family: var(--ob-font-display);
|
|
21
|
+
font-weight: 750;
|
|
22
|
+
line-height: 1.25;
|
|
23
|
+
text-wrap: balance;
|
|
24
|
+
}
|
|
25
|
+
.ob-page h1 { font-size: clamp(1.875rem, 4vw, 3rem); letter-spacing: -0.035em; }
|
|
26
|
+
.ob-page h2 { font-size: 1.5rem; }
|
|
27
|
+
.ob-page h3 { font-size: 1.25rem; }
|
|
28
|
+
.ob-page h4, .ob-page h5, .ob-page h6 { font-size: 1.125rem; }
|
|
29
|
+
.ob-page img { max-width: 100%; height: auto; }
|
|
30
|
+
.ob-page button { cursor: pointer; font: inherit; }
|
|
31
|
+
.ob-page button:disabled { cursor: default; }
|
|
32
|
+
.ob-main { max-width: var(--ob-page-width); margin: auto; padding: 3rem 1rem; }
|
|
33
|
+
.ob-header, .ob-footer {
|
|
34
|
+
max-width: var(--ob-page-width);
|
|
35
|
+
margin: auto;
|
|
36
|
+
padding: 1.25rem 1rem;
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-wrap: wrap;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 1rem;
|
|
41
|
+
}
|
|
42
|
+
.ob-header { border-bottom: 1px solid var(--ob-border); }
|
|
43
|
+
.ob-header nav { margin-left: auto; }
|
|
44
|
+
.ob-site-name { font-size: 1.125rem; font-weight: 800; text-decoration: none; }
|
|
45
|
+
.ob-footer { border-top: 1px solid var(--ob-border); font-size: 0.875rem; }
|
|
46
|
+
.ob-footer nav { display: flex; flex-wrap: wrap; gap: 1rem; }
|
|
47
|
+
.ob-index-heading, .ob-list-heading { margin-bottom: 1.5rem; }
|
|
48
|
+
.ob-index-heading h1 { font-size: 2.25rem; font-weight: 800; }
|
|
49
|
+
.ob-tagline, .ob-list-description, .ob-lede { color: var(--ob-text-muted); font-size: 1.125rem; margin-top: 0.75rem; }
|
|
50
|
+
.ob-list-layout { display: grid; gap: 2rem; }
|
|
51
|
+
.ob-list-main, .ob-sidebar, .ob-post-card, .ob-featured-details { min-width: 0; }
|
|
52
|
+
.ob-search { margin-bottom: 1.5rem; }
|
|
53
|
+
.ob-search label { display: block; margin-bottom: 0.5rem; font-weight: 700; }
|
|
54
|
+
.ob-search-controls { display: flex; gap: 0.5rem; }
|
|
55
|
+
.ob-search input { min-width: 0; width: 100%; padding: 0.5rem; border: 1px solid var(--ob-border-strong); border-radius: 0.5rem; background: var(--ob-surface); color: var(--ob-text); font: inherit; }
|
|
56
|
+
.ob-search button { flex-shrink: 0; white-space: nowrap; padding: 0.5rem 0.75rem; border-radius: 0.5rem; background: var(--ob-accent); color: var(--ob-accent-contrast); }
|
|
57
|
+
.ob-search-results { margin-top: 0.75rem; border: 1px solid var(--ob-border); border-radius: 0.5rem; background: var(--ob-surface-raised); }
|
|
58
|
+
.ob-sidebar .ob-search-results a { display: block; border-radius: 0.5rem; }
|
|
59
|
+
.ob-search-results a:hover { background: var(--ob-accent-soft); }
|
|
60
|
+
.ob-series-nav { margin: 2rem 0; padding: 1.25rem; border: 1px solid var(--ob-border); border-radius: 0.75rem; }
|
|
61
|
+
.ob-series-nav h2 { font-size: 1.125rem; margin-bottom: 0.75rem; }
|
|
62
|
+
.ob-series-nav p + p { margin-top: 0.5rem; }
|
|
63
|
+
.ob-sidebar h2 { font-size: 1rem; margin-bottom: 0.75rem; }
|
|
64
|
+
.ob-sidebar-categories ul { display: flex; gap: 0.5rem; overflow-x: auto; padding-bottom: 0.5rem; }
|
|
65
|
+
.ob-sidebar-categories li { flex-shrink: 0; }
|
|
66
|
+
.ob-sidebar a { display: block; padding: 0.4rem 0.8rem; border-radius: var(--ob-radius-pill); text-decoration: none; }
|
|
67
|
+
.ob-sidebar a[aria-current="page"] { color: var(--ob-accent-contrast); background: var(--ob-accent); }
|
|
68
|
+
.ob-sidebar-popular { margin-top: 1.5rem; }
|
|
69
|
+
.ob-sidebar-popular li + li { margin-top: 0.25rem; }
|
|
70
|
+
.ob-sidebar-tags, .ob-count { display: none; }
|
|
71
|
+
.ob-post-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2rem; }
|
|
72
|
+
.ob-card-media { display: block; overflow: hidden; border-radius: var(--ob-radius-media); }
|
|
73
|
+
.ob-card-image, .ob-card-placeholder { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
|
|
74
|
+
.ob-card-image { transition: transform 180ms ease; }
|
|
75
|
+
.ob-card-media:hover .ob-card-image { transform: scale(1.05); }
|
|
76
|
+
.ob-card-placeholder { background: linear-gradient(135deg, var(--ob-accent-soft), var(--ob-surface-sunken)); }
|
|
77
|
+
.ob-card-category, .ob-post-category { display: inline-block; margin-top: 1rem; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.07em; font-weight: 750; }
|
|
78
|
+
.ob-card-title { margin: 0.5rem 0; }
|
|
79
|
+
.ob-card-title a, .ob-featured-details h2 a { text-decoration: none; }
|
|
80
|
+
.ob-card-title, .ob-card-description { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
|
|
81
|
+
.ob-card-title { -webkit-line-clamp: 2; }
|
|
82
|
+
.ob-card-description { -webkit-line-clamp: 2; color: var(--ob-text-muted); margin: 0.75rem 0; }
|
|
83
|
+
.ob-card-author { display: flex; align-items: center; gap: 0.5rem; margin-top: 1rem; }
|
|
84
|
+
.ob-card-author a { color: var(--ob-text); }
|
|
85
|
+
.ob-avatar { display: inline-flex; flex: 0 0 2.5rem; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; border-radius: 50%; object-fit: cover; vertical-align: middle; }
|
|
86
|
+
.ob-avatar--initials { background: var(--ob-accent-soft); color: var(--ob-heading); font-weight: 750; }
|
|
87
|
+
.ob-date, .ob-reading-time { color: var(--ob-text-subtle); font-size: 0.875rem; display: inline-block; margin: 0.35rem 0.75rem 0.35rem 0; }
|
|
88
|
+
.ob-featured-card { display: grid; min-width: 0; margin-bottom: 2rem; border: 1px solid var(--ob-border); border-top: 4px solid var(--ob-accent); border-radius: var(--ob-radius-card); overflow: hidden; box-shadow: var(--ob-shadow); }
|
|
89
|
+
.ob-featured-card:hover { border-color: var(--ob-accent); }
|
|
90
|
+
.ob-featured-card .ob-card-media { border-radius: 0; }
|
|
91
|
+
.ob-featured-details { padding: 1.25rem; }
|
|
92
|
+
.ob-featured-details .ob-card-description { -webkit-line-clamp: 3; }
|
|
93
|
+
.ob-featured-label { color: var(--ob-accent); font-weight: 750; font-size: 0.8rem; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
94
|
+
.ob-pagination { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin-top: 2.5rem; }
|
|
95
|
+
.ob-pagination-link, .ob-pagination-current { min-width: 2.5rem; min-height: 2.5rem; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--ob-radius-pill); text-decoration: none; }
|
|
96
|
+
.ob-pagination-current { color: var(--ob-accent-contrast); background: var(--ob-accent); }
|
|
97
|
+
.ob-breadcrumbs ol { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.5rem; font-size: 0.875rem; }
|
|
98
|
+
.ob-breadcrumbs li + li::before { content: "/"; margin-right: 0.5rem; color: var(--ob-text-subtle); }
|
|
99
|
+
.ob-post { max-width: var(--ob-content-width); margin: auto; }
|
|
100
|
+
.ob-post-title { line-height: 1.15; margin: 0.75rem 0 1rem; font-weight: 800; }
|
|
101
|
+
.ob-post-category { margin-top: 0; }
|
|
102
|
+
.ob-byline { margin: 1rem 0 1.5rem; }
|
|
103
|
+
.ob-byline-author { font-weight: 700; }
|
|
104
|
+
.ob-cover { margin: 1.5rem -1rem; }
|
|
105
|
+
.ob-cover img { width: 100%; aspect-ratio: 2 / 1; object-fit: cover; }
|
|
106
|
+
.ob-highlights { padding: 1rem; border: 1px solid var(--ob-border); border-radius: var(--ob-radius-card); margin: 1.5rem 0; background: var(--ob-surface-raised); }
|
|
107
|
+
.ob-highlights h2 { font-size: 1rem; margin-bottom: 0.5rem; }
|
|
108
|
+
.ob-highlights ul { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }
|
|
109
|
+
.ob-notice { background: var(--ob-notice-bg); color: var(--ob-notice-text); border: 1px solid var(--ob-notice-border); border-radius: var(--ob-radius-card); padding: 1rem 1.25rem; margin: 1rem 0; }
|
|
110
|
+
.ob-notice h2 { color: inherit; font-size: 1.125rem; margin-bottom: 0.5rem; }
|
|
111
|
+
.ob-notice ul { list-style: disc; padding-left: 1.25rem; }
|
|
112
|
+
.ob-correction time { display: inline-block; }
|
|
113
|
+
.ob-toc { border-left: 3px solid var(--ob-accent); background: var(--ob-surface-raised); padding: 1rem 1.25rem; margin: 2rem 0; border-radius: 0 var(--ob-radius-card) var(--ob-radius-card) 0; }
|
|
114
|
+
.ob-toc h2 { font-size: 1rem; margin-bottom: 0.75rem; }
|
|
115
|
+
.ob-toc-item { margin: 0.35rem 0; }
|
|
116
|
+
.ob-toc-item--h3 { padding-left: 1rem; }
|
|
117
|
+
.ob-toc a[aria-current="location"] { color: var(--ob-heading); font-weight: 800; text-decoration-thickness: 0.15em; }
|
|
118
|
+
.ob-prose { font-size: var(--ob-prose-size); line-height: var(--ob-prose-leading); }
|
|
119
|
+
.ob-prose > * { margin-block: 1.25rem; }
|
|
120
|
+
.ob-prose h2 { border-bottom: 1px solid var(--ob-border); padding-bottom: 0.5rem; margin-top: 2.5rem; }
|
|
121
|
+
.ob-prose h3, .ob-prose h4 { margin-top: 2rem; }
|
|
122
|
+
.ob-prose :is(h2,h3,h4,h5,h6) { scroll-margin-top: 2rem; }
|
|
123
|
+
.ob-heading-anchor { margin-left: 0.4em; font-size: 0.8em; text-decoration: none; }
|
|
124
|
+
.ob-prose ul, .ob-prose ol { padding-left: 1.5rem; }
|
|
125
|
+
.ob-prose ul { list-style: disc; }
|
|
126
|
+
.ob-prose ol { list-style: decimal; }
|
|
127
|
+
.ob-prose li::marker { color: var(--ob-accent); }
|
|
128
|
+
.ob-prose li + li { margin-top: 0.35rem; }
|
|
129
|
+
.ob-prose input[type="checkbox"] { margin-right: 0.4rem; }
|
|
130
|
+
.ob-prose blockquote { border-left: 3px solid var(--ob-accent); padding: 0.75rem 1.25rem; background: var(--ob-accent-soft); border-radius: 0 var(--ob-radius-card) var(--ob-radius-card) 0; }
|
|
131
|
+
.ob-prose :not(pre) > code { background: var(--ob-surface-sunken); border-radius: 0.3rem; padding: 0.15em 0.3em; font-size: 0.9em; font-family: var(--ob-font-mono); }
|
|
132
|
+
.ob-prose pre { background: var(--ob-code-bg); position: relative; max-width: 100%; overflow-x: auto; border: 1px solid var(--ob-code-border); border-radius: var(--ob-radius-card); padding: 3.25rem 1rem 1rem; line-height: 1.6; font-size: 0.9rem; overflow-wrap: normal; }
|
|
133
|
+
.ob-prose .ob-highlight .w { background-color: transparent; }
|
|
134
|
+
.ob-prose pre code { display: block; width: max-content; min-width: 100%; font-family: var(--ob-font-mono); }
|
|
135
|
+
.ob-code-language { position: absolute; left: 1rem; top: 0.65rem; color: var(--ob-text-subtle); font-family: var(--ob-font-body); font-size: 0.8rem; }
|
|
136
|
+
.ob-code-copy { position: absolute; right: 0.65rem; top: 0.5rem; }
|
|
137
|
+
.ob-code-copy, .ob-theme-toggle, .ob-share-button { display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem; min-height: 2.5rem; padding: 0.4rem 0.75rem; background: var(--ob-surface-raised); color: var(--ob-text); border: 1px solid var(--ob-border-strong); border-radius: var(--ob-radius-pill); }
|
|
138
|
+
.ob-icon { width: 1.125rem; height: 1.125rem; flex-shrink: 0; }
|
|
139
|
+
.ob-prose hr { height: 1px; border: 0; background: linear-gradient(to right, var(--ob-border), var(--ob-accent), var(--ob-border)); margin: 2.5rem 0; }
|
|
140
|
+
.ob-figure { margin-inline: 0; }
|
|
141
|
+
.ob-figure img { border-radius: var(--ob-radius-media); }
|
|
142
|
+
.ob-figure figcaption { font-size: 0.9rem; color: var(--ob-text-muted); margin-top: 0.5rem; }
|
|
143
|
+
.ob-table { max-width: 100%; overflow-x: auto; border: 1px solid var(--ob-border); border-radius: var(--ob-radius-media); }
|
|
144
|
+
.ob-table table { border-collapse: collapse; min-width: 100%; width: max-content; font-size: 0.95rem; }
|
|
145
|
+
.ob-table th, .ob-table td { text-align: left; padding: 0.65rem 1rem; border-bottom: 1px solid var(--ob-border); }
|
|
146
|
+
.ob-table th { background: var(--ob-surface-raised); color: var(--ob-heading); font-weight: 700; }
|
|
147
|
+
.ob-prose .markdown-alert { padding: 1rem 1.25rem; border-left: 4px solid var(--ob-note); background: var(--ob-surface-raised); border-radius: 0 var(--ob-radius-card) var(--ob-radius-card) 0; }
|
|
148
|
+
.ob-prose .markdown-alert-title { color: var(--ob-note); font-weight: 750; margin-bottom: 0.5rem; }
|
|
149
|
+
.ob-prose .markdown-alert-tip { border-color: var(--ob-tip); }
|
|
150
|
+
.ob-prose .markdown-alert-tip .markdown-alert-title { color: var(--ob-tip); }
|
|
151
|
+
.ob-prose .markdown-alert-warning, .ob-prose .markdown-alert-caution { border-color: var(--ob-warning); }
|
|
152
|
+
.ob-prose .markdown-alert-warning .markdown-alert-title, .ob-prose .markdown-alert-caution .markdown-alert-title { color: var(--ob-warning); }
|
|
153
|
+
.ob-prose--dropcap > p:first-of-type::first-letter { float: left; font-size: 3.5em; line-height: 0.9; padding-right: 0.1em; color: var(--ob-accent); }
|
|
154
|
+
.ob-faq { margin-block: 2.5rem; padding-top: 2rem; border-top: 1px solid var(--ob-border); min-width: 0; }
|
|
155
|
+
.ob-faq > h2 { margin-bottom: 1.5rem; scroll-margin-top: 2rem; }
|
|
156
|
+
.ob-faq-entry + .ob-faq-entry { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--ob-border); }
|
|
157
|
+
.ob-faq-entry h3 { margin-bottom: 0.75rem; }
|
|
158
|
+
.ob-faq-entry p { font-size: var(--ob-prose-size); line-height: var(--ob-prose-leading); overflow-wrap: anywhere; }
|
|
159
|
+
.ob-faq-entry p + p { margin-top: 1rem; }
|
|
160
|
+
.ob-faq-entry a { text-decoration: underline; }
|
|
161
|
+
.ob-tags, .ob-author-profiles, .ob-share { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin: 1.5rem 0; }
|
|
162
|
+
.ob-tags a { border-radius: var(--ob-radius-pill); padding: 0.25rem 0.75rem; background: var(--ob-accent-soft); }
|
|
163
|
+
.ob-share-status { color: var(--ob-text-muted); font-size: 0.875rem; }
|
|
164
|
+
.ob-author-box, .ob-call-to-action { margin-block: 2rem; padding: 1.5rem; background: var(--ob-surface-raised); border: 1px solid var(--ob-border); border-radius: var(--ob-radius-card); }
|
|
165
|
+
.ob-author-box h2, .ob-call-to-action h2 { margin-bottom: 0.75rem; }
|
|
166
|
+
.ob-author-bio { margin-top: 0.75rem; }
|
|
167
|
+
.ob-related-posts, .ob-recent-posts { margin-top: 3rem; }
|
|
168
|
+
.ob-related-posts > h2, .ob-recent-posts > h2 { margin-bottom: 1.5rem; }
|
|
169
|
+
.ob-empty { color: var(--ob-text-muted); padding-block: 2rem; }
|
|
170
|
+
.ob-reading-progress { position: fixed; inset: 0 0 auto; height: 3px; z-index: 50; background: var(--ob-border); pointer-events: none; }
|
|
171
|
+
.ob-reading-progress-fill { height: 100%; width: 0; background: var(--ob-accent); }
|
|
172
|
+
@media (min-width: 40rem) {
|
|
173
|
+
.ob-post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
174
|
+
.ob-featured-card { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
|
175
|
+
.ob-featured-card .ob-card-image, .ob-featured-card .ob-card-placeholder { height: 100%; }
|
|
176
|
+
.ob-cover { margin-inline: 0; }
|
|
177
|
+
.ob-cover img { border-radius: var(--ob-radius-media); }
|
|
178
|
+
}
|
|
179
|
+
@media (min-width: 64rem) {
|
|
180
|
+
.ob-list-layout { grid-template-columns: 14rem minmax(0, 1fr); }
|
|
181
|
+
.ob-sidebar { align-self: start; position: sticky; top: 2rem; }
|
|
182
|
+
.ob-sidebar-categories ul { display: block; overflow: visible; }
|
|
183
|
+
.ob-sidebar-categories li { margin-bottom: 0.25rem; }
|
|
184
|
+
.ob-sidebar a { display: flex; justify-content: space-between; gap: 0.75rem; }
|
|
185
|
+
.ob-sidebar-tags { display: block; margin-top: 1.5rem; }
|
|
186
|
+
.ob-sidebar-tags ul { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
187
|
+
.ob-count { display: inline; }
|
|
188
|
+
.ob-related-posts .ob-post-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
189
|
+
}
|
|
190
|
+
@media (prefers-reduced-motion: reduce) {
|
|
191
|
+
.ob-card-image { transition: none; }
|
|
192
|
+
.ob-card-media:hover .ob-card-image { transform: none; }
|
|
193
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--ob-surface: #ffffff;
|
|
3
|
+
--ob-surface-raised: #f8fafc;
|
|
4
|
+
--ob-surface-sunken: #f1f5f9;
|
|
5
|
+
--ob-heading: #0f172a;
|
|
6
|
+
--ob-text: #334155;
|
|
7
|
+
--ob-text-muted: #475569;
|
|
8
|
+
--ob-text-subtle: #526176;
|
|
9
|
+
--ob-border: #cbd5e1;
|
|
10
|
+
--ob-border-strong: #64748b;
|
|
11
|
+
--ob-accent: #1d4ed8;
|
|
12
|
+
--ob-accent-hover: #1e40af;
|
|
13
|
+
--ob-accent-soft: #dbeafe;
|
|
14
|
+
--ob-accent-contrast: #ffffff;
|
|
15
|
+
--ob-code-bg: #f6f8fa;
|
|
16
|
+
--ob-code-border: #94a3b8;
|
|
17
|
+
--ob-notice-bg: #eff6ff;
|
|
18
|
+
--ob-notice-border: #60a5fa;
|
|
19
|
+
--ob-notice-text: #1e3a8a;
|
|
20
|
+
--ob-note: #1d4ed8;
|
|
21
|
+
--ob-tip: #166534;
|
|
22
|
+
--ob-warning: #854d0e;
|
|
23
|
+
--ob-shadow: 0 8px 24px #0f172a1a;
|
|
24
|
+
--ob-font-display: ui-sans-serif, system-ui, sans-serif;
|
|
25
|
+
--ob-font-body: ui-sans-serif, system-ui, sans-serif;
|
|
26
|
+
--ob-font-mono: ui-monospace, SFMono-Regular, Consolas, monospace;
|
|
27
|
+
--ob-radius-card: 1rem;
|
|
28
|
+
--ob-radius-media: 0.75rem;
|
|
29
|
+
--ob-radius-pill: 999px;
|
|
30
|
+
--ob-prose-size: 1.125rem;
|
|
31
|
+
--ob-prose-leading: 1.8;
|
|
32
|
+
--ob-content-width: 56rem;
|
|
33
|
+
--ob-page-width: 72rem;
|
|
34
|
+
}
|
|
35
|
+
:root[data-theme="dark"] {
|
|
36
|
+
--ob-surface: #0f172a;
|
|
37
|
+
--ob-surface-raised: #1e293b;
|
|
38
|
+
--ob-surface-sunken: #020617;
|
|
39
|
+
--ob-heading: #f8fafc;
|
|
40
|
+
--ob-text: #e2e8f0;
|
|
41
|
+
--ob-text-muted: #cbd5e1;
|
|
42
|
+
--ob-text-subtle: #94a3b8;
|
|
43
|
+
--ob-border: #475569;
|
|
44
|
+
--ob-border-strong: #94a3b8;
|
|
45
|
+
--ob-accent: #93c5fd;
|
|
46
|
+
--ob-accent-hover: #bfdbfe;
|
|
47
|
+
--ob-accent-soft: #172554;
|
|
48
|
+
--ob-accent-contrast: #0f172a;
|
|
49
|
+
--ob-code-bg: #161b22;
|
|
50
|
+
--ob-code-border: #64748b;
|
|
51
|
+
--ob-notice-bg: #172554;
|
|
52
|
+
--ob-notice-border: #60a5fa;
|
|
53
|
+
--ob-notice-text: #dbeafe;
|
|
54
|
+
--ob-note: #93c5fd;
|
|
55
|
+
--ob-tip: #86efac;
|
|
56
|
+
--ob-warning: #fde047;
|
|
57
|
+
}
|
|
58
|
+
@media (prefers-color-scheme: dark) {
|
|
59
|
+
:root:not([data-theme="light"]) {
|
|
60
|
+
--ob-surface: #0f172a;
|
|
61
|
+
--ob-surface-raised: #1e293b;
|
|
62
|
+
--ob-surface-sunken: #020617;
|
|
63
|
+
--ob-heading: #f8fafc;
|
|
64
|
+
--ob-text: #e2e8f0;
|
|
65
|
+
--ob-text-muted: #cbd5e1;
|
|
66
|
+
--ob-text-subtle: #94a3b8;
|
|
67
|
+
--ob-border: #475569;
|
|
68
|
+
--ob-border-strong: #94a3b8;
|
|
69
|
+
--ob-accent: #93c5fd;
|
|
70
|
+
--ob-accent-hover: #bfdbfe;
|
|
71
|
+
--ob-accent-soft: #172554;
|
|
72
|
+
--ob-accent-contrast: #0f172a;
|
|
73
|
+
--ob-code-bg: #161b22;
|
|
74
|
+
--ob-code-border: #64748b;
|
|
75
|
+
--ob-notice-bg: #172554;
|
|
76
|
+
--ob-notice-border: #60a5fa;
|
|
77
|
+
--ob-notice-text: #dbeafe;
|
|
78
|
+
--ob-note: #93c5fd;
|
|
79
|
+
--ob-tip: #86efac;
|
|
80
|
+
--ob-warning: #fde047;
|
|
81
|
+
}
|
|
82
|
+
}
|