dochub 1.0.4 → 2.0.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 +198 -52
- package/index.js +85 -31
- package/package.json +27 -10
- package/src/build.js +90 -0
- package/src/init.js +49 -219
- package/src/markdown.js +23 -0
- package/src/serve.js +134 -66
- package/src/template.js +390 -0
- package/src/utils.js +111 -0
- package/templates/README.md +69 -43
- package/templates/folder/README.md +27 -3
- package/templates/guide.md +75 -3
- package/templates/server.js +0 -271
package/templates/guide.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
-
# Guide
|
|
2
|
-
|
|
3
|
-
This
|
|
1
|
+
# Writing Guide
|
|
2
|
+
|
|
3
|
+
This guide covers the Markdown syntax and features available in DocHub.
|
|
4
|
+
|
|
5
|
+
## Text Formatting
|
|
6
|
+
|
|
7
|
+
**Bold text** and *italic text* are supported. You can also use ~~strikethrough~~ and `inline code`.
|
|
8
|
+
|
|
9
|
+
## Code Blocks
|
|
10
|
+
|
|
11
|
+
Code blocks with syntax highlighting are supported for over 190 languages:
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
function fibonacci(n) {
|
|
15
|
+
if (n <= 1) return n;
|
|
16
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(fibonacci(10)); // 55
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
def fibonacci(n):
|
|
24
|
+
if n <= 1:
|
|
25
|
+
return n
|
|
26
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
27
|
+
|
|
28
|
+
print(fibonacci(10)) # 55
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
fn fibonacci(n: u32) -> u32 {
|
|
33
|
+
match n {
|
|
34
|
+
0 | 1 => n,
|
|
35
|
+
_ => fibonacci(n - 1) + fibonacci(n - 2),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn main() {
|
|
40
|
+
println!("{}", fibonacci(10)); // 55
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Lists
|
|
45
|
+
|
|
46
|
+
### Unordered
|
|
47
|
+
|
|
48
|
+
- Item one
|
|
49
|
+
- Item two
|
|
50
|
+
- Nested item
|
|
51
|
+
- Another nested item
|
|
52
|
+
- Item three
|
|
53
|
+
|
|
54
|
+
### Ordered
|
|
55
|
+
|
|
56
|
+
1. First step
|
|
57
|
+
2. Second step
|
|
58
|
+
3. Third step
|
|
59
|
+
|
|
60
|
+
## Blockquotes
|
|
61
|
+
|
|
62
|
+
> This is a blockquote. It can span multiple lines and is useful for highlighting important information or callouts.
|
|
63
|
+
|
|
64
|
+
## Tables
|
|
65
|
+
|
|
66
|
+
| Feature | Status | Version |
|
|
67
|
+
|---------|--------|---------|
|
|
68
|
+
| Live reload | Done | 2.0.0 |
|
|
69
|
+
| Static build | Done | 2.0.0 |
|
|
70
|
+
| Full-text search | Done | 2.0.0 |
|
|
71
|
+
| Dark mode | Done | 2.0.0 |
|
|
72
|
+
|
|
73
|
+
## Links
|
|
74
|
+
|
|
75
|
+
Visit the [DocHub GitHub repository](https://github.com/tyler-Github/dochub) for more information.
|
package/templates/server.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
const http = require('http');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const hljs = require('highlight.js');
|
|
5
|
-
const socketio = require('socket.io');
|
|
6
|
-
|
|
7
|
-
let htmlContent = ''; // Variable to store HTML content
|
|
8
|
-
|
|
9
|
-
const configPath = path.join(__dirname, 'config.json');
|
|
10
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
11
|
-
const projectTitle = config.title || 'Documentation Project';
|
|
12
|
-
|
|
13
|
-
// Function to convert Markdown to HTML
|
|
14
|
-
function convertMarkdownToHtml(markdownContent, filePath) {
|
|
15
|
-
// Regular expression to match Markdown code blocks
|
|
16
|
-
const codeBlockRegex = /```(\w+)\n([\s\S]*?)```/g;
|
|
17
|
-
|
|
18
|
-
// Basic conversion: Replace Markdown syntax with HTML equivalents
|
|
19
|
-
let htmlContent = markdownContent
|
|
20
|
-
// Code blocks
|
|
21
|
-
.replace(codeBlockRegex, (match, language, code) => {
|
|
22
|
-
const validLanguage = language || 'plaintext'; // Default to plaintext if no language specified
|
|
23
|
-
const highlightedCode = hljs.highlight(code, { language: validLanguage }).value;
|
|
24
|
-
// Count the number of lines in the code content
|
|
25
|
-
const lines = code.split('\n').length;
|
|
26
|
-
return `<pre class="bg-gray-800 text-white p-4 rounded-md mb-4 code-block" style="height: ${lines * 1.5}rem;"><code class="language-${validLanguage}" data-lines="${lines}">${highlightedCode}</code></pre>`;
|
|
27
|
-
})
|
|
28
|
-
// Inline code
|
|
29
|
-
.replace(/`([^`]*)`/g, '<code class="bg-gray-800 text-white rounded px-1">$1</code>')
|
|
30
|
-
// Headers, emphasis, lists, links
|
|
31
|
-
.replace(/^# (.*)$/gm, '<h1 class="text-4xl font-bold mt-8 mb-4">$1</h1>')
|
|
32
|
-
.replace(/^## (.*)$/gm, '<h2 class="text-3xl font-bold mt-6 mb-3">$1</h2>')
|
|
33
|
-
.replace(/^### (.*)$/gm, '<h3 class="text-2xl font-bold mt-4 mb-2">$1</h3>')
|
|
34
|
-
.replace(/^#### (.*)$/gm, '<h4 class="text-xl font-bold mt-3 mb-2">$1</h4>')
|
|
35
|
-
.replace(/^##### (.*)$/gm, '<h5 class="text-lg font-bold mt-3 mb-2">$1</h5>')
|
|
36
|
-
.replace(/^###### (.*)$/gm, '<h6 class="text-base font-bold mt-2 mb-2">$1</h6>')
|
|
37
|
-
.replace(/\*\*(.*)\*\*/g, '<strong class="font-bold">$1</strong>')
|
|
38
|
-
.replace(/\*(.*)\*/g, '<em class="italic">$1</em>')
|
|
39
|
-
.replace(/^\s*-\s(.*)$/gm, '<li class="list-disc ml-4">$1</li>')
|
|
40
|
-
.replace(/^\s*\d\.\s(.*)$/gm, '<li class="list-decimal ml-4">$1</li>')
|
|
41
|
-
.replace(/<li>(.*)<\/li>/g, '<ul class="list-inside list-disc mb-4">$1</ul>')
|
|
42
|
-
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" class="text-blue-500 hover:underline">$1</a>')
|
|
43
|
-
// Paragraphs and Line breaks
|
|
44
|
-
.replace(/\n\n/gm, '</p><p class="mb-6">') // Replace double newline with closing and opening paragraph tags
|
|
45
|
-
.replace(/^\s*$/gm, '<p class="mb-6">') // Replace empty lines with opening paragraph tag
|
|
46
|
-
.replace(/^/gm, '') // Remove the beginning of each line
|
|
47
|
-
.replace(/$/gm, ''); // Remove the end of each line
|
|
48
|
-
|
|
49
|
-
// Generate sidebar based on file structure
|
|
50
|
-
const sidebar = generateSidebar(path.join(__dirname, 'markdown'));
|
|
51
|
-
|
|
52
|
-
// Return the entire HTML structure
|
|
53
|
-
return `
|
|
54
|
-
<!DOCTYPE html>
|
|
55
|
-
<html lang="en">
|
|
56
|
-
<head>
|
|
57
|
-
<meta charset="UTF-8">
|
|
58
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
59
|
-
<title>Markdown to HTML</title>
|
|
60
|
-
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
61
|
-
<link href="https://cdn.jsdelivr.net/npm/highlight.js/styles/vs2015.css" rel="stylesheet">
|
|
62
|
-
<style>
|
|
63
|
-
/* Dark mode theme */
|
|
64
|
-
body {
|
|
65
|
-
background-color: #1a202c;
|
|
66
|
-
color: #e2e8f0;
|
|
67
|
-
}
|
|
68
|
-
.bg-gray-200 {
|
|
69
|
-
background-color: #2d3748;
|
|
70
|
-
}
|
|
71
|
-
.text-blue-500 {
|
|
72
|
-
color: #90cdf4;
|
|
73
|
-
}
|
|
74
|
-
.hover\:underline:hover {
|
|
75
|
-
text-decoration: underline;
|
|
76
|
-
}
|
|
77
|
-
/* Adjust sidebar height */
|
|
78
|
-
.sidebar {
|
|
79
|
-
min-height: 100vh; /* Ensure sidebar stretches to full viewport height */
|
|
80
|
-
display: flex;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
align-items: flex-start; /* Align items to the start (left) */
|
|
83
|
-
justify-content: flex-start; /* Start content from the top */
|
|
84
|
-
padding: 20px;
|
|
85
|
-
}
|
|
86
|
-
.code-block {
|
|
87
|
-
color: #e2e8f0; /* Match the body text color */
|
|
88
|
-
padding: 1rem;
|
|
89
|
-
margin-bottom: 1rem;
|
|
90
|
-
border-radius: 0.5rem;
|
|
91
|
-
overflow: hidden; /* Prevents scroll bars */
|
|
92
|
-
white-space: pre-wrap; /* Preserves white space */
|
|
93
|
-
}
|
|
94
|
-
.search-bar {
|
|
95
|
-
width: 100%;
|
|
96
|
-
padding: 0.5rem;
|
|
97
|
-
margin-bottom: 1rem;
|
|
98
|
-
background-color: #4a5568;
|
|
99
|
-
color: #e2e8f0;
|
|
100
|
-
border: none;
|
|
101
|
-
border-radius: 0.25rem;
|
|
102
|
-
outline: none;
|
|
103
|
-
}
|
|
104
|
-
.search-bar:focus {
|
|
105
|
-
background-color: #2d3748;
|
|
106
|
-
}
|
|
107
|
-
</style>
|
|
108
|
-
</head>
|
|
109
|
-
<body class="flex bg-gray-100">
|
|
110
|
-
<aside class="w-1/5 bg-gray-800 p-4 sidebar">
|
|
111
|
-
<h1 class="text-xl font-bold mb-4">${projectTitle}</h1>
|
|
112
|
-
<input type="text" id="search-input" class="search-bar" placeholder="Search...">
|
|
113
|
-
<ul id="sidebar-list" class="space-y-2">
|
|
114
|
-
${sidebar}
|
|
115
|
-
</ul>
|
|
116
|
-
</aside>
|
|
117
|
-
<main id="main-content" class="w-4/5 p-4 bg-gray-200">
|
|
118
|
-
${htmlContent}
|
|
119
|
-
</main>
|
|
120
|
-
<script src="https://cdn.socket.io/4.3.1/socket.io.min.js"></script>
|
|
121
|
-
<script>
|
|
122
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
123
|
-
const socket = io(); // Declare socket variable here
|
|
124
|
-
|
|
125
|
-
socket.on('fileChange', function() {
|
|
126
|
-
fetchCurrentPageContent();
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const codeBlocks = document.querySelectorAll('.code-block');
|
|
130
|
-
codeBlocks.forEach(block => {
|
|
131
|
-
const code = block.querySelector('code');
|
|
132
|
-
const lines = parseInt(code.getAttribute('data-lines'));
|
|
133
|
-
const lineHeight = parseInt(getComputedStyle(code).lineHeight);
|
|
134
|
-
block.style.height = (lines * lineHeight) + 'px'; // Set height using proper concatenation
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Fetch the sidebar list and handle search input
|
|
138
|
-
const sidebarList = document.getElementById('sidebar-list');
|
|
139
|
-
if (sidebarList) {
|
|
140
|
-
const sidebarItems = sidebarList.getElementsByTagName('li');
|
|
141
|
-
|
|
142
|
-
document.getElementById('search-input').addEventListener('input', function() {
|
|
143
|
-
const searchValue = this.value.trim().toLowerCase();
|
|
144
|
-
|
|
145
|
-
Array.from(sidebarItems).forEach(item => {
|
|
146
|
-
const link = item.querySelector('a');
|
|
147
|
-
if (link) {
|
|
148
|
-
const textContent = link.textContent.trim().toLowerCase();
|
|
149
|
-
|
|
150
|
-
if (textContent.includes(searchValue)) {
|
|
151
|
-
item.style.display = 'block';
|
|
152
|
-
} else {
|
|
153
|
-
item.style.display = 'none';
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
} else {
|
|
159
|
-
console.error('Sidebar list element not found.');
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
function fetchCurrentPageContent() {
|
|
164
|
-
// Fetch the current page's HTML content
|
|
165
|
-
fetch(window.location.href)
|
|
166
|
-
.then(response => response.text())
|
|
167
|
-
.then(html => {
|
|
168
|
-
// Replace the entire HTML document with the fetched content
|
|
169
|
-
document.open();
|
|
170
|
-
document.write(html);
|
|
171
|
-
document.close();
|
|
172
|
-
})
|
|
173
|
-
.catch(error => {
|
|
174
|
-
console.error('Error fetching current page content:', error);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
</script>
|
|
178
|
-
|
|
179
|
-
</body>
|
|
180
|
-
</html>
|
|
181
|
-
`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Function to generate sidebar links based on directory structure
|
|
185
|
-
function generateSidebar(rootDir) {
|
|
186
|
-
const generateLinks = (dir, baseUrl, depth = 0) => {
|
|
187
|
-
let links = '';
|
|
188
|
-
const items = fs.readdirSync(dir);
|
|
189
|
-
|
|
190
|
-
items.forEach(item => {
|
|
191
|
-
const itemPath = path.join(dir, item);
|
|
192
|
-
const relativePath = path.relative(rootDir, itemPath);
|
|
193
|
-
const urlPath = baseUrl + '/' + relativePath.replace(/\\/g, '/').replace('.md', '');
|
|
194
|
-
|
|
195
|
-
if (fs.statSync(itemPath).isDirectory()) {
|
|
196
|
-
// If directory, recursively generate nested list with increased depth
|
|
197
|
-
links += `<li class="font-bold ml-${depth * 2}">${item}</li>\n`;
|
|
198
|
-
links += `<ul class="ml-4 space-y-2">${generateLinks(itemPath, baseUrl, depth + 1)}</ul>\n`;
|
|
199
|
-
} else if (item.endsWith('.md')) {
|
|
200
|
-
// If Markdown file, generate list item with link
|
|
201
|
-
links += `<li class="ml-${depth * 2}"><a href="${urlPath}" class="text-blue-500 hover:underline">${item.replace('.md', '')}</a></li>\n`;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return links;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// Start generating sidebar from root directory
|
|
209
|
-
return generateLinks(rootDir, '');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Create HTTP server
|
|
213
|
-
const server = http.createServer((req, res) => {
|
|
214
|
-
let filePath = '.' + req.url;
|
|
215
|
-
|
|
216
|
-
if (filePath === './' || filePath === './index.html') {
|
|
217
|
-
filePath = './README.md'; // Default to README.md for root URL
|
|
218
|
-
} else if (!path.extname(filePath)) {
|
|
219
|
-
// Append '.md' for paths that do not have an extension
|
|
220
|
-
filePath += '.md';
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
filePath = path.join(__dirname, 'markdown', filePath.replace(/^\//, ''));
|
|
224
|
-
|
|
225
|
-
fs.readFile(filePath, (err, data) => {
|
|
226
|
-
if (err) {
|
|
227
|
-
if (err.code === 'ENOENT') {
|
|
228
|
-
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
229
|
-
res.end('<h1>404 Not Found</h1><p>The requested URL was not found on this server.</p>');
|
|
230
|
-
} else {
|
|
231
|
-
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
232
|
-
res.end('<h1>500 Internal Server Error</h1><p>Sorry, something went wrong.</p>');
|
|
233
|
-
}
|
|
234
|
-
} else {
|
|
235
|
-
let contentType = 'text/html';
|
|
236
|
-
|
|
237
|
-
if (filePath.endsWith('.md')) {
|
|
238
|
-
// Convert Markdown to HTML
|
|
239
|
-
const markdownContent = data.toString();
|
|
240
|
-
htmlContent = convertMarkdownToHtml(markdownContent, filePath);
|
|
241
|
-
|
|
242
|
-
// Serve HTML with proper Tailwind CSS and sidebar
|
|
243
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
244
|
-
res.end(htmlContent);
|
|
245
|
-
} else {
|
|
246
|
-
// Serve other file types directly (e.g., CSS files)
|
|
247
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
248
|
-
res.end(data);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Attach Socket.IO to the HTTP server
|
|
255
|
-
const io = socketio(server);
|
|
256
|
-
|
|
257
|
-
// Watch changes in the Markdown directory
|
|
258
|
-
const markdownDir = path.join(__dirname, 'markdown');
|
|
259
|
-
if (fs.existsSync(markdownDir)) {
|
|
260
|
-
fs.watch(markdownDir, { recursive: true }, (eventType, filename) => {
|
|
261
|
-
console.log(`File ${filename} ${eventType}`);
|
|
262
|
-
io.emit('fileChange'); // Emit event to clients when file changes
|
|
263
|
-
});
|
|
264
|
-
} else {
|
|
265
|
-
console.error('Markdown directory does not exist.');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const port = process.env.PORT || 3000;
|
|
269
|
-
server.listen(port, () => {
|
|
270
|
-
console.log(`Server is running on http://localhost:${port}`);
|
|
271
|
-
});
|