docyard 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -1
  3. data/README.md +56 -11
  4. data/lib/docyard/build/asset_bundler.rb +17 -1
  5. data/lib/docyard/build/social_cards/card_renderer.rb +132 -0
  6. data/lib/docyard/build/social_cards/doc_card.rb +97 -0
  7. data/lib/docyard/build/social_cards/homepage_card.rb +98 -0
  8. data/lib/docyard/build/social_cards_generator.rb +188 -0
  9. data/lib/docyard/build/step_runner.rb +2 -0
  10. data/lib/docyard/builder.rb +10 -0
  11. data/lib/docyard/cli.rb +27 -0
  12. data/lib/docyard/config/branding_resolver.rb +1 -1
  13. data/lib/docyard/config/schema/definition.rb +2 -1
  14. data/lib/docyard/config/schema/sections.rb +0 -1
  15. data/lib/docyard/config/schema/simple_sections.rb +7 -0
  16. data/lib/docyard/config.rb +3 -1
  17. data/lib/docyard/customizer.rb +196 -0
  18. data/lib/docyard/rendering/markdown.rb +4 -0
  19. data/lib/docyard/rendering/og_helpers.rb +19 -1
  20. data/lib/docyard/rendering/renderer.rb +10 -1
  21. data/lib/docyard/search/build_indexer.rb +8 -3
  22. data/lib/docyard/search/dev_indexer.rb +3 -2
  23. data/lib/docyard/search/pagefind_binary.rb +185 -0
  24. data/lib/docyard/search/pagefind_support.rb +9 -7
  25. data/lib/docyard/server/asset_handler.rb +28 -2
  26. data/lib/docyard/server/dev_server.rb +1 -0
  27. data/lib/docyard/server/file_watcher.rb +10 -5
  28. data/lib/docyard/server/rack_application.rb +1 -1
  29. data/lib/docyard/templates/assets/css/variables.css +0 -6
  30. data/lib/docyard/templates/assets/js/components/abbreviation.js +20 -11
  31. data/lib/docyard/templates/assets/js/components/code-block.js +8 -3
  32. data/lib/docyard/templates/assets/js/components/code-group.js +20 -3
  33. data/lib/docyard/templates/assets/js/components/file-tree.js +9 -3
  34. data/lib/docyard/templates/assets/js/components/heading-anchor.js +71 -72
  35. data/lib/docyard/templates/assets/js/components/lightbox.js +10 -3
  36. data/lib/docyard/templates/assets/js/components/tabs.js +8 -3
  37. data/lib/docyard/templates/assets/js/components/tooltip.js +32 -23
  38. data/lib/docyard/templates/assets/js/hot-reload.js +42 -2
  39. data/lib/docyard/templates/partials/_head.html.erb +4 -0
  40. data/lib/docyard/templates/partials/_scripts.html.erb +3 -0
  41. data/lib/docyard/version.rb +1 -1
  42. data/lib/docyard.rb +2 -0
  43. metadata +7 -1
@@ -8,8 +8,9 @@ module Docyard
8
8
  TEMPLATES_ASSETS_PATH = File.join(__dir__, "../templates", "assets")
9
9
  CACHE_MAX_AGE = 3600
10
10
  DEFAULT_PUBLIC_DIR = "docs/public"
11
+ DEFAULT_DOCS_PATH = "docs"
11
12
 
12
- attr_reader :public_dir
13
+ attr_reader :public_dir, :docs_path
13
14
 
14
15
  CONTENT_TYPES = {
15
16
  ".css" => "text/css; charset=utf-8",
@@ -30,8 +31,9 @@ module Docyard
30
31
  ".webm" => "video/webm"
31
32
  }.freeze
32
33
 
33
- def initialize(public_dir: DEFAULT_PUBLIC_DIR)
34
+ def initialize(public_dir: DEFAULT_PUBLIC_DIR, docs_path: DEFAULT_DOCS_PATH)
34
35
  @public_dir = public_dir
36
+ @docs_path = docs_path
35
37
  end
36
38
 
37
39
  def serve_docyard_assets(request_path)
@@ -39,6 +41,8 @@ module Docyard
39
41
 
40
42
  return serve_components_css if asset_path == "css/components.css"
41
43
  return serve_components_js if asset_path == "js/components.js"
44
+ return serve_custom_css if asset_path == "css/custom.css"
45
+ return serve_custom_js if asset_path == "js/custom.js"
42
46
 
43
47
  file_path = safe_asset_path(asset_path, TEMPLATES_ASSETS_PATH)
44
48
  return forbidden_response unless file_path
@@ -94,6 +98,28 @@ module Docyard
94
98
  [200, headers, [content]]
95
99
  end
96
100
 
101
+ def serve_custom_css
102
+ custom_path = File.join(docs_path, "_custom", "styles.css")
103
+ return not_found_response unless File.exist?(custom_path)
104
+
105
+ content = File.read(custom_path)
106
+ headers = build_cache_headers(content, File.mtime(custom_path))
107
+ headers["Content-Type"] = "text/css; charset=utf-8"
108
+
109
+ [200, headers, [content]]
110
+ end
111
+
112
+ def serve_custom_js
113
+ custom_path = File.join(docs_path, "_custom", "scripts.js")
114
+ return not_found_response unless File.exist?(custom_path)
115
+
116
+ content = File.read(custom_path)
117
+ headers = build_cache_headers(content, File.mtime(custom_path))
118
+ headers["Content-Type"] = "application/javascript; charset=utf-8"
119
+
120
+ [200, headers, [content]]
121
+ end
122
+
97
123
  def concatenate_component_js
98
124
  components_dir = File.join(TEMPLATES_ASSETS_PATH, "js", "components")
99
125
  return "" unless Dir.exist?(components_dir)
@@ -111,6 +111,7 @@ module Docyard
111
111
  message = case change_type
112
112
  when :content then "Content changed, reloading..."
113
113
  when :full then "Config changed, full reload..."
114
+ when :css then "CSS changed, injecting styles..."
114
115
  when :asset then "Asset changed, reloading..."
115
116
  else "File changed, reloading..."
116
117
  end
@@ -7,7 +7,8 @@ module Docyard
7
7
  DEBOUNCE_DELAY = 0.1
8
8
  CONFIG_FILES = %w[docyard.yml _sidebar.yml].freeze
9
9
  CONTENT_EXTENSIONS = %w[.md .markdown].freeze
10
- ASSET_EXTENSIONS = %w[.css .js .html .erb].freeze
10
+ CSS_EXTENSIONS = %w[.css].freeze
11
+ ASSET_EXTENSIONS = %w[.js .html .erb].freeze
11
12
 
12
13
  def initialize(docs_path:, on_change:)
13
14
  @docs_path = File.expand_path(docs_path)
@@ -15,7 +16,7 @@ module Docyard
15
16
  @on_change = on_change
16
17
  @docs_listener = nil
17
18
  @config_listener = nil
18
- @pending_changes = { content: false, config: false, asset: false }
19
+ @pending_changes = { content: false, config: false, css: false, asset: false }
19
20
  @debounce_timer = nil
20
21
  @mutex = Mutex.new
21
22
  end
@@ -51,12 +52,15 @@ module Docyard
51
52
 
52
53
  def categorize_change(path)
53
54
  filename = File.basename(path)
55
+ extension = File.extname(path)
54
56
 
55
57
  if CONFIG_FILES.include?(filename)
56
58
  @pending_changes[:config] = true
57
- elsif CONTENT_EXTENSIONS.include?(File.extname(path))
59
+ elsif CONTENT_EXTENSIONS.include?(extension)
58
60
  @pending_changes[:content] = true
59
- elsif ASSET_EXTENSIONS.include?(File.extname(path))
61
+ elsif CSS_EXTENSIONS.include?(extension)
62
+ @pending_changes[:css] = true
63
+ elsif ASSET_EXTENSIONS.include?(extension)
60
64
  @pending_changes[:asset] = true
61
65
  end
62
66
  end
@@ -73,7 +77,7 @@ module Docyard
73
77
  changes = nil
74
78
  @mutex.synchronize do
75
79
  changes = @pending_changes.dup
76
- @pending_changes = { content: false, config: false, asset: false }
80
+ @pending_changes = { content: false, config: false, css: false, asset: false }
77
81
  end
78
82
 
79
83
  change_type = determine_change_type(changes)
@@ -83,6 +87,7 @@ module Docyard
83
87
  def determine_change_type(changes)
84
88
  return :full if changes[:config]
85
89
  return :content if changes[:content]
90
+ return :css if changes[:css]
86
91
  return :asset if changes[:asset]
87
92
 
88
93
  nil
@@ -28,7 +28,7 @@ module Docyard
28
28
  @router = Router.new(docs_path: docs_path)
29
29
  @renderer = Renderer.new(base_url: "/", config: config, dev_mode: @dev_mode,
30
30
  sse_port: sse_port)
31
- @asset_handler = AssetHandler.new(public_dir: config&.public_dir || "docs/public")
31
+ @asset_handler = AssetHandler.new(public_dir: config&.public_dir || "docs/public", docs_path: docs_path)
32
32
  @pagefind_handler = PagefindHandler.new(pagefind_path: pagefind_path, config: config)
33
33
  @page_diagnostics = PageDiagnostics.new(docs_path) if @dev_mode
34
34
  end
@@ -35,12 +35,6 @@
35
35
  --sidebar-border: oklch(0.92 0.004 286.32);
36
36
  --sidebar-ring: oklch(0.705 0.015 286.067);
37
37
 
38
- --chart-1: oklch(0.87 0.12 207);
39
- --chart-2: oklch(0.80 0.13 212);
40
- --chart-3: oklch(0.71 0.13 215);
41
- --chart-4: oklch(0.61 0.11 222);
42
- --chart-5: oklch(0.52 0.09 223);
43
-
44
38
  --code-background: oklch(0.967 0.001 286.375);
45
39
  --code-foreground: oklch(0.141 0.005 285.823);
46
40
 
@@ -1,21 +1,27 @@
1
- function initializeAbbreviations() {
2
- const abbreviations = document.querySelectorAll('.docyard-abbr');
3
- if (abbreviations.length === 0) return;
1
+ var abbrPopover = null;
2
+ var abbrHideTimeout = null;
4
3
 
5
- const popover = createPopover();
6
- document.body.appendChild(popover);
4
+ function initializeAbbreviations(root = document) {
5
+ const abbreviations = root.querySelectorAll('.docyard-abbr');
6
+ if (abbreviations.length === 0) return;
7
7
 
8
- let hideTimeout;
8
+ if (!abbrPopover) {
9
+ abbrPopover = createPopover();
10
+ document.body.appendChild(abbrPopover);
11
+ }
9
12
 
10
13
  abbreviations.forEach(abbr => {
14
+ if (abbr.hasAttribute('data-abbr-initialized')) return;
15
+ abbr.setAttribute('data-abbr-initialized', 'true');
16
+
11
17
  abbr.addEventListener('mouseenter', () => {
12
- clearTimeout(hideTimeout);
13
- showPopover(popover, abbr);
18
+ clearTimeout(abbrHideTimeout);
19
+ showPopover(abbrPopover, abbr);
14
20
  });
15
21
 
16
22
  abbr.addEventListener('mouseleave', () => {
17
- hideTimeout = setTimeout(() => {
18
- hidePopover(popover);
23
+ abbrHideTimeout = setTimeout(() => {
24
+ hidePopover(abbrPopover);
19
25
  }, 100);
20
26
  });
21
27
  });
@@ -79,7 +85,10 @@ function hidePopover(popover) {
79
85
  }
80
86
 
81
87
  if (document.readyState === 'loading') {
82
- document.addEventListener('DOMContentLoaded', initializeAbbreviations);
88
+ document.addEventListener('DOMContentLoaded', function() { initializeAbbreviations(); });
83
89
  } else {
84
90
  initializeAbbreviations();
85
91
  }
92
+
93
+ window.docyard = window.docyard || {};
94
+ window.docyard.initAbbreviations = initializeAbbreviations;
@@ -125,20 +125,25 @@ class CodeBlockManager {
125
125
  }
126
126
  }
127
127
 
128
- function initializeCodeBlocks() {
129
- const codeBlocks = document.querySelectorAll('.docyard-code-block');
128
+ function initializeCodeBlocks(root = document) {
129
+ const codeBlocks = root.querySelectorAll('.docyard-code-block');
130
130
 
131
131
  codeBlocks.forEach(container => {
132
+ if (container.hasAttribute('data-code-block-initialized')) return;
133
+ container.setAttribute('data-code-block-initialized', 'true');
132
134
  new CodeBlockManager(container);
133
135
  });
134
136
  }
135
137
 
136
138
  if (document.readyState === 'loading') {
137
- document.addEventListener('DOMContentLoaded', initializeCodeBlocks);
139
+ document.addEventListener('DOMContentLoaded', function() { initializeCodeBlocks(); });
138
140
  } else {
139
141
  initializeCodeBlocks();
140
142
  }
141
143
 
144
+ window.docyard = window.docyard || {};
145
+ window.docyard.initCodeBlocks = initializeCodeBlocks;
146
+
142
147
  if (typeof module !== 'undefined' && module.exports) {
143
148
  module.exports = { CodeBlockManager };
144
149
  }
@@ -9,6 +9,8 @@ class CodeGroupManager {
9
9
  if (containers.length === 0) return;
10
10
 
11
11
  containers.forEach(container => {
12
+ if (container.hasAttribute('data-code-group-initialized')) return;
13
+ container.setAttribute('data-code-group-initialized', 'true');
12
14
  this.groups.push(new CodeGroup(container, this));
13
15
  });
14
16
 
@@ -275,12 +277,27 @@ class CodeGroup {
275
277
  }
276
278
  }
277
279
 
278
- function initializeCodeGroups() {
279
- new CodeGroupManager();
280
+ function initializeCodeGroups(root = document) {
281
+ const containers = root.querySelectorAll('.docyard-code-group');
282
+ if (containers.length === 0) return;
283
+
284
+ if (!window.docyardCodeGroupManager) {
285
+ window.docyardCodeGroupManager = new CodeGroupManager();
286
+ } else {
287
+ containers.forEach(container => {
288
+ if (container.hasAttribute('data-code-group-initialized')) return;
289
+ container.setAttribute('data-code-group-initialized', 'true');
290
+ window.docyardCodeGroupManager.groups.push(new CodeGroup(container, window.docyardCodeGroupManager));
291
+ });
292
+ window.docyardCodeGroupManager.loadPreference();
293
+ }
280
294
  }
281
295
 
282
296
  if (document.readyState === 'loading') {
283
- document.addEventListener('DOMContentLoaded', initializeCodeGroups);
297
+ document.addEventListener('DOMContentLoaded', function() { initializeCodeGroups(); });
284
298
  } else {
285
299
  initializeCodeGroups();
286
300
  }
301
+
302
+ window.docyard = window.docyard || {};
303
+ window.docyard.initCodeGroups = initializeCodeGroups;
@@ -1,7 +1,10 @@
1
- function initializeFileTrees() {
2
- const fileTrees = document.querySelectorAll('.docyard-filetree');
1
+ function initializeFileTrees(root = document) {
2
+ const fileTrees = root.querySelectorAll('.docyard-filetree');
3
3
 
4
4
  fileTrees.forEach(tree => {
5
+ if (tree.hasAttribute('data-filetree-initialized')) return;
6
+ tree.setAttribute('data-filetree-initialized', 'true');
7
+
5
8
  const folders = tree.querySelectorAll('.docyard-filetree__item--folder');
6
9
 
7
10
  folders.forEach(folder => {
@@ -33,7 +36,10 @@ function initializeFileTrees() {
33
36
  }
34
37
 
35
38
  if (document.readyState === 'loading') {
36
- document.addEventListener('DOMContentLoaded', initializeFileTrees);
39
+ document.addEventListener('DOMContentLoaded', function() { initializeFileTrees(); });
37
40
  } else {
38
41
  initializeFileTrees();
39
42
  }
43
+
44
+ window.docyard = window.docyard || {};
45
+ window.docyard.initFileTrees = initializeFileTrees;
@@ -1,92 +1,91 @@
1
- class HeadingAnchorManager {
2
- constructor() {
3
- this.anchors = document.querySelectorAll('.heading-anchor');
4
- this.init();
5
- }
6
-
7
- init() {
8
- this.anchors.forEach(anchor => {
9
- anchor.addEventListener('click', (e) => this.handleClick(e, anchor));
10
- });
11
- }
1
+ function initializeHeadingAnchors(root = document) {
2
+ const anchors = root.querySelectorAll('.heading-anchor');
3
+ if (anchors.length === 0) return;
4
+
5
+ anchors.forEach(anchor => {
6
+ if (anchor.hasAttribute('data-anchor-initialized')) return;
7
+ anchor.setAttribute('data-anchor-initialized', 'true');
8
+ anchor.addEventListener('click', (e) => handleHeadingAnchorClick(e, anchor));
9
+ });
10
+ }
12
11
 
13
-
14
- handleClick(e, anchor) {
15
- e.preventDefault();
12
+ function handleHeadingAnchorClick(e, anchor) {
13
+ e.preventDefault();
16
14
 
17
- const headingId = anchor.dataset.headingId;
18
- const url = `${window.location.origin}${window.location.pathname}#${headingId}`;
15
+ const headingId = anchor.dataset.headingId;
16
+ const url = `${window.location.origin}${window.location.pathname}#${headingId}`;
19
17
 
20
- this.copyToClipboard(url, anchor);
18
+ copyAnchorToClipboard(url, anchor);
21
19
 
22
- history.pushState(null, null, `#${headingId}`);
20
+ history.pushState(null, null, `#${headingId}`);
23
21
 
24
- const heading = document.getElementById(headingId);
25
- if (heading) {
26
- const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - this.getScrollOffset();
27
- window.scrollTo({
28
- top: offsetTop,
29
- behavior: 'smooth'
30
- });
31
- }
22
+ const heading = document.getElementById(headingId);
23
+ if (heading) {
24
+ const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - getAnchorScrollOffset();
25
+ window.scrollTo({
26
+ top: offsetTop,
27
+ behavior: 'smooth'
28
+ });
32
29
  }
30
+ }
33
31
 
34
- getScrollOffset() {
35
- const hasTabs = document.body.classList.contains('has-tabs');
36
- const headerHeight = 64;
37
- const tabBarHeight = hasTabs ? 48 : 0;
38
- const buffer = 24;
39
-
40
- if (window.innerWidth > 1024 && window.innerWidth <= 1280) {
41
- return headerHeight + 48 + buffer;
42
- }
43
-
44
- if (window.innerWidth <= 1024) {
45
- return headerHeight + 48 + buffer;
46
- }
32
+ function getAnchorScrollOffset() {
33
+ const hasTabs = document.body.classList.contains('has-tabs');
34
+ const headerHeight = 64;
35
+ const tabBarHeight = hasTabs ? 48 : 0;
36
+ const buffer = 24;
47
37
 
48
- return headerHeight + tabBarHeight + buffer;
38
+ if (window.innerWidth > 1024 && window.innerWidth <= 1280) {
39
+ return headerHeight + 48 + buffer;
49
40
  }
50
41
 
51
-
52
- async copyToClipboard(text, anchor) {
53
- try {
54
- await navigator.clipboard.writeText(text);
55
- this.showFeedback(anchor, true);
56
- } catch (err) {
57
- this.fallbackCopyToClipboard(text);
58
- this.showFeedback(anchor, true);
59
- }
42
+ if (window.innerWidth <= 1024) {
43
+ return headerHeight + 48 + buffer;
60
44
  }
61
45
 
62
-
63
- fallbackCopyToClipboard(text) {
64
- const textarea = document.createElement('textarea');
65
- textarea.value = text;
66
- textarea.style.position = 'fixed';
67
- textarea.style.opacity = '0';
68
- document.body.appendChild(textarea);
69
- textarea.select();
70
- document.execCommand('copy');
71
- document.body.removeChild(textarea);
46
+ return headerHeight + tabBarHeight + buffer;
47
+ }
48
+
49
+ async function copyAnchorToClipboard(text, anchor) {
50
+ try {
51
+ await navigator.clipboard.writeText(text);
52
+ showAnchorFeedback(anchor, true);
53
+ } catch (err) {
54
+ fallbackAnchorCopy(text);
55
+ showAnchorFeedback(anchor, true);
72
56
  }
57
+ }
73
58
 
74
-
75
- showFeedback(anchor, success) {
76
- const originalTitle = anchor.getAttribute('aria-label');
77
- anchor.setAttribute('aria-label', success ? 'Link copied!' : 'Failed to copy');
59
+ function fallbackAnchorCopy(text) {
60
+ const textarea = document.createElement('textarea');
61
+ textarea.value = text;
62
+ textarea.style.position = 'fixed';
63
+ textarea.style.opacity = '0';
64
+ document.body.appendChild(textarea);
65
+ textarea.select();
66
+ document.execCommand('copy');
67
+ document.body.removeChild(textarea);
68
+ }
78
69
 
79
- anchor.style.color = success ? 'var(--color-success, #10b981)' : 'var(--color-danger, #ef4444)';
70
+ function showAnchorFeedback(anchor, success) {
71
+ const originalTitle = anchor.getAttribute('aria-label');
72
+ anchor.setAttribute('aria-label', success ? 'Link copied!' : 'Failed to copy');
80
73
 
81
- setTimeout(() => {
82
- anchor.setAttribute('aria-label', originalTitle);
83
- anchor.style.color = '';
84
- }, 2000);
85
- }
74
+ anchor.style.color = success ? 'var(--color-success, #10b981)' : 'var(--color-danger, #ef4444)';
75
+
76
+ setTimeout(() => {
77
+ anchor.setAttribute('aria-label', originalTitle);
78
+ anchor.style.color = '';
79
+ }, 2000);
86
80
  }
87
81
 
88
82
  if (typeof window !== 'undefined') {
89
- document.addEventListener('DOMContentLoaded', () => {
90
- new HeadingAnchorManager();
91
- });
83
+ if (document.readyState === 'loading') {
84
+ document.addEventListener('DOMContentLoaded', function() { initializeHeadingAnchors(); });
85
+ } else {
86
+ initializeHeadingAnchors();
87
+ }
88
+
89
+ window.docyard = window.docyard || {};
90
+ window.docyard.initHeadingAnchors = initializeHeadingAnchors;
92
91
  }
@@ -49,8 +49,9 @@
49
49
  }
50
50
  }
51
51
 
52
- function initLightbox() {
53
- var contentImages = document.querySelectorAll('.content img');
52
+ function initLightbox(root) {
53
+ var container = root || document;
54
+ var contentImages = container.querySelectorAll('.content img');
54
55
 
55
56
  contentImages.forEach(function(img) {
56
57
  if (img.hasAttribute('data-no-zoom')) {
@@ -58,6 +59,9 @@
58
59
  return;
59
60
  }
60
61
 
62
+ if (img.hasAttribute('data-lightbox-initialized')) return;
63
+ img.setAttribute('data-lightbox-initialized', 'true');
64
+
61
65
  img.addEventListener('click', function() {
62
66
  openLightbox(this.src, this.alt);
63
67
  });
@@ -65,8 +69,11 @@
65
69
  }
66
70
 
67
71
  if (document.readyState === 'loading') {
68
- document.addEventListener('DOMContentLoaded', initLightbox);
72
+ document.addEventListener('DOMContentLoaded', function() { initLightbox(); });
69
73
  } else {
70
74
  initLightbox();
71
75
  }
76
+
77
+ window.docyard = window.docyard || {};
78
+ window.docyard.initLightbox = initLightbox;
72
79
  })();
@@ -304,20 +304,25 @@ class TabsManager {
304
304
  }
305
305
  }
306
306
 
307
- function initializeTabs() {
308
- const tabsContainers = document.querySelectorAll('.docyard-tabs');
307
+ function initializeTabs(root = document) {
308
+ const tabsContainers = root.querySelectorAll('.docyard-tabs');
309
309
 
310
310
  tabsContainers.forEach(container => {
311
+ if (container.hasAttribute('data-tabs-initialized')) return;
312
+ container.setAttribute('data-tabs-initialized', 'true');
311
313
  new TabsManager(container);
312
314
  });
313
315
  }
314
316
 
315
317
  if (document.readyState === 'loading') {
316
- document.addEventListener('DOMContentLoaded', initializeTabs);
318
+ document.addEventListener('DOMContentLoaded', function() { initializeTabs(); });
317
319
  } else {
318
320
  initializeTabs();
319
321
  }
320
322
 
323
+ window.docyard = window.docyard || {};
324
+ window.docyard.initTabs = initializeTabs;
325
+
321
326
  if (typeof module !== 'undefined' && module.exports) {
322
327
  module.exports = { TabsManager };
323
328
  }
@@ -1,35 +1,41 @@
1
- function initializeTooltips() {
2
- const tooltips = document.querySelectorAll('.docyard-tooltip');
3
- if (tooltips.length === 0) return;
1
+ var tooltipPopover = null;
2
+ var tooltipHideTimeout = null;
3
+ var tooltipIsHoveringPopover = false;
4
4
 
5
- const popover = createTooltipPopover();
6
- document.body.appendChild(popover);
5
+ function initializeTooltips(root = document) {
6
+ const tooltips = root.querySelectorAll('.docyard-tooltip');
7
+ if (tooltips.length === 0) return;
7
8
 
8
- let hideTimeout;
9
- let isHoveringPopover = false;
9
+ if (!tooltipPopover) {
10
+ tooltipPopover = createTooltipPopover();
11
+ document.body.appendChild(tooltipPopover);
10
12
 
11
- popover.addEventListener('mouseenter', () => {
12
- isHoveringPopover = true;
13
- clearTimeout(hideTimeout);
14
- }, { passive: true });
13
+ tooltipPopover.addEventListener('mouseenter', () => {
14
+ tooltipIsHoveringPopover = true;
15
+ clearTimeout(tooltipHideTimeout);
16
+ }, { passive: true });
15
17
 
16
- popover.addEventListener('mouseleave', () => {
17
- isHoveringPopover = false;
18
- hideTimeout = setTimeout(() => {
19
- hideTooltipPopover(popover);
20
- }, 100);
21
- }, { passive: true });
18
+ tooltipPopover.addEventListener('mouseleave', () => {
19
+ tooltipIsHoveringPopover = false;
20
+ tooltipHideTimeout = setTimeout(() => {
21
+ hideTooltipPopover(tooltipPopover);
22
+ }, 100);
23
+ }, { passive: true });
24
+ }
22
25
 
23
26
  tooltips.forEach(tooltip => {
27
+ if (tooltip.hasAttribute('data-tooltip-initialized')) return;
28
+ tooltip.setAttribute('data-tooltip-initialized', 'true');
29
+
24
30
  tooltip.addEventListener('mouseenter', () => {
25
- clearTimeout(hideTimeout);
26
- showTooltipPopover(popover, tooltip);
31
+ clearTimeout(tooltipHideTimeout);
32
+ showTooltipPopover(tooltipPopover, tooltip);
27
33
  }, { passive: true });
28
34
 
29
35
  tooltip.addEventListener('mouseleave', () => {
30
- hideTimeout = setTimeout(() => {
31
- if (!isHoveringPopover) {
32
- hideTooltipPopover(popover);
36
+ tooltipHideTimeout = setTimeout(() => {
37
+ if (!tooltipIsHoveringPopover) {
38
+ hideTooltipPopover(tooltipPopover);
33
39
  }
34
40
  }, 100);
35
41
  }, { passive: true });
@@ -112,7 +118,10 @@ function hideTooltipPopover(popover) {
112
118
  }
113
119
 
114
120
  if (document.readyState === 'loading') {
115
- document.addEventListener('DOMContentLoaded', initializeTooltips);
121
+ document.addEventListener('DOMContentLoaded', function() { initializeTooltips(); });
116
122
  } else {
117
123
  initializeTooltips();
118
124
  }
125
+
126
+ window.docyard = window.docyard || {};
127
+ window.docyard.initTooltips = initializeTooltips;
@@ -10,6 +10,9 @@
10
10
  if (data.type === 'content') {
11
11
  console.log('[Docyard] Content updated');
12
12
  reloadContent();
13
+ } else if (data.type === 'css') {
14
+ console.log('[Docyard] CSS updated');
15
+ reloadStyles();
13
16
  } else {
14
17
  console.log('[Docyard] Full reload');
15
18
  location.reload();
@@ -20,6 +23,44 @@
20
23
  eventSource.close();
21
24
  };
22
25
 
26
+ function reloadStyles() {
27
+ var links = document.querySelectorAll('link[rel="stylesheet"]');
28
+ var cacheBuster = Date.now();
29
+
30
+ links.forEach(function (link) {
31
+ var href = link.getAttribute('href');
32
+ if (!href) return;
33
+
34
+ var baseHref = href.split('?')[0];
35
+ var newHref = baseHref + '?_dc=' + cacheBuster;
36
+
37
+ var newLink = document.createElement('link');
38
+ newLink.rel = 'stylesheet';
39
+ newLink.href = newHref;
40
+
41
+ newLink.onload = function () {
42
+ link.remove();
43
+ };
44
+
45
+ link.parentNode.insertBefore(newLink, link.nextSibling);
46
+ });
47
+ }
48
+
49
+ function reinitializeComponents(container) {
50
+ if (window.Prism) window.Prism.highlightAll();
51
+ if (window.docyardTOC) window.docyardTOC.init();
52
+
53
+ var docyard = window.docyard || {};
54
+ if (docyard.initTabs) docyard.initTabs(container);
55
+ if (docyard.initFileTrees) docyard.initFileTrees(container);
56
+ if (docyard.initCodeGroups) docyard.initCodeGroups(container);
57
+ if (docyard.initCodeBlocks) docyard.initCodeBlocks(container);
58
+ if (docyard.initHeadingAnchors) docyard.initHeadingAnchors(container);
59
+ if (docyard.initAbbreviations) docyard.initAbbreviations(container);
60
+ if (docyard.initLightbox) docyard.initLightbox(container);
61
+ if (docyard.initTooltips) docyard.initTooltips(container);
62
+ }
63
+
23
64
  function reloadContent() {
24
65
  var cacheBuster = '_dc=' + Date.now();
25
66
  var separator = location.href.indexOf('?') === -1 ? '?' : '&';
@@ -33,8 +74,7 @@
33
74
 
34
75
  if (newContent && currentContent) {
35
76
  currentContent.innerHTML = newContent.innerHTML;
36
- if (window.Prism) window.Prism.highlightAll();
37
- if (window.docyardTOC) window.docyardTOC.init();
77
+ reinitializeComponents(currentContent);
38
78
  updateErrorOverlay(newDoc);
39
79
  } else {
40
80
  location.reload();