@ampernic/vitepress-theme-alt-docs 0.1.15 → 0.1.18

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.
@@ -0,0 +1,228 @@
1
+ <template>
2
+ <div v-if="exports.length > 0" class="ad-export" ref="rootRef">
3
+ <p class="ad-export-label">Скачать документацию</p>
4
+ <div class="ad-export-dropdown">
5
+ <button
6
+ class="ad-export-trigger"
7
+ :class="{ 'is-open': isOpen }"
8
+ @click="isOpen = !isOpen"
9
+ :aria-expanded="isOpen"
10
+ >
11
+ <svg class="ad-export-dl-icon" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
12
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
13
+ <polyline points="7 10 12 15 17 10"/>
14
+ <line x1="12" y1="15" x2="12" y2="3"/>
15
+ </svg>
16
+ Скачать
17
+ <svg class="ad-export-chevron" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
18
+ <polyline points="6 9 12 15 18 9"/>
19
+ </svg>
20
+ </button>
21
+
22
+ <Transition name="ad-dropdown">
23
+ <div v-show="isOpen" class="ad-export-menu">
24
+ <a
25
+ v-for="item in exports"
26
+ :key="item.format"
27
+ :href="item.url"
28
+ :download="item.filename"
29
+ :title="item.title"
30
+ class="ad-export-option"
31
+ @click="isOpen = false"
32
+ >
33
+ <span class="ad-export-option-icon" v-html="item.icon" />
34
+ <span>{{ item.label }}</span>
35
+ </a>
36
+ </div>
37
+ </Transition>
38
+ </div>
39
+ </div>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
44
+ import { useData, useRoute } from 'vitepress'
45
+
46
+ const { site } = useData()
47
+ const route = useRoute()
48
+
49
+ interface ExportItem { format: string; url: string; filename: string; label: string; title: string; icon: string }
50
+
51
+ const manifest = ref<Record<string, { pdf?: string; epub?: string; html?: string }> | null>(null)
52
+ // Base prefix that actually served the manifest — may differ from site.base in vitepress preview
53
+ const resolvedBase = ref('')
54
+ const isOpen = ref(false)
55
+ const rootRef = ref<HTMLElement | null>(null)
56
+
57
+ onMounted(async () => {
58
+ try {
59
+ const base = site.value.base.replace(/\/$/, '')
60
+ let res = await fetch(`${base}/export-manifest.json`)
61
+ if (res.ok) {
62
+ resolvedBase.value = base
63
+ } else if (base) {
64
+ // vitepress preview strips base from file paths — try without prefix
65
+ res = await fetch('/export-manifest.json')
66
+ if (res.ok) resolvedBase.value = ''
67
+ }
68
+ if (res.ok) {
69
+ const data = await res.json()
70
+ manifest.value = data.versions ?? null
71
+ }
72
+ } catch { /* manifest absent — silent */ }
73
+
74
+ document.addEventListener('click', onClickOutside)
75
+ })
76
+
77
+ onBeforeUnmount(() => {
78
+ document.removeEventListener('click', onClickOutside)
79
+ })
80
+
81
+ function onClickOutside(e: MouseEvent) {
82
+ if (rootRef.value && !rootRef.value.contains(e.target as Node)) {
83
+ isOpen.value = false
84
+ }
85
+ }
86
+
87
+ const currentVersion = computed(() => {
88
+ const base = site.value.base
89
+ const p = route.path
90
+ const contentPath = base.length > 1 && p.startsWith(base) ? p.slice(base.length - 1) : p
91
+ const parts = contentPath.split('/').filter(Boolean)
92
+ return parts[0] && /^\d+\.\d+/.test(parts[0]) ? parts[0] : null
93
+ })
94
+
95
+ const exports = computed((): ExportItem[] => {
96
+ if (!currentVersion.value || !manifest.value) return []
97
+ const vEntry = manifest.value[currentVersion.value]
98
+ if (!vEntry) return []
99
+
100
+ const items: ExportItem[] = []
101
+
102
+ if (vEntry.pdf) {
103
+ items.push({
104
+ format: 'pdf',
105
+ url: `${resolvedBase.value}/${vEntry.pdf}`,
106
+ filename: vEntry.pdf,
107
+ label: 'PDF',
108
+ title: `Скачать версию ${currentVersion.value} в формате PDF`,
109
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>',
110
+ })
111
+ }
112
+
113
+ return items
114
+ })
115
+ </script>
116
+
117
+ <style scoped>
118
+ .ad-export {
119
+ padding: 16px 0 8px;
120
+ border-top: 1px solid var(--vp-c-divider);
121
+ margin-top: 8px;
122
+ }
123
+
124
+ .ad-export-label {
125
+ font-size: 12px;
126
+ font-weight: 600;
127
+ color: var(--vp-c-text-2);
128
+ text-transform: uppercase;
129
+ letter-spacing: 0.04em;
130
+ margin-bottom: 8px;
131
+ }
132
+
133
+ .ad-export-dropdown {
134
+ position: relative;
135
+ display: block;
136
+ width: 100%;
137
+ }
138
+
139
+ .ad-export-trigger {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ gap: 5px;
144
+ padding: 0 12px;
145
+ width: 100%;
146
+ height: 34px;
147
+ font-size: 13px;
148
+ font-weight: 500;
149
+ color: var(--vp-c-text-1);
150
+ background: var(--vp-c-default-soft);
151
+ border: 1px solid var(--vp-c-divider);
152
+ border-radius: 4px;
153
+ cursor: pointer;
154
+ transition: border-color 0.25s, background-color 0.25s, color 0.25s;
155
+ white-space: nowrap;
156
+ user-select: none;
157
+ }
158
+
159
+ .ad-export-trigger:hover,
160
+ .ad-export-trigger.is-open {
161
+ background: var(--vp-c-default-soft);
162
+ border-color: var(--vp-c-brand-1);
163
+ color: var(--vp-c-brand-1);
164
+ }
165
+
166
+ .ad-export-dl-icon {
167
+ flex-shrink: 0;
168
+ }
169
+
170
+ .ad-export-chevron {
171
+ flex-shrink: 0;
172
+ transition: transform 0.2s;
173
+ }
174
+
175
+ .ad-export-trigger.is-open .ad-export-chevron {
176
+ transform: rotate(180deg);
177
+ }
178
+
179
+ .ad-export-menu {
180
+ position: absolute;
181
+ top: calc(100% + 6px);
182
+ left: 0;
183
+ right: 0;
184
+ z-index: 100;
185
+ min-width: 130px;
186
+ background: var(--vp-c-bg-elv);
187
+ border: 1px solid var(--vp-c-divider);
188
+ border-radius: 8px;
189
+ box-shadow: var(--vp-shadow-3);
190
+ overflow: hidden;
191
+ padding: 4px;
192
+ }
193
+
194
+ .ad-export-option {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 8px;
198
+ padding: 6px 10px;
199
+ font-size: 13px;
200
+ font-weight: 500;
201
+ color: var(--vp-c-text-1);
202
+ border-radius: 5px;
203
+ text-decoration: none;
204
+ transition: background-color 0.15s, color 0.15s;
205
+ }
206
+
207
+ .ad-export-option:hover {
208
+ background: var(--vp-c-default-soft);
209
+ color: var(--vp-c-brand-1);
210
+ }
211
+
212
+ .ad-export-option-icon {
213
+ display: flex;
214
+ align-items: center;
215
+ flex-shrink: 0;
216
+ }
217
+
218
+ .ad-dropdown-enter-active,
219
+ .ad-dropdown-leave-active {
220
+ transition: opacity 0.15s ease, transform 0.15s ease;
221
+ }
222
+
223
+ .ad-dropdown-enter-from,
224
+ .ad-dropdown-leave-to {
225
+ opacity: 0;
226
+ transform: translateY(-4px);
227
+ }
228
+ </style>
@@ -1,6 +1,7 @@
1
1
  import { DefaultTheme } from 'vitepress';
2
2
  import type { SectionInfo } from '@ampernic/vitepress-plugin-alt-docs-versioning';
3
- export type { SectionInfo };
3
+ import type { ExportPluginOptions } from '@ampernic/vitepress-plugin-export';
4
+ export type { SectionInfo, ExportPluginOptions };
4
5
  export interface SharedConfigOptions {
5
6
  /** Distro slug for this VitePress instance, e.g. `'alt-domain'`. */
6
7
  distroName: string;
@@ -27,6 +28,11 @@ export interface SharedConfigOptions {
27
28
  * @default 'https://altlinux.space/alt-docs/docs-vitepress'
28
29
  */
29
30
  editLinkRepo?: string;
31
+ /**
32
+ * Export plugin options. When provided, generates per-version export bundles
33
+ * (PDF, etc.) after the build and writes `export-manifest.json` to the dist dir.
34
+ */
35
+ export?: ExportPluginOptions;
30
36
  }
31
37
  /**
32
38
  * Creates the shared VitePress config for a single-distro instance.
@@ -11,6 +11,7 @@ var _markdownItKbd = _interopRequireDefault(require("markdown-it-kbd"));
11
11
  var _vitepressPluginAltDocsVersioning = require("@ampernic/vitepress-plugin-alt-docs-versioning");
12
12
  var _vitepressPluginHtmlImage = require("@ampernic/vitepress-plugin-html-image");
13
13
  var _vitepressPluginPagefind = require("@ampernic/vitepress-plugin-pagefind");
14
+ var _vitepressPluginExport = require("@ampernic/vitepress-plugin-export");
14
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
16
  function createSharedConfig(options) {
16
17
  const base = process.env.VITE_BASE ?? "/";
@@ -23,8 +24,13 @@ function createSharedConfig(options) {
23
24
  } : null;
24
25
  }
25
26
  });
27
+ const exportInst = options.export ? (0, _vitepressPluginExport.ExportPlugin)(options.export) : null;
26
28
  return (0, _vitepress.defineConfigWithTheme)({
27
29
  ...pagefind,
30
+ buildEnd: async () => {
31
+ await pagefind.buildEnd();
32
+ await exportInst?.buildEnd();
33
+ },
28
34
  vue: {
29
35
  template: {
30
36
  transformAssetUrls: {
@@ -45,9 +51,9 @@ function createSharedConfig(options) {
45
51
  exclude: ["@nolebase/vitepress-plugin-enhanced-readabilities/client", "vitepress", "@nolebase/ui", "@ampernic/vitepress-theme-alt-docs"]
46
52
  },
47
53
  ssr: {
48
- noExternal: ["@nolebase/vitepress-plugin-enhanced-readabilities", "@nolebase/ui", "@ampernic/vitepress-plugin-alt-docs-versioning", "@ampernic/vitepress-theme-alt-docs"]
54
+ noExternal: ["@nolebase/vitepress-plugin-enhanced-readabilities", "@nolebase/ui", "@ampernic/vitepress-plugin-alt-docs-versioning", "@ampernic/vitepress-theme-alt-docs", "@ampernic/vitepress-plugin-export"]
49
55
  },
50
- plugins: [...pagefind.vite.plugins, (0, _vitepressPluginAltDocsVersioning.VersioningPlugin)({
56
+ plugins: [...pagefind.vite.plugins, ...(exportInst?.vite.plugins ?? []), (0, _vitepressPluginAltDocsVersioning.VersioningPlugin)({
51
57
  distroName: options.distroName,
52
58
  allDistros: options.allDistros,
53
59
  sections: options.sections
@@ -5,6 +5,7 @@ import markdownItKbd from "markdown-it-kbd";
5
5
  import { VersioningPlugin } from "@ampernic/vitepress-plugin-alt-docs-versioning";
6
6
  import { htmlImagePlugin } from "@ampernic/vitepress-plugin-html-image";
7
7
  import { PagefindPlugin } from "@ampernic/vitepress-plugin-pagefind";
8
+ import { ExportPlugin } from "@ampernic/vitepress-plugin-export";
8
9
  export function createSharedConfig(options) {
9
10
  const base = process.env.VITE_BASE ?? "/";
10
11
  const pagefind = PagefindPlugin({
@@ -14,8 +15,13 @@ export function createSharedConfig(options) {
14
15
  return m ? { version: m[1] } : null;
15
16
  }
16
17
  });
18
+ const exportInst = options.export ? ExportPlugin(options.export) : null;
17
19
  return defineConfigWithTheme({
18
20
  ...pagefind,
21
+ buildEnd: async () => {
22
+ await pagefind.buildEnd();
23
+ await exportInst?.buildEnd();
24
+ },
19
25
  vue: {
20
26
  template: {
21
27
  transformAssetUrls: {
@@ -45,11 +51,13 @@ export function createSharedConfig(options) {
45
51
  "@nolebase/vitepress-plugin-enhanced-readabilities",
46
52
  "@nolebase/ui",
47
53
  "@ampernic/vitepress-plugin-alt-docs-versioning",
48
- "@ampernic/vitepress-theme-alt-docs"
54
+ "@ampernic/vitepress-theme-alt-docs",
55
+ "@ampernic/vitepress-plugin-export"
49
56
  ]
50
57
  },
51
58
  plugins: [
52
59
  ...pagefind.vite.plugins,
60
+ ...exportInst?.vite.plugins ?? [],
53
61
  VersioningPlugin({
54
62
  distroName: options.distroName,
55
63
  allDistros: options.allDistros,
package/dist/index.mjs CHANGED
@@ -18,6 +18,7 @@ import ADProductsSidebar from "./components/ADProductsSidebar.vue";
18
18
  import ADInlineImage from "./components/ADInlineImage.vue";
19
19
  import ADAssetLink from "./components/ADAssetLink.vue";
20
20
  import ADNavBarSearch from "./components/ADNavBarSearch.vue";
21
+ import ExportButton from "@ampernic/vitepress-plugin-export/components/ExportButton.vue";
21
22
  import "./styles/theme.css";
22
23
  import "./styles/custom.css";
23
24
  import "./styles/icons.css";
@@ -27,7 +28,8 @@ export const Theme = {
27
28
  "nav-bar-content-before": () => h(ADNavBarSearch),
28
29
  "nav-bar-content-after": () => h(NolebaseEnhancedReadabilitiesMenu),
29
30
  "nav-screen-content-after": () => h(NolebaseEnhancedReadabilitiesScreenMenu),
30
- "sidebar-nav-before": () => h(ADProductsSidebar)
31
+ "sidebar-nav-before": () => h(ADProductsSidebar),
32
+ "aside-outline-after": () => h(ExportButton)
31
33
  }),
32
34
  enhanceApp({ app, router, siteData }) {
33
35
  installCrossSiteRouter(router, siteData);
@@ -10,10 +10,10 @@
10
10
  .VPSidebar,
11
11
  .VPSocialLink,
12
12
  .vp-sponsor,
13
-
13
+
14
14
  .products-menu,
15
15
  .versioning-menu,
16
-
16
+
17
17
  .VPDoc .aside,
18
18
  .VPDoc .aside-container {
19
19
  display: none !important;
@@ -33,4 +33,22 @@
33
33
  display: block !important;
34
34
  visibility: visible !important;
35
35
  }
36
+
37
+ /* Keep blocks and elements from splitting across pages */
38
+ .custom-block,
39
+ .custom-block-title + p,
40
+ pre,
41
+ blockquote,
42
+ figure,
43
+ table,
44
+ img {
45
+ break-inside: avoid;
46
+ page-break-inside: avoid;
47
+ }
48
+
49
+ /* Keep heading with its following content */
50
+ h1, h2, h3, h4, h5, h6 {
51
+ break-after: avoid;
52
+ page-break-after: avoid;
53
+ }
36
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampernic/vitepress-theme-alt-docs",
3
- "version": "0.1.15",
3
+ "version": "0.1.18",
4
4
  "description": "Shared VitePress theme for ALT Linux documentation",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Ampernic",
@@ -34,9 +34,10 @@
34
34
  "markdown-it-kbd": "^1.0.0",
35
35
  "vitepress-plugin-tabs": "^0.6.0",
36
36
  "@ampernic/vitepress-plugin-alt-docs-versioning": "0.1.5",
37
+ "@ampernic/vitepress-plugin-export": "0.1.17",
38
+ "@ampernic/vitepress-plugin-pagefind": "0.1.8",
37
39
  "@ampernic/vitepress-plugin-cross-site-router": "0.1.2",
38
- "@ampernic/vitepress-plugin-html-image": "0.1.2",
39
- "@ampernic/vitepress-plugin-pagefind": "0.1.8"
40
+ "@ampernic/vitepress-plugin-html-image": "0.1.2"
40
41
  },
41
42
  "devDependencies": {
42
43
  "builtin-modules": "^3.3.0",