diff --git a/README.md b/README.md index 124b2a5..25f42ee 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ finished, it was only a proof of concept. But now it works for created a entire Next will be, -* Some Tests -* Standalone, handle Webpack only as wrapper +* Some more Tests +* Image Handling in Markdown Maybe later, @@ -95,7 +95,7 @@ After parsing Markdown-Files a Page always this values, | hidden | {Boolean} | false | If hidden, file will not be written | | blocks | {Object} | Object | Blocks of Directory | | content | {String} | empty | Parsed Markdown | -| view | {String} | page.njk | Tempate to render file | +| view | {String} | page.njk | Tempate to render file | ## Nesting @@ -148,6 +148,54 @@ can be used like this, {% endFor %} ``` +## Media + +Image Files can be add to the Markdown-Structure and will be processed by [Sharp](https://sharp.pixelplumbing.com/). + +``` +recipes +└ index.md + _images + └ dog.jpg +``` + +In Fields all keys with "src" will be handled as Path to Images. Files will be search first +in current Directory of page, if nothing found, it will be search in Root-Directory of +Markdown-Structure. Blocks can also have there own Images. + +``` +--- +title: "health goth DIY tattooed" +view: "home.njk" +meta: + description: "La" +media: + teaser: + src: "_images/dog.jpg" + alt: "cold-pressed" +--- +``` + +It is also possible add + +``` +--- +title: "health goth DIY tattooed" +view: "home.njk" +meta: + description: "La" +media: + teaser: + src: + src: '_images/dog.jpg' + sizes: + - width: 300 + - width: 500 + height: 100 + alt: "cold-pressed" +--- +``` + ## Queries Queries can be used in Templates to get Pages. @@ -207,25 +255,6 @@ This function handle manifest-File from [https://laravel-mix.com/](Laravel-Mix). ``` -#### Filters - -##### resize - -The Filter is using [sharp](https://github.com/lovell/sharp), for crop, resize and -optimize Images. The Filter needs a relative Path in the File Structure. - -Basic Usage: - -``` -{% page.teaser.src | resize({ 'width': '300' }) %} -``` - -Add options: - -``` -{% page.teaser.src | resize({ 'width': '300' }, { sigma: 2 }) %} -``` - ## Json Results from PageQuery can also be created as json-File. The can use with a diff --git a/package.json b/package.json index efeeef1..4274504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@site-o-mat/core", - "version": "0.2.0", + "version": "0.3.0", "build": "webpack", "author": "Björn Hase ", "main": "index.js", diff --git a/ressources/_blocks/_images/dog-tired.webp b/ressources/_blocks/_images/dog-tired.webp new file mode 100644 index 0000000..b4d8942 Binary files /dev/null and b/ressources/_blocks/_images/dog-tired.webp differ diff --git a/ressources/_blocks/data.md b/ressources/_blocks/data.md index c8f8909..443565d 100644 --- a/ressources/_blocks/data.md +++ b/ressources/_blocks/data.md @@ -1,5 +1,7 @@ --- title: "Data" +media: + src: '_images/dog-tired.webp' --- ## Normcore cold-pressed ramps DSA diff --git a/ressources/_images/dog.jpg b/ressources/_images/dog.jpg new file mode 100644 index 0000000..3ac27f1 Binary files /dev/null and b/ressources/_images/dog.jpg differ diff --git a/ressources/blog/_images/dog.jpg b/ressources/blog/_images/dog.jpg new file mode 100644 index 0000000..6d21613 Binary files /dev/null and b/ressources/blog/_images/dog.jpg differ diff --git a/ressources/blog/article.md b/ressources/blog/article.md index dd527cf..23e002d 100644 --- a/ressources/blog/article.md +++ b/ressources/blog/article.md @@ -3,6 +3,8 @@ title: "article" view: "article.njk" meta: description: "DSA yes plz hot chicken green juice" +media: + src: '_images/dog.jpg' --- ## Normcore cold-pressed ramps DSA diff --git a/ressources/blog/index.md b/ressources/blog/index.md index c9a0769..8a7d246 100644 --- a/ressources/blog/index.md +++ b/ressources/blog/index.md @@ -3,6 +3,13 @@ title: "blog" view: "page.njk" meta: description: "DSA yes plz hot chicken green juice" +media: + src: + src: '_images/dog.jpg' + sizes: + - width: 300 + - width: 500 + height: 100 --- ## Normcore cold-pressed ramps DSA diff --git a/ressources/index.md b/ressources/index.md index 997e599..de99570 100644 --- a/ressources/index.md +++ b/ressources/index.md @@ -3,6 +3,13 @@ title: "health goth DIY tattooed" view: "page.njk" meta: description: "DSA yes plz hot chicken green juice" +media: + src: + src: '_images/dog.jpg' + sizes: + - width: 300 + - width: 500 + height: 100 --- ## Normcore cold-pressed ramps DSA diff --git a/src/engines/helpers.js b/src/engines/helpers.js index fba26db..a501f30 100644 --- a/src/engines/helpers.js +++ b/src/engines/helpers.js @@ -1,8 +1,6 @@ const path = require('path') const fs = require('fs') -const Media = require('./../factories/media.js') - /** * asset - checks manifest.json for given path and return * file path with id for cache busting @@ -33,21 +31,4 @@ function asset(staticPath) { return result } -/** - * asset - checks manifest.json for given path and return - * file path with id for cache busting - * - * - * @param {String} publicPath - * - */ - -async function resize(src, sizes, options, done) -{ - const media = new Media() - - src = await media.resize(src, sizes, options) - done(null, src) -} - -module.exports = { asset, resize } \ No newline at end of file +module.exports = { asset, media } diff --git a/src/engines/nunjucks.js b/src/engines/nunjucks.js index a63308d..98ecfdc 100644 --- a/src/engines/nunjucks.js +++ b/src/engines/nunjucks.js @@ -4,7 +4,7 @@ const assign = require('assign-deep') const { minify } = require('html-minifier') const configStore = require('./../config.js') -const { asset, resize } = require('./helpers.js') +const { asset } = require('./helpers.js') const PageQuery = require('./../queries/pages.js') const dayjs = require('dayjs') @@ -37,14 +37,6 @@ class Engine { this.nunjucks = nunjucks.configure(views, this._options) - // add filter: resize - this.nunjucks.addFilter('resize', (...args) => { - const done = args.pop() - const options = args?.[2] ? {} : args[2] - - resize(args[0], args[1], options, done) - }, true) - // adding defaults for view, data from site.yml, functions and pageQuery this._defaults = assign(this._options.defaults, { site: site, diff --git a/src/factories/block.js b/src/factories/block.js index 4234165..a784ac2 100644 --- a/src/factories/block.js +++ b/src/factories/block.js @@ -1,6 +1,5 @@ const path = require('path') const parseMarkdownFile = require('./../parsers/markdown.js') - const assign = require('assign-deep') /** @@ -23,11 +22,13 @@ class Block { * @param {string} fileString * */ - constructor(fileString) { + constructor(fileString, dirPath) { // parse string of file const parsedFile = parseMarkdownFile(fileString) + this._dirPath = dirPath + // getting parsed data this._content = parsedFile.content this._fields = parsedFile.fields @@ -46,4 +47,4 @@ class Block { } } -module.exports = Block \ No newline at end of file +module.exports = Block diff --git a/src/factories/media.js b/src/factories/media.js index 9fe420f..ba25ab6 100644 --- a/src/factories/media.js +++ b/src/factories/media.js @@ -8,16 +8,25 @@ const slugify = require('slugify') const configStore = require('./../config.js') /** + * Media * + * change size, optimize and copy media to assets + * + * @author Björn Hase + * @license http://opensource.org/licenses/MIT The MIT License + * @link https://gitea.node001.net/HerrHase/siteomat-webpack-plugin.git * */ + class Media { - constructor() { + constructor(path = NULL) { + this._path = path this._DIR_ASSETS = '/assets/' } /** + * resolve media * * @param {string} src * @param {object} sizes @@ -25,75 +34,183 @@ class Media { * @return {string} * */ - async resize(src, sizes, options = {}) { + resolve(src, sizes = {}, options = {}) { - this._extension = path.extname(src) - this._filename = slugify(path.basename(src, this._extension)) + let extension = path.extname(src) + let sourcePath + + const filename = slugify(path.basename(src, extension)) - this._process = await sharp(configStore.get('source') + '/' + src) + // check for images in path + if (this._path && fs.existsSync(configStore.get('source') + this._path + '/' + src)) { + sourcePath = configStore.get('source') + this._path + '/' + src + } else { + sourcePath = configStore.get('source') + '/' + src + } + + // getting sharp + const process = sharp(sourcePath) - // resize without options and with options - if (Object.getOwnPropertyNames(options).length === 0) { - await this._process - .resize(sizes) + if (extension === '.gif') { + process + .gif({ + reoptimise: true + }) } else { - this._process - .resize(sizes, options) + + // change extension + extension = '.webp' + process + .webp({ + lossless: true + }) } - // optimize - this._optimize() + // destination + const destinationPath = this._getDestinationPath(sourcePath) + + // create files to write + const filesToWrite = this._getFilesToWrite(filename, extension, destinationPath, sizes) - const fileBuffer = await this._process - .toBuffer() + // results contains only path as strings + const results = {} + + // create path if not exists + if (!fs.existsSync(configStore.get('destination') + destinationPath)) { + mkdirp.sync(configStore.get('destination') + destinationPath) + } - const relativeDestinationPath = this._DIR_ASSETS + this._resolveRelativeDestinationPath(fileBuffer) + filesToWrite.forEach((file) => { + if (!fs.existsSync(configStore.get('destination') + file.path)) { + this._writeFile(file, process, options) + } - // create directories and write file - mkdirp.sync(configStore.get('destination') + relativeDestinationPath) - fs.writeFileSync(configStore.get('destination') + relativeDestinationPath + '/' + this._filename + this._extension, fileBuffer) + results[file.name] = file.path + }) - return relativeDestinationPath + '/' + this._filename + this._extension + return this._reduce(results) } /** - * @TODO much nicer to add a hook system so behavior can be change + * if only full is in results, reduce object to string * + * @param {Object} results + * @return {mixed} * + */ + _reduce(results) { + if (Object.getOwnPropertyNames(results).length === 1) { + results = results['full'] + } + + return results + } + + /** + * getting files to write + * + * @param {string} src * @param {string} extension - * + * @param {Object} sizes + * @return {string} */ - _optimize() { - if (this._extension === '.gif') { - this._process - .gif({ - reoptimise: true - }) - } else { + _getFilesToWrite(filename, extension, destinationPath, sizes) { + + const results = [] + + // add orginal + results.push(this._getFile(filename, destinationPath, extension)) + + // check for sizes + if (typeof sizes === 'object' && !Array.isArray(sizes)) { + results.push(this._getFile(filename, destinationPath, extension, sizes)) + } else if (Array.isArray(sizes)) { + sizes.forEach((size) => { + results.push(this._getFile(filename, destinationPath, extension, size)) + }) + } - // change extension - this._extension = '.webp' - this._process - .webp({ - lossless: true - }) + return results + } + + /** + * write files to destination + * + * @param {string} file + * @param {object} process + * @param {Object} options + */ + _writeFile(file, process, options) { + + // resize without options and with options + if (file.sizes && Object.getOwnPropertyNames(options).length === 0 && Object.getOwnPropertyNames(file.sizes).length > 0) { + process.resize(file.sizes) + } else if (file.sizes && Object.getOwnPropertyNames(options).length > 0 && Object.getOwnPropertyNames(file.sizes).length > 0) { + process.resize(file.sizes, options) } + + process.toFile(configStore.get('destination') + file.path) } /** - * resolve path to write file, hash will be get = fileBuffer and - * + * generate destination path from hash of file * - * @param {object} fileBuffer + * @param {string} * @return {string} - * */ - _resolveRelativeDestinationPath(fileBuffer) { + _getDestinationPath(sourcePath) { const hash = crypto.createHash('sha1') - hash.update(fileBuffer) + const file = fs.readFileSync(sourcePath) + + // getting hash from file + hash.update(file) + + return this._DIR_ASSETS + hash.digest('hex').match(new RegExp('.{1,8}', 'g')).join('/') + } + + /** + * create file as object, adding path, name with sizes + * + * @param {string} filename + * @param {object} destinationPath + * @param {Object} extension + * @param {sizes} sizes + * @return {object} + */ + _getFile(filename, destinationPath, extension, sizes = undefined) { + + let file = { + name: '' + } + + let prefix = '' + + // check for sizes + if (sizes && sizes.width) { + file.name += sizes.width + } + + if (sizes && sizes.height) { + if (sizes.width) { + prefix = 'x' + } + + file.name += prefix + sizes.height + } + + // create path before name is set to orginal as fallback + file.path = destinationPath + '/' + filename + file.name + extension + + if (!file.name) { + file.name = 'full' + } + + if (sizes) { + file.sizes = sizes + } - return hash.digest('hex').match(new RegExp('.{1,8}', 'g')).join('/') + return file } } -module.exports = Media \ No newline at end of file +module.exports = Media diff --git a/src/factories/page.js b/src/factories/page.js index fc859f3..e8b1c09 100644 --- a/src/factories/page.js +++ b/src/factories/page.js @@ -4,10 +4,11 @@ const merge = require('lodash.merge') const nunjucks = require('nunjucks') const assign = require('assign-deep') +const Media = require('./../factories/media.js') const parseMarkdownFile = require('./../parsers/markdown.js') /** - * Page + * Page - building from markdown file * * * @author Björn Hase @@ -56,6 +57,24 @@ class Page { this._content = result.content this._blocks = blocks + + // check for fields and resolve media + if (this._fields) { + this._fields = this._resolveMedia(this._fields, this._dirPath) + } + + // check for fields and resolve media + if (this._blocks) { + for (const key of Object.keys(this._blocks)) { + if (Array.isArray(this._blocks[key])) { + this._blocks[key].forEach((fields, index) => { + this._blocks[key][index] = this._resolveMedia(fields, this._dirPath + '/_blocks') + }) + } else { + this._blocks[key] = this._resolveMedia(this._blocks[key], this._dirPath + '/_blocks') + } + } + } } /** @@ -76,6 +95,38 @@ class Page { }, this._fields) } + /** + * + * + */ + _resolveMedia(fields, dirPath) { + for (const key of Object.keys(fields)) { + if (key === 'src') { + fields[key] = this._resolveMediaSrc(fields[key], dirPath) + } + + if (toString.call(fields[key]) === '[object Object]') { + fields[key] = this._resolveMedia(fields[key], dirPath) + } + } + + return fields + } + + _resolveMediaSrc(field, dirPath) { + const media = new Media(dirPath) + + if (typeof field === 'string' || field instanceof String) { + field = media.resolve(field) + } + + if (typeof field === 'object' || field instanceof Object) { + field = media.resolve(field.src, field.sizes) + } + + return field + } + /** * * diff --git a/src/queries/blocks.js b/src/queries/blocks.js index 878e57a..2c19348 100644 --- a/src/queries/blocks.js +++ b/src/queries/blocks.js @@ -43,7 +43,6 @@ class Blocks { * @return {array} */ find() { - if (fs.existsSync(this._dirPath)) { this._findFiles(this._dirPath) } @@ -72,11 +71,6 @@ class Blocks { return } - // if directory going deep - if (file.isDirectory()) { - this._findFiles(dirPath, parent + '/' + file.name) - } - // get file const fileString = this._getFile(file, dirPath + parent) @@ -86,7 +80,7 @@ class Blocks { } // create page object and add to page - const block = new BlockFactory(fileString) + const block = new BlockFactory(fileString, this._dirPath) const blockname = this._parseBlockname(file.name) if (this._isArray(file.name)) { @@ -150,4 +144,4 @@ class Blocks { } -module.exports =Blocks +module.exports = Blocks diff --git a/src/queries/pages.js b/src/queries/pages.js index 9876d5c..8be0afa 100644 --- a/src/queries/pages.js +++ b/src/queries/pages.js @@ -135,7 +135,7 @@ class Pages { // check for filters and skip if (this._filter && !this._filter.validate(page.get())) { - return; + return } this._results.push(page.get()) diff --git a/src/siteomat.js b/src/siteomat.js index 4c93a01..76d4e2f 100644 --- a/src/siteomat.js +++ b/src/siteomat.js @@ -12,7 +12,7 @@ const PagesQuery = require('./queries/pages.js') const parseYamlFile = require('./parsers/yaml.js') /** - * Siteomat + * Site-O-Mat * * * diff --git a/test/blocks.js b/test/blocks.js index 04e76de..1d60f4c 100644 --- a/test/blocks.js +++ b/test/blocks.js @@ -1,8 +1,13 @@ const { assert } = require('chai') const fs = require('fs') +const configStore = require('./../src/config.js') + describe('Blocks / Array', function () { + configStore.set('source', './ressources') + configStore.set('destination', './dist') + // get function parseMarkdownFile const BlocksQuery = require('./../src/queries/blocks.js') @@ -25,6 +30,9 @@ describe('Blocks / Array', function () { describe('Blocks / Single', function () { + configStore.set('source', './ressources') + configStore.set('destination', './dist') + // get function parseMarkdownFile const BlocksQuery = require('./../src/queries/blocks.js') diff --git a/test/pages.js b/test/pages.js index 217a567..7a73390 100644 --- a/test/pages.js +++ b/test/pages.js @@ -2,9 +2,13 @@ const { assert } = require('chai') const fs = require('fs') const PagesQuery = require('./../src/queries/pages.js') +const configStore = require('./../src/config.js') describe('Page /index.md', function () { + configStore.set('source', './ressources') + configStore.set('destination', './dist') + const query = new PagesQuery('./ressources'); const results = query.find() @@ -29,6 +33,9 @@ describe('Page /index.md', function () { describe('Page /blog/index.md', function () { + configStore.set('source', './ressources') + configStore.set('destination', './dist') + const query = new PagesQuery('./ressources'); const results = query.find() @@ -49,10 +56,21 @@ describe('Page /blog/index.md', function () { it('permalink', function() { assert.equal(page.permalink, '/blog') }) + + it('fields has media src', function() { + assert.deepEqual(page.media.src, { + "300": "/assets/88c010ea/4ca9b5f5/6024c57d/05899fae/a33d9a45/dog300.webp", + "500x100": "/assets/88c010ea/4ca9b5f5/6024c57d/05899fae/a33d9a45/dog500x100.webp", + "full": "/assets/88c010ea/4ca9b5f5/6024c57d/05899fae/a33d9a45/dog.webp" + }) + }) }) describe('Page /blog/article.md', function () { + configStore.set('source', './ressources') + configStore.set('destination', './dist') + const query = new PagesQuery('./ressources'); const results = query.find() @@ -69,4 +87,8 @@ describe('Page /blog/article.md', function () { it('permalink', function() { assert.equal(page.permalink, '/blog/article') }) + + it('fields has media src', function() { + assert.equal(page.media.src, '/assets/a6c45d17/11bf0a4e/a2b1d75d/dc85ca56/71c63294/dog.webp') + }) }) diff --git a/test/parserMarkdown.js b/test/parserMarkdown.js index d5b565d..c84eaa9 100644 --- a/test/parserMarkdown.js +++ b/test/parserMarkdown.js @@ -23,6 +23,19 @@ describe('Parser Markdown', function () { it('fields are valid', function() { assert.deepEqual(result.fields, { + media: { + src: { + sizes: [{ + width: 300 + }, + { + height: 100, + width: 500 + } + ], + src: '_images/dog.jpg' + } + }, title: 'health goth DIY tattooed', view: 'page.njk', meta: {