parent
397d9003d2
commit
5c29b92a6e
@ -0,0 +1,24 @@
|
|||||||
|
import { validate } from 'schema-utils'
|
||||||
|
|
||||||
|
// schema for options object
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
test: {
|
||||||
|
type: 'string',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class HappySiteWebpackPlugin {
|
||||||
|
constructor(options = {}) {
|
||||||
|
validate(schema, options, {
|
||||||
|
name: 'Hello World Plugin',
|
||||||
|
baseDataPath: 'options',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(compiler) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "happy-site-webpack-plugin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"author": "Björn Hase <me@herr-hase.wtf>",
|
||||||
|
"main": "index.js",
|
||||||
|
"description": "Generating a Website from a Markdown Generator",
|
||||||
|
"dependencies": {
|
||||||
|
"dayjs": "^1.11.6",
|
||||||
|
"fast-xml-parser": "^4.0.11",
|
||||||
|
"html-minifier": "^4.0.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"marked": "^4.1.1",
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
|
"nunjucks": "^3.2.3",
|
||||||
|
"schema-utils": "^4.0.0",
|
||||||
|
"sharp": "^0.31.1",
|
||||||
|
"slugify": "^1.6.5",
|
||||||
|
"yaml": "^2.1.3"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 66 KiB |
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "health goth DIY tattooed"
|
||||||
|
view: "page.njk"
|
||||||
|
meta:
|
||||||
|
description: "DSA yes plz hot chicken green juice"
|
||||||
|
---
|
||||||
|
## Normcore cold-pressed ramps DSA
|
||||||
|
|
||||||
|
Normcore cold-pressed ramps DSA yes plz hot chicken green juice succulents leggings messenger bag truffaut iceland pabst ethical godard. Semiotics air plant marfa, drinking vinegar authentic iceland pug fit cloud bread cronut kickstarter glossier crucifix tumeric. Chicharrones polaroid flexitarian, seitan lumbersexual viral fam master cleanse four dollar toast scenester. Succulents poutine vegan keffiyeh meh, health goth DIY tattooed. Praxis roof party celiac chartreuse banjo butcher you probably haven't heard of them schlitz beard. Ethical tattooed kinfolk, cliche vegan messenger bag mukbang dreamcatcher cloud bread farm-to-table gatekeep trust fund.
|
||||||
|
|
||||||
|
## Palo santo leggings normcore aesthetic
|
||||||
|
|
||||||
|
bicycle rights sartorial godard slow-carb thundercats art party cray JOMO. Truffaut four dollar toast hoodie pour-over. Fanny pack iPhone jean shorts tote bag, master cleanse succulents tbh fixie gatekeep pok pok letterpress cornhole. Dreamcatcher tattooed hot chicken gatekeep, glossier salvia 8-bit cred. Fit lomo chillwave cold-pressed humblebrag narwhal. Meggings edison bulb fanny pack irony af pug pok pok whatever vexillologist vibecession cred butcher trust fund chia.
|
||||||
|
|
||||||
|
## Bitters kale chips chambray activated charcoal
|
||||||
|
|
||||||
|
wolf keffiyeh hell of selfies. Wolf readymade shoreditch flexitarian venmo single-origin coffee, knausgaard fit actually street art cold-pressed iPhone gatekeep. Migas bruh adaptogen semiotics marfa pickled yuccie. Locavore normcore lomo, shoreditch fashion axe actually glossier iPhone photo booth blue bottle DIY XOXO williamsburg. Pinterest whatever taxidermy, kale chips prism XOXO schlitz twee tote bag woke swag. Wayfarers fashion axe heirloom humblebrag synth. Whatever succulents PBR&B, pop-up enamel pin echo park tonx stumptown taiyaki.
|
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: "Cliche Vegan Messenger"
|
||||||
|
date_published: "2022-01-10 10:00"
|
||||||
|
view: "post.njk"
|
||||||
|
tags:
|
||||||
|
- tumeric
|
||||||
|
- heard
|
||||||
|
- bag
|
||||||
|
media:
|
||||||
|
teaser: 'jep.jpg'
|
||||||
|
meta:
|
||||||
|
description: "DSA yes plz hot chicken green juice"
|
||||||
|
robots: "index, follow"
|
||||||
|
---
|
||||||
|
## TEST
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: "Blog"
|
||||||
|
view: "blog.njk"
|
||||||
|
meta:
|
||||||
|
description: "DSA yes plz hot chicken green juice"
|
||||||
|
robots: "index, follow"
|
||||||
|
---
|
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: "health goth DIY tattooed"
|
||||||
|
view: "home.njk"
|
||||||
|
meta:
|
||||||
|
description: "La"
|
||||||
|
media:
|
||||||
|
teaser:
|
||||||
|
src: "_images/test.jpeg"
|
||||||
|
alt: "cold-pressed"
|
||||||
|
---
|
||||||
|
## Normcore cold-pressed ramps DSA
|
||||||
|
|
||||||
|
Normcore cold-pressed ramps DSA yes plz hot chicken green juice succulents leggings messenger bag truffaut iceland pabst ethical godard. Semiotics air plant marfa, drinking vinegar authentic iceland pug fit cloud bread cronut kickstarter glossier crucifix tumeric. Chicharrones polaroid flexitarian, seitan lumbersexual viral fam master cleanse four dollar toast scenester. Succulents poutine vegan keffiyeh meh, health goth DIY tattooed. Praxis roof party celiac chartreuse banjo butcher you probably haven't heard of them schlitz beard. Ethical tattooed kinfolk, cliche vegan messenger bag mukbang dreamcatcher cloud bread farm-to-table gatekeep trust fund.
|
||||||
|
|
||||||
|
## Palo santo leggings normcore aesthetic
|
||||||
|
|
||||||
|
bicycle rights sartorial godard slow-carb thundercats art party cray JOMO. Truffaut four dollar toast hoodie pour-over. Fanny pack iPhone jean shorts tote bag, master cleanse succulents tbh fixie gatekeep pok pok letterpress cornhole. Dreamcatcher tattooed hot chicken gatekeep, glossier salvia 8-bit cred. Fit lomo chillwave cold-pressed humblebrag narwhal. Meggings edison bulb fanny pack irony af pug pok pok whatever vexillologist vibecession cred butcher trust fund chia.
|
||||||
|
|
||||||
|
## Bitters kale chips chambray activated charcoal
|
||||||
|
|
||||||
|
wolf keffiyeh hell of selfies. Wolf readymade shoreditch flexitarian venmo single-origin coffee, knausgaard fit actually street art cold-pressed iPhone gatekeep. Migas bruh adaptogen semiotics marfa pickled yuccie. Locavore normcore lomo, shoreditch fashion axe actually glossier iPhone photo booth blue bottle DIY XOXO williamsburg. Pinterest whatever taxidermy, kale chips prism XOXO schlitz twee tote bag woke swag. Wayfarers fashion axe heirloom humblebrag synth. Whatever succulents PBR&B, pop-up enamel pin echo park tonx stumptown taiyaki.
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: "health goth DIY tattooed"
|
||||||
|
view: "page.njk"
|
||||||
|
meta:
|
||||||
|
description: "DSA yes plz hot chicken green juice"
|
||||||
|
robots: "index, nofollow"
|
||||||
|
---
|
@ -0,0 +1 @@
|
|||||||
|
lkjlkjlkj
|
@ -0,0 +1 @@
|
|||||||
|
kljkljlkj
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: "health goth DIY tattooed"
|
||||||
|
view: "page.njk"
|
||||||
|
meta:
|
||||||
|
description: "DSA yes plz hot chicken green juice"
|
||||||
|
robots: "index, nofollow"
|
||||||
|
---
|
@ -0,0 +1,3 @@
|
|||||||
|
title: "test"
|
||||||
|
language: "en"
|
||||||
|
domain: "test.io"
|
@ -0,0 +1 @@
|
|||||||
|
{% extends('layout.njk') %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% macro meta(page) %}
|
||||||
|
{% if (page.meta) %}
|
||||||
|
{% for key, content in page.meta %}
|
||||||
|
<meta name="{{ key }}" content="{{ content }}" />
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends('layout.njk') %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<img src="{{ page.media.teaser.src | media }}" />
|
||||||
|
|
||||||
|
{{ page.content | safe }}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,54 @@
|
|||||||
|
{% import "helpers/meta.njk" as helpers %}
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ site.language }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>
|
||||||
|
{{ site.title }} | {{ page.title }}
|
||||||
|
</title>
|
||||||
|
|
||||||
|
{{ helpers.meta(page) }}
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="{{ asset('/css/styles.css') }}" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
{% block addHead %}{% endblock %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="site-body">
|
||||||
|
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="bar">
|
||||||
|
<div class="bar__start">
|
||||||
|
<h1 class="site-header__title">
|
||||||
|
{{ page.title }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="site-main">
|
||||||
|
<div class="container">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="col-12">
|
||||||
|
{% block main %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="col-12">
|
||||||
|
MIT License
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="{{ asset('/js/app.js') }}"></script>
|
||||||
|
{% block addFooter %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,9 @@
|
|||||||
|
{% extends('layout.njk') %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{{ page.content | safe }}
|
||||||
|
|
||||||
|
{% for hero in page.blocks.hero %}
|
||||||
|
{{ hero.content }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,5 @@
|
|||||||
|
{% extends('layout.njk') %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{{ page.content | safe }}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,55 @@
|
|||||||
|
import nunjucks from 'nunjucks'
|
||||||
|
import { minify } from 'html-minifier'
|
||||||
|
|
||||||
|
import { asset, media } from './helpers/engine.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* engine - handle eta.js
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Engine {
|
||||||
|
|
||||||
|
constructor(views, site, options) {
|
||||||
|
|
||||||
|
// merge options
|
||||||
|
this._options = Object.assign({}, {
|
||||||
|
autoescapes: true,
|
||||||
|
throwOnUndefined: true
|
||||||
|
}, options)
|
||||||
|
|
||||||
|
this.nunjucks = nunjucks.configure(views, this._options)
|
||||||
|
this.nunjucks.addFilter('media', function(options) {
|
||||||
|
return media(options)
|
||||||
|
})
|
||||||
|
|
||||||
|
// adding defaults for view, function and data from config.yml
|
||||||
|
this._defaults = {
|
||||||
|
site: site,
|
||||||
|
asset: asset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* render
|
||||||
|
*
|
||||||
|
* @param {string} view
|
||||||
|
* @param {object} data
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
render(view, data) {
|
||||||
|
data = Object.assign({}, data, this._defaults)
|
||||||
|
|
||||||
|
return minify(this.nunjucks.render(view, data), {
|
||||||
|
removeComments: true,
|
||||||
|
collapseWhitespace: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Engine
|
@ -0,0 +1,70 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import mkdirp from 'mkdirp'
|
||||||
|
|
||||||
|
import Engine from './engine.js'
|
||||||
|
import Sitemap from './sitemap.js'
|
||||||
|
|
||||||
|
import PagesQuery from './queries/pages.js'
|
||||||
|
import parseYamlFile from './parsers/yaml.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class HappySite {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} source
|
||||||
|
* @param {string} destination
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(source, destination, views) {
|
||||||
|
this._source = source
|
||||||
|
this._destination = destination
|
||||||
|
this._views = views
|
||||||
|
|
||||||
|
// get config for site
|
||||||
|
if (fs.existsSync(this._source + '/site.yml')) {
|
||||||
|
const file = fs.readFileSync(this._source + '/site.yml', 'utf8')
|
||||||
|
this._site = parseYamlFile(file)
|
||||||
|
} else {
|
||||||
|
throw new Error('site.yml not found in ' + this._source + '!')
|
||||||
|
}
|
||||||
|
|
||||||
|
this._engine = new Engine(views, this._site)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* let it rain \o/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
run() {
|
||||||
|
const query = new PagesQuery(this._source)
|
||||||
|
const results = query.find()
|
||||||
|
|
||||||
|
const sitemap = new Sitemap(this._site)
|
||||||
|
|
||||||
|
// run through pages and generate html files
|
||||||
|
results.forEach((page) => {
|
||||||
|
const content = page.render(this._engine)
|
||||||
|
|
||||||
|
// create directories and write file from page
|
||||||
|
mkdirp(this._destination + page.pathname).then(() => {
|
||||||
|
fs.writeFileSync(this._destination + page.pathname + '/' + page.filename, content)
|
||||||
|
})
|
||||||
|
|
||||||
|
sitemap.addPage(page)
|
||||||
|
})
|
||||||
|
|
||||||
|
// write sitemap
|
||||||
|
fs.writeFileSync(this._destination + '/sitemap.xml', sitemap.getXmlAsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HappySite
|
@ -0,0 +1,56 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import sharp from 'sharp'
|
||||||
|
|
||||||
|
const basePath = path.join(path.resolve())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asset - checks manifest.json for given path and return
|
||||||
|
* file path with id for cache busting
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {String} publicPath
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function asset(staticPath)
|
||||||
|
{
|
||||||
|
// getting basePath
|
||||||
|
let result = staticPath
|
||||||
|
|
||||||
|
// path to mix-manifest
|
||||||
|
const file = basePath + 'mix-manifest.json'
|
||||||
|
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
|
||||||
|
const manifest = fs.readFileSync(file)
|
||||||
|
const files = JSON.parse(manifest)
|
||||||
|
|
||||||
|
if (files[staticPath]) {
|
||||||
|
result = files[staticPath]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asset - checks manifest.json for given path and return
|
||||||
|
* file path with id for cache busting
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {String} publicPath
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function media(src, options)
|
||||||
|
{
|
||||||
|
console.log(basePath)
|
||||||
|
console.log(path.resolve(src))
|
||||||
|
sharp(src)
|
||||||
|
.toFile('output.png', (error, info) => { console.log(error) })
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
export { asset, media }
|
@ -0,0 +1,34 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import parseMarkdownFile from './../parsers/markdown.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block
|
||||||
|
*
|
||||||
|
* parsed markdown-file that can contains fields
|
||||||
|
* as yaml
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Block {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} fileString
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(fileString) {
|
||||||
|
|
||||||
|
// parse string of file
|
||||||
|
const parsedFile = parseMarkdownFile(fileString)
|
||||||
|
|
||||||
|
this._content = parsedFile.content
|
||||||
|
this._fields = parsedFile.fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Block
|
@ -0,0 +1,113 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import slugify from 'slugify'
|
||||||
|
import merge from 'lodash.merge'
|
||||||
|
|
||||||
|
import parseMarkdownFile from './../parsers/markdown.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Page {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {object} file
|
||||||
|
* @param {string} parent
|
||||||
|
* @param {string} fileString
|
||||||
|
* @param {object} [blocks=null]
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(file, parent, fileString, blocks = {}) {
|
||||||
|
|
||||||
|
// parse file
|
||||||
|
const result = parseMarkdownFile(fileString)
|
||||||
|
|
||||||
|
// adding filename for html as pathname and relative path in structure
|
||||||
|
this.filename = this._resolveFilename(file)
|
||||||
|
this.pathname = this._resolvePathname(parent)
|
||||||
|
|
||||||
|
// fields merge by default values
|
||||||
|
this._fields = merge({
|
||||||
|
view: 'page',
|
||||||
|
meta: {
|
||||||
|
robots: 'index'
|
||||||
|
}
|
||||||
|
}, result.fields)
|
||||||
|
|
||||||
|
this._content = result.content
|
||||||
|
this._blocks = blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* render view of page
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
render(engine) {
|
||||||
|
|
||||||
|
const page = Object.assign({}, this._fields)
|
||||||
|
|
||||||
|
page.content = this._content
|
||||||
|
page.blocks = this._blocks
|
||||||
|
page.path = this.pathname + '/' + this.filename
|
||||||
|
|
||||||
|
const result = engine.render(this._fields.view, {
|
||||||
|
page: page
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create html-filename from filename
|
||||||
|
*
|
||||||
|
* @param {string} file
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_resolveFilename(file) {
|
||||||
|
|
||||||
|
let filename = file.name
|
||||||
|
|
||||||
|
if (filename === 'index.md') {
|
||||||
|
filename = 'index'
|
||||||
|
} else {
|
||||||
|
if (path.extname(filename) === '.md') {
|
||||||
|
filename = filename.replace('.md', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = slugify(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename + '.html'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pathname from parent
|
||||||
|
*
|
||||||
|
* @param {string} parent
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_resolvePathname(parent) {
|
||||||
|
let pathname = parent
|
||||||
|
|
||||||
|
if (parent === '/') {
|
||||||
|
pathname = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page
|
@ -0,0 +1,42 @@
|
|||||||
|
import yaml from 'js-yaml'
|
||||||
|
import { marked } from 'marked'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse string of file, parse yaml and parse markdown
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parseMarkdownFile(fileString) {
|
||||||
|
|
||||||
|
// regex get yaml section and markdown
|
||||||
|
// thanks to, https://github.com/getgrav/grav
|
||||||
|
const regex = new RegExp(/^(---\n(.+?)\n---){0,}(.*)$/gs)
|
||||||
|
const matches = regex.exec(fileString)
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
fields: undefined,
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if yaml section not exists throw error
|
||||||
|
if (matches?.[2]) {
|
||||||
|
try {
|
||||||
|
result.fields = yaml.load(matches[2])
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Yaml has errors!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if markdown section exits parse it to html 6565
|
||||||
|
if (matches?.[3]) {
|
||||||
|
result.content = marked.parse(matches[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseMarkdownFile
|
@ -0,0 +1,25 @@
|
|||||||
|
import yaml from 'js-yaml'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse string of file and only parse yaml
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
function parseYamlFile(file) {
|
||||||
|
|
||||||
|
let config
|
||||||
|
|
||||||
|
try {
|
||||||
|
config = yaml.load(file)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Yaml has errors!')
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseYamlFile
|
@ -0,0 +1,131 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import Block from './../models/block.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* search, filter and find pages
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Blocks {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} dirPath
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(dirPath) {
|
||||||
|
|
||||||
|
this.DIRECTORY_BLOCKS = '_blocks'
|
||||||
|
|
||||||
|
this._dirPath = dirPath + '/' + this.DIRECTORY_BLOCKS;
|
||||||
|
this._results = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return {array}
|
||||||
|
*/
|
||||||
|
find() {
|
||||||
|
|
||||||
|
if (fs.existsSync(this._dirPath)) {
|
||||||
|
this._findFiles(this._dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._results
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find files
|
||||||
|
*
|
||||||
|
* @param {[type]} dirPath
|
||||||
|
* @param {Object} [parent = '']
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_findFiles(dirPath, parent = '') {
|
||||||
|
|
||||||
|
//
|
||||||
|
const files = fs.readdirSync(dirPath, {
|
||||||
|
withFileTypes: true
|
||||||
|
})
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
|
||||||
|
// skip for file that is not markdown
|
||||||
|
if (file.isFile() && path.extname(file.name) !== '.md') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if directory going deep
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
this._findFiles(dirPath, parent + '/' + file.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get file
|
||||||
|
const fileString = this._getFile(file, dirPath + parent)
|
||||||
|
|
||||||
|
// skip if empty
|
||||||
|
if (!fileString) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create page object and add to page
|
||||||
|
const block = new Block(fileString)
|
||||||
|
const blockname = this._parseBlockname(file.name)
|
||||||
|
|
||||||
|
if (!this._results[blockname]) {
|
||||||
|
this._results[blockname] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
this._results[blockname].push({
|
||||||
|
fields: block._fields,
|
||||||
|
content: block._content
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove '.md' and also ordering number from filename
|
||||||
|
*
|
||||||
|
* @param {string} filename
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_parseBlockname(filename) {
|
||||||
|
const regex = new RegExp(/[-_]?[0-9]*\b.md\b$/)
|
||||||
|
return filename.replace(regex, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get file content
|
||||||
|
*
|
||||||
|
* @param {string} slug
|
||||||
|
* @param {string} sourcePath
|
||||||
|
* @return {mixed}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_getFile(file, dirPath) {
|
||||||
|
|
||||||
|
// file
|
||||||
|
let result = null
|
||||||
|
|
||||||
|
// path of file, first try with slug
|
||||||
|
let filePath = dirPath + '/' + file.name
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath) && file.isFile()) {
|
||||||
|
result = fs.readFileSync(filePath, 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Blocks
|
@ -0,0 +1,158 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import Page from './../models/page.js'
|
||||||
|
import Blocks from './../queries/blocks.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pages - search, filter and find pages
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Pages {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} dirPath
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(dirPath) {
|
||||||
|
|
||||||
|
// constants
|
||||||
|
this.FILE_EXTENSION = '.md'
|
||||||
|
this.FILE_INDEX = 'index'
|
||||||
|
this.DIRECTORY_BLOCKS = '_blocks'
|
||||||
|
|
||||||
|
// default options for find
|
||||||
|
this._options = {
|
||||||
|
parent: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dirPath = dirPath
|
||||||
|
this._results = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find pages
|
||||||
|
*
|
||||||
|
* @param {Object} [options={}]
|
||||||
|
* @return {array}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
find(options = {}) {
|
||||||
|
this._results = []
|
||||||
|
|
||||||
|
options = Object.assign({}, this._options, options)
|
||||||
|
this._findFiles(this._dirPath, options)
|
||||||
|
|
||||||
|
return this._results
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find files
|
||||||
|
*
|
||||||
|
* @param {[type]} dirPath [description]
|
||||||
|
* @param {Object} [parameters={}] [description]
|
||||||
|
* @param {Object} [options={}] [description]
|
||||||
|
* @return {[type]}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_findFiles(dirPath, options) {
|
||||||
|
|
||||||
|
//
|
||||||
|
const files = fs.readdirSync(dirPath + options.parent, {
|
||||||
|
withFileTypes: true
|
||||||
|
})
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
|
||||||
|
// skip for file that is not markdown
|
||||||
|
if (file.isFile() && path.extname(file.name) !== this.FILE_EXTENSION ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip for file that is index but not root
|
||||||
|
if (file.isFile() && file.name === (this.FILE_INDEX + this.FILE_EXTENSION) && options.parent !== '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip for directory that contains partials
|
||||||
|
if (file.isDirectory() && file.name === this.DIRECTORY_BLOCKS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if directory going deep
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
const childrenOptions = Object.assign({}, options, {
|
||||||
|
'parent': options.parent + '/' + file.name
|
||||||
|
})
|
||||||
|
|
||||||
|
this._findFiles(dirPath, childrenOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get file
|
||||||
|
const content = this._getFile(file, dirPath + options.parent)
|
||||||
|
|
||||||
|
// skip if empty
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if
|
||||||
|
const blocks = this._getBlocks(dirPath + options.parent + '/' + file.name)
|
||||||
|
|
||||||
|
// create page object and add to page
|
||||||
|
const page = new Page(file, options.parent, content, blocks)
|
||||||
|
this._results.push(page)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {string} dirPath
|
||||||
|
* @return {array}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_getBlocks(dirPath) {
|
||||||
|
const blocksQuery = new Blocks(dirPath)
|
||||||
|
return blocksQuery.find()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get file content
|
||||||
|
*
|
||||||
|
* @param {string} slug
|
||||||
|
* @param {string} sourcePath
|
||||||
|
* @return {mixed}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_getFile(file, dirPath) {
|
||||||
|
|
||||||
|
// file
|
||||||
|
let result = null
|
||||||
|
|
||||||
|
// path of file, first try with slug
|
||||||
|
let filePath = dirPath + '/' + file.name
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath) && file.isFile()) {
|
||||||
|
result = fs.readFileSync(filePath, 'utf8')
|
||||||
|
} else {
|
||||||
|
filePath = dirPath + '/' + file.name + '/' + this.FILE_INDEX + this.FILE_EXTENSION
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
result = fs.readFileSync(filePath, 'utf8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Pages
|
@ -0,0 +1,106 @@
|
|||||||
|
import { XMLParser, XMLBuilder, XMLValidator} from 'fast-xml-parser'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Björn Hase <me@herr-hase.wtf>
|
||||||
|
* @license http://opensource.org/licenses/MIT The MIT License
|
||||||
|
* @link https://gitea.node001.net/HerrHase/happy-site-webpack-plugin.git
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Sitemap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {[type]} site
|
||||||
|
* @param {[type]} pages
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(site) {
|
||||||
|
this._site = site
|
||||||
|
this._urls = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adding page to urls of sitemap, check if page is valid for sitemap
|
||||||
|
*
|
||||||
|
* @param {object} page
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
addPage(page) {
|
||||||
|
if (this._isValid(page)) {
|
||||||
|
this._urls.push({
|
||||||
|
loc: 'https://' + this._site.domain + page.pathname + '/' + page.filename,
|
||||||
|
lastmod: dayjs().format()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get xml as string
|
||||||
|
*
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getXmlAsString() {
|
||||||
|
return this._createXml(this._urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if robots has a noindex
|
||||||
|
*
|
||||||
|
* @param {object} page
|
||||||
|
* @return {boolean}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_isValid(page) {
|
||||||
|
|
||||||
|
let result = true
|
||||||
|
|
||||||
|
if (page.meta) {
|
||||||
|
page.meta.forEach((meta) => {
|
||||||
|
if (meta['name'] === 'robots' && meta['content'].includes('noindex')) {
|
||||||
|
result = false
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create xml with urls and return it as string
|
||||||
|
*
|
||||||
|
* @param {object} urls
|
||||||
|
* @return {string}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_createXml(urls) {
|
||||||
|
|
||||||
|
// builder for XML
|
||||||
|
const builder = new XMLBuilder({
|
||||||
|
format: true,
|
||||||
|
processEntities: false,
|
||||||
|
ignoreAttributes: false,
|
||||||
|
attributeNamePrefix: '@'
|
||||||
|
})
|
||||||
|
|
||||||
|
const xmlString = builder.build({
|
||||||
|
'?xml': {
|
||||||
|
'@version': '1.0',
|
||||||
|
'@encoding': 'UTF-8'
|
||||||
|
},
|
||||||
|
'urlset': {
|
||||||
|
'@xmlns': 'http://www.sitemaps.org/schemas/sitemap/0.9',
|
||||||
|
'url': urls
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return xmlString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sitemap
|
Loading…
Reference in new issue