├── .gitignore
├── assets
├── favicon.ico
├── app.js
└── style.styl
├── dist
├── assets
│ ├── favicon.ico
│ ├── app.js
│ └── style.css
└── bin
│ └── gallery.js
├── LICENSE
├── gulpfile.babel.js
├── views
└── index.hbs
├── README.md
├── package.json
└── bin
└── gallery.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasmcdermott/micro-gallery/HEAD/assets/favicon.ico
--------------------------------------------------------------------------------
/dist/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreasmcdermott/micro-gallery/HEAD/dist/assets/favicon.ico
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Andreas McDermott
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp'
2 | import babel from 'gulp-babel'
3 | import cached from 'gulp-cached'
4 | import chmod from 'gulp-chmod'
5 | import stylus from 'gulp-stylus'
6 |
7 | gulp.task('js-bin', () =>
8 | gulp
9 | .src('./bin/*.js')
10 | .pipe(cached('bin'))
11 | .pipe(babel())
12 | .pipe(chmod(755))
13 | .pipe(gulp.dest('./dist/bin'))
14 | )
15 |
16 | gulp.task('js-client', () =>
17 | gulp
18 | .src('./assets/app.js')
19 | .pipe(babel())
20 | .pipe(cached('clientjs'))
21 | .pipe(gulp.dest('./dist/assets'))
22 | )
23 |
24 | gulp.task('stylus', () =>
25 | gulp.src('./assets/*.styl').pipe(stylus()).pipe(gulp.dest('./dist/assets'))
26 | )
27 |
28 | gulp.task('other-assets', () =>
29 | gulp.src('./assets/favicon.ico').pipe(gulp.dest('./dist/assets'))
30 | )
31 |
32 | gulp.task('watch-js', () => {
33 | gulp.watch('./bin/*.js', ['js-bin'])
34 | gulp.watch('./assets/*.js', ['js-client'])
35 | })
36 |
37 | gulp.task('watch-stylus', () => {
38 | gulp.watch('./assets/*.styl', ['stylus'])
39 | })
40 |
41 | gulp.task('watch', ['watch-js', 'watch-stylus'])
42 | gulp.task('build', ['js-bin', 'js-client', 'stylus', 'other-assets'])
43 |
44 | gulp.task('default', ['build', 'watch'])
45 |
--------------------------------------------------------------------------------
/views/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Images in {{folder}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Images in {{#each path}}
16 | {{name}} {{/each}}
17 |
18 |
19 |
20 | {{#each directories}}
21 | {{name}}
22 | {{/each}}
23 |
24 |
25 |
26 | {{#each images}}
27 |
28 | {{/each}}
29 |
30 |
31 |
36 |
37 |
38 |
39 | {
2 | document.body.className = 'zoomed'
3 |
4 | const zoomedImg = document.getElementById('zoomedImage')
5 | zoomedImg.parentNode.parentNode.focus()
6 | zoomedImg.setAttribute('src', img.getAttribute('src'))
7 | zoomedImg.setAttribute('title', img.getAttribute('title'))
8 | zoomedImg.setAttribute('alt', img.getAttribute('alt'))
9 |
10 | zoomedImg.setAttribute('style',
11 | `margin: ${(window.innerHeight - zoomedImg.height) / 2}px ${(window.innerWidth - zoomedImg.width) / 2}px;`)
12 | }
13 |
14 | const unzoom = () => {
15 | document.body.className = ''
16 | }
17 |
18 | window.onload = () => {
19 | document.body.addEventListener('click', e => {
20 | if (e.target.className === 'images__img') {
21 | zoom(e.target)
22 | } else if (e.target.className === 'images__zoom') {
23 | zoom(e.target.firstChild)
24 | } else if (e.target.className.indexOf('js-unzoom') >= 0) {
25 | unzoom()
26 | } else {
27 | return
28 | }
29 |
30 | e.preventDefault()
31 |
32 | }, {capture: true})
33 |
34 | const TAB = 9
35 | const ESC = 27
36 | document.body.addEventListener('keydown', e => {
37 | if (document.body.className === 'zoomed') {
38 | if (e.which === TAB) {
39 | document.getElementById('zoomedImage').parentNode.focus()
40 | } else if (e.which === ESC) {
41 | unzoom()
42 | } else {
43 | return
44 | }
45 |
46 | e.preventDefault()
47 | }
48 | })
49 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # micro-gallery
2 | Like [zeit's serve](https://github.com/zeit/serve), but for images.
3 |
4 | Share you folder(s) of images with a single command.
5 |
6 | ## Demo
7 |
8 | https://micro-gallery.now.sh
9 |
10 | ### Demo source
11 |
12 | https://micro-gallery.now.sh/_src
13 |
14 | There isn't much "source" to talk about though, it is mainly a folder of images and a package.json file.
15 |
16 | ## Usage
17 | Install it (requires Node v6.0.0 and above)
18 |
19 | ```
20 | $ npm install -g micro-gallery
21 | ```
22 |
23 | Run it
24 |
25 | ```
26 | $ gallery [options]
27 | ```
28 |
29 | ### Options
30 |
31 | | Usage | Description | Default value |
32 | | ---------------------- | ----------- | ------------------ |
33 | | -h, --help | Output all available options | - |
34 | | -v, --version | The version tag of the micro-gallery instance on your device | - |
35 | | -p, --port [port] | A custom port on which the app will be running | 3000 |
36 | | -d, --dev | Use development mode. When active assets and template aren't cached | - |
37 |
38 | ## Contribute
39 |
40 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
41 | 2. Uninstall now-serve if it's already installed: `npm uninstall -g micro-gallery`
42 | 3. Link it to the global module directory: `npm link`
43 | 4. Transpile the source code and watch for changes: `npm start`
44 |
--------------------------------------------------------------------------------
/dist/assets/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const zoom = img => {
4 | document.body.className = 'zoomed';
5 |
6 | const zoomedImg = document.getElementById('zoomedImage');
7 | zoomedImg.parentNode.parentNode.focus();
8 | zoomedImg.setAttribute('src', img.getAttribute('src'));
9 | zoomedImg.setAttribute('title', img.getAttribute('title'));
10 | zoomedImg.setAttribute('alt', img.getAttribute('alt'));
11 |
12 | zoomedImg.setAttribute('style', `margin: ${ (window.innerHeight - zoomedImg.height) / 2 }px ${ (window.innerWidth - zoomedImg.width) / 2 }px;`);
13 | };
14 |
15 | const unzoom = () => {
16 | document.body.className = '';
17 | };
18 |
19 | window.onload = () => {
20 | document.body.addEventListener('click', e => {
21 | if (e.target.className === 'images__img') {
22 | zoom(e.target);
23 | } else if (e.target.className === 'images__zoom') {
24 | zoom(e.target.firstChild);
25 | } else if (e.target.className.indexOf('js-unzoom') >= 0) {
26 | unzoom();
27 | } else {
28 | return;
29 | }
30 |
31 | e.preventDefault();
32 | }, { capture: true });
33 |
34 | const TAB = 9;
35 | const ESC = 27;
36 | document.body.addEventListener('keydown', e => {
37 | if (document.body.className === 'zoomed') {
38 | if (e.which === TAB) {
39 | document.getElementById('zoomedImage').parentNode.focus();
40 | } else if (e.which === ESC) {
41 | unzoom();
42 | } else {
43 | return;
44 | }
45 |
46 | e.preventDefault();
47 | }
48 | });
49 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "micro-gallery",
3 | "version": "0.4.5",
4 | "description": "Like zeit's micro-list, but for images.",
5 | "bin": {
6 | "gallery": "./dist/bin/gallery.js"
7 | },
8 | "scripts": {
9 | "dev": "gallery",
10 | "build": "gulp build",
11 | "start": "gulp"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+ssh://git@github.com/andreasmcdermott/micro-gallery.git"
16 | },
17 | "keywords": ["gallery", "images", "micro", "micro-list"],
18 | "author": "Andreas McDermott ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/andreasmcdermott/micro-gallery/issues"
22 | },
23 | "homepage": "https://github.com/andreasmcdermott/micro-gallery#readme",
24 | "xo": {
25 | "semicolon": false,
26 | "space": true,
27 | "esnext": true
28 | },
29 | "babel": {
30 | "plugins": [
31 | "transform-es2015-modules-commonjs",
32 | "transform-async-to-generator"
33 | ]
34 | },
35 | "dependencies": {
36 | "chalk": "^1.1.3",
37 | "denodeify": "^1.2.1",
38 | "handlebars": "^4.0.5",
39 | "micro": "^6.0.2",
40 | "mime": "^1.3.4",
41 | "minimist": "^1.2.0",
42 | "url": "^0.11.0"
43 | },
44 | "devDependencies": {
45 | "babel-plugin-transform-async-to-generator": "^6.8.0",
46 | "babel-plugin-transform-es2015-modules-commonjs": "^6.14.0",
47 | "gulp": "^3.9.1",
48 | "gulp-babel": "^6.1.2",
49 | "gulp-cached": "^1.1.0",
50 | "gulp-chmod": "^1.3.0",
51 | "gulp-stylus": "^2.5.0",
52 | "prettier": "^1.5.3",
53 | "xo": "^0.16.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/assets/style.styl:
--------------------------------------------------------------------------------
1 | body
2 | background #fff
3 | margin 0
4 | padding 30px
5 | -webkit-font-smoothing antialiased
6 | font-family Menlo
7 |
8 | @media (min-width: 992px)
9 | padding 45px
10 |
11 | a
12 | color #1A00F2
13 | text-decoration none
14 |
15 | &:focus
16 | outline none
17 | border 1px dotted black
18 | margin -1px
19 |
20 | h1
21 | font-size 18px
22 | font-weight 500
23 | margin-top 0
24 | color #000
25 | font-family -apple-system, Helvetica
26 | display flex
27 |
28 | i
29 | font-style normal
30 |
31 | a
32 | color inherit
33 | font-weight bold
34 | border-bottom 1px dashed transparent
35 |
36 | &:after
37 | content '/'
38 |
39 | &:hover
40 | color #7d7d7d
41 |
42 | @media (min-width: 992px)
43 | font-size 15px
44 |
45 | main
46 | //max-width 920px
47 |
48 | ul
49 | margin 0
50 | padding 20px 0 0 0
51 |
52 | li
53 | list-style none
54 | padding 0
55 | font-size 14px
56 | display flex
57 | justify-content space-between
58 |
59 | i
60 | color #9B9B9B
61 | font-size 11px
62 | display block
63 | font-style normal
64 |
65 | @media (min-width: 992px)
66 | font-size 13px
67 | box-sizing border-box
68 | justify-content flex-start
69 |
70 | &:hover i
71 | opacity 1
72 |
73 | i
74 | font-size 10px
75 | opacity 0
76 | margin-left 10px
77 | margin-top 3px
78 |
79 | @media (min-width: 768px)
80 | display flex
81 | flex-wrap wrap
82 |
83 | .directories
84 | li
85 | @media (min-width: 768px)
86 | width 230px
87 | padding 10px 20px 10px 0
88 |
89 | a
90 | color #1A00F2
91 | white-space nowrap
92 | overflow hidden
93 | display block
94 |
95 | &:before
96 | content '\f114'
97 | font-family FontAwesome
98 | display inline-block
99 | margin-right 10px
100 | color #000
101 |
102 | &:hover
103 | color #000
104 |
105 | .images
106 | li
107 | @media (min-width: 768px)
108 | height 230px
109 | padding-right 20px
110 | margin-bottom: 10px
111 |
112 | img
113 | height 230px
114 |
115 | .image--zoomed
116 | display none
117 | position fixed
118 | top 0
119 | left 0
120 | width 100%
121 | height 100%
122 | padding 0
123 | margin 0
124 | background rgba(255, 255, 255, 0.7)
125 |
126 | img
127 | width auto
128 | height auto
129 | max-width 90%
130 | max-height 90%
131 | box-shadow 4px 4px 8px rgba(0, 0, 0, 0.5)
132 |
133 | .zoomed
134 | overflow hidden
135 |
136 | .image--zoomed
137 | display block
138 |
--------------------------------------------------------------------------------
/dist/assets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #fff;
3 | margin: 0;
4 | padding: 30px;
5 | -webkit-font-smoothing: antialiased;
6 | font-family: Menlo;
7 | }
8 | @media (min-width: 992px) {
9 | body {
10 | padding: 45px;
11 | }
12 | }
13 | a {
14 | color: #1a00f2;
15 | text-decoration: none;
16 | }
17 | a:focus {
18 | outline: none;
19 | border: 1px dotted #000;
20 | margin: -1px;
21 | }
22 | h1 {
23 | font-size: 18px;
24 | font-weight: 500;
25 | margin-top: 0;
26 | color: #000;
27 | font-family: -apple-system, Helvetica;
28 | display: flex;
29 | }
30 | h1 i {
31 | font-style: normal;
32 | }
33 | h1 a {
34 | color: inherit;
35 | font-weight: bold;
36 | border-bottom: 1px dashed transparent;
37 | }
38 | h1 a:after {
39 | content: '/';
40 | }
41 | h1 a:hover {
42 | color: #7d7d7d;
43 | }
44 | @media (min-width: 992px) {
45 | h1 {
46 | font-size: 15px;
47 | }
48 | }
49 | ul {
50 | margin: 0;
51 | padding: 20px 0 0 0;
52 | }
53 | ul li {
54 | list-style: none;
55 | padding: 0;
56 | font-size: 14px;
57 | display: flex;
58 | justify-content: space-between;
59 | }
60 | ul li i {
61 | color: #9b9b9b;
62 | font-size: 11px;
63 | display: block;
64 | font-style: normal;
65 | }
66 | @media (min-width: 992px) {
67 | ul li {
68 | font-size: 13px;
69 | box-sizing: border-box;
70 | justify-content: flex-start;
71 | }
72 | ul li:hover i {
73 | opacity: 1;
74 | }
75 | ul li i {
76 | font-size: 10px;
77 | opacity: 0;
78 | margin-left: 10px;
79 | margin-top: 3px;
80 | }
81 | }
82 | @media (min-width: 768px) {
83 | ul {
84 | display: flex;
85 | flex-wrap: wrap;
86 | }
87 | }
88 | @media (min-width: 768px) {
89 | .directories li {
90 | width: 230px;
91 | padding: 10px 20px 10px 0;
92 | }
93 | }
94 | .directories a {
95 | color: #1a00f2;
96 | white-space: nowrap;
97 | overflow: hidden;
98 | display: block;
99 | }
100 | .directories a:before {
101 | content: '\f114';
102 | font-family: FontAwesome;
103 | display: inline-block;
104 | margin-right: 10px;
105 | color: #000;
106 | }
107 | .directories a:hover {
108 | color: #000;
109 | }
110 | @media (min-width: 768px) {
111 | .images li {
112 | height: 230px;
113 | padding-right: 20px;
114 | margin-bottom: 10px;
115 | }
116 | }
117 | .images li img {
118 | height: 230px;
119 | }
120 | .image--zoomed {
121 | display: none;
122 | position: fixed;
123 | top: 0;
124 | left: 0;
125 | width: 100%;
126 | height: 100%;
127 | padding: 0;
128 | margin: 0;
129 | background: rgba(255,255,255,0.7);
130 | }
131 | .image--zoomed img {
132 | width: auto;
133 | height: auto;
134 | max-width: 90%;
135 | max-height: 90%;
136 | box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
137 | }
138 | .zoomed {
139 | overflow: hidden;
140 | }
141 | .zoomed .image--zoomed {
142 | display: block;
143 | }
144 |
--------------------------------------------------------------------------------
/bin/gallery.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 |
4 | import fs from 'fs'
5 | import path from 'path'
6 | import minimist from 'minimist'
7 | import toPromise from 'denodeify'
8 | import micro, { send } from 'micro'
9 | import mime from 'mime'
10 | import { parse } from 'url'
11 | import { green, red } from 'chalk'
12 | import Handlebars from 'handlebars'
13 |
14 | const defaultPort = 3000
15 | const validExtensions = new Set([
16 | '.jpeg',
17 | '.jpg',
18 | '.png',
19 | '.gif',
20 | '.bmp',
21 | '.tiff',
22 | '.tif'
23 | ])
24 | const ignoredFiles = new Set(['.git', '.DS_Store'])
25 |
26 | const argv = minimist(process.argv.slice(2), {
27 | alias: {
28 | dev: ['d'],
29 | port: ['p']
30 | }
31 | })
32 |
33 | const root =
34 | argv._.length > 0 ? path.resolve(process.cwd(), argv._[0]) : process.cwd()
35 | const rootObj = path.parse(root)
36 |
37 | const isDirectory = async directory => {
38 | try {
39 | const stats = await toPromise(fs.stat)(directory)
40 | return stats.isDirectory()
41 | } catch (err) {
42 | return false
43 | }
44 | }
45 |
46 | const exists = async filePath => {
47 | try {
48 | await toPromise(fs.stat)(filePath)
49 | return true
50 | } catch (err) {
51 | return false
52 | }
53 | }
54 |
55 | let cachedView = null
56 | const getView = async () => {
57 | if (!cachedView || argv.dev) {
58 | try {
59 | let file = await toPromise(fs.readFile)(
60 | path.resolve(__dirname, '../../views/index.hbs'),
61 | 'utf8'
62 | )
63 | cachedView = Handlebars.compile(file)
64 | } catch (err) {
65 | throw err
66 | }
67 | }
68 |
69 | return cachedView
70 | }
71 |
72 | let cachedAssets = {}
73 | const getAsset = async assetPath => {
74 | if (!cachedAssets[assetPath] || argv.dev) {
75 | try {
76 | let file = await toPromise(fs.readFile)(
77 | path.resolve(__dirname, '../../dist/assets', assetPath),
78 | 'utf8'
79 | )
80 | cachedAssets[assetPath] = file
81 | } catch (err) {
82 | throw err
83 | }
84 | }
85 |
86 | return cachedAssets[assetPath]
87 | }
88 |
89 | const renderDir = async directory => {
90 | const files = await toPromise(fs.readdir)(directory)
91 | const dirObj = path.parse(directory)
92 | let dirPath = `${dirObj.dir}/${dirObj.base}`.replace(`${rootObj.dir}/`, ``)
93 | let dirPathParts = dirPath.split('/')
94 |
95 | const data = {
96 | directories: [],
97 | images: [],
98 | path: [],
99 | assetsDir: '/assets',
100 | folder: dirObj.name
101 | }
102 |
103 | let url = []
104 | for (let i = 0; i < dirPathParts.length; ++i) {
105 | if (dirPathParts[i] !== rootObj.base) {
106 | url.push(dirPathParts[i])
107 | }
108 |
109 | data.path.push({
110 | url: url.join('/'),
111 | name: dirPathParts[i]
112 | })
113 | }
114 |
115 | for (let i = 0; i < files.length; ++i) {
116 | if (ignoredFiles.has(files[i])) {
117 | continue
118 | }
119 |
120 | const filePath = path.resolve(root, path.resolve(directory, files[i]))
121 | const relativeFilePath = path.relative(
122 | root,
123 | path.resolve(directory, files[i])
124 | )
125 | if (await isDirectory(filePath)) {
126 | data.directories.push({
127 | relative: relativeFilePath,
128 | name: files[i]
129 | })
130 | } else if (validExtensions.has(path.parse(filePath).ext)) {
131 | data.images.push({
132 | relative: relativeFilePath,
133 | name: files[i]
134 | })
135 | }
136 | }
137 |
138 | let view = await getView()
139 | return view(data)
140 | }
141 |
142 | const renderImage = async file => {
143 | try {
144 | const content = await toPromise(fs.readFile)(path.resolve(root, file))
145 | return {
146 | content: content,
147 | mime: mime.lookup(file)
148 | }
149 | } catch (err) {
150 | throw err
151 | }
152 | }
153 |
154 | const server = micro(async (req, res) => {
155 | const { pathname } = parse(req.url)
156 | const pathObj = path.parse(path.join(root, pathname))
157 | const reqPath = decodeURIComponent(path.format(pathObj))
158 |
159 | if (pathname.startsWith('/assets')) {
160 | let asset = await getAsset(pathname.replace('/assets/', ''))
161 | res.setHeader('Content-Type', `${mime.lookup(pathname)}; charset=utf-8`)
162 | return send(res, 200, asset)
163 | }
164 |
165 | if (!await exists(reqPath)) {
166 | return send(res, 404, 'Not found')
167 | }
168 |
169 | if (pathObj.ext === '') {
170 | const renderedDir = await renderDir(reqPath)
171 | return send(res, 200, renderedDir)
172 | } else if (validExtensions.has(pathObj.ext)) {
173 | try {
174 | const image = await renderImage(reqPath)
175 | res.setHeader('Content-Type', `${image.mime}; charset=utf-8`)
176 | return send(res, 200, image.content)
177 | } catch (err) {
178 | return send(res, 500, 'Error reading file content')
179 | }
180 | } else {
181 | return send(res, 400, 'Bad request')
182 | }
183 | })
184 |
185 | server.listen(argv.port || defaultPort, async () => {
186 | process.on('SIGINT', () => {
187 | server.close()
188 | process.exit(0)
189 | })
190 |
191 | if (!await isDirectory(root)) {
192 | console.error(red(`Specified directory doesn't exist!`))
193 | process.exit(1)
194 | }
195 |
196 | console.log(green(`Running on http://localhost:${server.address().port}`))
197 | })
198 |
--------------------------------------------------------------------------------
/dist/bin/gallery.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | var _fs = require('fs');
6 |
7 | var _fs2 = _interopRequireDefault(_fs);
8 |
9 | var _path = require('path');
10 |
11 | var _path2 = _interopRequireDefault(_path);
12 |
13 | var _minimist = require('minimist');
14 |
15 | var _minimist2 = _interopRequireDefault(_minimist);
16 |
17 | var _denodeify = require('denodeify');
18 |
19 | var _denodeify2 = _interopRequireDefault(_denodeify);
20 |
21 | var _micro = require('micro');
22 |
23 | var _micro2 = _interopRequireDefault(_micro);
24 |
25 | var _mime = require('mime');
26 |
27 | var _mime2 = _interopRequireDefault(_mime);
28 |
29 | var _url = require('url');
30 |
31 | var _chalk = require('chalk');
32 |
33 | var _handlebars = require('handlebars');
34 |
35 | var _handlebars2 = _interopRequireDefault(_handlebars);
36 |
37 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
38 |
39 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step("next", value); }, function (err) { return step("throw", err); }); } } return step("next"); }); }; }
40 |
41 | const defaultPort = 3000;
42 | const validExtensions = new Set(['.jpeg', '.jpg', '.png', '.gif', '.bmp', '.tiff', '.tif']);
43 | const ignoredFiles = new Set(['.git', '.DS_Store']);
44 |
45 | const argv = (0, _minimist2.default)(process.argv.slice(2), {
46 | alias: {
47 | dev: ['d'],
48 | port: ['p']
49 | }
50 | });
51 |
52 | const root = argv._.length > 0 ? _path2.default.resolve(process.cwd(), argv._[0]) : process.cwd();
53 | const rootObj = _path2.default.parse(root);
54 |
55 | const isDirectory = (() => {
56 | var _ref = _asyncToGenerator(function* (directory) {
57 | try {
58 | const stats = yield (0, _denodeify2.default)(_fs2.default.stat)(directory);
59 | return stats.isDirectory();
60 | } catch (err) {
61 | return false;
62 | }
63 | });
64 |
65 | return function isDirectory(_x) {
66 | return _ref.apply(this, arguments);
67 | };
68 | })();
69 |
70 | const exists = (() => {
71 | var _ref2 = _asyncToGenerator(function* (filePath) {
72 | try {
73 | yield (0, _denodeify2.default)(_fs2.default.stat)(filePath);
74 | return true;
75 | } catch (err) {
76 | return false;
77 | }
78 | });
79 |
80 | return function exists(_x2) {
81 | return _ref2.apply(this, arguments);
82 | };
83 | })();
84 |
85 | let cachedView = null;
86 | const getView = (() => {
87 | var _ref3 = _asyncToGenerator(function* () {
88 | if (!cachedView || argv.dev) {
89 | try {
90 | let file = yield (0, _denodeify2.default)(_fs2.default.readFile)(_path2.default.resolve(__dirname, '../../views/index.hbs'), 'utf8');
91 | cachedView = _handlebars2.default.compile(file);
92 | } catch (err) {
93 | throw err;
94 | }
95 | }
96 |
97 | return cachedView;
98 | });
99 |
100 | return function getView() {
101 | return _ref3.apply(this, arguments);
102 | };
103 | })();
104 |
105 | let cachedAssets = {};
106 | const getAsset = (() => {
107 | var _ref4 = _asyncToGenerator(function* (assetPath) {
108 | if (!cachedAssets[assetPath] || argv.dev) {
109 | try {
110 | let file = yield (0, _denodeify2.default)(_fs2.default.readFile)(_path2.default.resolve(__dirname, '../../dist/assets', assetPath), 'utf8');
111 | cachedAssets[assetPath] = file;
112 | } catch (err) {
113 | throw err;
114 | }
115 | }
116 |
117 | return cachedAssets[assetPath];
118 | });
119 |
120 | return function getAsset(_x3) {
121 | return _ref4.apply(this, arguments);
122 | };
123 | })();
124 |
125 | const renderDir = (() => {
126 | var _ref5 = _asyncToGenerator(function* (directory) {
127 | const files = yield (0, _denodeify2.default)(_fs2.default.readdir)(directory);
128 | const dirObj = _path2.default.parse(directory);
129 | let dirPath = `${ dirObj.dir }/${ dirObj.base }`.replace(`${ rootObj.dir }/`, ``);
130 | let dirPathParts = dirPath.split('/');
131 |
132 | const data = {
133 | directories: [],
134 | images: [],
135 | path: [],
136 | assetsDir: '/assets',
137 | folder: dirObj.name
138 | };
139 |
140 | let url = [];
141 | for (let i = 0; i < dirPathParts.length; ++i) {
142 | if (dirPathParts[i] !== rootObj.base) {
143 | url.push(dirPathParts[i]);
144 | }
145 |
146 | data.path.push({
147 | url: url.join('/'),
148 | name: dirPathParts[i]
149 | });
150 | }
151 |
152 | for (let i = 0; i < files.length; ++i) {
153 | if (ignoredFiles.has(files[i])) {
154 | continue;
155 | }
156 |
157 | const filePath = _path2.default.resolve(root, _path2.default.resolve(directory, files[i]));
158 | const relativeFilePath = _path2.default.relative(root, _path2.default.resolve(directory, files[i]));
159 | if (yield isDirectory(filePath)) {
160 | data.directories.push({
161 | relative: relativeFilePath,
162 | name: files[i]
163 | });
164 | } else if (validExtensions.has(_path2.default.parse(filePath).ext)) {
165 | data.images.push({
166 | relative: relativeFilePath,
167 | name: files[i]
168 | });
169 | }
170 | }
171 |
172 | let view = yield getView();
173 | return view(data);
174 | });
175 |
176 | return function renderDir(_x4) {
177 | return _ref5.apply(this, arguments);
178 | };
179 | })();
180 |
181 | const renderImage = (() => {
182 | var _ref6 = _asyncToGenerator(function* (file) {
183 | try {
184 | const content = yield (0, _denodeify2.default)(_fs2.default.readFile)(_path2.default.resolve(root, file));
185 | return {
186 | content: content,
187 | mime: _mime2.default.lookup(file)
188 | };
189 | } catch (err) {
190 | throw err;
191 | }
192 | });
193 |
194 | return function renderImage(_x5) {
195 | return _ref6.apply(this, arguments);
196 | };
197 | })();
198 |
199 | const server = (0, _micro2.default)((() => {
200 | var _ref7 = _asyncToGenerator(function* (req, res) {
201 | const { pathname } = (0, _url.parse)(req.url);
202 | const pathObj = _path2.default.parse(_path2.default.join(root, pathname));
203 | const reqPath = decodeURIComponent(_path2.default.format(pathObj));
204 |
205 | if (pathname.startsWith('/assets')) {
206 | let asset = yield getAsset(pathname.replace('/assets/', ''));
207 | res.setHeader('Content-Type', `${ _mime2.default.lookup(pathname) }; charset=utf-8`);
208 | return (0, _micro.send)(res, 200, asset);
209 | }
210 |
211 | if (!(yield exists(reqPath))) {
212 | return (0, _micro.send)(res, 404, 'Not found');
213 | }
214 |
215 | if (pathObj.ext === '') {
216 | const renderedDir = yield renderDir(reqPath);
217 | return (0, _micro.send)(res, 200, renderedDir);
218 | } else if (validExtensions.has(pathObj.ext)) {
219 | try {
220 | const image = yield renderImage(reqPath);
221 | res.setHeader('Content-Type', `${ image.mime }; charset=utf-8`);
222 | return (0, _micro.send)(res, 200, image.content);
223 | } catch (err) {
224 | return (0, _micro.send)(res, 500, 'Error reading file content');
225 | }
226 | } else {
227 | return (0, _micro.send)(res, 400, 'Bad request');
228 | }
229 | });
230 |
231 | return function (_x6, _x7) {
232 | return _ref7.apply(this, arguments);
233 | };
234 | })());
235 |
236 | server.listen(argv.port || defaultPort, _asyncToGenerator(function* () {
237 | process.on('SIGINT', function () {
238 | server.close();
239 | process.exit(0);
240 | });
241 |
242 | if (!(yield isDirectory(root))) {
243 | console.error((0, _chalk.red)(`Specified directory doesn't exist!`));
244 | process.exit(1);
245 | }
246 |
247 | console.log((0, _chalk.green)(`Running on http://localhost:${ server.address().port }`));
248 | }));
--------------------------------------------------------------------------------