jekyll-vitepress-theme 1.1.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b441f74c3173193c1236784b889d74d8c3d63920b8df58c7a810de72e35d2953
4
- data.tar.gz: a4ff1d25d78309553f30ea9bf660accbbdad9c4ec5001cd2e25fac63541f7911
3
+ metadata.gz: d9f21e5b46c57108b586ef2fd12ffeb0a63b67e2a8a9728814cd1b4a59a4c592
4
+ data.tar.gz: 36ab852e71ca5c212f3d4c9b0f14c5f9ad117447d79ee14fa036180f7585b6db
5
5
  SHA512:
6
- metadata.gz: b10e1e1833cac956149bb63b925027950196d9a9bd806a3a51548128083b98d5935a0f4f7da79f86117905e026c12e4b198cd4969fbc3cddd31726c91ef343c2
7
- data.tar.gz: ac38b254d36662b388cd457872d3d689f6fda67cd6ecbcb499ce00b728d1a576ee31343145d6ad87603ac038aeb633f6978257b5b39eb664c7b6e943d0772945
6
+ metadata.gz: a0835b87b12521255a8c25114be2c9f05143f0317c0eb0387864c243b3e3215980f5d91cb24583036c4acaef2d7dbc777ecde37cc450273c4530fbdcf9cb3ac7
7
+ data.tar.gz: 7238521b04688036a2d79258cecfab250b8c99ff31abe3352b2b9a6ddedaf61e45b5accfa106f54c8c5845c31d7d8802853b54a0357b077f27a1f33792ea7ba1
data/README.md CHANGED
@@ -9,10 +9,29 @@ A reusable Jekyll theme gem that reproduces the VitePress default docs experienc
9
9
  - Local search modal (`/`, `Ctrl/Cmd+K`, `Cmd+K`)
10
10
  - Optional GitHub star button with live count (`jekyll_vitepress.github_star`)
11
11
  - Code block copy button, language labels, file-title bars and icons
12
+ - Page-level "Copy page" split button with raw Markdown copy + plain `.md` view (`jekyll_vitepress.copy_page`, enabled by default)
12
13
  - Rouge-native syntax theme config (`jekyll_vitepress.syntax.light_theme/dark_theme`)
13
14
  - Last-updated hook via plugin
14
15
  - Jekyll-native extension hooks (`_includes/jekyll_vitepress/head_end.html`, `doc_footer_end.html`, `layout_end.html`)
15
16
 
17
+ ## Screenshots
18
+
19
+ ### Home (Light)
20
+
21
+ ![Home light mode](assets/images/screenshots/home-light.png)
22
+
23
+ ### Home (Dark)
24
+
25
+ ![Home dark mode](assets/images/screenshots/home-dark.png)
26
+
27
+ ### Getting Started (Light)
28
+
29
+ ![Getting Started light mode](assets/images/screenshots/getting-started-light.png)
30
+
31
+ ### Getting Started (Dark)
32
+
33
+ ![Getting Started dark mode](assets/images/screenshots/getting-started-dark.png)
34
+
16
35
  ## Installation
17
36
 
18
37
  1. Add the gem to your `Gemfile`:
@@ -0,0 +1,29 @@
1
+ {% assign view_md_url = page.url | replace: '.html', '' | append: '.md' | replace: '/.md', '.md' %}
2
+ {% if page.url == '/' %}
3
+ {% assign view_md_url = '/index.md' %}
4
+ {% endif %}
5
+ <div class="copy-md-group">
6
+ <button class="copy-md-btn" type="button" title="Copy page as Markdown">
7
+ <span class="vpi-copy"></span>
8
+ <span class="copy-md-label">Copy page</span>
9
+ </button>
10
+ <button class="copy-md-toggle" type="button" aria-label="More options" aria-expanded="false">
11
+ <span class="vpi-chevron-down copy-md-chevron"></span>
12
+ </button>
13
+ <div class="copy-md-dropdown" hidden>
14
+ <button class="copy-md-dropdown-item" type="button" data-action="copy">
15
+ <span class="vpi-copy copy-md-dropdown-icon"></span>
16
+ <div class="copy-md-dropdown-text">
17
+ <span class="copy-md-dropdown-title">Copy page</span>
18
+ <span class="copy-md-dropdown-desc">Copy page as Markdown for LLMs</span>
19
+ </div>
20
+ </button>
21
+ <a class="copy-md-dropdown-item" href="{{ view_md_url | relative_url }}" target="_blank" rel="noreferrer">
22
+ <span class="vpi-file-text copy-md-dropdown-icon"></span>
23
+ <div class="copy-md-dropdown-text">
24
+ <span class="copy-md-dropdown-title">View as Markdown</span>
25
+ <span class="copy-md-dropdown-desc">View this page as plain text</span>
26
+ </div>
27
+ </a>
28
+ </div>
29
+ </div>
@@ -61,8 +61,23 @@
61
61
  {% assign show_auto_page_title = true %}
62
62
  {% endunless %}
63
63
  {% endif %}
64
- {% if show_auto_page_title %}
65
- <h1>{{ page.title }}</h1>
64
+ {% assign copy_page_enabled = true %}
65
+ {% if theme.copy_page and theme.copy_page.enabled == false %}
66
+ {% assign copy_page_enabled = false %}
67
+ {% endif %}
68
+ {% if page_theme.copy_page == false %}
69
+ {% assign copy_page_enabled = false %}
70
+ {% endif %}
71
+ <div class="vp-doc-header">
72
+ {% if show_auto_page_title %}
73
+ <h1>{{ page.title }}</h1>
74
+ {% endif %}
75
+ {% if copy_page_enabled and page._raw_markdown and page._raw_markdown != "" %}
76
+ {% include copy_page_button.html %}
77
+ {% endif %}
78
+ </div>
79
+ {% if copy_page_enabled and page._raw_markdown and page._raw_markdown != "" %}
80
+ <textarea class="vp-raw-markdown" style="display:none" aria-hidden="true">{{ page._raw_markdown | xml_escape }}</textarea>
66
81
  {% endif %}
67
82
  {{ content }}
68
83
  </div>
@@ -87,8 +102,23 @@
87
102
  {% assign show_auto_page_title = true %}
88
103
  {% endunless %}
89
104
  {% endif %}
90
- {% if show_auto_page_title %}
91
- <h1>{{ page.title }}</h1>
105
+ {% assign copy_page_enabled = true %}
106
+ {% if theme.copy_page and theme.copy_page.enabled == false %}
107
+ {% assign copy_page_enabled = false %}
108
+ {% endif %}
109
+ {% if page_theme.copy_page == false %}
110
+ {% assign copy_page_enabled = false %}
111
+ {% endif %}
112
+ <div class="vp-doc-header">
113
+ {% if show_auto_page_title %}
114
+ <h1>{{ page.title }}</h1>
115
+ {% endif %}
116
+ {% if copy_page_enabled and page._raw_markdown and page._raw_markdown != "" %}
117
+ {% include copy_page_button.html %}
118
+ {% endif %}
119
+ </div>
120
+ {% if copy_page_enabled and page._raw_markdown and page._raw_markdown != "" %}
121
+ <textarea class="vp-raw-markdown" style="display:none" aria-hidden="true">{{ page._raw_markdown | xml_escape }}</textarea>
92
122
  {% endif %}
93
123
  {{ content }}
94
124
  </div>
@@ -1698,3 +1698,168 @@ html.dark .only-light {
1698
1698
  margin-left: 0;
1699
1699
  }
1700
1700
  }
1701
+
1702
+ /* Copy as Markdown button group */
1703
+ .vp-doc-header {
1704
+ display: flex;
1705
+ align-items: center;
1706
+ justify-content: space-between;
1707
+ gap: 12px;
1708
+ }
1709
+
1710
+ .vp-doc-header h1 {
1711
+ margin-top: 0;
1712
+ }
1713
+
1714
+ /* Copy page icon defs */
1715
+ .vpi-copy {
1716
+ --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect x='9' y='9' width='13' height='13' rx='2' ry='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/g%3E%3C/svg%3E");
1717
+ }
1718
+
1719
+ .vpi-file-text {
1720
+ --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='16' y1='13' x2='8' y2='13'/%3E%3Cline x1='16' y1='17' x2='8' y2='17'/%3E%3Cpolyline points='10 9 9 9 8 9'/%3E%3C/g%3E%3C/svg%3E");
1721
+ }
1722
+
1723
+ /* Copy page split button */
1724
+ .copy-md-group {
1725
+ position: relative;
1726
+ display: inline-flex;
1727
+ align-items: stretch;
1728
+ flex-shrink: 0;
1729
+ border: 1px solid var(--vp-c-divider);
1730
+ border-radius: 6px;
1731
+ background: var(--vp-c-bg-soft);
1732
+ transition: color 0.25s, border-color 0.25s, background-color 0.25s;
1733
+ }
1734
+
1735
+ .copy-md-group:hover {
1736
+ border-color: var(--vp-c-brand-1);
1737
+ }
1738
+
1739
+ .copy-md-btn {
1740
+ display: inline-flex;
1741
+ align-items: center;
1742
+ gap: 6px;
1743
+ padding: 4px 10px;
1744
+ border: none;
1745
+ border-radius: 6px 0 0 6px;
1746
+ background: transparent;
1747
+ color: var(--vp-c-text-2);
1748
+ font-size: 12px;
1749
+ font-weight: 600;
1750
+ cursor: pointer;
1751
+ transition: color 0.25s, background-color 0.25s;
1752
+ white-space: nowrap;
1753
+ }
1754
+
1755
+ .copy-md-btn .vpi-copy {
1756
+ width: 14px;
1757
+ height: 14px;
1758
+ }
1759
+
1760
+ .copy-md-btn:hover {
1761
+ color: var(--vp-c-text-1);
1762
+ background: var(--vp-c-bg-elv);
1763
+ }
1764
+
1765
+ .copy-md-btn.copied {
1766
+ color: var(--vp-c-green-1, #42b883);
1767
+ }
1768
+
1769
+ .copy-md-toggle {
1770
+ display: inline-flex;
1771
+ align-items: center;
1772
+ padding: 4px 6px;
1773
+ border: none;
1774
+ border-left: 1px solid var(--vp-c-divider);
1775
+ border-radius: 0 6px 6px 0;
1776
+ background: transparent;
1777
+ color: var(--vp-c-text-2);
1778
+ cursor: pointer;
1779
+ transition: color 0.25s, background-color 0.25s;
1780
+ }
1781
+
1782
+ .copy-md-toggle .vpi-chevron-down {
1783
+ width: 14px;
1784
+ height: 14px;
1785
+ }
1786
+
1787
+ .copy-md-toggle:hover {
1788
+ color: var(--vp-c-text-1);
1789
+ background: var(--vp-c-bg-elv);
1790
+ }
1791
+
1792
+ .copy-md-toggle[aria-expanded="true"] .copy-md-chevron {
1793
+ transform: rotate(180deg);
1794
+ }
1795
+
1796
+ .copy-md-chevron {
1797
+ transition: transform 0.2s;
1798
+ }
1799
+
1800
+ /* Dropdown */
1801
+ .copy-md-dropdown {
1802
+ position: absolute;
1803
+ top: calc(100% + 6px);
1804
+ right: 0;
1805
+ z-index: 10;
1806
+ min-width: 240px;
1807
+ padding: 6px;
1808
+ border: 1px solid var(--vp-c-divider);
1809
+ border-radius: 8px;
1810
+ background: var(--vp-c-bg);
1811
+ box-shadow: var(--vp-shadow-1);
1812
+ }
1813
+
1814
+ .copy-md-dropdown-item {
1815
+ display: flex;
1816
+ align-items: flex-start;
1817
+ gap: 10px;
1818
+ width: 100%;
1819
+ padding: 8px 10px;
1820
+ border: none;
1821
+ border-radius: 6px;
1822
+ background: transparent;
1823
+ color: var(--vp-c-text-1);
1824
+ text-decoration: none;
1825
+ text-align: left;
1826
+ cursor: pointer;
1827
+ transition: background-color 0.15s;
1828
+ }
1829
+
1830
+ .copy-md-dropdown-item:hover {
1831
+ background: var(--vp-c-bg-soft);
1832
+ }
1833
+
1834
+ a.copy-md-dropdown-item,
1835
+ a.copy-md-dropdown-item:hover {
1836
+ text-decoration: none;
1837
+ color: var(--vp-c-text-1);
1838
+ }
1839
+
1840
+ .copy-md-dropdown-icon {
1841
+ flex-shrink: 0;
1842
+ width: 18px;
1843
+ height: 18px;
1844
+ margin-top: 2px;
1845
+ color: var(--vp-c-text-2);
1846
+ }
1847
+
1848
+ .copy-md-dropdown-text {
1849
+ display: flex;
1850
+ flex-direction: column;
1851
+ gap: 2px;
1852
+ }
1853
+
1854
+ .copy-md-dropdown-title {
1855
+ font-size: 13px;
1856
+ font-weight: 500;
1857
+ line-height: 1.3;
1858
+ display: inline;
1859
+ }
1860
+
1861
+ .copy-md-dropdown-desc {
1862
+ font-size: 12px;
1863
+ color: var(--vp-c-text-3);
1864
+ line-height: 1.3;
1865
+ }
@@ -1163,6 +1163,83 @@
1163
1163
 
1164
1164
  addCopyButtons();
1165
1165
 
1166
+ // Copy page as Markdown - split button with dropdown
1167
+ function copyPageMarkdown(btn) {
1168
+ var textarea = document.querySelector('.vp-raw-markdown');
1169
+ if (!textarea) return;
1170
+
1171
+ var md = textarea.value;
1172
+ if (!/^#\s/.test(md.trim())) {
1173
+ var header = document.querySelector('.vp-doc-header');
1174
+ var autoH1 = header && header.querySelector('h1');
1175
+ if (autoH1) {
1176
+ var titleText = autoH1.textContent.trim();
1177
+ if (titleText) {
1178
+ md = '# ' + titleText + '\n\n' + md;
1179
+ }
1180
+ }
1181
+ }
1182
+
1183
+ var label = btn.querySelector('.copy-md-label');
1184
+ writeToClipboard(md)
1185
+ .then(function () {
1186
+ btn.classList.add('copied');
1187
+ if (label) label.textContent = 'Copied';
1188
+ if (btn._copyTimeout) window.clearTimeout(btn._copyTimeout);
1189
+ btn._copyTimeout = window.setTimeout(function () {
1190
+ btn.classList.remove('copied');
1191
+ if (label) label.textContent = 'Copy page';
1192
+ }, 2000);
1193
+ })
1194
+ .catch(function () {});
1195
+ }
1196
+
1197
+ var copyMdBtn = document.querySelector('.copy-md-btn');
1198
+ if (copyMdBtn) {
1199
+ copyMdBtn.addEventListener('click', function () {
1200
+ copyPageMarkdown(copyMdBtn);
1201
+ });
1202
+ }
1203
+
1204
+ // Dropdown toggle
1205
+ var toggle = document.querySelector('.copy-md-toggle');
1206
+ var dropdown = document.querySelector('.copy-md-dropdown');
1207
+ if (toggle && dropdown) {
1208
+ toggle.addEventListener('click', function (e) {
1209
+ e.stopPropagation();
1210
+ var open = !dropdown.hidden;
1211
+ dropdown.hidden = !dropdown.hidden;
1212
+ toggle.setAttribute('aria-expanded', !open);
1213
+ });
1214
+
1215
+ // Copy from dropdown item
1216
+ var dropdownCopy = dropdown.querySelector('[data-action="copy"]');
1217
+ if (dropdownCopy) {
1218
+ dropdownCopy.addEventListener('click', function () {
1219
+ dropdown.hidden = true;
1220
+ toggle.setAttribute('aria-expanded', 'false');
1221
+ copyPageMarkdown(copyMdBtn);
1222
+ });
1223
+ }
1224
+
1225
+ // Close on outside click
1226
+ document.addEventListener('click', function (e) {
1227
+ if (!dropdown.hidden && !dropdown.contains(e.target) && !toggle.contains(e.target)) {
1228
+ dropdown.hidden = true;
1229
+ toggle.setAttribute('aria-expanded', 'false');
1230
+ }
1231
+ });
1232
+
1233
+ // Close on Escape
1234
+ document.addEventListener('keydown', function (e) {
1235
+ if (e.key === 'Escape' && !dropdown.hidden) {
1236
+ dropdown.hidden = true;
1237
+ toggle.setAttribute('aria-expanded', 'false');
1238
+ toggle.focus();
1239
+ }
1240
+ });
1241
+ }
1242
+
1166
1243
  var content = document.querySelector('.vp-doc');
1167
1244
  if (!content) {
1168
1245
  return;
@@ -113,6 +113,18 @@ Jekyll::Hooks.register :site, :post_read do |site|
113
113
  end
114
114
 
115
115
  Jekyll::Hooks.register :documents, :pre_render do |document|
116
+ theme_config = document.site.config['jekyll_vitepress']
117
+ copy_page_disabled = theme_config.is_a?(Hash) &&
118
+ theme_config['copy_page'].is_a?(Hash) &&
119
+ theme_config['copy_page']['enabled'] == false
120
+
121
+ unless copy_page_disabled
122
+ page_theme = document.data['jekyll_vitepress']
123
+ copy_page_disabled = page_theme == false || (page_theme.is_a?(Hash) && page_theme['copy_page'] == false)
124
+ end
125
+
126
+ document.data['_raw_markdown'] = document.content unless copy_page_disabled
127
+
116
128
  next if document.data.key?('last_updated_at')
117
129
 
118
130
  updated_at = Jekyll::VitePressTheme::LastUpdated.source_file_time(document.site, document.path)
@@ -120,8 +132,53 @@ Jekyll::Hooks.register :documents, :pre_render do |document|
120
132
  end
121
133
 
122
134
  Jekyll::Hooks.register :pages, :pre_render do |page|
135
+ theme_config = page.site.config['jekyll_vitepress']
136
+ copy_page_disabled = theme_config.is_a?(Hash) &&
137
+ theme_config['copy_page'].is_a?(Hash) &&
138
+ theme_config['copy_page']['enabled'] == false
139
+
140
+ unless copy_page_disabled
141
+ page_theme = page.data['jekyll_vitepress']
142
+ copy_page_disabled = page_theme == false || (page_theme.is_a?(Hash) && page_theme['copy_page'] == false)
143
+ end
144
+
145
+ page.data['_raw_markdown'] = page.content unless copy_page_disabled
146
+
123
147
  next if page.data.key?('last_updated_at')
124
148
 
125
149
  updated_at = Jekyll::VitePressTheme::LastUpdated.source_file_time(page.site, page.path)
126
150
  page.data['last_updated_at'] = updated_at if updated_at
127
151
  end
152
+
153
+ # Write raw .md files after site build using the same content as "Copy page"
154
+ Jekyll::Hooks.register :site, :post_write do |site|
155
+ theme_config = site.config['jekyll_vitepress']
156
+ if theme_config.is_a?(Hash) &&
157
+ theme_config['copy_page'].is_a?(Hash) &&
158
+ theme_config['copy_page']['enabled'] == false
159
+ next
160
+ end
161
+
162
+ items = site.pages.select { |p| p.output_ext == '.html' } +
163
+ site.collections.values.flat_map(&:docs)
164
+
165
+ items.each do |item|
166
+ raw = item.data['_raw_markdown']
167
+ next if raw.nil? || raw.empty?
168
+
169
+ base_path = item.url.sub(/\.html$/, '').sub(%r{/$}, '')
170
+ md_path = "#{base_path}.md"
171
+ md_path = '/index.md' if item.url == '/'
172
+
173
+ title = item.data['title']
174
+ body = if title && !title.empty? && !raw.strip.start_with?('# ')
175
+ "# #{title}\n\n#{raw}"
176
+ else
177
+ raw
178
+ end
179
+
180
+ dest = File.join(site.dest, md_path)
181
+ FileUtils.mkdir_p(File.dirname(dest))
182
+ File.write(dest, body)
183
+ end
184
+ end
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module VitePressTheme
3
- VERSION = "1.1.1".freeze
3
+ VERSION = "1.2.0".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-vitepress-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-19 00:00:00.000000000 Z
11
+ date: 2026-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -41,6 +41,7 @@ files:
41
41
  - LICENSE
42
42
  - README.md
43
43
  - _includes/alert.html
44
+ - _includes/copy_page_button.html
44
45
  - _includes/doc_footer.html
45
46
  - _includes/head.html
46
47
  - _includes/home.html
@@ -89,6 +90,10 @@ files:
89
90
  - assets/images/file-icons/yaml-dark.svg
90
91
  - assets/images/file-icons/yaml.svg
91
92
  - assets/images/logo.svg
93
+ - assets/images/screenshots/getting-started-dark.png
94
+ - assets/images/screenshots/getting-started-light.png
95
+ - assets/images/screenshots/home-dark.png
96
+ - assets/images/screenshots/home-light.png
92
97
  - assets/images/social-icons/bitbucket.svg
93
98
  - assets/images/social-icons/bluesky.svg
94
99
  - assets/images/social-icons/devdotto.svg