@docsector/docsector-reader 0.7.2 → 0.8.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.
- package/README.md +10 -2
- package/bin/docsector.js +1 -1
- package/package.json +1 -1
- package/src/components/DPageBar.vue +54 -7
- package/src/i18n/helpers.js +10 -2
- package/src/i18n/languages/en-US.hjson +5 -1
- package/src/i18n/languages/pt-BR.hjson +5 -1
- package/src/quasar.factory.js +30 -7
package/README.md
CHANGED
|
@@ -18,13 +18,20 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
+
## 🤖 AI-Friendly Features
|
|
22
|
+
|
|
23
|
+
- 📋 **Copy Page** — One-click button copies the current page as raw Markdown, ready to paste into LLMs
|
|
24
|
+
- 📄 **View as Markdown** — Open any page as plain text by appending `.md` to the URL, with locale support (`?lang=`)
|
|
25
|
+
- 🤖 **Open in ChatGPT / Claude** — One-click links to open the current page directly in ChatGPT or Claude for Q&A
|
|
26
|
+
- 🤖 **LLM Bot Detection** — Automatically serves raw Markdown to known AI crawlers (GPTBot, ClaudeBot, PerplexityBot, GrokBot, and others)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
21
30
|
## ✨ Features
|
|
22
31
|
|
|
23
32
|
- 📝 **Markdown Rendering** — Write docs in Markdown, rendered with syntax highlighting (Prism.js)
|
|
24
33
|
- 🧩 **Mermaid Diagrams** — Native support for fenced ` ```mermaid ` blocks, with automatic dark/light theme switching
|
|
25
34
|
- 🚨 **GitHub-Style Alerts** — Native support for `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, and `[!CAUTION]`
|
|
26
|
-
- 🤖 **AI-Friendly** — "Copy page" button copies raw Markdown for LLMs; "View as Markdown" opens any page as plain text via `.md` URL suffix
|
|
27
|
-
- 📅 **Last Updated Date** — Automatic per-page "last updated" date from git commit history, locale-formatted
|
|
28
35
|
- 🌍 **Internationalization (i18n)** — Multi-language support with HJSON locale files and per-page translations
|
|
29
36
|
- 🌗 **Dark/Light Mode** — Automatic theme switching with Quasar Dark Plugin
|
|
30
37
|
- 🔗 **Anchor Navigation** — Right-side Table of Contents tree with scroll tracking
|
|
@@ -32,6 +39,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
|
|
|
32
39
|
- 📱 **Responsive** — Mobile-friendly with collapsible sidebar and drawers
|
|
33
40
|
- 🏷️ **Status Badges** — Mark pages as `done`, `draft`, or `empty` with visual indicators
|
|
34
41
|
- ✏️ **Edit on GitHub** — Direct links to edit pages on your repository
|
|
42
|
+
- 📅 **Last Updated Date** — Automatic per-page "last updated" date from git commit history, locale-formatted
|
|
35
43
|
- 📊 **Translation Progress** — Automatic translation percentage based on header coverage
|
|
36
44
|
- ⚙️ **Single Config File** — Customize branding, links, and languages via `docsector.config.js`
|
|
37
45
|
|
package/bin/docsector.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
|
|
5
5
|
"productName": "Docsector Reader",
|
|
6
6
|
"author": "Rodrigo de Araujo Vieira",
|
|
@@ -3,7 +3,7 @@ import { ref, computed } from 'vue'
|
|
|
3
3
|
import { useRoute } from 'vue-router'
|
|
4
4
|
import { useStore } from 'vuex'
|
|
5
5
|
import { useI18n } from 'vue-i18n'
|
|
6
|
-
import { copyToClipboard
|
|
6
|
+
import { copyToClipboard } from 'quasar'
|
|
7
7
|
|
|
8
8
|
import gitDates from 'virtual:docsector-git-dates'
|
|
9
9
|
|
|
@@ -13,6 +13,9 @@ const { t, locale } = useI18n()
|
|
|
13
13
|
|
|
14
14
|
const copied = ref(false)
|
|
15
15
|
|
|
16
|
+
const openaiIcon = `img:data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="%23888" d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/></svg>')}`
|
|
17
|
+
const claudeIcon = `img:data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" fill="%23888" fill-rule="evenodd" viewBox="0 0 24 24"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/></svg>')}`
|
|
18
|
+
|
|
16
19
|
const subpage = computed(() => {
|
|
17
20
|
const rel = store.state.page.relative
|
|
18
21
|
return rel ? rel.replace(/^\//, '') : 'overview'
|
|
@@ -56,6 +59,19 @@ const markdownURL = computed(() => {
|
|
|
56
59
|
return `${route.path}.md`
|
|
57
60
|
})
|
|
58
61
|
|
|
62
|
+
const fullMarkdownURL = computed(() => {
|
|
63
|
+
return `${window.location.origin}${route.path}.md`
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const chatgptURL = computed(() => {
|
|
67
|
+
const prompt = `Read ${fullMarkdownURL.value} and answer questions about the content.`
|
|
68
|
+
return `https://chat.openai.com/?q=${encodeURIComponent(prompt)}`
|
|
69
|
+
})
|
|
70
|
+
const claudeURL = computed(() => {
|
|
71
|
+
const prompt = `Read ${fullMarkdownURL.value} and answer questions about the content.`
|
|
72
|
+
return `https://claude.ai/new?q=${encodeURIComponent(prompt)}`
|
|
73
|
+
})
|
|
74
|
+
|
|
59
75
|
const copyPage = () => {
|
|
60
76
|
if (!rawMarkdown.value) return
|
|
61
77
|
|
|
@@ -64,16 +80,12 @@ const copyPage = () => {
|
|
|
64
80
|
setTimeout(() => { copied.value = false }, 2000)
|
|
65
81
|
})
|
|
66
82
|
}
|
|
67
|
-
|
|
68
|
-
const viewAsMarkdown = () => {
|
|
69
|
-
openURL(markdownURL.value)
|
|
70
|
-
}
|
|
71
83
|
</script>
|
|
72
84
|
|
|
73
85
|
<template>
|
|
74
86
|
<div class="d-page-bar">
|
|
75
87
|
<span v-if="formattedDate" class="d-page-bar__date">
|
|
76
|
-
{{ t('page.lastUpdated') }}: {{ formattedDate }}
|
|
88
|
+
{{ t('page.lastUpdated') }}: <br class="d-page-bar__date-break"> {{ formattedDate }}
|
|
77
89
|
</span>
|
|
78
90
|
<span v-else class="d-page-bar__date"></span>
|
|
79
91
|
|
|
@@ -98,7 +110,7 @@ const viewAsMarkdown = () => {
|
|
|
98
110
|
</q-item-section>
|
|
99
111
|
</q-item>
|
|
100
112
|
|
|
101
|
-
<q-item clickable v-close-popup
|
|
113
|
+
<q-item clickable v-close-popup :href="markdownURL" target="_blank" class="q-py-sm">
|
|
102
114
|
<q-item-section avatar>
|
|
103
115
|
<q-icon name="description" />
|
|
104
116
|
</q-item-section>
|
|
@@ -110,6 +122,34 @@ const viewAsMarkdown = () => {
|
|
|
110
122
|
<q-icon name="open_in_new" size="xs" />
|
|
111
123
|
</q-item-section>
|
|
112
124
|
</q-item>
|
|
125
|
+
|
|
126
|
+
<q-separator />
|
|
127
|
+
|
|
128
|
+
<q-item clickable v-close-popup :href="chatgptURL" target="_blank" class="q-py-sm">
|
|
129
|
+
<q-item-section avatar>
|
|
130
|
+
<q-icon :name="openaiIcon" size="xs" />
|
|
131
|
+
</q-item-section>
|
|
132
|
+
<q-item-section>
|
|
133
|
+
<q-item-label>{{ t('page.openInChatGPT') }}</q-item-label>
|
|
134
|
+
<q-item-label caption>{{ t('page.openInChatGPTCaption') }}</q-item-label>
|
|
135
|
+
</q-item-section>
|
|
136
|
+
<q-item-section side>
|
|
137
|
+
<q-icon name="open_in_new" size="xs" />
|
|
138
|
+
</q-item-section>
|
|
139
|
+
</q-item>
|
|
140
|
+
|
|
141
|
+
<q-item clickable v-close-popup :href="claudeURL" target="_blank" class="q-py-sm">
|
|
142
|
+
<q-item-section avatar>
|
|
143
|
+
<q-icon :name="claudeIcon" size="xs" />
|
|
144
|
+
</q-item-section>
|
|
145
|
+
<q-item-section>
|
|
146
|
+
<q-item-label>{{ t('page.openInClaude') }}</q-item-label>
|
|
147
|
+
<q-item-label caption>{{ t('page.openInClaudeCaption') }}</q-item-label>
|
|
148
|
+
</q-item-section>
|
|
149
|
+
<q-item-section side>
|
|
150
|
+
<q-icon name="open_in_new" size="xs" />
|
|
151
|
+
</q-item-section>
|
|
152
|
+
</q-item>
|
|
113
153
|
</q-list>
|
|
114
154
|
</q-btn-dropdown>
|
|
115
155
|
</div>
|
|
@@ -126,10 +166,17 @@ const viewAsMarkdown = () => {
|
|
|
126
166
|
font-size: 0.8rem
|
|
127
167
|
opacity: 0.6
|
|
128
168
|
|
|
169
|
+
&__date-break
|
|
170
|
+
display: none
|
|
171
|
+
|
|
129
172
|
&__actions
|
|
130
173
|
font-size: 0.75rem
|
|
131
174
|
|
|
132
175
|
body.body--dark
|
|
133
176
|
.d-page-bar__date
|
|
134
177
|
color: rgba(255, 255, 255, 0.7)
|
|
178
|
+
|
|
179
|
+
@media (max-width: 376px)
|
|
180
|
+
.d-page-bar__date-break
|
|
181
|
+
display: block
|
|
135
182
|
</style>
|
package/src/i18n/helpers.js
CHANGED
|
@@ -29,7 +29,11 @@ const engineDefaults = {
|
|
|
29
29
|
copyPageCaption: 'Copy page as Markdown for LLMs',
|
|
30
30
|
copied: 'Copied!',
|
|
31
31
|
viewAsMarkdown: 'View as Markdown',
|
|
32
|
-
viewAsMarkdownCaption: 'View this page as plain text'
|
|
32
|
+
viewAsMarkdownCaption: 'View this page as plain text',
|
|
33
|
+
openInChatGPT: 'Open in ChatGPT',
|
|
34
|
+
openInChatGPTCaption: 'Ask ChatGPT about this page',
|
|
35
|
+
openInClaude: 'Open in Claude',
|
|
36
|
+
openInClaudeCaption: 'Ask Claude about this page'
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
'pt-BR': {
|
|
@@ -39,7 +43,11 @@ const engineDefaults = {
|
|
|
39
43
|
copyPageCaption: 'Copiar página como Markdown para LLMs',
|
|
40
44
|
copied: 'Copiado!',
|
|
41
45
|
viewAsMarkdown: 'Ver como Markdown',
|
|
42
|
-
viewAsMarkdownCaption: 'Ver esta página como texto simples'
|
|
46
|
+
viewAsMarkdownCaption: 'Ver esta página como texto simples',
|
|
47
|
+
openInChatGPT: 'Abrir no ChatGPT',
|
|
48
|
+
openInChatGPTCaption: 'Pergunte ao ChatGPT sobre esta página',
|
|
49
|
+
openInClaude: 'Abrir no Claude',
|
|
50
|
+
openInClaudeCaption: 'Pergunte ao Claude sobre esta página'
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
}
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
copyPageCaption: 'Copy page as Markdown for LLMs',
|
|
37
37
|
copied: 'Copied!',
|
|
38
38
|
viewAsMarkdown: 'View as Markdown',
|
|
39
|
-
viewAsMarkdownCaption: 'View this page as plain text'
|
|
39
|
+
viewAsMarkdownCaption: 'View this page as plain text',
|
|
40
|
+
openInChatGPT: 'Open in ChatGPT',
|
|
41
|
+
openInChatGPTCaption: 'Ask ChatGPT about this page',
|
|
42
|
+
openInClaude: 'Open in Claude',
|
|
43
|
+
openInClaudeCaption: 'Ask Claude about this page'
|
|
40
44
|
},
|
|
41
45
|
|
|
42
46
|
menu: {
|
|
@@ -35,7 +35,11 @@
|
|
|
35
35
|
copyPageCaption: 'Copiar página como Markdown para LLMs',
|
|
36
36
|
copied: 'Copiado!',
|
|
37
37
|
viewAsMarkdown: 'Ver como Markdown',
|
|
38
|
-
viewAsMarkdownCaption: 'Ver esta página como texto simples'
|
|
38
|
+
viewAsMarkdownCaption: 'Ver esta página como texto simples',
|
|
39
|
+
openInChatGPT: 'Abrir no ChatGPT',
|
|
40
|
+
openInChatGPTCaption: 'Pergunte ao ChatGPT sobre esta página',
|
|
41
|
+
openInClaude: 'Abrir no Claude',
|
|
42
|
+
openInClaudeCaption: 'Pergunte ao Claude sobre esta página'
|
|
39
43
|
},
|
|
40
44
|
|
|
41
45
|
menu: {
|
package/src/quasar.factory.js
CHANGED
|
@@ -331,6 +331,9 @@ function createMarkdownEndpointPlugin (projectRoot) {
|
|
|
331
331
|
return null
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
// LLM bot user-agent patterns
|
|
335
|
+
const LLM_BOT_PATTERN = /GPTBot|ChatGPT-User|OAI-SearchBot|ClaudeBot|Claude-User|Claude-SearchBot|anthropic-ai|Google-Extended|Gemini-Deep-Research|PerplexityBot|Perplexity-User|Bytespider|CCBot|Meta-ExternalAgent|FacebookBot|Amazonbot|Applebot-Extended|cohere-ai|DuckAssistBot|GrokBot|AI2Bot|YouBot|PetalBot/i
|
|
336
|
+
|
|
334
337
|
return {
|
|
335
338
|
name: 'docsector-markdown-endpoint',
|
|
336
339
|
|
|
@@ -349,15 +352,35 @@ function createMarkdownEndpointPlugin (projectRoot) {
|
|
|
349
352
|
|
|
350
353
|
server.middlewares.use((req, res, next) => {
|
|
351
354
|
const url = new URL(req.url, 'http://localhost')
|
|
352
|
-
if (!url.pathname.endsWith('.md')) return next()
|
|
353
355
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
// Explicit .md request
|
|
357
|
+
if (url.pathname.endsWith('.md')) {
|
|
358
|
+
const lang = url.searchParams.get('lang') || defaultLang
|
|
359
|
+
const file = resolveMarkdownFile(url.pathname, lang)
|
|
360
|
+
if (!file) return next()
|
|
361
|
+
|
|
362
|
+
const content = readFileSync(file, 'utf-8')
|
|
363
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
364
|
+
res.end(content)
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Auto-serve markdown to LLM bot crawlers
|
|
369
|
+
const ua = req.headers['user-agent'] || ''
|
|
370
|
+
if (LLM_BOT_PATTERN.test(ua)) {
|
|
371
|
+
const lang = url.searchParams.get('lang') || defaultLang
|
|
372
|
+
// Try appending /overview as the default subpage
|
|
373
|
+
const mdPath = url.pathname.replace(/\/$/, '') + '/overview.md'
|
|
374
|
+
const file = resolveMarkdownFile(mdPath, lang)
|
|
375
|
+
if (file) {
|
|
376
|
+
const content = readFileSync(file, 'utf-8')
|
|
377
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
378
|
+
res.end(content)
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
}
|
|
357
382
|
|
|
358
|
-
|
|
359
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
360
|
-
res.end(content)
|
|
383
|
+
next()
|
|
361
384
|
})
|
|
362
385
|
},
|
|
363
386
|
|