Compare commits
45 commits
Author | SHA1 | Date | |
---|---|---|---|
6ff402afec | |||
4a16fe5417 | |||
36fc0140cc | |||
d9226b238e | |||
eace238d4d | |||
70bc602fb1 | |||
c54d6d4bda | |||
35700e72d1 | |||
f9bf65e107 | |||
20f0760451 | |||
0c2479d849 | |||
d7c98883a6 | |||
dc21ee9b3b | |||
a818722fd2 | |||
ca8109bf76 | |||
15d515d9a3 | |||
6593fc02bd | |||
a50dc87e7a | |||
2f99ed9703 | |||
fc1ef42652 | |||
d0296c7570 | |||
dfbfdaf8d9 | |||
7a1624f650 | |||
4ec4b04278 | |||
ef88e79ae2 | |||
092042a42d | |||
985324970e | |||
37b78f6c61 | |||
5be1cd5d65 | |||
b41f20f0cc | |||
efcba45486 | |||
bda4cdc140 | |||
b0da70bdeb | |||
7796bab36e | |||
925c978f16 | |||
e7d2ce2f30 | |||
90e2022098 | |||
c235162984 | |||
12717687d3 | |||
82f8894897 | |||
98d64a6f7d | |||
1422306456 | |||
023df5f56c | |||
077ad50808 | |||
c616e0efb4 |
24 changed files with 1059 additions and 34 deletions
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Folders
|
||||||
|
.idea/
|
||||||
|
.github/
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Normalize EOL for all files that Git considers text files.
|
||||||
|
* text=auto eol=lf
|
11
.github/workflows/docker-image-rootless.yml
vendored
11
.github/workflows/docker-image-rootless.yml
vendored
|
@ -3,15 +3,20 @@ name: Publish Docker rootless image
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
name: Push Docker image to Docker Hub
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: node:20-bullseye
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Docker Login
|
- name: Docker Login
|
||||||
uses: docker/login-action@v1.12.0
|
uses: docker/login-action@v1.12.0
|
||||||
|
@ -25,4 +30,4 @@ jobs:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
file: Dockerfile-rootless
|
file: Dockerfile-rootless
|
||||||
tags: mohamnag/nginx-file-browser:latest-rootless
|
tags: aroyart/iso-share:latest:latest-rootless
|
||||||
|
|
11
.github/workflows/docker-image.yml
vendored
11
.github/workflows/docker-image.yml
vendored
|
@ -3,15 +3,20 @@ name: Publish Docker image
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
push_to_registry:
|
||||||
name: Push Docker image to Docker Hub
|
name: Push Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: docker
|
||||||
|
container:
|
||||||
|
image: docker:cli
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Docker Login
|
- name: Docker Login
|
||||||
uses: docker/login-action@v1.12.0
|
uses: docker/login-action@v1.12.0
|
||||||
|
@ -24,4 +29,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: mohamnag/nginx-file-browser:latest
|
tags: aroyart/iso-share:latest
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
LABEL author="Mohammad Naghavi <mohamnag@gmail.com>"
|
LABEL author="Aroy <Aroy-Art@pm.me>"
|
||||||
|
|
||||||
ADD default.conf /etc/nginx/conf.d/default.conf
|
ADD default.conf /etc/nginx/conf.d/default.conf
|
||||||
ADD css/ /opt/www/file-browser/css/
|
ADD css/ /opt/www/file-browser/css/
|
||||||
ADD image/ /opt/www/file-browser/image/
|
ADD image/ /opt/www/file-browser/image/
|
||||||
ADD js/ /opt/www/file-browser/js/
|
ADD js/ /opt/www/file-browser/js/
|
||||||
|
ADD node_modules/ /opt/www/file-browser/node_modules/
|
||||||
ADD index.html /opt/www/file-browser/
|
ADD index.html /opt/www/file-browser/
|
||||||
|
ADD favicon.png /opt/www/file-browser/favicon.png
|
||||||
|
|
||||||
VOLUME /opt/www/files/
|
VOLUME /opt/www/files/
|
||||||
EXPOSE 80
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# nginx file browser
|
# ISO-Share
|
||||||
|
|
||||||
This web application is a very simple file browser which can be used
|
This web application is a very simple file browser which can be used
|
||||||
effectively together with [nginx's autoindex module](http://nginx.org/en/docs/http/ngx_http_autoindex_module.html).
|
effectively together with [nginx's autoindex module](http://nginx.org/en/docs/http/ngx_http_autoindex_module.html).
|
||||||
|
|
||||||
![nginx file browser in action - light theme](assets/screenshot-light.jpg)
|
![nginx file browser in action - light theme](assets/screenshot-light.jpg)
|
||||||
![nginx file browser in action - light theme](assets/screenshot-dark.jpg)
|
![nginx file browser in action - dark theme](assets/screenshot-dark.jpg)
|
||||||
|
|
||||||
A sample nginx configuration is also included which mounts **file browser** under root (`/`) and mounts files to be listed under `/files` path. Hence is the `filesBaseUrl` under
|
A sample nginx configuration is also included which mounts **file browser** under root (`/`) and mounts files to be listed under `/files` path. Hence is the `filesBaseUrl` under
|
||||||
|
|
||||||
|
@ -46,3 +46,7 @@ shall be changed to
|
||||||
```
|
```
|
||||||
|
|
||||||
And the mounting point is now `/home/myuser/files-to-serve/` instead of `/opt/www/files/`.
|
And the mounting point is now `/home/myuser/files-to-serve/` instead of `/opt/www/files/`.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This is a fork from [mohamnag/nginx-file-browser](https://github.com/mohamnag/nginx-file-browser)
|
||||||
|
|
60
css/main.css
60
css/main.css
|
@ -7,6 +7,27 @@ a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding-left: 4rem;
|
||||||
|
color: #0044ff;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header::before {
|
||||||
|
background: no-repeat center center;
|
||||||
|
background-image: url(../favicon.png);
|
||||||
|
background-size: contain;
|
||||||
|
width: 4rem;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
@ -28,6 +49,11 @@ a {
|
||||||
|
|
||||||
.file-list li {
|
.file-list li {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-color: aqua;
|
||||||
|
border-style: dashed;
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
padding: 0px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list .file-name {
|
.file-list .file-name {
|
||||||
|
@ -50,7 +76,7 @@ a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list .directory .file-name::before {
|
.file-list .directory .file-name::before {
|
||||||
background-image: url(../image/directory.png);
|
background-image: url(../image/folder.png);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +103,7 @@ a {
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list .directory .file-date {
|
.file-list .directory .file-date {
|
||||||
|
@ -93,6 +120,7 @@ a {
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
margin: 0 .5rem;
|
margin: 0 .5rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list .other .file-size,
|
.file-list .other .file-size,
|
||||||
|
@ -109,17 +137,25 @@ footer small + small {
|
||||||
margin-left: 2rem;
|
margin-left: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zero-md {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
padding: 16px;
|
||||||
|
border: solid 4px gray;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Start of alternations for dark theme
|
Start of alternations for dark theme
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body.dark {
|
body.dark {
|
||||||
background-color: #252525;
|
background-color: #040b1e;
|
||||||
color: #ecdbb2;
|
color: #ecdbb2;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark h1 {
|
body.dark h1 {
|
||||||
color: #9dd08e;
|
color: #00ffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark a {
|
body.dark a {
|
||||||
|
@ -131,12 +167,16 @@ body.dark a.file-name {
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark .controls {
|
body.dark .controls {
|
||||||
background: #413e3d;
|
background: #0f1e31;
|
||||||
color: #ecdbb2;
|
color: #ecdbb2;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark input[name="sort"] {
|
body.dark input[name="sort"] {
|
||||||
accent-color: #fe6142;
|
accent-color: #fe00ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark input[name="theme"] {
|
||||||
|
accent-color: #fe00ee;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark .file-list .file-date {
|
body.dark .file-list .file-date {
|
||||||
|
@ -147,4 +187,12 @@ body.dark .file-list .file-date {
|
||||||
body.dark .file-list .file-size {
|
body.dark .file-list .file-size {
|
||||||
background: #79aa7d;
|
background: #79aa7d;
|
||||||
color: #ecdbb2;
|
color: #ecdbb2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dark li {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-color: aqua;
|
||||||
|
border-style: dashed;
|
||||||
|
background-color: #0f1e31;
|
||||||
|
padding: 0px 8px;
|
||||||
|
}
|
||||||
|
|
2
css/vendor/skeleton.css
vendored
2
css/vendor/skeleton.css
vendored
|
@ -124,7 +124,7 @@ body {
|
||||||
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
|
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: "Roboto Mono", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
color: #222; }
|
color: #222; }
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ server {
|
||||||
disable_symlinks off;
|
disable_symlinks off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico {
|
||||||
|
alias /opt/www/file-browser/favicon.png;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /opt/www/file-browser/;
|
root /opt/www/file-browser/;
|
||||||
}
|
}
|
||||||
|
@ -23,4 +27,4 @@ server {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
nginx-file-browser:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
||||||
|
volumes:
|
||||||
|
- ./:/opt/www/files/
|
||||||
|
image: git.aroy-art.com/aroy/nginx-file-browser
|
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
BIN
image/file.png
BIN
image/file.png
Binary file not shown.
Before Width: | Height: | Size: 748 B After Width: | Height: | Size: 12 KiB |
BIN
image/folder.png
Normal file
BIN
image/folder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
25
index.html
25
index.html
|
@ -5,7 +5,7 @@
|
||||||
<title>File Browser</title>
|
<title>File Browser</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
|
<link href="//fonts.googleapis.com/css?family=Roboto+Mono:400,300,600" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
<link href="css/vendor/normalize.css" rel="stylesheet" type="text/css">
|
<link href="css/vendor/normalize.css" rel="stylesheet" type="text/css">
|
||||||
<link href="css/vendor/skeleton.css" rel="stylesheet" type="text/css">
|
<link href="css/vendor/skeleton.css" rel="stylesheet" type="text/css">
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
<script src="js/vendor/jquery-2.2.0.min.js"></script>
|
<script src="js/vendor/jquery-2.2.0.min.js"></script>
|
||||||
<script src="js/vendor/moment-with-locales.js"></script>
|
<script src="js/vendor/moment-with-locales.js"></script>
|
||||||
|
<script type="module" src="node_modules/zero-md/dist/zero-md.min.js"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -21,28 +22,28 @@
|
||||||
<h1>File Browser</h1>
|
<h1>File Browser</h1>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<span>sort list by:</span>
|
<span>Sort List by:</span>
|
||||||
<label>
|
<label>
|
||||||
date
|
Date
|
||||||
<input type="radio" name="sort" value="date" checked>
|
<input type="radio" name="sort" value="date">
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
name
|
Name
|
||||||
<input type="radio" name="sort" value="name">
|
<input type="radio" name="sort" value="name" checked>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
size
|
Size
|
||||||
<input type="radio" name="sort" value="size">
|
<input type="radio" name="sort" value="size">
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span>theme:</span>
|
<span>Theme:</span>
|
||||||
<label>
|
<label>
|
||||||
light
|
Light
|
||||||
<input type="radio" name="theme" value="" checked>
|
<input type="radio" name="theme" value="">
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
dark
|
Dark
|
||||||
<input type="radio" name="theme" value="dark">
|
<input type="radio" name="theme" value="dark" checked>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
59
js/main.js
59
js/main.js
|
@ -81,6 +81,19 @@ $(document).ready(function () {
|
||||||
return parentDir;
|
return parentDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getREADMEmdFile(filesData, path) {
|
||||||
|
|
||||||
|
if (path === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var readmeFile = filesData.find(function (fileData) {
|
||||||
|
return fileData.name === "README.md" && fileData.type === "file";
|
||||||
|
});
|
||||||
|
|
||||||
|
return readmeFile ? path + "README.md" : null;
|
||||||
|
}
|
||||||
|
|
||||||
function renderFileList(filesData, path) {
|
function renderFileList(filesData, path) {
|
||||||
|
|
||||||
var sortBy = $('input[name=sort]:checked').val();
|
var sortBy = $('input[name=sort]:checked').val();
|
||||||
|
@ -94,8 +107,13 @@ $(document).ready(function () {
|
||||||
} else if (sortBy === "name") {
|
} else if (sortBy === "name") {
|
||||||
console.log("sort by name");
|
console.log("sort by name");
|
||||||
|
|
||||||
|
var collator = new Intl.Collator([], {numeric: true});
|
||||||
filesData.sort(function (fileA, fileB) {
|
filesData.sort(function (fileA, fileB) {
|
||||||
return fileA.name.toLowerCase().localeCompare(fileB.name.toLowerCase());
|
return collator.compare(fileA.name.toLowerCase(), fileB.name.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
filesData.sort(function (fileA, fileB) {
|
||||||
|
return collator.compare(fileA.type.toLowerCase(), fileB.type.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (sortBy === "size") {
|
} else if (sortBy === "size") {
|
||||||
|
@ -110,6 +128,16 @@ $(document).ready(function () {
|
||||||
|
|
||||||
fileListElement.empty();
|
fileListElement.empty();
|
||||||
|
|
||||||
|
var directories = [];
|
||||||
|
var files = [];
|
||||||
|
filesData.forEach(function (fileData) {
|
||||||
|
if (fileData.type === "folder") {
|
||||||
|
directories.push(fileData);
|
||||||
|
} else {
|
||||||
|
files.push(fileData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var parentDir = getParentDir(path);
|
var parentDir = getParentDir(path);
|
||||||
|
|
||||||
if (parentDir) {
|
if (parentDir) {
|
||||||
|
@ -120,7 +148,17 @@ $(document).ready(function () {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
filesData.forEach(function (fileData) {
|
directories.forEach(function (fileData) {
|
||||||
|
fileListElement.append(renderFileElement(
|
||||||
|
path,
|
||||||
|
fileData.name,
|
||||||
|
fileData.type,
|
||||||
|
fileData.size,
|
||||||
|
fileData.mtime
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
files.forEach(function (fileData) {
|
||||||
fileListElement.append(renderFileElement(
|
fileListElement.append(renderFileElement(
|
||||||
path,
|
path,
|
||||||
fileData.name,
|
fileData.name,
|
||||||
|
@ -165,6 +203,19 @@ $(document).ready(function () {
|
||||||
console.log("replaceState", path);
|
console.log("replaceState", path);
|
||||||
history.replaceState(null, path, '#' + path);
|
history.replaceState(null, path, '#' + path);
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
var readmePath = getREADMEmdFile(filesData, path);
|
||||||
|
if (readmePath) {
|
||||||
|
var zeroMdElement = document.createElement("zero-md");
|
||||||
|
zeroMdElement.setAttribute("src", filesBaseUrl + readmePath);
|
||||||
|
document.querySelector("body > div.container").appendChild(zeroMdElement);
|
||||||
|
} else {
|
||||||
|
var zeroMdElement = document.querySelector("zero-md");
|
||||||
|
if (zeroMdElement) {
|
||||||
|
document.querySelector("body > div.container").removeChild(zeroMdElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isNavigating = false;
|
isNavigating = false;
|
||||||
},
|
},
|
||||||
|
@ -203,7 +254,7 @@ $(document).ready(function () {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var result = value.toFixed(2);
|
var result = value.toFixed(2);
|
||||||
return result + ' ' + 'KMGTPEZY'[exp - 1] + 'B';
|
return result + ' ' + 'KMGTPEZY'[exp - 1] + 'iB';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -235,4 +286,4 @@ $(document).ready(function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
navigateToUrlLocation();
|
navigateToUrlLocation();
|
||||||
});
|
});
|
||||||
|
|
16
node_modules/.package-lock.json
generated
vendored
Normal file
16
node_modules/.package-lock.json
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "nginx-file-browser",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"node_modules/zero-md": {
|
||||||
|
"version": "2.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zero-md/-/zero-md-2.5.4.tgz",
|
||||||
|
"integrity": "sha512-FbfH1k2kX3W0l/Wck4PuVJXmdS68JpbW3Iq5JthJctyga4PdVRmgPYt2AuBzmtekihFre+PhX+Hav2H1N41aWA==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
node_modules/zero-md/LICENSE
generated
vendored
Normal file
15
node_modules/zero-md/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Jason Lee <jason@zerodevx.com>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
445
node_modules/zero-md/README.md
generated
vendored
Normal file
445
node_modules/zero-md/README.md
generated
vendored
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
![GitHub package.json version](https://img.shields.io/github/package-json/v/zerodevx/zero-md)
|
||||||
|
![jsDelivr hits (GitHub)](https://img.shields.io/jsdelivr/gh/hm/zerodevx/zero-md)
|
||||||
|
|
||||||
|
# <zero-md>
|
||||||
|
|
||||||
|
> Ridiculously simple zero-config markdown displayer
|
||||||
|
|
||||||
|
A native markdown-to-html web component based on
|
||||||
|
[Custom Elements V1 specs](https://www.w3.org/TR/custom-elements/) to load and display an external
|
||||||
|
MD file. Under the hood, it uses [Marked](https://github.com/markedjs/marked) for super-fast
|
||||||
|
markdown transformation, and [Prism](https://github.com/PrismJS/prism) for feature-packed syntax
|
||||||
|
highlighting - automagically rendering into its own self-contained shadow DOM container, while
|
||||||
|
encapsulating implementation details into one embarassingly easy-to-use package.
|
||||||
|
|
||||||
|
**NOTE: This is the V2 branch. If you're looking for the older version, see the
|
||||||
|
[V1 branch](https://github.com/zerodevx/zero-md/tree/v1).**
|
||||||
|
|
||||||
|
Featuring:
|
||||||
|
|
||||||
|
- [x] Automated hash-link scrolls
|
||||||
|
- [x] Built-in FOUC prevention
|
||||||
|
- [x] Automatically rewrite URLs relative to `src`
|
||||||
|
- [x] Automatically re-render when `src` changes
|
||||||
|
- [x] Automatically re-render when inline markdown or style template changes
|
||||||
|
- [x] Support for >200 code languages with detection for unhinted code blocks
|
||||||
|
- [x] Easy styling mechanism
|
||||||
|
- [x] Highly extensible
|
||||||
|
|
||||||
|
Documentation: https://zerodevx.github.io/zero-md/
|
||||||
|
|
||||||
|
**NOTE: Your markdown file(s) needs to be hosted! Browsers don't generally allow javascript to
|
||||||
|
access files on the local hard drive because of security concerns. Standard
|
||||||
|
[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) rules apply.**
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Load via CDN (recommended)
|
||||||
|
|
||||||
|
`zero-md` is designed to be zero-config with good defaults. For most use-cases, just importing the
|
||||||
|
script from CDN and consuming the component directly should suffice.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<head>
|
||||||
|
...
|
||||||
|
<!-- Import element definition -->
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@2/dist/zero-md.min.js"
|
||||||
|
></script>
|
||||||
|
...
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
...
|
||||||
|
<!-- Profit! -->
|
||||||
|
<zero-md src="/example.md"></zero-md>
|
||||||
|
...
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
Latest stable: `https://cdn.jsdelivr.net/gh/zerodevx/zero-md@2/dist/zero-md.min.js`
|
||||||
|
|
||||||
|
Latest beta: `https://cdn.jsdelivr.net/npm/zero-md@next/dist/zero-md.min.js`
|
||||||
|
|
||||||
|
### Install in web projects
|
||||||
|
|
||||||
|
Install package with `npm` or `yarn`. Note that you'll need [Node.js](https://nodejs.org/)
|
||||||
|
installed.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm install --save zero-md
|
||||||
|
```
|
||||||
|
|
||||||
|
Import the class, register the element, and use anywhere.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Import the element definition
|
||||||
|
import ZeroMd from 'zero-md'
|
||||||
|
|
||||||
|
// Register the custom element
|
||||||
|
customElements.define('zero-md', ZeroMd)
|
||||||
|
|
||||||
|
// Render anywhere
|
||||||
|
app.render(`<zero-md src=${src}></zero-md>`, target)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or load the distribution directly in HTML.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module" src="/node_modules/zero-md/dist/zero-md.min.js"></script>
|
||||||
|
...
|
||||||
|
<zero-md src="example.md"></zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Display an external markdown file
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Simply set the `src` attribute and win -->
|
||||||
|
<zero-md src="https://example.com/markdown.md"></zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
At its most basic, `<zero-md>` loads and displays an external MD file with **default stylesheets** -
|
||||||
|
a Github-themed stylesheet paired with a light-themed one for code blocks, just like what you see in
|
||||||
|
these docs. So internally, the above code block is semantically equivalent to the one below:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<zero-md src="https://example.com/markdown.md">
|
||||||
|
<!-- By default, this style template gets loaded -->
|
||||||
|
<template>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/gh/sindresorhus/github-markdown-css@4/github-markdown.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/gh/PrismJS/prism@1/themes/prism.min.css"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using your own styles
|
||||||
|
|
||||||
|
To override the default theme, supply your own style template.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<zero-md src="https://example.com/markdown.md">
|
||||||
|
<!-- Wrap with a <template> tag -->
|
||||||
|
<template>
|
||||||
|
<!-- Define your own styles inside a `<style>` tag -->
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
...;
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Or your own stylesheets
|
||||||
|
|
||||||
|
```html
|
||||||
|
<zero-md src="https://example.com/markdown.md">
|
||||||
|
<!-- Wrap with a <template> tag -->
|
||||||
|
<template>
|
||||||
|
<!-- Load external stylesheets with a `<link rel="stylesheet">` tag -->
|
||||||
|
<link rel="stylesheet" href="markdown-styles.css" />
|
||||||
|
<link rel="stylesheet" href="highlight-styles.css" />
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Or both
|
||||||
|
|
||||||
|
```html
|
||||||
|
<zero-md src="https://example.com/markdown.md">
|
||||||
|
<template>
|
||||||
|
<!-- The CSS load order is respected -->
|
||||||
|
<link rel="stylesheet" href="markdown-styles.css" />
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="highlight-styles.css" />
|
||||||
|
<style>
|
||||||
|
code {
|
||||||
|
background: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Write markdown inline
|
||||||
|
|
||||||
|
You can pass in your markdown inline too.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Do not set the `src` attribute -->
|
||||||
|
<zero-md>
|
||||||
|
<!-- Write your markdown inside a `<script type="text/markdown">` tag -->
|
||||||
|
<script type="text/markdown">
|
||||||
|
# **This** is my [markdown](https://example.com)
|
||||||
|
</script>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, `<zero-md>` first tries to render `src`. If `src` is falsy (undefined, file not found,
|
||||||
|
empty file etc), it **falls-back** to the contents inside the `<script type="text/markdown">` tag.
|
||||||
|
|
||||||
|
### Put it all together
|
||||||
|
|
||||||
|
```html
|
||||||
|
<zero-md src="https://example.com/markdown.md">
|
||||||
|
<template>
|
||||||
|
<link rel="stylesheet" href="markdown-styles.css" />
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="highlight-styles.css" />
|
||||||
|
<style>
|
||||||
|
code {
|
||||||
|
background: yellow;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
<script type="text/markdown">
|
||||||
|
This is the fall-back markdown that will **only show** when `src` is falsy.
|
||||||
|
</script>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
Advanced usage: https://zerodevx.github.io/zero-md/advanced-usage/
|
||||||
|
|
||||||
|
Attributes and helpers: https://zerodevx.github.io/zero-md/attributes-and-helpers/
|
||||||
|
|
||||||
|
Configuration: https://zerodevx.github.io/zero-md/configuration/
|
||||||
|
|
||||||
|
## Migrating from V1 to V2
|
||||||
|
|
||||||
|
1. Support for `<xmp>` tag is removed; use `<script type="text/markdown">` instead.
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
```html
|
||||||
|
<!-- Previous -->
|
||||||
|
<zero-md>
|
||||||
|
<template>
|
||||||
|
<xmp>
|
||||||
|
# `This` is my [markdown](example.md)
|
||||||
|
</xmp>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
|
||||||
|
<!-- Now -->
|
||||||
|
<zero-md>
|
||||||
|
<!-- No need to wrap with <template> tag -->
|
||||||
|
<script type="text/markdown">
|
||||||
|
# `This` is my [markdown](example.md)
|
||||||
|
</script>
|
||||||
|
</zero-md>
|
||||||
|
|
||||||
|
<!-- If you need your code to be pretty, -->
|
||||||
|
<zero-md>
|
||||||
|
<!-- Set `data-dedent` to opt-in to dedent the text during render -->
|
||||||
|
<script type="text/markdown" data-dedent>
|
||||||
|
# It is important to be pretty
|
||||||
|
|
||||||
|
So having spacing makes me happy.
|
||||||
|
</script>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Markdown source behaviour has changed. Think of `<script type="text/markdown">` as a "fallback".
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
```html
|
||||||
|
<!-- Previous -->
|
||||||
|
<zero-md src="will-not-render.md">
|
||||||
|
<template>
|
||||||
|
<xmp>
|
||||||
|
# This has first priority and will be rendered instead of `will-not-render.md`
|
||||||
|
</xmp>
|
||||||
|
</template>
|
||||||
|
<zero-md>
|
||||||
|
|
||||||
|
<!-- Now -->
|
||||||
|
<zero-md src="will-render-unless-falsy.md">
|
||||||
|
<script type="text/markdown">
|
||||||
|
# This will NOT be rendered _unless_ `src` resolves to falsy
|
||||||
|
</script>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The `css-urls` attribute is deprecated. Use `<link rel="stylesheet">` instead.
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
```html
|
||||||
|
<!-- Previous -->
|
||||||
|
<zero-md src="example.md" css-urls='["/style1.css", "/style2.css"]'><zero-md>
|
||||||
|
|
||||||
|
<!-- Now, this... -->
|
||||||
|
<zero-md src="example.md"></zero-md>
|
||||||
|
|
||||||
|
<!-- ...is actually equivalent to this -->
|
||||||
|
<zero-md src="example.md">
|
||||||
|
<template>
|
||||||
|
<!-- These are the default stylesheets -->
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/gh/sindresorhus/github-markdown-css@4/github-markdown.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/gh/PrismJS/prism@1/themes/prism.min.css"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
|
||||||
|
<!-- So, to apply your own external stylesheets... -->
|
||||||
|
<zero-md src="example.md">
|
||||||
|
<!-- ...you overwrite the default template -->
|
||||||
|
<template>
|
||||||
|
<!-- Use <link> tags to reference your own stylesheets -->
|
||||||
|
<link rel="stylesheet" href="/style1.css" />
|
||||||
|
<link rel="stylesheet" href="/style2.css" />
|
||||||
|
<!-- You can even apply additional styles -->
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
|
||||||
|
<!-- If you like the default stylesheets but wish to apply some overrides -->
|
||||||
|
<zero-md src="example.md">
|
||||||
|
<!-- Set `data-merge` to "append" to apply this template AFTER the default template -->
|
||||||
|
<!-- Or "prepend" to apply this template BEFORE -->
|
||||||
|
<template data-merge="append">
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</zero-md>
|
||||||
|
```
|
||||||
|
|
||||||
|
4. The attributes `marked-url` and `prism-url` are deprecated. To load `marked` or `prism` from
|
||||||
|
another location, simply load their scripts _before_ importing `zero-md`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<head>
|
||||||
|
...
|
||||||
|
<script defer src="/lib/marked.js"></script>
|
||||||
|
<script defer src="/lib/prism.js"></script>
|
||||||
|
<script type="module" src="/lib/zero-md.min.js"></script>
|
||||||
|
</head>
|
||||||
|
```
|
||||||
|
|
||||||
|
5. The global config object has been renamed from `ZeroMd.config` to `ZeroMdConfig`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Previous -->
|
||||||
|
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
|
||||||
|
<script>
|
||||||
|
window.ZeroMd = {
|
||||||
|
config: {
|
||||||
|
cssUrls: ['/styles/my-markdown-theme.css', '/styles/my-highlight-theme.css']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@1/src/zero-md.min.js"
|
||||||
|
></script>
|
||||||
|
|
||||||
|
<!-- Now -->
|
||||||
|
<script>
|
||||||
|
window.ZeroMdConfig = {
|
||||||
|
cssUrls: ['/styles/my-markdown-theme.css', '/styles/my-highlight-theme.css']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="https://cdn.jsdelivr.net/gh/zerodevx/zero-md@2/dist/zero-md.min.js"
|
||||||
|
></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
6. The convenience events `zero-md-marked-ready` and `zero-md-prism-ready` are removed and **will no
|
||||||
|
longer fire**. Instead, the `zero-md-ready` event guarantees that everything is ready, and that
|
||||||
|
render can begin.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Noticed a bug? Have a feature request?
|
||||||
|
|
||||||
|
Open a new [issue](https://github.com/zerodevx/zero-md/issues) in the Github repo, or raise a
|
||||||
|
[PR](https://github.com/zerodevx/zero-md/pulls)! I'd be stoked to accept any contributions!
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
Standard Github [contribution workflow](https://github.com/firstcontributions/first-contributions)
|
||||||
|
applies.
|
||||||
|
|
||||||
|
#### Run the dev server
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Point your browser to `http://localhost:8000` and you should see the test suite running.
|
||||||
|
|
||||||
|
#### Testing
|
||||||
|
|
||||||
|
Tests are browser-based and run on [Mocha](https://mochajs.org/) with
|
||||||
|
[Chai](https://www.chaijs.com/) asserts. If you're adding a new feature or bugfixing, please add the
|
||||||
|
corresponding regression test into `test/index.spec.js` accordingly.
|
||||||
|
|
||||||
|
#### Format and lint
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm run format
|
||||||
|
$ npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Formatting and linting by [prettier](https://github.com/prettier/prettier) and
|
||||||
|
[eslint](https://github.com/eslint/eslint) respectively.
|
||||||
|
|
||||||
|
#### Making changes to docs
|
||||||
|
|
||||||
|
Documentation is in the `/docs` folder in the form of `readme.md` files, and published on
|
||||||
|
[Github Pages](https://pages.github.com/). This setup is based on
|
||||||
|
[`zero-md-docs`](https://github.com/zerodevx/zero-md-docs).
|
||||||
|
|
||||||
|
To make changes to docs, simply raise a PR on any `readme.md` files should suffice.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
Check out the [releases](https://github.com/zerodevx/zero-md/releases) page.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
ISC
|
||||||
|
|
||||||
|
## Acknowledgement
|
||||||
|
|
||||||
|
A big thank you to the following contributors and sponsors! :pray:
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
<kbd>[<img src="https://github.com/alifeee.png" width="60px;"/>](https://github.com/alifeee)</kbd> <kbd>[<img src="https://github.com/EmilePerron.png" width="60px;"/>](https://github.com/EmilePerron)</kbd> <kbd>[<img src="https://github.com/bennypowers.png" width="60px;"/>](https://github.com/bennypowers)</kbd> <kbd>[<img src="https://github.com/TheUnlocked.png" width="60px;"/>](https://github.com/TheUnlocked)</kbd> <kbd>[<img src="https://github.com/ernsheong.png" width="60px;"/>](https://github.com/ernsheong)</kbd>
|
||||||
|
|
||||||
|
### Sponsors
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
<kbd>[<img src="https://github.com/RootofalleviI.png" width="60px;"/>](https://github.com/RootofalleviI)</kbd><kbd>[<img src="https://github.com/alifeee.png" width="60px;"/>](https://github.com/alifeee)</kbd>
|
2
node_modules/zero-md/dist/zero-md.legacy.min.js
generated
vendored
Normal file
2
node_modules/zero-md/dist/zero-md.legacy.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/zero-md/dist/zero-md.legacy.min.js.map
generated
vendored
Normal file
1
node_modules/zero-md/dist/zero-md.legacy.min.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
node_modules/zero-md/dist/zero-md.min.js
generated
vendored
Normal file
2
node_modules/zero-md/dist/zero-md.min.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
node_modules/zero-md/dist/zero-md.min.js.map
generated
vendored
Normal file
1
node_modules/zero-md/dist/zero-md.min.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
53
node_modules/zero-md/package.json
generated
vendored
Normal file
53
node_modules/zero-md/package.json
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"name": "zero-md",
|
||||||
|
"version": "2.5.4",
|
||||||
|
"description": "Ridiculously simple zero-config markdown displayer",
|
||||||
|
"author": "Jason Lee <jason@zerodevx.com>",
|
||||||
|
"type": "module",
|
||||||
|
"exports": "./src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "rollup -c -w",
|
||||||
|
"format": "prettier --write --ignore-path=.prettierignore .",
|
||||||
|
"lint": "prettier --check --ignore-path=.prettierignore . && eslint .",
|
||||||
|
"test": "npm run dev",
|
||||||
|
"build": "rollup -c",
|
||||||
|
"prepublishOnly": "npm run format && npm run lint && npm run build",
|
||||||
|
"docs:build": "zero-md-docs && sscli -b https://zerodevx.github.io/zero-md/ -r docs -i 'v1/**' --slash"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.23.9",
|
||||||
|
"@babel/preset-env": "^7.23.9",
|
||||||
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"chai": "^4.4.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"mocha": "^10.3.0",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"rollup": "^3.29.4",
|
||||||
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
|
"rollup-plugin-serve": "^2.0.3",
|
||||||
|
"static-sitemap-cli": "^2.2.3",
|
||||||
|
"zero-md-docs": "^0.1.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/",
|
||||||
|
"dist/"
|
||||||
|
],
|
||||||
|
"license": "ISC",
|
||||||
|
"repository": "github:zerodevx/zero-md",
|
||||||
|
"homepage": "https://zerodevx.github.io/zero-md/",
|
||||||
|
"keywords": [
|
||||||
|
"webcomponents",
|
||||||
|
"customelements",
|
||||||
|
"markdown-to-html",
|
||||||
|
"marked",
|
||||||
|
"prism"
|
||||||
|
]
|
||||||
|
}
|
353
node_modules/zero-md/src/index.js
generated
vendored
Normal file
353
node_modules/zero-md/src/index.js
generated
vendored
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
export class ZeroMd extends HTMLElement {
|
||||||
|
get src() {
|
||||||
|
return this.getAttribute('src')
|
||||||
|
}
|
||||||
|
|
||||||
|
set src(val) {
|
||||||
|
this.reflect('src', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
get manualRender() {
|
||||||
|
return this.hasAttribute('manual-render')
|
||||||
|
}
|
||||||
|
|
||||||
|
set manualRender(val) {
|
||||||
|
this.reflect('manual-render', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
reflect(name, val) {
|
||||||
|
if (val === false) {
|
||||||
|
this.removeAttribute(name)
|
||||||
|
} else {
|
||||||
|
this.setAttribute(name, val === true ? '' : val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['src']
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name, old, val) {
|
||||||
|
if (name === 'src' && this.connected && !this.manualRender && val !== old) {
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(defaults) {
|
||||||
|
super()
|
||||||
|
this.version = '$VERSION'
|
||||||
|
this.config = {
|
||||||
|
markedUrl: 'https://cdn.jsdelivr.net/gh/markedjs/marked@4/marked.min.js',
|
||||||
|
prismUrl: [
|
||||||
|
['https://cdn.jsdelivr.net/gh/PrismJS/prism@1/prism.min.js', 'data-manual'],
|
||||||
|
'https://cdn.jsdelivr.net/gh/PrismJS/prism@1/plugins/autoloader/prism-autoloader.min.js'
|
||||||
|
],
|
||||||
|
cssUrls: [
|
||||||
|
'https://cdn.jsdelivr.net/gh/sindresorhus/github-markdown-css@4/github-markdown.min.css',
|
||||||
|
'https://cdn.jsdelivr.net/gh/PrismJS/prism@1/themes/prism.min.css'
|
||||||
|
],
|
||||||
|
hostCss:
|
||||||
|
':host{display:block;position:relative;contain:content;}:host([hidden]){display:none;}',
|
||||||
|
...defaults,
|
||||||
|
...window.ZeroMdConfig
|
||||||
|
}
|
||||||
|
this.root = this.hasAttribute('no-shadow') ? this : this.attachShadow({ mode: 'open' })
|
||||||
|
this.root.prepend(
|
||||||
|
...this.makeNodes(`<div class="markdown-styles"></div><div class="markdown-body"></div>`)
|
||||||
|
)
|
||||||
|
if (!this.constructor.ready) {
|
||||||
|
this.constructor.ready = Promise.all([
|
||||||
|
!!window.marked || this.loadScript(this.config.markedUrl),
|
||||||
|
!!window.Prism || this.loadScript(this.config.prismUrl)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
this.clicked = this.clicked.bind(this)
|
||||||
|
if (!this.manualRender) {
|
||||||
|
// Scroll to hash id after first render. However, `history.scrollRestoration` inteferes with this
|
||||||
|
// on refresh. It's much better to use a `setTimeout` rather than to alter the browser's behaviour.
|
||||||
|
this.render().then(() => setTimeout(() => this.goto(location.hash), 250))
|
||||||
|
}
|
||||||
|
this.observer = new MutationObserver(() => {
|
||||||
|
this.observeChanges()
|
||||||
|
if (!this.manualRender) this.render()
|
||||||
|
})
|
||||||
|
this.observer.observe(this, { childList: true })
|
||||||
|
this.observeChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start observing changes, if not already so, in `template` and `script`.
|
||||||
|
*/
|
||||||
|
observeChanges() {
|
||||||
|
this.querySelectorAll('template,script[type="text/markdown"]').forEach((n) => {
|
||||||
|
this.observer.observe(n.content || n, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
characterData: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.connected = true
|
||||||
|
this.fire('zero-md-connected', {}, { bubbles: false, composed: false })
|
||||||
|
this.waitForReady().then(() => {
|
||||||
|
this.fire('zero-md-ready')
|
||||||
|
})
|
||||||
|
if (this.shadowRoot) {
|
||||||
|
this.shadowRoot.addEventListener('click', this.clicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.connected = false
|
||||||
|
if (this.shadowRoot) {
|
||||||
|
this.shadowRoot.removeEventListener('click', this.clicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForReady() {
|
||||||
|
const ready =
|
||||||
|
this.connected ||
|
||||||
|
new Promise((resolve) => {
|
||||||
|
this.addEventListener('zero-md-connected', function handler() {
|
||||||
|
this.removeEventListener('zero-md-connected', handler)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return Promise.all([this.constructor.ready, ready])
|
||||||
|
}
|
||||||
|
|
||||||
|
fire(name, detail = {}, opts = { bubbles: true, composed: true }) {
|
||||||
|
if (detail.msg) {
|
||||||
|
console.warn(detail.msg)
|
||||||
|
}
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent(name, {
|
||||||
|
detail: { node: this, ...detail },
|
||||||
|
...opts
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
return new Promise((resolve) => requestAnimationFrame(resolve))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coerce anything into an array
|
||||||
|
arrify(any) {
|
||||||
|
return any ? (Array.isArray(any) ? any : [any]) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promisify an element's onload callback
|
||||||
|
onload(node) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
node.onload = resolve
|
||||||
|
node.onerror = (err) => reject(err.path ? err.path[0] : err.composedPath()[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a url or load (in order) an array of urls via <script> tags
|
||||||
|
loadScript(urls) {
|
||||||
|
return Promise.all(
|
||||||
|
this.arrify(urls).map((item) => {
|
||||||
|
const [url, ...attrs] = this.arrify(item)
|
||||||
|
const el = document.createElement('script')
|
||||||
|
el.src = url
|
||||||
|
el.async = false
|
||||||
|
attrs.forEach((attr) => el.setAttribute(attr, ''))
|
||||||
|
return this.onload(document.head.appendChild(el))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to selected element
|
||||||
|
goto(sel) {
|
||||||
|
let el
|
||||||
|
try {
|
||||||
|
el = this.root.querySelector(sel)
|
||||||
|
} catch {}
|
||||||
|
if (el) el.scrollIntoView()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack same-doc anchor hash links
|
||||||
|
clicked(ev) {
|
||||||
|
if (ev.metaKey || ev.ctrlKey || ev.altKey || ev.shiftKey || ev.defaultPrevented) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const a = ev.target.closest('a')
|
||||||
|
if (a && a.hash && a.host === location.host && a.pathname === location.pathname) {
|
||||||
|
this.goto(a.hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dedent(str) {
|
||||||
|
str = str.replace(/^\n/, '')
|
||||||
|
const match = str.match(/^\s+/)
|
||||||
|
return match ? str.replace(new RegExp(`^${match[0]}`, 'gm'), '') : str
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseUrl(src) {
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = src
|
||||||
|
return a.href.substring(0, a.href.lastIndexOf('/') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs Prism highlight async; falls back to sync if Web Workers throw
|
||||||
|
highlight(container) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const unhinted = container.querySelectorAll('pre>code:not([class*="language-"])')
|
||||||
|
unhinted.forEach((n) => {
|
||||||
|
// Dead simple language detection :)
|
||||||
|
const lang = n.innerText.match(/^\s*</)
|
||||||
|
? 'markup'
|
||||||
|
: n.innerText.match(/^\s*(\$|#)/)
|
||||||
|
? 'bash'
|
||||||
|
: 'js'
|
||||||
|
n.classList.add(`language-${lang}`)
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
window.Prism.highlightAllUnder(container, true, resolve())
|
||||||
|
} catch {
|
||||||
|
window.Prism.highlightAllUnder(container)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts HTML string into HTMLCollection of nodes
|
||||||
|
* @param {string} html
|
||||||
|
* @returns {HTMLCollection}
|
||||||
|
*/
|
||||||
|
makeNodes(html) {
|
||||||
|
const tpl = document.createElement('template')
|
||||||
|
tpl.innerHTML = html
|
||||||
|
return tpl.content.children
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the styles dom and returns HTML string
|
||||||
|
* @returns {string} `markdown-styles` string
|
||||||
|
*/
|
||||||
|
buildStyles() {
|
||||||
|
const get = (query) => {
|
||||||
|
const node = this.querySelector(query)
|
||||||
|
return node ? node.innerHTML || ' ' : ''
|
||||||
|
}
|
||||||
|
const urls = this.arrify(this.config.cssUrls)
|
||||||
|
const html = `<style>${this.config.hostCss}</style>${get('template[data-merge="prepend"]')}${
|
||||||
|
get('template:not([data-merge])') ||
|
||||||
|
urls.reduce((a, c) => `${a}<link rel="stylesheet" href="${c}">`, '')
|
||||||
|
}${get('template[data-merge="append"]')}`
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the markdown body nodes and returns HTML string
|
||||||
|
* @param {*} opts Markedjs options
|
||||||
|
* @returns {Promise<string>} `markdown-body` string
|
||||||
|
*/
|
||||||
|
async buildMd(opts = {}) {
|
||||||
|
const src = async () => {
|
||||||
|
if (!this.src) return ''
|
||||||
|
const resp = await fetch(this.src)
|
||||||
|
if (resp.ok) {
|
||||||
|
const md = await resp.text()
|
||||||
|
return window.marked.parse(md, {
|
||||||
|
baseUrl: this.getBaseUrl(this.src),
|
||||||
|
...opts
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.fire('zero-md-error', {
|
||||||
|
msg: `[zero-md] HTTP error ${resp.status} while fetching src`,
|
||||||
|
status: resp.status,
|
||||||
|
src: this.src
|
||||||
|
})
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const script = () => {
|
||||||
|
const el = this.querySelector('script[type="text/markdown"]')
|
||||||
|
if (!el) return ''
|
||||||
|
const md = el.hasAttribute('data-dedent') ? this.dedent(el.text) : el.text
|
||||||
|
return window.marked.parse(md, opts)
|
||||||
|
}
|
||||||
|
return (await src()) || script()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns 32-bit DJB2a hash in base36
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getHash(str) {
|
||||||
|
let hash = 5381
|
||||||
|
for (let index = 0; index < str.length; index++) {
|
||||||
|
hash = ((hash << 5) + hash) ^ str.charCodeAt(index)
|
||||||
|
}
|
||||||
|
return (hash >>> 0).toString(36)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert or replace styles node in root from a HTML string. If there are external stylesheet
|
||||||
|
* links, wait for them to load.
|
||||||
|
* @param {string} html styles string
|
||||||
|
* @returns {Promise<boolean|undefined>} returns true if stamped
|
||||||
|
*/
|
||||||
|
async stampStyles(html) {
|
||||||
|
const hash = this.getHash(html)
|
||||||
|
const target = this.root.querySelector('.markdown-styles')
|
||||||
|
if (target.getAttribute('data-hash') !== hash) {
|
||||||
|
target.setAttribute('data-hash', hash)
|
||||||
|
const nodes = this.makeNodes(html)
|
||||||
|
const links = [...nodes].filter(
|
||||||
|
(i) => i.tagName === 'LINK' && i.getAttribute('rel') === 'stylesheet'
|
||||||
|
)
|
||||||
|
target.innerHTML = ''
|
||||||
|
target.append(...nodes)
|
||||||
|
await Promise.all(links.map((l) => this.onload(l))).catch((err) => {
|
||||||
|
this.fire('zero-md-error', {
|
||||||
|
msg: '[zero-md] An external stylesheet failed to load',
|
||||||
|
status: undefined,
|
||||||
|
src: err.href
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert or replace HTML body string into DOM
|
||||||
|
* @param {string} html markdown-body string
|
||||||
|
* @param {string[]} [classes] list of classes to apply to `.markdown-body` wrapper
|
||||||
|
* @returns {Promise<boolean|undefined>} returns true if stamped
|
||||||
|
*/
|
||||||
|
async stampBody(html, classes) {
|
||||||
|
const names = this.arrify(classes)
|
||||||
|
const hash = this.getHash(html + JSON.stringify(names))
|
||||||
|
const target = this.root.querySelector('.markdown-body')
|
||||||
|
if (target.getAttribute('data-hash') !== hash) {
|
||||||
|
target.setAttribute('data-hash', hash)
|
||||||
|
names.unshift('markdown-body')
|
||||||
|
target.setAttribute('class', names.join(' '))
|
||||||
|
const nodes = this.makeNodes(html)
|
||||||
|
target.innerHTML = ''
|
||||||
|
target.append(...nodes)
|
||||||
|
await this.highlight(target)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(opts = {}) {
|
||||||
|
await this.waitForReady()
|
||||||
|
const pending = this.buildMd(opts)
|
||||||
|
const styles = await this.stampStyles(this.buildStyles())
|
||||||
|
await this.tick()
|
||||||
|
const body = await this.stampBody(await pending, opts.classes)
|
||||||
|
this.fire('zero-md-rendered', { node: this, stamped: { styles, body } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('zero-md', ZeroMd)
|
Loading…
Reference in a new issue