explicode 1.0.0 → 1.0.2

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
@@ -2,40 +2,33 @@
2
2
 
3
3
  > Turn your codebase into documentation.
4
4
 
5
- **Explicode** lets you write rich Markdown documentation directly inside your code comments, turning a single source file into both runnable code and beautifully rendered documentation.
5
+ **Explicode** is an open-source project that lets you write rich Markdown documentation directly inside your code comments, turning a single source file into both runnable code and beautifully rendered documentation. The VS Code extension provides live previews of your documentation right inside your IDE. Additionally, the `npm` package can convert supported source files into `.md` Markdown via the terminal or generate a GitHub Pages-ready `docs/` folder.
6
6
 
7
- The [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Explicode.explicode) lets you preview the render in your IDE. The npm package converts supported source files into `.md` Markdown, or generates a GitHub Pages-ready `docs/` folder styled after GitHub's own Markdown renderer.
7
+ [![](https://img.shields.io/badge/Download-0078D4?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=Explicode.explicode)[![](https://img.shields.io/badge/VS_Code_Extension-005a9e?style=for-the-badge)](https://marketplace.visualstudio.com/items?itemName=Explicode.explicode)
8
8
 
9
- [![Download](https://img.shields.io/badge/Download-0078D4?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=Explicode.explicode)
10
- [![VS Code Extension](https://img.shields.io/badge/VS_Code_Extension-005a9e?style=for-the-badge)](https://marketplace.visualstudio.com/items?itemName=Explicode.explicode)
11
9
  [![View on GitHub](https://img.shields.io/badge/View_on_GitHub-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/benatfroemming/explicode)
12
10
 
13
- ---
11
+ ## How It Works
14
12
 
15
- ## Usage
13
+ Explicode lets you write Markdown directly inside your source code comments. The rules for each language are:
16
14
 
17
- No install needed run from the root of your project:
15
+ - **Python**Triple-quoted docstrings (`""" ... """` or `''' ... '''`) placed in the normal docstring position are treated as prose. All other code is rendered as code blocks.
16
+ - **C-style languages** — Block comments (`/* ... */`) are treated as prose, while the rest of the code is rendered as code blocks.
17
+ - **Markdown files** — Passed through directly without changes.
18
18
 
19
- ```bash
20
- npx explicode build
21
- npx explicode build --dark
22
- npx explicode convert <file>
23
- ```
24
-
25
- ---
19
+ **Supported Languages:** Python, JavaScript, TypeScript, JSX, TSX, Java, C, C++, C#, CUDA, Rust, Go, Swift, Kotlin, Scala, Dart, PHP, Objective-C, SQL, Markdown
26
20
 
27
21
  ## Commands
28
22
 
29
23
  ### `convert`
30
24
 
31
- Converts a single file to Markdown without building the whole site.
25
+ Converts a single file to Markdown.
32
26
 
33
27
  ```bash
34
- npx explicode convert src/utils.py
28
+ npx explicode convert <file> # usage
29
+ npx explicode convert src/utils.py # example: outputs `src/utils.py.md` alongside the original file
35
30
  ```
36
31
 
37
- Outputs `src/utils.py.md` alongside the original file.
38
-
39
32
  ### `build`
40
33
 
41
34
  Scans the current directory and generates a `docs/` folder.
@@ -50,32 +43,14 @@ npx explicode build --dark # dark theme
50
43
  - Wraps code in syntax-highlighted fenced blocks
51
44
  - Copies your `README.md` as the docs home page
52
45
  - Generates a `_sidebar.md` with your full file tree
53
- - Writes an `index.html` ready for Docsify + GitHub Pages
46
+ - Writes an `index.html` ready for [Docsify](https://docsify.js.org/#/) + [GitHub Pages](https://docs.github.com/pages)
54
47
  - Adds "View on GitHub" source links if a GitHub remote is detected
55
48
 
56
49
  **Skipped directories:** `node_modules`, `.git`, `dist`, `build`, `out`, `docs`, `.next`, `.nuxt`, `.cache`, `.venv`, `venv`, `__pycache__`
57
50
 
58
- ---
59
-
60
- ## Supported Languages
61
-
62
- Python, JavaScript, TypeScript, JSX, TSX, Java, C, C++, C#, CUDA, Rust, Go, Swift, Kotlin, Scala, Dart, PHP, Objective-C, SQL, Markdown
63
-
64
- ---
65
-
66
- ## How It Works
67
-
68
- - **Python** — Triple-quoted docstrings (`"""` / `'''`) in docstring position become prose. Everything else becomes a code block.
69
- - **C-style languages** — Block comments (`/* ... */`) become prose. Everything else becomes a code block.
70
- - **Markdown** — Passed through as-is.
71
-
72
- Consecutive segments of the same type are merged, producing clean alternating prose/code sections rather than fragmented blocks.
73
-
74
- ---
75
-
76
51
  ## GitHub Pages
77
52
 
78
- After running `build`, push the `docs/` folder and enable GitHub Pages from the `docs/` directory in your repo settings. Your site will be live at:
53
+ After running `npx explicode build`, push your changes including the generated `docs/` folder to your repository. Then, in your repository settings, enable GitHub Pages using the `docs/` folder as the source. Your site will be live at:
79
54
 
80
55
  ```
81
56
  https://<user>.github.io/<repo>
@@ -83,23 +58,23 @@ https://<user>.github.io/<repo>
83
58
 
84
59
  ### Automatic Deployment with GitHub Actions
85
60
 
86
- Add the following file to your repository to automatically build and deploy docs on every push:
61
+ Add the following workflow file to your repository to automatically build and deploy your Explicode docs on every push:
87
62
 
88
- `.github/workflows/<any_name>.yml`
63
+ `.github/workflows/<workflow_name>.yml`
89
64
 
90
65
  ```yml
91
- name: Deploy Docs
66
+ name: Deploy Explicode Docs
92
67
 
93
68
  on:
94
69
  push:
95
- branches: [main] # change to your target branch
96
- workflow_dispatch: # allows manual trigger from GitHub UI
70
+ branches: [main] # replace with your desired branch
71
+ workflow_dispatch: # lets users trigger it manually from GitHub UI
97
72
 
98
73
  jobs:
99
74
  deploy:
100
75
  runs-on: ubuntu-latest
101
76
  permissions:
102
- contents: write # needed to push to gh-pages branch
77
+ contents: write # needed to push to gh-pages branch
103
78
 
104
79
  steps:
105
80
  - name: Checkout repo
@@ -111,7 +86,7 @@ jobs:
111
86
  node-version: 20
112
87
 
113
88
  - name: Build docs
114
- run: node explicode build # add --dark to use dark theme
89
+ run: npx explicode build
115
90
 
116
91
  - name: Deploy to GitHub Pages
117
92
  uses: peaceiris/actions-gh-pages@v4
@@ -120,20 +95,18 @@ jobs:
120
95
  publish_dir: ./docs
121
96
  ```
122
97
 
123
- This publishes to a `gh-pages` branch. Enable GitHub Pages from the `/(root)` of that branch in your repo settings.
98
+ This workflow publishes your docs to a `gh-pages` branch. In your repository settings, enable GitHub Pages and select the root `(/)` of the `gh-pages` branch as the source.
124
99
 
125
- ---
126
100
 
127
101
  ## Themes
128
102
 
129
103
  | Flag | Style |
130
104
  |------|-------|
131
- | _(none)_ | GitHub Light |
132
- | `--dark` | GitHub Dark / One Dark Pro |
105
+ | `none` | GitHub Light |
106
+ | `--dark` | GitHub Dark |
133
107
 
134
- The theme is baked into `docs/ghmd.css` at build time. Re-run with or without `--dark` to switch. You can further customize `index.html` or `ghmd.css` as needed.
135
108
 
136
- ---
109
+ The theme is baked into `docs/ghmd.css` at build time. Re-run with or without `--dark` to switch. You can further customize `index.html` or `ghmd.css` as needed.
137
110
 
138
111
  ## Output Structure
139
112
 
@@ -149,8 +122,6 @@ docs/
149
122
  <your files>.md # rendered source files
150
123
  ```
151
124
 
152
- ---
153
-
154
125
  ## License
155
126
 
156
127
  MIT
package/ghmd-dark.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* Explicode — GitHub Dark theme for Docsify */
2
2
 
3
- /* ── Reset & base ── */
3
+ /* Reset & base */
4
4
  *, *::before, *::after { box-sizing: border-box; }
5
5
  * { -webkit-font-smoothing: antialiased; -webkit-overflow-scrolling: touch; -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-text-size-adjust: none; }
6
6
  html, body { height: 100%; }
@@ -12,19 +12,20 @@ div#app { font-size: 30px; font-weight: lighter; margin: 40vh auto; text-align:
12
12
  div#app:empty::before { content: 'Loading...'; }
13
13
  .progress { background-color: #58a6ff; height: 2px; left: 0; position: fixed; right: 0; top: 0; transition: width 0.2s, opacity 0.4s; width: 0%; z-index: 999999; }
14
14
 
15
- /* ── Layout ── */
16
- main { display: block; position: relative; width: 100vw; height: 100%; z-index: 0; }
15
+ main { display: block; position: relative; width: 100vw; height: 100%; }
17
16
  main.hidden { display: none; }
18
17
  body.sticky .sidebar, body.sticky .sidebar-toggle { position: fixed; }
19
18
  .content { padding-top: 60px; position: absolute; top: 0; right: 0; bottom: 0; left: 270px; transition: left 280ms cubic-bezier(0.4,0,0.2,1); }
20
19
  body.close .content { left: 0; }
21
20
 
22
- /* ── Sidebar ── */
23
21
  .sidebar { background-color: #161b22; border-right: 1px solid #30363d; color: #e6edf3; display: flex; flex-direction: column; overflow: hidden; position: absolute; top: 0; bottom: 0; left: 0; transition: transform 280ms cubic-bezier(0.4,0,0.2,1); width: 270px; z-index: 20; }
24
- .sidebar > h1 { margin: 0; font-size: 1.05rem; font-weight: 600; text-align: center; flex-shrink: 0; padding: 14px 16px 13px; border-bottom: 1px solid #30363d; }
22
+ .sidebar > h1 { margin: 0; font-size: 1.05rem; font-weight: 600; text-align: center; flex-shrink: 0; padding: 14px 16px 13px; border-bottom: 1px solid #30363d; position: relative; display: flex; align-items: center; justify-content: center; }
25
23
  .sidebar > h1 a { color: #e6edf3; text-decoration: none; display: block; }
26
24
 
27
- /* ── Scrollable nav area ── */
25
+ #xp-sidebar-close { display: none; position: absolute; left: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; color: #8b949e; font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: 4px; transition: color 0.15s, background 0.15s; }
26
+ #xp-sidebar-close:hover { color: #e6edf3; background: rgba(177,186,196,0.08); }
27
+ @media screen and (max-width: 599px) { #xp-sidebar-close { display: flex; align-items: center; justify-content: center; } }
28
+
28
29
  .xp-sidebar-scroll { flex: 1; overflow-y: auto; padding: 24px 0 8px; min-height: 0; }
29
30
  .xp-sidebar-scroll .sidebar-nav { margin-left: 8px; padding-bottom: 16px; }
30
31
  .xp-sidebar-scroll::-webkit-scrollbar { width: 4px; }
@@ -39,30 +40,22 @@ body.close .content { left: 0; }
39
40
  .sidebar li > p { margin-left: 8px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #6e7681; padding: 12px 8px 4px; }
40
41
  .sidebar ul li .section-link { font-size: 12px; color: #8b949e; }
41
42
 
42
- /* ── Sidebar footer ── */
43
43
  .xp-sidebar-footer { flex-shrink: 0; border-top: 1px solid #21262d; padding: 10px 16px; display: flex; align-items: center; justify-content: center; }
44
44
  .xp-sidebar-footer span, .xp-sidebar-footer { font-size: 11px; color: #484f58; white-space: nowrap; }
45
45
  .xp-sidebar-footer a { color: #6e7681; text-decoration: none; font-size: 11px; }
46
46
  .xp-sidebar-footer a:hover { color: #8b949e; text-decoration: underline; }
47
47
 
48
- /* ── Mobile nav button ── */
49
- #xp-mobile-btn { display: none; position: fixed; top: 14px; left: 14px; z-index: 31; width: 36px; height: 36px; background: none; border: none; cursor: pointer; align-items: center; justify-content: center; padding: 0; }
48
+ #xp-mobile-btn { display: none; position: fixed; top: 14px; left: 14px; z-index: 18; width: 36px; height: 36px; background: none; border: none; cursor: pointer; align-items: center; justify-content: center; padding: 0; color: #484f58; transition: color 0.15s; }
49
+ #xp-mobile-btn:hover { color: #8b949e; }
50
50
  @media screen and (max-width: 599px) { #xp-mobile-btn { display: flex; } }
51
51
  .xp-mb-bars { display: flex; flex-direction: column; gap: 5px; align-items: center; justify-content: center; }
52
- .xp-mb-bars span { display: block; width: 16px; height: 1.5px; background: #8b949e; border-radius: 2px; }
53
- .xp-mb-close { font-size: 14px; color: #8b949e; line-height: 1; }
54
- #xp-mobile-btn[data-open="false"] .xp-mb-bars { display: flex; }
55
- #xp-mobile-btn[data-open="false"] .xp-mb-close { display: none; }
56
- #xp-mobile-btn[data-open="true"] .xp-mb-bars { display: none; }
57
- #xp-mobile-btn[data-open="true"] .xp-mb-close { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
58
-
59
- /* ── Folder buttons ── */
52
+ .xp-mb-bars span { display: block; width: 16px; height: 1.5px; background: currentColor; border-radius: 2px; }
53
+
60
54
  .xp-folder-btn { display: flex; align-items: center; gap: 6px; width: calc(100% - 16px); margin: 0 8px; background: none; border: none; cursor: pointer; color: #8b949e; font-size: 13px; font-weight: 600; padding: 5px 8px; border-radius: 6px; text-align: left; transition: background 0.1s, color 0.1s; }
61
55
  .xp-folder-btn:hover { background: rgba(177,186,196,0.08); color: #c9d1d9; }
62
56
  .xp-folder-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
63
57
  .sidebar ul li ul { border-left: 1px solid #21262d; margin-left: 21px !important; padding-left: 0; }
64
58
 
65
- /* ── SVG icons via CSS masks ── */
66
59
  .xp-item-icon, .xp-folder-icon { display: inline-flex; flex-shrink: 0; width: 14px; height: 14px; background-color: currentColor; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; -webkit-mask-size: contain; mask-size: contain; }
67
60
  .xp-hash-icon { width: 12px; height: 12px; }
68
61
  .xp-home-icon { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.906.664a1.749 1.749 0 0 1 2.187 0l5.25 4.2c.415.332.657.835.657 1.367v7.019A1.75 1.75 0 0 1 13.25 15h-3.5a.75.75 0 0 1-.75-.75V9H7v5.25a.75.75 0 0 1-.75.75h-3.5A1.75 1.75 0 0 1 1 13.25V6.23c0-.531.242-1.034.657-1.366l5.25-4.2Zm1.25 1.171a.25.25 0 0 0-.312 0l-5.25 4.2a.25.25 0 0 0-.094.196v7.019c0 .138.112.25.25.25H5.5V8.25a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 .75.75v5.25h2.75a.25.25 0 0 0 .25-.25V6.23a.25.25 0 0 0-.094-.195Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6.906.664a1.749 1.749 0 0 1 2.187 0l5.25 4.2c.415.332.657.835.657 1.367v7.019A1.75 1.75 0 0 1 13.25 15h-3.5a.75.75 0 0 1-.75-.75V9H7v5.25a.75.75 0 0 1-.75.75h-3.5A1.75 1.75 0 0 1 1 13.25V6.23c0-.531.242-1.034.657-1.366l5.25-4.2Zm1.25 1.171a.25.25 0 0 0-.312 0l-5.25 4.2a.25.25 0 0 0-.094.196v7.019c0 .138.112.25.25.25H5.5V8.25a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 .75.75v5.25h2.75a.25.25 0 0 0 .25-.25V6.23a.25.25 0 0 0-.094-.195Z'/%3E%3C/svg%3E"); }
@@ -71,7 +64,6 @@ body.close .content { left: 0; }
71
64
  .xp-folder-open { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M.513 1.513A1.75 1.75 0 0 1 1.75 1h3.5c.55 0 1.07.26 1.4.7l.9 1.2a.25.25 0 0 0 .2.1H14.25c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75c0-.464.184-.91.513-1.237ZM1.75 2.5a.25.25 0 0 0-.25.25v.5h13V4.75a.25.25 0 0 0-.25-.25H7.75A1.75 1.75 0 0 1 6.35 3.8l-.9-1.2a.25.25 0 0 0-.2-.1H1.75Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M.513 1.513A1.75 1.75 0 0 1 1.75 1h3.5c.55 0 1.07.26 1.4.7l.9 1.2a.25.25 0 0 0 .2.1H14.25c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75c0-.464.184-.91.513-1.237ZM1.75 2.5a.25.25 0 0 0-.25.25v.5h13V4.75a.25.25 0 0 0-.25-.25H7.75A1.75 1.75 0 0 1 6.35 3.8l-.9-1.2a.25.25 0 0 0-.2-.1H1.75Z'/%3E%3C/svg%3E"); }
72
65
  .xp-folder-closed { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1Z'/%3E%3C/svg%3E"); }
73
66
 
74
- /* ── Nav link colors (dark) ── */
75
67
  .sidebar ul li a { color: #8b949e; font-size: 13px; padding: 5px 8px 5px 16px; margin-right: 8px; border-radius: 6px; text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 6px; transition: background 0.1s, color 0.1s; }
76
68
  .sidebar ul li a:hover { background: rgba(177,186,196,0.08); color: #e6edf3; }
77
69
  .sidebar ul li.active > a { background: rgba(56,139,253,0.1); color: #e6edf3; }
@@ -81,7 +73,6 @@ body.close .content { left: 0; }
81
73
  .xp-folder-btn .xp-folder-icon { color: #484f58; }
82
74
  .xp-folder-btn:hover .xp-folder-icon { color: #6e7681; }
83
75
 
84
- /* ── Sub-sidebar header links (dark) ── */
85
76
  .app-sub-sidebar { margin: 1px 0 3px 0 !important; padding-left: 0 !important; border-left: 1px solid #21262d !important; margin-left: 21px !important; }
86
77
  .app-sub-sidebar li > a { padding: 4px 8px !important; font-size: 12px !important; color: #6e7681 !important; display: flex !important; align-items: center !important; gap: 5px !important; }
87
78
  .app-sub-sidebar li > a:hover { color: #8b949e !important; background: rgba(177,186,196,0.06) !important; }
@@ -89,23 +80,18 @@ body.close .content { left: 0; }
89
80
  .app-sub-sidebar .xp-item-icon { color: #484f58; }
90
81
  .app-sub-sidebar li.active > a .xp-hash-icon { color: #388bfd; }
91
82
 
92
- /* ── Drag handle ── */
93
- .xp-drag { position: fixed; top: 0; left: 270px; width: 6px; height: 100%; cursor: col-resize; z-index: 25; background: rgba(110,118,129,0.2); }
83
+ .xp-drag { position: fixed; top: 0; left: 270px; width: 6px; height: 100%; cursor: col-resize; z-index: 25; background: rgba(110,118,129,0.2); transition: opacity 280ms cubic-bezier(0.4,0,0.2,1); }
94
84
  .xp-drag:hover, .xp-drag:active { background: rgba(88,166,255,0.5); }
95
85
 
96
- /* ── Desktop toggle pill ── */
97
86
  #xp-toggle { position: fixed; top: 50%; left: 269px; transform: translateY(-50%); z-index: 26; width: 16px; height: 48px; background: #21262d; border: 1px solid #30363d; border-left: none; border-radius: 0 6px 6px 0; cursor: pointer; color: #8b949e; font-size: 14px; line-height: 1; display: flex; align-items: center; justify-content: center; padding: 0; transition: background 0.15s, color 0.15s; }
98
87
  #xp-toggle:hover { background: #30363d; color: #e6edf3; }
99
88
  @media screen and (max-width: 599px) { #xp-toggle { display: none !important; } }
100
89
 
101
- /* ── Hide docsify built-in toggle ── */
102
90
  .sidebar-toggle { display: none !important; }
103
91
 
104
- /* ── Close state ── */
105
92
  body.close .sidebar { transform: translateX(-100%); }
106
93
  body.close .content { left: 0; }
107
94
 
108
- /* ── Markdown content ── */
109
95
  .markdown-section { margin: 0 auto; max-width: 860px; padding: 32px 40px 60px; position: relative; }
110
96
  .markdown-section > * { box-sizing: border-box; font-size: inherit; }
111
97
  .markdown-section > :first-child { margin-top: 0 !important; }
@@ -144,7 +130,6 @@ body.close .content { left: 0; }
144
130
  .markdown-section p.warn { color: #e6edf3; border-left: 3px solid #388bfd; padding: 0 1rem; margin: 2em 0; }
145
131
  .markdown-section p.warn:before { content: "💡 Tip\A"; color: #388bfd; font-weight: 600; white-space: pre-wrap; line-height: 2.5; }
146
132
 
147
- /* ── Prism — One Dark Pro ── */
148
133
  .markdown-section pre { background-color: #1e2330 !important; border: 1px solid #2d3248; }
149
134
  .token.comment, .token.prolog, .token.doctype, .token.cdata { color: #636d83; font-style: italic; }
150
135
  .token.namespace { opacity: 0.8; }
@@ -170,28 +155,14 @@ body.close .content { left: 0; }
170
155
  .token.important { color: #e06c75; }
171
156
  .token.entity { cursor: help; }
172
157
 
173
- /* ── Unrendered (non-clickable) sidebar items ── */
174
158
  .xp-unrendered { color: #3d444d; font-size: 13px; padding: 5px 8px 5px 16px; display: flex; align-items: center; gap: 6px; cursor: default; user-select: none; }
175
159
  .xp-unrendered::before { content: ''; display: inline-flex; flex-shrink: 0; width: 14px; height: 14px; background-color: #3d444d; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688Z'/%3E%3C/svg%3E"); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; -webkit-mask-size: contain; mask-size: contain; }
176
-
177
- /* ── GitHub source icon button ── */
178
160
  span.xp-unrendered:hover { color: #6e7681; cursor: pointer; }
179
161
 
180
- /* ── Title link ── */
181
- .sidebar > h1 { position: relative; display: flex; align-items: center; justify-content: center; }
182
-
183
- /* ── GitHub icon — fixed top-right, bare icon, all rendered pages ── */
184
162
  #xp-gh-page-btn { position: fixed; top: 14px; right: 16px; z-index: 18; display: flex; align-items: center; justify-content: center; color: #484f58; text-decoration: none !important; transition: color 0.15s, transform 0.15s; }
185
163
  #xp-gh-page-btn:hover { color: #8b949e; transform: scale(1.15); }
186
164
  #xp-gh-page-btn svg { width: 18px; height: 18px; display: block; }
187
- @media screen and (max-width: 599px) {
188
- body:not(.close) #xp-gh-page-btn { display: none; }
189
- }
190
-
191
- /* ── Drag hidden when sidebar closed ── */
192
- body.close .xp-drag { opacity: 0 !important; pointer-events: none !important; }
193
165
 
194
- /* ── Responsive ── */
195
166
  @media screen and (max-width: 1100px) and (min-width: 600px) {
196
167
  .sidebar { width: 240px; }
197
168
  .content { left: 240px; }
package/ghmd-light.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* Explicode — GitHub Light theme for Docsify */
2
2
 
3
- /* ── Reset & base ── */
3
+ /* Reset & base */
4
4
  *, *::before, *::after { box-sizing: border-box; }
5
5
  * { -webkit-font-smoothing: antialiased; -webkit-overflow-scrolling: touch; -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-text-size-adjust: none; }
6
6
  html, body { height: 100%; }
@@ -11,14 +11,19 @@ body:not(.ready) [data-cloak], body:not(.ready) .app-nav, body:not(.ready) > nav
11
11
  div#app { font-size: 30px; font-weight: lighter; margin: 40vh auto; text-align: center; color: #1f2328; }
12
12
  div#app:empty::before { content: 'Loading...'; }
13
13
  .progress { background-color: #0969da; height: 2px; left: 0; position: fixed; right: 0; top: 0; transition: width 0.2s, opacity 0.4s; width: 0%; z-index: 999999; }
14
- main { display: block; position: relative; width: 100vw; height: 100%; z-index: 0; }
14
+ main { display: block; position: relative; width: 100vw; height: 100%; }
15
15
  main.hidden { display: none; }
16
16
  body.sticky .sidebar, body.sticky .sidebar-toggle { position: fixed; }
17
17
  .content { padding-top: 60px; position: absolute; top: 0; right: 0; bottom: 0; left: 270px; transition: left 280ms cubic-bezier(0.4,0,0.2,1); }
18
18
  body.close .content { left: 0; }
19
19
  .sidebar { background-color: #f6f8fa; border-right: 1px solid #d0d7de; color: #1f2328; display: flex; flex-direction: column; overflow: hidden; position: absolute; top: 0; bottom: 0; left: 0; transition: transform 280ms cubic-bezier(0.4,0,0.2,1); width: 270px; z-index: 20; }
20
- .sidebar > h1 { margin: 0; font-size: 1.05rem; font-weight: 600; text-align: center; flex-shrink: 0; padding: 14px 16px 13px; border-bottom: 1px solid #d0d7de; }
20
+ .sidebar > h1 { margin: 0; font-size: 1.05rem; font-weight: 600; text-align: center; flex-shrink: 0; padding: 14px 16px 13px; border-bottom: 1px solid #d0d7de; position: relative; display: flex; align-items: center; justify-content: center; }
21
21
  .sidebar > h1 a { color: #1f2328; text-decoration: none; display: block; }
22
+
23
+ #xp-sidebar-close { display: none; position: absolute; left: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; color: #57606a; font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: 4px; transition: color 0.15s, background 0.15s; }
24
+ #xp-sidebar-close:hover { color: #1f2328; background: rgba(208,215,222,0.32); }
25
+ @media screen and (max-width: 599px) { #xp-sidebar-close { display: flex; align-items: center; justify-content: center; } }
26
+
22
27
  .xp-sidebar-scroll { flex: 1; overflow-y: auto; padding: 24px 0 8px; min-height: 0; }
23
28
  .xp-sidebar-scroll .sidebar-nav { margin-left: 8px; padding-bottom: 16px; }
24
29
  .xp-sidebar-scroll::-webkit-scrollbar { width: 4px; }
@@ -36,15 +41,13 @@ body.close .content { left: 0; }
36
41
  .xp-sidebar-footer span, .xp-sidebar-footer { font-size: 11px; color: #8c959f; white-space: nowrap; }
37
42
  .xp-sidebar-footer a { color: #6e7781; text-decoration: none; font-size: 11px; }
38
43
  .xp-sidebar-footer a:hover { color: #57606a; text-decoration: underline; }
39
- #xp-mobile-btn { display: none; position: fixed; top: 14px; left: 14px; z-index: 31; width: 36px; height: 36px; background: none; border: none; cursor: pointer; align-items: center; justify-content: center; padding: 0; }
44
+
45
+ #xp-mobile-btn { display: none; position: fixed; top: 14px; left: 14px; z-index: 18; width: 36px; height: 36px; background: none; border: none; cursor: pointer; align-items: center; justify-content: center; padding: 0; color: #8c959f; transition: color 0.15s; }
46
+ #xp-mobile-btn:hover { color: #57606a; }
40
47
  @media screen and (max-width: 599px) { #xp-mobile-btn { display: flex; } }
41
48
  .xp-mb-bars { display: flex; flex-direction: column; gap: 5px; align-items: center; justify-content: center; }
42
- .xp-mb-bars span { display: block; width: 16px; height: 1.5px; background: #57606a; border-radius: 2px; }
43
- .xp-mb-close { font-size: 14px; color: #57606a; line-height: 1; }
44
- #xp-mobile-btn[data-open="false"] .xp-mb-bars { display: flex; }
45
- #xp-mobile-btn[data-open="false"] .xp-mb-close { display: none; }
46
- #xp-mobile-btn[data-open="true"] .xp-mb-bars { display: none; }
47
- #xp-mobile-btn[data-open="true"] .xp-mb-close { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
49
+ .xp-mb-bars span { display: block; width: 16px; height: 1.5px; background: currentColor; border-radius: 2px; }
50
+
48
51
  .xp-folder-btn { display: flex; align-items: center; gap: 6px; width: calc(100% - 16px); margin: 0 8px; background: none; border: none; cursor: pointer; color: #57606a; font-size: 13px; font-weight: 600; padding: 5px 8px; border-radius: 6px; text-align: left; transition: background 0.1s, color 0.1s; }
49
52
  .xp-folder-btn:hover { background: rgba(208,215,222,0.32); color: #1f2328; }
50
53
  .xp-folder-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -142,12 +145,11 @@ body.close .content { left: 0; }
142
145
  .xp-unrendered { color: #c8cdd3; font-size: 13px; padding: 5px 8px 5px 16px; display: flex; align-items: center; gap: 6px; cursor: default; user-select: none; }
143
146
  .xp-unrendered::before { content: ''; display: inline-flex; flex-shrink: 0; width: 14px; height: 14px; background-color: #c8cdd3; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688Z'/%3E%3C/svg%3E"); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; -webkit-mask-size: contain; mask-size: contain; }
144
147
  span.xp-unrendered:hover { color: #8c959f; cursor: pointer; }
145
- .sidebar > h1 { position: relative; display: flex; align-items: center; justify-content: center; }
148
+
146
149
  #xp-gh-page-btn { position: fixed; top: 14px; right: 16px; z-index: 18; display: flex; align-items: center; justify-content: center; color: #b1bac4; text-decoration: none !important; transition: color 0.15s, transform 0.15s; }
147
150
  #xp-gh-page-btn:hover { color: #57606a; transform: scale(1.15); }
148
151
  #xp-gh-page-btn svg { width: 18px; height: 18px; display: block; }
149
- @media screen and (max-width: 599px) { body:not(.close) #xp-gh-page-btn { display: none; } }
150
- body.close .xp-drag { opacity: 0 !important; pointer-events: none !important; }
152
+
151
153
  @media screen and (max-width: 1100px) and (min-width: 600px) { .sidebar { width: 240px; } .content { left: 240px; } .xp-drag { left: 240px; } #xp-toggle { left: 239px; } }
152
154
  @media screen and (max-width: 599px) { .sidebar { position: fixed; width: 100vw !important; z-index: 30; } .content { left: 0 !important; padding-top: 56px; } .xp-drag { display: none !important; } }
153
155
  @media print { .sidebar, #xp-toggle, #xp-mobile-btn, .xp-drag, .app-nav, #xp-gh-page-btn { display: none !important; } .content { left: 0 !important; } }
@@ -6,6 +6,12 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
  <link rel="stylesheet" href="ghmd.css" />
8
8
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" />
9
+ <style>
10
+ /* Closed sidebar must never intercept touches on mobile */
11
+ @media screen and (max-width: 599px) {
12
+ body.close .sidebar { pointer-events: none !important; }
13
+ }
14
+ </style>
9
15
  </head>
10
16
  <body>
11
17
  <div id="app"></div>
@@ -15,10 +21,86 @@
15
21
 
16
22
  var explicodePlugin = function(hook) {
17
23
  var closedFolders = new Set();
18
- var foldersInitialized = false;
24
+ var seenFolders = new Set();
19
25
  var TRANSITION = '280ms cubic-bezier(0.4,0,0.2,1)';
20
26
  var currentFilePath = 'README.md';
21
27
 
28
+ // These are set once in mounted and reused everywhere.
29
+ var sidebar, toggle, mobileBtn, drag;
30
+ // Source of truth for sidebar open/closed — never read body.close for logic.
31
+ var sidebarOpen = true;
32
+
33
+ function isMobile() { return window.innerWidth < 600; }
34
+
35
+ function updateDragVisibility(open) {
36
+ if (isMobile()) {
37
+ drag.style.opacity = '0';
38
+ drag.style.pointerEvents = 'none';
39
+ } else if (open) {
40
+ drag.style.opacity = '1';
41
+ drag.style.pointerEvents = '';
42
+ } else {
43
+ drag.style.opacity = '0';
44
+ drag.style.pointerEvents = 'none';
45
+ }
46
+ }
47
+
48
+ function syncPositions(w, animate) {
49
+ var content = document.querySelector('.content');
50
+ var dur = animate ? TRANSITION : 'none';
51
+ sidebar.style.transition = 'transform ' + dur + ', width ' + dur;
52
+ if (content) { content.style.transition = 'left ' + dur; content.style.left = w + 'px'; }
53
+ drag.style.transition = 'left ' + dur;
54
+ drag.style.left = w + 'px';
55
+ toggle.style.transition = 'left ' + dur;
56
+ toggle.style.left = (w - 1) + 'px';
57
+ }
58
+
59
+ function setOpen(open, animate) {
60
+ sidebarOpen = open;
61
+ var mobile = isMobile();
62
+ var dur = animate ? TRANSITION : 'none';
63
+ var content = document.querySelector('.content');
64
+
65
+ if (open) {
66
+ document.body.classList.remove('close');
67
+ toggle.innerHTML = '&#x2039;';
68
+ if (mobile) {
69
+ sidebar.style.transition = 'transform ' + dur;
70
+ sidebar.style.transform = 'translateX(0)';
71
+ sidebar.style.pointerEvents = '';
72
+ if (content) content.style.left = '';
73
+ } else {
74
+ var w = parseInt(sidebar.style.width) || sidebar.offsetWidth || 270;
75
+ // Animate drag left from 0 → w in sync with sidebar sliding in
76
+ drag.style.transition = animate ? 'left ' + TRANSITION + ', opacity ' + TRANSITION : 'none';
77
+ drag.style.left = w + 'px';
78
+ drag.style.opacity = '1';
79
+ drag.style.pointerEvents = '';
80
+ syncPositions(w, animate);
81
+ }
82
+ } else {
83
+ document.body.classList.add('close');
84
+ toggle.innerHTML = '&#x203a;';
85
+ if (mobile) {
86
+ sidebar.style.transition = 'transform ' + dur;
87
+ sidebar.style.transform = 'translateX(-100%)';
88
+ sidebar.style.pointerEvents = 'none';
89
+ if (content) content.style.left = '';
90
+ } else {
91
+ sidebar.style.transition = 'transform ' + dur;
92
+ toggle.style.transition = 'left ' + dur;
93
+ toggle.style.left = '0px';
94
+ if (content) { content.style.transition = 'left ' + dur; content.style.left = '0'; }
95
+ // Animate drag left from w → 0 in sync with sidebar sliding out
96
+ drag.style.transition = animate ? 'left ' + TRANSITION + ', opacity ' + TRANSITION : 'none';
97
+ drag.style.left = '0px';
98
+ drag.style.opacity = '0';
99
+ drag.style.pointerEvents = 'none';
100
+ }
101
+ }
102
+ }
103
+
22
104
  hook.beforeEach(function(content, next) {
23
105
  var hash = window.location.hash.replace(/^#\/?/, '').replace(/\?.*$/, '');
24
106
  if (!hash) {
@@ -40,7 +122,7 @@
40
122
  var builtIn = document.querySelector('.sidebar-toggle');
41
123
  if (builtIn) builtIn.style.display = 'none';
42
124
 
43
- var sidebar = document.querySelector('.sidebar');
125
+ sidebar = document.querySelector('.sidebar');
44
126
  sidebar.style.overflow = 'hidden';
45
127
  sidebar.style.display = 'flex';
46
128
  sidebar.style.flexDirection = 'column';
@@ -48,8 +130,7 @@
48
130
 
49
131
  var scrollWrap = document.createElement('div');
50
132
  scrollWrap.className = 'xp-sidebar-scroll';
51
- var nodes = Array.from(sidebar.childNodes);
52
- nodes.forEach(function(n) {
133
+ Array.from(sidebar.childNodes).forEach(function(n) {
53
134
  if (n.nodeName !== 'H1') scrollWrap.appendChild(n);
54
135
  });
55
136
  sidebar.appendChild(scrollWrap);
@@ -61,123 +142,71 @@
61
142
  footer.appendChild(wmSpan);
62
143
  sidebar.appendChild(footer);
63
144
 
64
- var toggle = document.createElement('button');
145
+ toggle = document.createElement('button');
65
146
  toggle.id = 'xp-toggle';
66
147
  toggle.setAttribute('aria-label', 'Toggle sidebar');
67
148
  toggle.innerHTML = '&#x2039;';
68
149
  document.body.appendChild(toggle);
69
150
 
70
- var mobileBtn = document.createElement('button');
151
+ mobileBtn = document.createElement('button');
71
152
  mobileBtn.id = 'xp-mobile-btn';
72
- mobileBtn.setAttribute('aria-label', 'Toggle sidebar');
153
+ mobileBtn.setAttribute('aria-label', 'Open sidebar');
73
154
  mobileBtn.innerHTML =
74
- '<span class="xp-mb-bars"><span></span><span></span><span></span></span>' +
75
- '<span class="xp-mb-close">&#x2715;</span>';
155
+ '<span class="xp-mb-bars"><span></span><span></span><span></span></span>';
76
156
  document.body.appendChild(mobileBtn);
77
157
 
78
- var drag = document.createElement('div');
79
- drag.className = 'xp-drag';
80
- document.body.appendChild(drag);
81
-
82
- var isMobile = function() { return window.innerWidth < 600; };
83
-
84
- function getSidebarWidth() {
85
- return parseInt(sidebar.style.width) || sidebar.offsetWidth || 270;
86
- }
87
-
88
- function updateDragVisibility(open) {
89
- if (isMobile() || !open) {
90
- drag.style.transition = 'opacity 120ms ease';
91
- drag.style.opacity = '0';
92
- drag.style.pointerEvents = 'none';
93
- } else {
94
- drag.style.transition = 'opacity 200ms ease 260ms';
95
- drag.style.opacity = '1';
96
- drag.style.pointerEvents = '';
97
- }
98
- }
99
-
100
- function syncPositions(w, animate) {
101
- if (isMobile()) return;
102
- var content = document.querySelector('.content');
103
- var dur = animate ? TRANSITION : 'none';
104
- sidebar.style.transition = 'transform ' + dur + ', width ' + dur;
105
- if (content) { content.style.transition = 'left ' + dur; content.style.left = w + 'px'; }
106
- drag.style.left = w + 'px';
107
- toggle.style.left = (w - 1) + 'px';
108
- }
109
-
110
- function setMobileBtnIcon(open) {
111
- mobileBtn.setAttribute('data-open', open ? 'true' : 'false');
158
+ var closeBtn = document.createElement('button');
159
+ closeBtn.id = 'xp-sidebar-close';
160
+ closeBtn.setAttribute('aria-label', 'Close sidebar');
161
+ closeBtn.innerHTML = '&#x2715;';
162
+ var h1 = sidebar.querySelector('h1');
163
+ if (h1) {
164
+ h1.insertBefore(closeBtn, h1.firstChild);
165
+ } else {
166
+ sidebar.insertBefore(closeBtn, sidebar.firstChild);
112
167
  }
113
168
 
114
- function setOpen(open, animate) {
115
- var w = getSidebarWidth();
116
- var mobile = isMobile();
117
- var dur = animate ? TRANSITION : 'none';
118
- var content = document.querySelector('.content');
169
+ closeBtn.addEventListener('click', function() {
170
+ setOpen(false, true);
171
+ });
119
172
 
120
- if (open) {
121
- document.body.classList.remove('close');
122
- toggle.innerHTML = '&#x2039;';
123
- if (mobile) {
124
- sidebar.style.transition = 'transform ' + dur;
125
- sidebar.style.transform = 'translateX(0)';
126
- setMobileBtnIcon(true);
127
- if (content) { content.style.left = ''; }
128
- } else {
129
- syncPositions(w, animate);
130
- }
131
- } else {
132
- document.body.classList.add('close');
133
- toggle.innerHTML = '&#x203a;';
134
- if (mobile) {
135
- sidebar.style.transition = 'transform ' + dur;
136
- sidebar.style.transform = 'translateX(-100%)';
137
- setMobileBtnIcon(false);
138
- if (content) { content.style.left = ''; }
139
- } else {
140
- sidebar.style.transition = 'transform ' + dur;
141
- toggle.style.transition = 'left ' + dur;
142
- toggle.style.left = '0px';
143
- if (content) { content.style.transition = 'left ' + dur; content.style.left = '0'; }
144
- }
145
- }
146
- updateDragVisibility(open);
147
- }
173
+ drag = document.createElement('div');
174
+ drag.className = 'xp-drag';
175
+ document.body.appendChild(drag);
148
176
 
149
177
  toggle.addEventListener('click', function() {
150
- var isOpen = !document.body.classList.contains('close');
151
- setOpen(!isOpen, true);
178
+ setOpen(!sidebarOpen, true);
152
179
  });
153
-
154
180
  mobileBtn.addEventListener('click', function() {
155
- var isOpen = !document.body.classList.contains('close');
156
- setOpen(!isOpen, true);
181
+ setOpen(!sidebarOpen, true);
157
182
  });
158
183
 
184
+ // Single delegated listener for nav link taps/clicks closing the sidebar.
185
+ sidebar.addEventListener('click', function(e) {
186
+ if (!isMobile()) return;
187
+ var a = e.target.closest('a');
188
+ if (a && sidebar.contains(a) && !a.closest('.xp-sidebar-footer')) {
189
+ setOpen(false, true);
190
+ }
191
+ });
192
+
193
+ // Resize handler
159
194
  var lastMobile = isMobile();
160
195
  window.addEventListener('resize', function() {
161
196
  var nowMobile = isMobile();
162
- var isOpen = !document.body.classList.contains('close');
163
- var w = getSidebarWidth();
164
-
197
+ var w = parseInt(sidebar.style.width) || sidebar.offsetWidth || 270;
165
198
  if (nowMobile !== lastMobile) {
166
199
  lastMobile = nowMobile;
167
200
  if (nowMobile) {
168
201
  var content = document.querySelector('.content');
169
202
  if (content) content.style.left = '';
170
- if (!isOpen) {
171
- sidebar.style.transition = 'none';
172
- sidebar.style.transform = 'translateX(-100%)';
173
- setMobileBtnIcon(false);
174
- } else {
175
- sidebar.style.transform = 'translateX(0)';
176
- setMobileBtnIcon(true);
177
- }
203
+ sidebar.style.transition = 'none';
204
+ sidebar.style.transform = sidebarOpen ? 'translateX(0)' : 'translateX(-100%)';
205
+ sidebar.style.pointerEvents = sidebarOpen ? '' : 'none';
178
206
  } else {
179
207
  sidebar.style.transform = '';
180
- if (isOpen) {
208
+ sidebar.style.pointerEvents = '';
209
+ if (sidebarOpen) {
181
210
  syncPositions(w, false);
182
211
  } else {
183
212
  var content = document.querySelector('.content');
@@ -185,43 +214,61 @@
185
214
  toggle.style.left = '0px';
186
215
  }
187
216
  }
188
- } else if (!nowMobile && isOpen) {
217
+ } else if (!nowMobile && sidebarOpen) {
189
218
  syncPositions(w, false);
190
219
  }
191
- updateDragVisibility(isOpen);
220
+ drag.style.transition = 'none';
221
+ if (isMobile()) {
222
+ drag.style.opacity = '0';
223
+ drag.style.pointerEvents = 'none';
224
+ } else if (sidebarOpen) {
225
+ drag.style.opacity = '1';
226
+ drag.style.pointerEvents = '';
227
+ } else {
228
+ drag.style.left = '0px';
229
+ drag.style.opacity = '0';
230
+ drag.style.pointerEvents = 'none';
231
+ }
192
232
  });
193
233
 
234
+ // Initial state
194
235
  if (isMobile()) {
195
236
  lastMobile = true;
237
+ sidebarOpen = false;
196
238
  sidebar.style.transition = 'none';
197
239
  sidebar.style.transform = 'translateX(-100%)';
240
+ sidebar.style.pointerEvents = 'none';
198
241
  document.body.classList.add('close');
199
- setMobileBtnIcon(false);
200
242
  var content = document.querySelector('.content');
201
243
  if (content) content.style.left = '';
244
+ drag.style.transition = 'none';
245
+ drag.style.opacity = '0';
246
+ drag.style.pointerEvents = 'none';
202
247
  } else {
203
- syncPositions(getSidebarWidth(), false);
248
+ sidebarOpen = true;
249
+ syncPositions(parseInt(sidebar.style.width) || sidebar.offsetWidth || 270, false);
250
+ drag.style.transition = 'none';
251
+ drag.style.opacity = '1';
252
+ drag.style.pointerEvents = '';
204
253
  }
205
- updateDragVisibility(!document.body.classList.contains('close'));
206
254
 
207
- var startX, startW;
255
+ // Drag to resize (desktop only)
208
256
  drag.addEventListener('mousedown', function(e) {
209
- if (isMobile()) return;
210
257
  e.preventDefault();
211
- startX = e.clientX;
212
- startW = getSidebarWidth();
258
+ var startX = e.clientX;
259
+ var startW = parseInt(sidebar.style.width) || sidebar.offsetWidth || 270;
213
260
  document.body.style.userSelect = 'none';
214
261
  document.body.style.cursor = 'col-resize';
215
262
  sidebar.style.transition = 'none';
263
+ toggle.style.transition = 'none';
216
264
  var content = document.querySelector('.content');
217
265
  if (content) content.style.transition = 'none';
218
266
  drag.style.transition = 'none';
219
- toggle.style.transition = 'none';
220
267
 
221
268
  function onMove(e) {
222
269
  var w = Math.max(180, Math.min(600, startW + e.clientX - startX));
223
- var content = document.querySelector('.content');
224
270
  sidebar.style.width = w + 'px';
271
+ var content = document.querySelector('.content');
225
272
  if (content) content.style.left = w + 'px';
226
273
  drag.style.left = w + 'px';
227
274
  toggle.style.left = (w - 1) + 'px';
@@ -238,47 +285,41 @@
238
285
  });
239
286
 
240
287
  hook.doneEach(function() {
241
- var sidebar = document.querySelector('.sidebar');
288
+ var sb = document.querySelector('.sidebar');
242
289
 
243
290
  if (repoUrl) {
244
- var h1 = sidebar.querySelector('h1');
291
+ var h1 = sb.querySelector('h1');
245
292
  if (h1) {
246
293
  var titleA = h1.querySelector('a');
247
294
  if (titleA) titleA.target = '_blank';
248
295
  }
249
296
  }
250
297
 
251
- var scrollWrap = sidebar.querySelector('.xp-sidebar-scroll');
252
- if (!scrollWrap) {
253
- scrollWrap = document.createElement('div');
298
+ // Ensure scroll wrapper exists
299
+ if (!sb.querySelector('.xp-sidebar-scroll')) {
300
+ var scrollWrap = document.createElement('div');
254
301
  scrollWrap.className = 'xp-sidebar-scroll';
255
- var footer = sidebar.querySelector('.xp-sidebar-footer');
256
- var nodes = Array.from(sidebar.childNodes).filter(function(n) {
257
- return !n.classList || !n.classList.contains('xp-sidebar-footer');
302
+ var footer = sb.querySelector('.xp-sidebar-footer');
303
+ Array.from(sb.childNodes).forEach(function(n) {
304
+ if (!n.classList || !n.classList.contains('xp-sidebar-footer')) {
305
+ scrollWrap.appendChild(n);
306
+ }
258
307
  });
259
- nodes.forEach(function(n) { scrollWrap.appendChild(n); });
260
- sidebar.insertBefore(scrollWrap, footer || null);
308
+ sb.insertBefore(scrollWrap, footer || null);
261
309
  }
262
310
 
263
- var drag = document.querySelector('.xp-drag');
264
- var toggle = document.getElementById('xp-toggle');
265
- var isOpen = !document.body.classList.contains('close');
266
- if (drag && toggle) {
267
- var w = parseInt(sidebar.style.width) || sidebar.offsetWidth || 270;
268
- if (isOpen && window.innerWidth >= 600) {
269
- drag.style.left = w + 'px';
270
- toggle.style.left = (w - 1) + 'px';
271
- drag.style.opacity = '1';
272
- drag.style.pointerEvents = '';
273
- } else {
274
- drag.style.left = w + 'px';
275
- toggle.style.left = '0px';
276
- drag.style.opacity = '0';
277
- drag.style.pointerEvents = 'none';
278
- }
311
+ // Re-sync desktop drag/toggle positions
312
+ if (!isMobile() && drag && toggle) {
313
+ var w = parseInt(sb.style.width) || sb.offsetWidth || 270;
314
+ drag.style.transition = 'none';
315
+ drag.style.left = sidebarOpen ? w + 'px' : '0px';
316
+ drag.style.opacity = sidebarOpen ? '1' : '0';
317
+ drag.style.pointerEvents = sidebarOpen ? '' : 'none';
318
+ toggle.style.left = sidebarOpen ? (w - 1) + 'px' : '0px';
279
319
  }
280
320
 
281
- function updateGhBtn() {
321
+ // GitHub source button, recreated on each navigation to update href
322
+ (function() {
282
323
  var existing = document.getElementById('xp-gh-page-btn');
283
324
  if (existing) existing.remove();
284
325
  if (!githubBase || !currentFilePath) return;
@@ -290,41 +331,38 @@
290
331
  btn.setAttribute('aria-label', 'View on GitHub');
291
332
  btn.title = 'View on GitHub';
292
333
  btn.innerHTML = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>';
293
- document.body.appendChild(btn);
294
- }
295
- updateGhBtn();
334
+ document.body.insertBefore(btn, mobileBtn || null);
335
+ })();
296
336
 
297
- var nav = sidebar.querySelector('.sidebar-nav');
337
+ var nav = sb.querySelector('.sidebar-nav');
298
338
  if (!nav) return;
299
339
 
340
+ // Reset Docsify collapse state
300
341
  nav.querySelectorAll('li').forEach(function(li) {
301
342
  li.classList.remove('collapse');
302
- var ul = li.querySelector(':scope > ul');
303
- if (ul && !ul.closest('[data-xp-folder-ul]')) ul.style.display = '';
343
+ });
344
+ nav.querySelectorAll('li > ul').forEach(function(ul) {
345
+ ul.style.display = '';
304
346
  });
305
347
 
348
+ // Folder buttons
306
349
  nav.querySelectorAll('li').forEach(function(li) {
307
350
  if (li.dataset.xpFolder) return;
308
351
  var labelEl = li.querySelector(':scope > p > strong') || li.querySelector(':scope > strong');
309
352
  if (!labelEl) return;
310
353
  var subUl = li.querySelector(':scope > ul');
311
354
  if (!subUl) return;
312
- subUl.setAttribute('data-xp-folder-ul', '1');
313
355
 
314
356
  var folderName = labelEl.textContent.trim();
315
-
316
357
  li.dataset.xpFolder = '1';
317
- li.classList.remove('collapse');
318
358
  subUl.setAttribute('data-xp-folder-ul', '1');
319
359
 
320
360
  var btn = document.createElement('button');
321
361
  btn.className = 'xp-folder-btn';
322
-
323
362
  var iconSpan = document.createElement('span');
324
363
  var labelSpan = document.createElement('span');
325
364
  labelSpan.className = 'xp-folder-label';
326
365
  labelSpan.textContent = folderName;
327
-
328
366
  btn.appendChild(iconSpan);
329
367
  btn.appendChild(labelSpan);
330
368
 
@@ -332,14 +370,18 @@
332
370
  li.insertBefore(btn, wrapper);
333
371
  wrapper.remove();
334
372
 
335
- var isOpen = foldersInitialized ? !closedFolders.has(folderName) : false;
336
- btn.setAttribute('data-open', isOpen ? 'true' : 'false');
337
- iconSpan.className = 'xp-folder-icon ' + (isOpen ? 'xp-folder-open' : 'xp-folder-closed');
338
- subUl.style.display = isOpen ? '' : 'none';
373
+ // add to closedFolders so it starts closed
374
+ if (!seenFolders.has(folderName)) {
375
+ seenFolders.add(folderName);
376
+ closedFolders.add(folderName);
377
+ }
378
+ var folderOpen = !closedFolders.has(folderName);
379
+ btn.setAttribute('data-open', folderOpen ? 'true' : 'false');
380
+ iconSpan.className = 'xp-folder-icon ' + (folderOpen ? 'xp-folder-open' : 'xp-folder-closed');
381
+ subUl.style.display = folderOpen ? '' : 'none';
339
382
 
340
383
  btn.addEventListener('click', function() {
341
- var open = btn.getAttribute('data-open') === 'true';
342
- var nowOpen = !open;
384
+ var nowOpen = btn.getAttribute('data-open') !== 'true';
343
385
  btn.setAttribute('data-open', nowOpen ? 'true' : 'false');
344
386
  iconSpan.className = 'xp-folder-icon ' + (nowOpen ? 'xp-folder-open' : 'xp-folder-closed');
345
387
  subUl.style.display = nowOpen ? '' : 'none';
@@ -348,8 +390,7 @@
348
390
  });
349
391
  });
350
392
 
351
- foldersInitialized = true;
352
-
393
+ // File icons
353
394
  nav.querySelectorAll('a').forEach(function(a) {
354
395
  if (a.dataset.xpIcon) return;
355
396
  if (a.closest('.app-sub-sidebar')) return;
@@ -367,32 +408,11 @@
367
408
  a.insertBefore(icon, a.firstChild);
368
409
  });
369
410
 
370
- nav.querySelectorAll('a').forEach(function(a) {
371
- if (a.dataset.xpMobileClose) return;
372
- a.dataset.xpMobileClose = '1';
373
- a.addEventListener('click', function() {
374
- if (window.innerWidth < 600) {
375
- setTimeout(function() {
376
- document.body.classList.add('close');
377
- var sb = document.querySelector('.sidebar');
378
- if (sb) {
379
- sb.style.transition = 'transform 280ms cubic-bezier(0.4,0,0.2,1)';
380
- sb.style.transform = 'translateX(-100%)';
381
- }
382
- var mb = document.getElementById('xp-mobile-btn');
383
- if (mb) mb.setAttribute('data-open', 'false');
384
- var tg = document.getElementById('xp-toggle');
385
- if (tg) tg.innerHTML = '&#x203a;';
386
- }, 10);
387
- }
388
- });
389
- });
390
-
411
+ // Unrendered file GitHub links
391
412
  if (githubBase) {
392
413
  nav.querySelectorAll('span.xp-unrendered[data-path]').forEach(function(span) {
393
414
  if (span.dataset.xpGh) return;
394
415
  span.dataset.xpGh = '1';
395
- span.style.cursor = 'pointer';
396
416
  span.addEventListener('click', function() {
397
417
  window.open(githubBase + '/' + span.dataset.path, '_blank', 'noopener noreferrer');
398
418
  });
package/package.json CHANGED
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "explicode",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Turn your codebase into documentation.",
5
+ "keywords": [
6
+ "documentation",
7
+ "markdown",
8
+ "comments",
9
+ "docs",
10
+ "literate-programming",
11
+ "developer-tools",
12
+ "github-pages"
13
+ ],
5
14
  "bin": {
6
15
  "explicode": "./cli.js"
7
16
  },