Compare commits

..

45 commits
master ... main

Author SHA1 Message Date
6ff402afec
Add: attribution to source of the fork 2024-09-11 11:30:32 +02:00
4a16fe5417
Add: docker-compose file 2024-06-21 21:49:06 +02:00
36fc0140cc
Add: favicon to docker build file 2024-06-21 21:47:59 +02:00
d9226b238e
Add: node modules to docker build step 2024-06-11 16:18:19 +02:00
eace238d4d
Update: maintainer 2024-06-09 20:51:54 +02:00
70bc602fb1
Fix: time & size tags silting on small screens 2024-06-09 20:46:57 +02:00
c54d6d4bda
Update: dark theme 2024-05-31 18:28:07 +02:00
35700e72d1
Change font to Roboto Mono 2024-05-30 22:03:13 +02:00
f9bf65e107
Add favicon to nginx config
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (push) Has been cancelled
2024-05-28 22:56:28 +02:00
20f0760451
Uppercase Labels, Set default sort to by name & theme to dark
Some checks are pending
Publish Docker rootless image / Push Docker image to Docker Hub (push) Waiting to run
Publish Docker image / Push Docker image to Docker Hub (push) Waiting to run
2024-05-27 22:10:48 +02:00
0c2479d849
Test: add Setup Docker Buildx step =?
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m20s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 45s
2024-05-22 23:46:26 +02:00
d7c98883a6
Test: add Setup Docker Buildx step
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 38s
Publish Docker image / Push Docker image to Docker Hub (push) Has been cancelled
2024-05-22 23:45:38 +02:00
dc21ee9b3b
Next test with debian node image
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 3m51s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 39s
2024-05-22 23:39:11 +02:00
a818722fd2
Next Test
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 46s
Publish Docker rootless image / Push Docker image to Docker Hub (push) Has been cancelled
2024-05-22 23:34:27 +02:00
ca8109bf76
Test: docker-cli image
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m22s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 1m12s
2024-05-22 23:28:28 +02:00
15d515d9a3
Add: dockerignore
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 35s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 47s
2024-04-03 22:09:18 +02:00
6593fc02bd
Fix: some typos
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m32s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 38s
2024-04-03 21:55:03 +02:00
a50dc87e7a
Add: new file icon
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m37s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 41s
2024-04-02 20:10:40 +02:00
2f99ed9703
Change: font to "Roboto Mono"
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m44s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 38s
2024-04-01 22:24:21 +02:00
fc1ef42652
Add: header styling
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m39s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 55s
2024-03-31 19:32:48 +02:00
d0296c7570
Add: better list item heligting
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m39s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 49s
2024-03-30 21:38:55 +01:00
dfbfdaf8d9
Add: zero-md basic styling for readability
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m11s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 44s
2024-03-29 12:44:21 +01:00
7a1624f650
Change folder icon
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m39s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 46s
2024-03-28 22:35:05 +01:00
4ec4b04278
Add: load zero-md model for md rendering
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 49s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 35s
2024-03-27 17:13:12 +01:00
ef88e79ae2
Add: README.md preview
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m14s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 1m19s
2024-03-27 15:39:24 +01:00
092042a42d
Add: npm zero-md package
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 38s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 41s
2024-03-27 14:37:57 +01:00
985324970e
Add Sort folders first in sort mode Name & Size
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m9s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 39s
2024-03-27 14:32:57 +01:00
37b78f6c61
Set file size to show size in iB to show that is is binary
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m39s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 36s
2024-03-27 13:46:26 +01:00
5be1cd5d65
Sort files by numeric values
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m0s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 39s
2024-03-25 20:37:43 +01:00
b41f20f0cc
Test to set docker image to docker in docker
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 50s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 1m59s
2024-03-25 20:02:41 +01:00
efcba45486
Enable docker
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 48s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 37s
2024-02-23 19:06:00 +01:00
bda4cdc140
update docker image
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1h25m21s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 38s
2024-02-23 17:17:27 +01:00
b0da70bdeb
Set container image & branch to main
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 7s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 1s
2024-02-23 17:13:33 +01:00
7796bab36e
Revert "Change branch to main"
This reverts commit 82f8894897.
2024-02-23 17:06:55 +01:00
925c978f16
set to runs-on ubuntu-latest
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Waiting to run
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 39s
2024-02-23 16:33:34 +01:00
e7d2ce2f30
2 try to get actions working
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 58s
Publish Docker image / Push Docker image to Docker Hub (push) Has been cancelled
2024-02-23 16:21:29 +01:00
90e2022098
Next try to get actions working
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 11s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 8s
2024-02-23 16:17:55 +01:00
c235162984
Add setup docker buildx step
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m44s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 1m17s
2024-02-23 16:08:50 +01:00
12717687d3
Update docker image tags
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 44s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 36s
2024-02-23 13:20:00 +01:00
82f8894897
Change branch to main
Some checks failed
Publish Docker rootless image / Push Docker image to Docker Hub (push) Failing after 1m14s
Publish Docker image / Push Docker image to Docker Hub (push) Failing after 37s
2024-02-23 13:08:55 +01:00
98d64a6f7d
Change runs_on ubuntu-latest to docker 2024-02-23 13:07:05 +01:00
1422306456
Normalize EOL for all files that Git considers text files 2024-02-08 17:18:30 +01:00
023df5f56c
Add new favicon 2024-02-08 17:12:51 +01:00
077ad50808
Merge branch 'master' 2024-02-08 17:05:05 +01:00
c616e0efb4 Initial commit 2024-02-06 17:47:00 +01:00
24 changed files with 1059 additions and 34 deletions

3
.dockerignore Normal file
View file

@ -0,0 +1,3 @@
# Folders
.idea/
.github/

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

View file

@ -3,15 +3,20 @@ name: Publish Docker rootless image
on:
workflow_dispatch:
push:
branches: [master]
branches: [main]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
runs-on: docker
container:
image: node:20-bullseye
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker Login
uses: docker/login-action@v1.12.0
@ -25,4 +30,4 @@ jobs:
context: .
push: true
file: Dockerfile-rootless
tags: mohamnag/nginx-file-browser:latest-rootless
tags: aroyart/iso-share:latest:latest-rootless

View file

@ -3,15 +3,20 @@ name: Publish Docker image
on:
workflow_dispatch:
push:
branches: [master]
branches: [main]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
runs-on: docker
container:
image: docker:cli
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker Login
uses: docker/login-action@v1.12.0
@ -24,4 +29,4 @@ jobs:
with:
context: .
push: true
tags: mohamnag/nginx-file-browser:latest
tags: aroyart/iso-share:latest

View file

@ -1,12 +1,15 @@
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 css/ /opt/www/file-browser/css/
ADD image/ /opt/www/file-browser/image/
ADD js/ /opt/www/file-browser/js/
ADD node_modules/ /opt/www/file-browser/node_modules/
ADD index.html /opt/www/file-browser/
ADD favicon.png /opt/www/file-browser/favicon.png
VOLUME /opt/www/files/
EXPOSE 80
EXPOSE 80

View file

@ -1,10 +1,10 @@
# nginx file browser
# ISO-Share
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).
![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
@ -46,3 +46,7 @@ shall be changed to
```
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)

View file

@ -7,6 +7,27 @@ a {
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 {
background: #ddd;
border-radius: 1rem;
@ -28,6 +49,11 @@ a {
.file-list li {
margin: 1rem 0;
margin-bottom: 1rem;
border-color: aqua;
border-style: dashed;
background-color: #e1e1e1;
padding: 0px 8px;
}
.file-list .file-name {
@ -50,7 +76,7 @@ a {
}
.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%;
margin: 0 .5rem;
color: #fff;
white-space: nowrap;
}
.file-list .directory .file-date {
@ -93,6 +120,7 @@ a {
font-size: 70%;
margin: 0 .5rem;
color: #fff;
white-space: nowrap;
}
.file-list .other .file-size,
@ -109,17 +137,25 @@ footer small + small {
margin-left: 2rem;
}
zero-md {
background-color: whitesmoke;
padding: 16px;
border: solid 4px gray;
border-radius: 8px;
}
/*
Start of alternations for dark theme
*/
body.dark {
background-color: #252525;
background-color: #040b1e;
color: #ecdbb2;
}
body.dark h1 {
color: #9dd08e;
color: #00ffff;
}
body.dark a {
@ -131,12 +167,16 @@ body.dark a.file-name {
}
body.dark .controls {
background: #413e3d;
background: #0f1e31;
color: #ecdbb2;
}
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 {
@ -147,4 +187,12 @@ body.dark .file-list .file-date {
body.dark .file-list .file-size {
background: #79aa7d;
color: #ecdbb2;
}
}
body.dark li {
margin-bottom: 1rem;
border-color: aqua;
border-style: dashed;
background-color: #0f1e31;
padding: 0px 8px;
}

View file

@ -124,7 +124,7 @@ body {
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
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; }

View file

@ -14,6 +14,10 @@ server {
disable_symlinks off;
}
location = /favicon.ico {
alias /opt/www/file-browser/favicon.png;
}
location / {
root /opt/www/file-browser/;
}
@ -23,4 +27,4 @@ server {
root /usr/share/nginx/html;
}
}
}

11
docker-compose.yml Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 12 KiB

BIN
image/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -5,7 +5,7 @@
<title>File Browser</title>
<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/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/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>
</head>
<body>
@ -21,28 +22,28 @@
<h1>File Browser</h1>
<div class="controls">
<span>sort list by:</span>
<span>Sort List by:</span>
<label>
date
<input type="radio" name="sort" value="date" checked>
Date
<input type="radio" name="sort" value="date">
</label>
<label>
name
<input type="radio" name="sort" value="name">
Name
<input type="radio" name="sort" value="name" checked>
</label>
<label>
size
Size
<input type="radio" name="sort" value="size">
</label>
<span>theme:</span>
<span>Theme:</span>
<label>
light
<input type="radio" name="theme" value="" checked>
Light
<input type="radio" name="theme" value="">
</label>
<label>
dark
<input type="radio" name="theme" value="dark">
Dark
<input type="radio" name="theme" value="dark" checked>
</label>
</div>

View file

@ -81,6 +81,19 @@ $(document).ready(function () {
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) {
var sortBy = $('input[name=sort]:checked').val();
@ -94,8 +107,13 @@ $(document).ready(function () {
} else if (sortBy === "name") {
console.log("sort by name");
var collator = new Intl.Collator([], {numeric: true});
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") {
@ -110,6 +128,16 @@ $(document).ready(function () {
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);
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(
path,
fileData.name,
@ -165,6 +203,19 @@ $(document).ready(function () {
console.log("replaceState", 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;
},
@ -203,7 +254,7 @@ $(document).ready(function () {
} else {
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();
});
});

16
node_modules/.package-lock.json generated vendored Normal file
View 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
View 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
View 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)
# &lt;zero-md&gt;
> 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

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

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

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

File diff suppressed because one or more lines are too long

53
node_modules/zero-md/package.json generated vendored Normal file
View 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
View 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)