markdowndocs 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a49d71e98d368188a3ed6a4437651f03880e691a8f520ee1067cd62c5fa4882
4
- data.tar.gz: 7b3646a743326288758bbfef6322b27a3e9ed75dd54c2c8edcec6f4671428f8c
3
+ metadata.gz: b10065bc9db477eab3c5c118832bf1bf3d0b8c68d879c6b3b5bd324b0deb4ec3
4
+ data.tar.gz: 4696b2b0c2e906ae7875ba69b32279d222f717db45abbed0987bb335f2d1e55d
5
5
  SHA512:
6
- metadata.gz: 2b7b057ab57104c261bdd5b6d3d5d06527450a2b68469d42af79f58652e9f3a752b4dbd69b4c386d3e323cfb6bb7f55a4279e9fe324e6b8d5bee211bdfc12f0b
7
- data.tar.gz: 0aa8ea43096b87a21396084f59648d8f23762af311e1459703c2074196d6d02cc69dfe950b84b2849e4aff513aacf80d6a40c2f38b98f03417248fa35be7d76b
6
+ metadata.gz: 81f260911757400e215ebdc586c40229032e381ee5fc445e6606509de5a8fa15e7d60ce5a22f2b33e110c4984f63f093ca9e78393935b1d3e5306002c9dfe9de
7
+ data.tar.gz: f41f6d08e1736a2d772a89e0d89a5f6d57d6da25342b1a426c0f9bd339f028280d9e1bb0814209ac43edea4823057392a2e2e18db7d52164d1363d0b8c22462e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-03-20
9
+
10
+ ### Added
11
+
12
+ - Keywords frontmatter support (`keywords: [login, signin, ...]`) indexed by full-text search with highest boost (4x), improving search relevance for docs with explicit keyword tags.
13
+
14
+ ## [0.2.3] - 2026-03-20
15
+
16
+ ### Fixed
17
+
18
+ - Host app route helpers (e.g., `about_path`) no longer resolve against the engine namespace. Replaced `method_missing` delegation with explicit `define_method` overrides built lazily on first request via `before_action`.
19
+
8
20
  ## [0.2.2] - 2026-03-18
9
21
 
10
22
  ### Changed
@@ -84,6 +96,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
84
96
  - i18n support for all UI strings
85
97
  - Install generator (`rails generate markdowndocs:install`)
86
98
 
99
+ [0.3.0]: https://github.com/dschmura/markdowndocs/releases/tag/v0.3.0
100
+ [0.2.3]: https://github.com/dschmura/markdowndocs/releases/tag/v0.2.3
87
101
  [0.2.2]: https://github.com/dschmura/markdowndocs/releases/tag/v0.2.2
88
102
  [0.2.1]: https://github.com/dschmura/markdowndocs/releases/tag/v0.2.1
89
103
  [0.2.0]: https://github.com/dschmura/markdowndocs/releases/tag/v0.2.0
@@ -23,10 +23,10 @@ export default class extends Controller {
23
23
  const MiniSearch = (await this.loadMiniSearch()).default || (await this.loadMiniSearch())
24
24
 
25
25
  this.miniSearch = new MiniSearch({
26
- fields: ["title", "description", "content", "keywords"],
26
+ fields: ["title", "description", "content", "keywords", "code"],
27
27
  storeFields: ["title", "description"],
28
28
  searchOptions: {
29
- boost: { title: 3, description: 2, keywords: 4 },
29
+ boost: { title: 3, description: 2, keywords: 4, code: 0.5 },
30
30
  fuzzy: 0.2,
31
31
  prefix: true
32
32
  }
@@ -58,7 +58,7 @@ export default class extends Controller {
58
58
 
59
59
  search() {
60
60
  if (this.debounceTimer) clearTimeout(this.debounceTimer)
61
- this.debounceTimer = setTimeout(() => this.performSearch(), 150)
61
+ this.debounceTimer = setTimeout(() => this.performSearch(), 50)
62
62
  }
63
63
 
64
64
  performSearch() {
@@ -71,16 +71,21 @@ export default class extends Controller {
71
71
 
72
72
  const results = this.miniSearch.search(query)
73
73
  const matchingSlugs = new Set(results.map(r => r.id))
74
+ const scoreBySlug = new Map(results.map(r => [r.id, r.score]))
74
75
 
75
- this.cardTargets.forEach(card => {
76
- const slug = card.dataset.slug
77
- card.classList.toggle("hidden", !matchingSlugs.has(slug))
78
- })
79
-
76
+ // Reorder cards within each category by relevance score
80
77
  this.categoryTargets.forEach(section => {
81
- const cards = section.querySelectorAll("[data-docs-search-target='card']")
82
- const hasVisible = Array.from(cards).some(c => !c.classList.contains("hidden"))
83
- section.classList.toggle("hidden", !hasVisible)
78
+ const cards = Array.from(section.querySelectorAll("[data-docs-search-target='card']"))
79
+ cards.forEach(card => {
80
+ const slug = card.dataset.slug
81
+ card.classList.toggle("hidden", !matchingSlugs.has(slug))
82
+ })
83
+
84
+ const visibleCards = cards.filter(c => !c.classList.contains("hidden"))
85
+ visibleCards.sort((a, b) => (scoreBySlug.get(b.dataset.slug) || 0) - (scoreBySlug.get(a.dataset.slug) || 0))
86
+ visibleCards.forEach(card => card.parentElement.appendChild(card))
87
+
88
+ section.classList.toggle("hidden", visibleCards.length === 0)
84
89
  })
85
90
 
86
91
  const hasResults = matchingSlugs.size > 0
@@ -91,7 +96,13 @@ export default class extends Controller {
91
96
 
92
97
  showAll() {
93
98
  this.cardTargets.forEach(card => card.classList.remove("hidden"))
94
- this.categoryTargets.forEach(section => section.classList.remove("hidden"))
99
+ this.categoryTargets.forEach(section => {
100
+ section.classList.remove("hidden")
101
+ // Restore original order by slug
102
+ const cards = Array.from(section.querySelectorAll("[data-docs-search-target='card']"))
103
+ cards.sort((a, b) => a.dataset.slug.localeCompare(b.dataset.slug))
104
+ cards.forEach(card => card.parentElement.appendChild(card))
105
+ })
95
106
  if (this.hasNoResultsTarget) {
96
107
  this.noResultsTarget.classList.add("hidden")
97
108
  }
@@ -4,19 +4,20 @@ module Markdowndocs
4
4
  class ApplicationController < ::ApplicationController
5
5
  protect_from_forgery with: :exception
6
6
 
7
- # Fix route helper isolation caused by isolate_namespace.
7
+ # Use the engine's own layout to avoid route helper conflicts.
8
+ # Host apps that shared their application layout with the engine
9
+ # would see route helpers (about_path, signout_path, etc.) resolve
10
+ # against the engine's namespace instead of the main app.
8
11
  #
9
- # The problem: host app route helpers (about_path, contact_path, etc.)
10
- # exist in the view context but resolve against the engine's namespace,
11
- # so about_path returns "/docs/about" instead of "/about".
12
+ # Host apps can customize by overriding this layout at:
13
+ # app/views/layouts/markdowndocs/application.html.erb
12
14
  #
13
- # method_missing doesn't help because the methods already exist —
14
- # they just resolve wrong. We need to override them explicitly.
15
- #
16
- # This before_action builds a helper module on first request (when
17
- # routes are fully loaded) that defines every host app route helper
18
- # as a delegation to main_app.
19
- before_action :ensure_host_route_helpers
15
+ # The layout provides content_for blocks:
16
+ # :docs_header rendered above main content (for nav/header)
17
+ # :docs_footer — rendered below main content (for footer)
18
+ # :head — injected into <head> (for extra stylesheets/scripts)
19
+ # :title — page title (defaults to "Documentation")
20
+ layout -> { Markdowndocs.config.layout }
20
21
 
21
22
  # Support Rails 8 built-in authentication (allow_unauthenticated_access)
22
23
  # without requiring it — works with any auth system or none at all
@@ -26,21 +27,5 @@ module Markdowndocs
26
27
 
27
28
  # Resume session if the host app supports it (Rails 8 auth)
28
29
  before_action :resume_session, if: -> { respond_to?(:resume_session, true) }
29
-
30
- private
31
-
32
- def ensure_host_route_helpers
33
- return if self.class.instance_variable_get(:@host_routes_delegated)
34
-
35
- helper_module = Module.new do
36
- Rails.application.routes.named_routes.names.each do |name|
37
- define_method(:"#{name}_path") { |*args, **kwargs| main_app.send(:"#{name}_path", *args, **kwargs) }
38
- define_method(:"#{name}_url") { |*args, **kwargs| main_app.send(:"#{name}_url", *args, **kwargs) }
39
- end
40
- end
41
-
42
- self.class.helper(helper_module)
43
- self.class.instance_variable_set(:@host_routes_delegated, true)
44
- end
45
30
  end
46
31
  end
@@ -27,7 +27,8 @@ module Markdowndocs
27
27
  title: doc.title,
28
28
  description: doc.description,
29
29
  content: doc.plain_text_content,
30
- keywords: doc.keywords.join(" ")
30
+ keywords: doc.keywords.join(" "),
31
+ code: doc.code_content
31
32
  }
32
33
  end.to_json
33
34
  end
@@ -98,6 +98,13 @@ module Markdowndocs
98
98
  text.strip
99
99
  end
100
100
 
101
+ # Returns text extracted from fenced code blocks for search indexing.
102
+ def code_content
103
+ parsed = parse_frontmatter
104
+ blocks = parsed[:markdown].scan(/```\w*\n([\s\S]*?)```/)
105
+ blocks.flatten.join(" ").gsub(/\s+/, " ").strip
106
+ end
107
+
101
108
  private
102
109
 
103
110
  def derive_slug
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title><%= content_for(:title) || "Documentation" %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= yield :head %>
9
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" rescue nil %>
10
+ <%= javascript_importmap_tags rescue nil %>
11
+ </head>
12
+
13
+ <body class="min-h-screen flex flex-col bg-gray-50">
14
+ <%= yield :docs_header %>
15
+
16
+ <main id="main-content" role="main" class="flex-1">
17
+ <%= yield %>
18
+ </main>
19
+
20
+ <%= yield :docs_footer %>
21
+ </body>
22
+ </html>
@@ -4,7 +4,8 @@ module Markdowndocs
4
4
  class Configuration
5
5
  attr_accessor :docs_path, :categories, :modes, :default_mode,
6
6
  :markdown_options, :rouge_theme, :cache_expiry,
7
- :user_mode_resolver, :user_mode_saver, :search_enabled
7
+ :user_mode_resolver, :user_mode_saver, :search_enabled,
8
+ :layout
8
9
 
9
10
  def initialize
10
11
  @docs_path = nil # Resolved lazily so Rails.root is available
@@ -17,6 +18,7 @@ module Markdowndocs
17
18
  @user_mode_resolver = nil
18
19
  @user_mode_saver = nil
19
20
  @search_enabled = false
21
+ @layout = "markdowndocs/application"
20
22
  end
21
23
 
22
24
  # Lazily resolve docs_path so Rails.root is available
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Markdowndocs
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdowndocs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Chmura
@@ -87,6 +87,7 @@ files:
87
87
  - app/helpers/markdowndocs/docs_helper.rb
88
88
  - app/models/markdowndocs/documentation.rb
89
89
  - app/services/markdowndocs/markdown_renderer.rb
90
+ - app/views/layouts/markdowndocs/application.html.erb
90
91
  - app/views/markdowndocs/docs/_breadcrumb.html.erb
91
92
  - app/views/markdowndocs/docs/_card.html.erb
92
93
  - app/views/markdowndocs/docs/_mode_switcher.html.erb