@docsector/docsector-reader 0.10.5 β†’ 0.12.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 CHANGED
@@ -38,7 +38,7 @@ Transform Markdown content into beautiful, navigable documentation sites β€” wit
38
38
  - 🚨 **GitHub-Style Alerts** β€” Native support for `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, and `[!CAUTION]`
39
39
  - 🌍 **Internationalization (i18n)** β€” Multi-language support with HJSON locale files and per-page translations
40
40
  - πŸŒ— **Dark/Light Mode** β€” Automatic theme switching with Quasar Dark Plugin
41
- - πŸ”— **Anchor Navigation** β€” Right-side Table of Contents tree with scroll tracking
41
+ - πŸ”— **Anchor Navigation** β€” Right-side Table of Contents tree with scroll tracking and auto-scroll to active section
42
42
  - πŸ”Ž **Search** β€” Menu search across all documentation content and tags
43
43
  - πŸ“± **Responsive** β€” Mobile-friendly with collapsible sidebar and drawers
44
44
  - 🏷️ **Status Badges** β€” Mark pages as `done`, `draft`, or `empty` with visual indicators
package/bin/docsector.js CHANGED
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
23
23
  const args = process.argv.slice(2)
24
24
  const command = args[0]
25
25
 
26
- const VERSION = '0.10.5'
26
+ const VERSION = '0.12.0'
27
27
 
28
28
  const HELP = `
29
29
  Docsector Reader v${VERSION}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "0.10.5",
3
+ "version": "0.12.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",
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
- import { computed, onMounted, onBeforeUnmount } from 'vue'
2
+ import { computed, ref, watch, onMounted, onBeforeUnmount } from 'vue'
3
3
  import { useStore } from 'vuex'
4
- import { useQuasar } from 'quasar'
4
+ import { useQuasar, scroll as quasarScroll } from 'quasar'
5
5
  import { useRoute } from "vue-router";
6
6
 
7
7
  import useNavigator from '../composables/useNavigator'
@@ -11,6 +11,8 @@ const $q = useQuasar()
11
11
  const route = useRoute()
12
12
  const { navigate, anchor, selected: navigatorSelected } = useNavigator()
13
13
 
14
+ const scrolling = ref(null)
15
+
14
16
  const nodes = computed(() => store.getters['page/nodes'])
15
17
  const expanded = computed({
16
18
  get() {
@@ -45,6 +47,34 @@ const stylize = computed(() => {
45
47
  }
46
48
  })
47
49
 
50
+ const scrollToActiveAnchor = () => {
51
+ if (scrolling.value) {
52
+ clearTimeout(scrolling.value)
53
+ }
54
+
55
+ scrolling.value = setTimeout(() => {
56
+ const anchorEl = document.getElementById('anchor')
57
+ if (anchorEl) {
58
+ const activeNode = anchorEl.querySelector('.q-tree__node--selected')
59
+ if (activeNode && typeof activeNode === 'object') {
60
+ const target = quasarScroll.getScrollTarget(activeNode)
61
+ const offsetTop = activeNode.offsetTop
62
+ const innerHeightBy2 = window.innerHeight / 2
63
+ const offset = offsetTop - innerHeightBy2
64
+
65
+ if (offset > 0) {
66
+ quasarScroll.setVerticalScrollPosition(target, offset, 300)
67
+ }
68
+ }
69
+ }
70
+ scrolling.value = null
71
+ }, 300)
72
+ }
73
+
74
+ watch(selected, () => {
75
+ scrollToActiveAnchor()
76
+ })
77
+
48
78
  onMounted(() => {
49
79
  store.commit('layout/setMetaToggle', true)
50
80
 
@@ -61,6 +91,10 @@ onMounted(() => {
61
91
  })
62
92
 
63
93
  onBeforeUnmount(() => {
94
+ if (scrolling.value) {
95
+ clearTimeout(scrolling.value)
96
+ }
97
+
64
98
  store.commit('layout/setMetaToggle', false)
65
99
 
66
100
  store.commit('page/resetAnchor')
@@ -247,11 +247,115 @@ function createGitDatesPlugin (projectRoot) {
247
247
  const resolvedId = '\0' + virtualId
248
248
  let dates = {}
249
249
 
250
- function collectDates () {
251
- dates = {}
252
- const pagesDir = resolve(projectRoot, 'src', 'pages')
253
- if (!existsSync(pagesDir)) return
250
+ /**
251
+ * Try to unshallow the repository so `git log` returns real commit dates
252
+ * instead of the single clone-time date that shallow CI clones produce.
253
+ */
254
+ function tryUnshallow () {
255
+ try {
256
+ const isShallow = execSync(
257
+ 'git rev-parse --is-shallow-repository',
258
+ { cwd: projectRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
259
+ ).trim()
260
+
261
+ if (isShallow === 'true') {
262
+ console.log('[docsector] Shallow repository detected β€” fetching full history…')
263
+ execSync('git fetch --unshallow', {
264
+ cwd: projectRoot,
265
+ encoding: 'utf-8',
266
+ stdio: ['pipe', 'pipe', 'pipe'],
267
+ timeout: 60_000
268
+ })
269
+ console.log('[docsector] Repository unshallowed successfully.')
270
+ return true
271
+ }
272
+ } catch {
273
+ console.warn('[docsector] Could not unshallow repository β€” will try GitHub API fallback.')
274
+ return false
275
+ }
276
+ return true // not shallow, full history available
277
+ }
278
+
279
+ /**
280
+ * Extract the GitHub `owner/repo` slug from the consumer's docsector.config.js.
281
+ * Looks for `github.editBaseUrl` (e.g. "https://github.com/bootgly/bootgly_docs/edit/…")
282
+ * or an explicit `github.repo` field (e.g. "bootgly/bootgly_docs").
283
+ */
284
+ function getGitHubRepo () {
285
+ try {
286
+ const configPath = resolve(projectRoot, 'docsector.config.js')
287
+ if (!existsSync(configPath)) return null
288
+
289
+ const src = readFileSync(configPath, 'utf-8')
290
+
291
+ // Explicit repo field: repo: 'owner/repo'
292
+ const repoMatch = src.match(/repo\s*:\s*['"]([^'"]+)['"]/)
293
+ if (repoMatch) return repoMatch[1]
294
+
295
+ // Derive from editBaseUrl: https://github.com/<owner>/<repo>/edit/…
296
+ const urlMatch = src.match(/editBaseUrl\s*:\s*['"]https:\/\/github\.com\/([^/]+\/[^/]+)\//)
297
+ if (urlMatch) return urlMatch[1]
298
+ } catch { /* ignore */ }
299
+ return null
300
+ }
301
+
302
+ /**
303
+ * Fetch last-commit dates from the GitHub API for all .md files under src/pages/.
304
+ * Uses the unauthenticated commits endpoint (60 req/hr) or authenticated if
305
+ * GITHUB_TOKEN is set (5 000 req/hr).
306
+ */
307
+ async function collectDatesFromGitHub (pagesDir, repo) {
308
+ console.log(`[docsector] Fetching file dates from GitHub API (${repo})…`)
309
+
310
+ const headers = { 'User-Agent': 'docsector-reader', Accept: 'application/vnd.github+json' }
311
+ const token = process.env.GITHUB_TOKEN
312
+ if (token) headers.Authorization = `Bearer ${token}`
313
+
314
+ const walkDir = (dir) => {
315
+ const entries = readdirSync(dir, { withFileTypes: true })
316
+ let files = []
317
+ for (const entry of entries) {
318
+ const fullPath = resolve(dir, entry.name)
319
+ if (entry.isDirectory()) {
320
+ files = files.concat(walkDir(fullPath))
321
+ } else if (entry.name.endsWith('.md')) {
322
+ files.push(fullPath)
323
+ }
324
+ }
325
+ return files
326
+ }
254
327
 
328
+ const files = walkDir(pagesDir)
329
+ let fetched = 0
330
+
331
+ for (const fullPath of files) {
332
+ const relKey = fullPath.slice(pagesDir.length + 1)
333
+ const apiPath = `src/pages/${relKey}`
334
+ const url = `https://api.github.com/repos/${repo}/commits?path=${encodeURIComponent(apiPath)}&per_page=1`
335
+
336
+ try {
337
+ const res = await fetch(url, { headers })
338
+ if (!res.ok) {
339
+ if (res.status === 403 || res.status === 429) {
340
+ console.warn('[docsector] GitHub API rate limit reached β€” stopping.')
341
+ break
342
+ }
343
+ continue
344
+ }
345
+ const commits = await res.json()
346
+ if (commits.length > 0 && commits[0].commit) {
347
+ dates[relKey] = commits[0].commit.committer.date
348
+ fetched++
349
+ }
350
+ } catch {
351
+ // Network error β€” skip this file
352
+ }
353
+ }
354
+
355
+ console.log(`[docsector] Fetched dates for ${fetched}/${files.length} files from GitHub API.`)
356
+ }
357
+
358
+ function collectDatesFromGit (pagesDir) {
255
359
  const walkDir = (dir) => {
256
360
  const entries = readdirSync(dir, { withFileTypes: true })
257
361
  for (const entry of entries) {
@@ -265,7 +369,6 @@ function createGitDatesPlugin (projectRoot) {
265
369
  { cwd: projectRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
266
370
  ).trim()
267
371
  if (date) {
268
- // Key relative to src/pages/, e.g. "manual/Bootgly/about/what.overview.en-US.md"
269
372
  const relKey = fullPath.slice(pagesDir.length + 1)
270
373
  dates[relKey] = date
271
374
  }
@@ -279,10 +382,33 @@ function createGitDatesPlugin (projectRoot) {
279
382
  walkDir(pagesDir)
280
383
  }
281
384
 
385
+ async function collectDates () {
386
+ dates = {}
387
+ const pagesDir = resolve(projectRoot, 'src', 'pages')
388
+ if (!existsSync(pagesDir)) return
389
+
390
+ // 1. Try to unshallow so git log returns real dates
391
+ const hasFullHistory = tryUnshallow()
392
+
393
+ // 2. Collect dates from local git
394
+ collectDatesFromGit(pagesDir)
395
+
396
+ // 3. Check if all dates are the same (sign of shallow clone with no unshallow)
397
+ const uniqueDates = new Set(Object.values(dates))
398
+ if (uniqueDates.size <= 1 && Object.keys(dates).length > 1 && !hasFullHistory) {
399
+ // All files share the same date β€” likely shallow clone fallback
400
+ const repo = getGitHubRepo()
401
+ if (repo) {
402
+ dates = {}
403
+ await collectDatesFromGitHub(pagesDir, repo)
404
+ }
405
+ }
406
+ }
407
+
282
408
  return {
283
409
  name: 'docsector-git-dates',
284
- buildStart () {
285
- collectDates()
410
+ async buildStart () {
411
+ await collectDates()
286
412
  },
287
413
  resolveId (id) {
288
414
  if (id === virtualId) return resolvedId