diff --git a/components/download-file-button/download-file-button.html.js b/components/download-file-button/download-file-button.html.js deleted file mode 100644 --- a/components/download-file-button/download-file-button.html.js +++ /dev/null @@ -1,33 +0,0 @@ -const Component = require('../component.class'); -const fs = require('fs'); -const util = require('util'); -const path = require('path'); - -const readFile = util.promisify(fs.readFile); - -class DownloadFileButton extends Component { - async renderFn(s, { text }) { - /* eslint-disable indent */ - return /* HTML */ ` - ${text} - `; - /* eslint-enable indent */ - } - static propsControls() { - return { text: 'text' }; - } -} - -module.exports = DownloadFileButton; diff --git a/components/download-file-button/download-file-button.js b/components/download-file-button/download-file-button.js new file mode 100644 --- /dev/null +++ b/components/download-file-button/download-file-button.js @@ -0,0 +1,35 @@ +const Component = require('../component.class'); + +class DownloadFileButton extends Component { + async renderFn(s, { text }) { + const fs = s.require('fs'); + const util = s.require('util'); + const path = s.require('path'); + + const readFile = util.promisify(fs.readFile); + /* eslint-disable indent */ + return /* HTML */ ` + ${text} + `; + /* eslint-enable indent */ + } + static propsControls() { + return { text: 'text' }; + } +} + +module.exports = DownloadFileButton; diff --git a/components/index.js b/components/index.js --- a/components/index.js +++ b/components/index.js @@ -1,9 +1,11 @@ const Markdown = require('./markdown/markdown.html'); const Navbar = require('./navbar/navbar.html'); -const DownloadFileButton = require('./download-file-button/download-file-button.html'); +const DownloadFileButton = require('./download-file-button/download-file-button'); +const ResponsiveImage = require('./responsive-image/responsive-image'); module.exports = { Markdown, Navbar, DownloadFileButton, + ResponsiveImage, }; diff --git a/components/responsive-image/responsive-image.html.js b/components/responsive-image/responsive-image.html.js deleted file mode 100644 --- a/components/responsive-image/responsive-image.html.js +++ /dev/null @@ -1,61 +0,0 @@ -const sharp = require('sharp'); -const path = require('path'); -const { promisify } = require('util'); -const locreq = require('locreq')(__dirname); -const fs = require('fs'); -const hashFile = require('../../../lib/hash-file.js'); -const access = promisify(fs.access); - -/* - image_path: string - resolutions: array of sizes ie. [200, 150 ...] - quality: number, quality of the resized image - sizes_attr: string, sizes attribute for responsive img html tag - alt: string, img's alt attribute -*/ - -module.exports = async function({ - image_path, - resolutions, - quality = 80, - sizes_attr, - alt = '', -}) { - try { - const image_path_to_checksum = {}; - - const created_files = await Promise.all( - resolutions.map(async resolution => { - let image_basename = path.basename(image_path); //Extract file's name - const extension = path.extname(image_basename); //Get the file extension - image_basename = path.basename(image_path, extension); - if (!image_path_to_checksum[image_path]) { - image_path_to_checksum[image_path] = hashFile( - locreq.resolve(image_path) - ).substring(0, 8); - } - const checksum = image_path_to_checksum[image_path]; - const responsive_file_name = `${image_basename}-${checksum}-${resolution}w${extension}`; - const destination = `/assets/${responsive_file_name}`; - // If resized image already exists do not resize it again - await access(locreq.resolve(image_path), fs.constants.OK).catch( - () => { - return -1; - } - ); - //Resize and save new, transformed file - await sharp(locreq.resolve(image_path)) - .resize(resolution) - .jpeg({ quality }) - .toFile(locreq.resolve(`public/${destination}`)); - return `${destination} ${resolution}w`; - }) - ); - //Generate appropriate repsonsive img tag - return /* HTML */ `${alt}`; - } catch (error) { - throw new Error(error); - } -}; diff --git a/components/responsive-image/responsive-image.js b/components/responsive-image/responsive-image.js new file mode 100644 --- /dev/null +++ b/components/responsive-image/responsive-image.js @@ -0,0 +1,78 @@ +const Component = require('../component.class'); + +/* + image_path: string + resolutions: array of sizes ie. [200, 150 ...] + quality: number, quality of the resized image + sizes_attr: string, sizes attribute for responsive img html tag + alt: string, img's alt attribute +*/ + +class ResponsiveImage extends Component { + async renderFn( + s, + { + image_path, + resolutions = [100, 400, 900, 1000, 1300], + quality = 80, + sizes_attr, + alt = '', + } + ) { + const sharp = s.require('sharp'); + const path = s.require('path'); + const { promisify } = s.require('util'); + const locreq = s.require('locreq')(__dirname); + const fs = s.require('fs'); + const hashFile = s.require( + path.resolve(__dirname, '../../lib/hash-file.js') + ); + if (!image_path) { + image_path = locreq.resolve('assets/sealpage-logo.png'); + } + const readFile = promisify(fs.readFile); + + let image_basename = path.basename(image_path); //Extract file's name + const extension = path.extname(image_basename); //Get the file extension + image_basename = path.basename(image_path, extension); + + const file_hash = hashFile(locreq.resolve(image_path)).substring(0, 8); + + const output_files = {}; + + for (let resolution of resolutions) { + const path = await s.addOutputFile({ + output_subdir: `images`, + base_name: `${image_basename}-${resolution}.${extension}`, + generator: () => + sharp(locreq.resolve(image_path)) + .resize(resolution) + .jpeg({ quality }) + .toBuffer(), + deps: [file_hash], + }); + output_files[resolution] = path; + } + + const median_resolution = + resolutions[Math.round((resolutions.length - 1) / 2)]; + + const srcset = resolutions + .map(resolution => `${output_files[resolution]} ${resolution}w`) + .join(',\n'); + + return /* HTML */ ` + ${alt} + `; + } + static propsControls() { + return {}; + } +} + +module.exports = ResponsiveImage; diff --git a/get-app.js b/get-app.js --- a/get-app.js +++ b/get-app.js @@ -42,7 +42,8 @@ let output_dir = path.resolve(temporary_path); const component_instances = {}; - const s = new S({ output_dir }); + const path_prefix = `/previews/${uuid}`; + const s = new S({ output_dir, path_prefix }); // creating componentsinstances for (const component_name in components_map) { @@ -58,7 +59,7 @@ await writeFile(`${output_dir}/index.html`, html); - return `/previews/${uuid}/index.html?${uuidv4()}`; + return `${path_prefix}/index.html?${uuidv4()}`; } module.exports = config => { diff --git a/lib/s.js b/lib/s.js --- a/lib/s.js +++ b/lib/s.js @@ -6,32 +6,56 @@ const writeFile = util.promisify(fs.writeFile); const md5 = require('md5'); +const makeDir = require('make-dir'); class S { - constructor({ output_dir }) { + constructor({ output_dir, path_prefix }) { this.output_dir = output_dir; + this.path_prefix = path_prefix; // for example: "/previews/92180392ueu093" } - async addOutputFile({ relative_path, base_name, generator, deps }) { + async addOutputFile({ output_subdir, base_name, generator, deps }) { const hash = md5(JSON.stringify(deps)); let output_filename = base_name.split('.'); output_filename.splice(output_filename.length - 1, 0, hash); - output_filename.join('.'); + output_filename = output_filename.join('.'); + + if (output_subdir[0] === '/') { + output_subdir = '.' + output_subdir; + } const output_path = path.resolve( this.output_dir, - relative_path, + output_subdir, output_filename ); + try { + await fileExists(path.resolve(output_path, '..')); + } catch (error) { + if (error.code === 'ENOENT') { + await makeDir(path.resolve(output_path, '..')); + } else throw error; + } + try { await fileExists(output_path); } catch (error) { await writeFile(output_path, await generator()); } + return path.join(this.path_prefix, output_subdir, output_filename); + } - return output_path; + require(module_name) { + try { + return require(module_name); + } catch (e) { + console.error(e); + throw new Error( + `Could not resolve module '${module_name}'. It might not be installed or you're running the s.require function in the browser, while it's only supposed to be called on backend side` + ); + } } } diff --git a/package-lock.json b/package-lock.json --- a/package-lock.json +++ b/package-lock.json @@ -6666,6 +6666,24 @@ "commondir": "^1.0.1", "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, "find-up": { @@ -9776,20 +9794,17 @@ } }, "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "semver": "^6.0.0" }, "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==" } } }, @@ -10658,18 +10673,18 @@ } }, "mongodb": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.3.tgz", - "integrity": "sha512-jw8UyPsq4QleZ9z+t/pIVy3L++51vKdaJ2Q/XXeYxk/3cnKioAH8H6f5tkkDivrQL4PUgUOHe9uZzkpRFH1XtQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.7.tgz", + "integrity": "sha512-2YdWrdf1PJgxcCrT1tWoL6nHuk6hCxhddAAaEh8QJL231ci4+P9FLyqopbTm2Z2sAU6mhCri+wd9r1hOcHdoMw==", "requires": { - "mongodb-core": "^3.2.3", + "mongodb-core": "3.2.7", "safe-buffer": "^5.1.2" } }, "mongodb-core": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.3.tgz", - "integrity": "sha512-UyI0rmvPPkjOJV8XGWa9VCTq7R4hBVipimhnAXeSXnuAPjuTqbyfA5Ec9RcYJ1Hhu+ISnc8bJ1KfGZd4ZkYARQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.2.7.tgz", + "integrity": "sha512-WypKdLxFNPOH/Jy6i9z47IjG2wIldA54iDZBmHMINcgKOUcWJh8og+Wix76oGd7EyYkHJKssQ2FAOw5Su/n4XQ==", "requires": { "bson": "^1.1.1", "require_optional": "^1.0.1", @@ -10694,9 +10709,9 @@ "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" }, "nanoid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.0.1.tgz", - "integrity": "sha512-k1u2uemjIGsn25zmujKnotgniC/gxQ9sdegdezeDiKdkDW56THUMqlz3urndKCXJxA6yPzSZbXx/QCMe/pxqsA==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.0.3.tgz", + "integrity": "sha512-NbaoqdhIYmY6FXDRB4eYtDVC9Z9eCbn8TyaiC16LNKtpPv/aqa0tOPD8y6gNE4yUNnaZ7LLhYtXOev/6+cBtfw==" }, "nanomatch": { "version": "1.2.13", @@ -10975,11 +10990,6 @@ "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=" }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" - }, "node-watch": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.5.9.tgz", @@ -14022,8 +14032,8 @@ } }, "sealious": { - "version": "github:sealcode/sealious#e311447ce5ead8462a8150bd6f8c5ef1fcc783f0", - "from": "github:sealcode/sealious#e311447ce5ead8462a8150bd6", + "version": "github:sealcode/sealious#39b71ca55fa8b49aa548e8f0e995b01a5d36733d", + "from": "github:sealcode/sealious#39b71ca55fa8b49aa548e8f0e9", "requires": { "@3846masa/axios-cookiejar-support": "^0.1.4", "@babel/polyfill": "^7.0.0", @@ -14032,7 +14042,7 @@ "bluebird-events": "^2.0.0", "boom": "^4.2.0", "clone": "^1.0.2", - "color": "^3.1.1", + "color": "^3.1.2", "deep-equal": "^1.0.1", "dot-prop": "^4.2.0", "escape-html": "^1.0.3", @@ -14060,23 +14070,31 @@ }, "dependencies": { "axios": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", - "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", "requires": { - "follow-redirects": "^1.3.0", - "is-buffer": "^1.1.5" + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" } }, "color": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.1.tgz", - "integrity": "sha512-PvUltIXRjehRKPSy89VnDWFKY58xyhTLyxIg21vwQBI6qLwZNPmC8k3C1uytIgFKEpOIzN4y32iPm8231zFHIg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", "requires": { "color-convert": "^1.9.1", "color-string": "^1.5.2" } }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -14085,6 +14103,29 @@ "is-obj": "^1.0.0" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", diff --git a/package.json b/package.json --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "glob": "^7.1.3", "http-server": "^0.11.1", "locreq": "^1.1.0", + "make-dir": "^3.0.0", "marked": "^0.6.2", "md5": "^2.2.1", "md5-file": "^4.0.0", @@ -35,7 +36,7 @@ "react": "^16.8.6", "react-dom": "^16.8.6", "react-router-dom": "^5.0.0", - "sealious": "sealcode/sealious#e311447ce5ead8462a8150bd6", + "sealious": "sealcode/sealious#39b71ca55fa8b49aa548e8f0e9", "sharp": "^0.21.3", "uuid": "^3.3.2", "yargs": "^13.2.2"