Skip to content
This repository was archived by the owner on Apr 5, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@
"dependencies": {
"chokidar": "^2.1.5",
"connect": "^3.6.6",
"css-select": "^2.1.0",
"domutils": "^2.0.0",
"dotenv": "^7.0.0",
"htmlparser2": "^4.1.0",
"marked": "^0.6.2",
"serve-static": "^1.13.2"
"serve-static": "^1.13.2",
"uid": "^1.0.0"
},
"devDependencies": {
"jest": "^24.7.1"
Expand Down
164 changes: 57 additions & 107 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
#!/usr/bin/env node
const fs = require('fs');
const { performance } = require('perf_hooks');
const domutils = require('domutils');
const marked = require('marked');
const {
html: {
nodes: { queryNodesByHTML },
changeTag,
prepareHTML,
},
} = require('./lib');
require('dotenv').config();

/**
Expand Down Expand Up @@ -52,14 +60,10 @@ const excludedFolders = [

const patterns = {
whitespace: /^\s+|\s+$/g,
templates: /<sergey-template name="([a-zA-Z0-9-_.\\\/]*)">(.*?)<\/sergey-template>/gms,
complexNamedSlots: /<sergey-slot name="([a-zA-Z0-9-_.\\\/]*)">(.*?)<\/sergey-slot>/gms,
simpleNamedSlots: /<sergey-slot name="([a-zA-Z0-9-_.\\\/]*)"\s?\/>/gm,
complexDefaultSlots: /<sergey-slot>(.*?)<\/sergey-slot>/gms,
simpleDefaultSlots: /<sergey-slot\s?\/>/gm,
complexImports: /<sergey-import src="([a-zA-Z0-9-_.\\\/]*)"(?:\sas="(.*?)")?>(.*?)<\/sergey-import>/gms,
simpleImports: /<sergey-import src="([a-zA-Z0-9-_.\\\/]*)"(?:\sas="(.*?)")?\s?\/>/gm,
links: /<sergey-link\s?(.*?)(?:to|href)="([a-zA-Z0-9-_.#?\\\/]*)"\s?(.*?)>(.*?)<\/sergey-link>/gms
template: 'sergey-template',
slot: 'sergey-slot',
import: 'sergey-import',
link: 'sergey-link',
};

/**
Expand Down Expand Up @@ -188,8 +192,6 @@ const getKey = (key, ext = '.html', folder = '') => {
const file = key.endsWith(ext) ? key : `${key}${ext}`;
return `${folder}${file}`;
};
const hasImports = x => x.includes('<sergey-import');
const hasLinks = x => x.includes('<sergey-link');
const primeExcludedFiles = name => {
if (!excludedFolders.includes(name)) {
excludedFolders.push(name);
Expand All @@ -210,91 +212,59 @@ const prepareImports = async folder => {
};

const primeImport = (path, body) => {
cachedImports[path] = body;
cachedImports[path] = path.endsWith('.html') ? prepareHTML(body) : body;
};

const getSlots = content => {
const getSlots = (content) => {
// Extract templates first
const slots = {
default: formatContent(content) || ''
default: formatContent(content) || '',
};

// Search content for templates
while ((m = patterns.templates.exec(content)) !== null) {
if (m.index === patterns.templates.lastIndex) {
patterns.templates.lastIndex++;
}
const { nodes } = queryNodesByHTML({
html: content,
selector: patterns.template,
});
nodes.forEach((node) => {
const find = domutils.getOuterHTML(node);
const name = domutils.getAttributeValue(node, 'name');
const data = domutils.getInnerHTML(node);

const [find, name, data] = m;
if (name !== 'default') {
// Remove it from the default content
slots.default = slots.default.replace(find, '');
}

// Add it as a named slot
slots[name] = formatContent(data);
}
});

slots.default = formatContent(slots.default);

return slots;
};

const compileSlots = (body, slots) => {
let m;
let copy;

// Complex named slots
copy = body;
while ((m = patterns.complexNamedSlots.exec(body)) !== null) {
if (m.index === patterns.complexNamedSlots.lastIndex) {
patterns.complexNamedSlots.lastIndex++;
}

const [find, name, fallback] = m;
copy = copy.replace(find, slots[name] || fallback || '');
}
body = copy;

// Simple named slots
while ((m = patterns.simpleNamedSlots.exec(body)) !== null) {
if (m.index === patterns.simpleNamedSlots.lastIndex) {
patterns.simpleNamedSlots.lastIndex++;
}

const [find, name] = m;
copy = copy.replace(find, slots[name] || '');
}
body = copy;

// Complex Default slots
while ((m = patterns.complexDefaultSlots.exec(body)) !== null) {
if (m.index === patterns.complexDefaultSlots.lastIndex) {
patterns.complexDefaultSlots.lastIndex++;
}
const compileSlots = (body_, slots) => {
let body = body_;

const [find, fallback] = m;
copy = copy.replace(find, slots.default || fallback || '');
}
body = copy;

// Simple default slots
body = body.replace(patterns.simpleDefaultSlots, slots.default);
body = changeTag.main({ html: body, selector: patterns.slot }, (node) => {
const name = domutils.getAttributeValue(node, 'name') || 'default';
const fallback = domutils.getInnerHTML(node);
return slots[name] || fallback || '';
});

return body;
};

const compileImport = (body, pattern) => {
let m;
// Simple imports
while ((m = pattern.exec(body)) !== null) {
if (m.index === pattern.lastIndex) {
pattern.lastIndex++;
}
const compileImport = (body_) => {
let body = body_;
body = changeTag.main({ html: body, selector: patterns.import }, (node) => {
let key = domutils.getAttributeValue(node, 'src');
let htmlAs = domutils.getAttributeValue(node, 'as') || '';
let content = domutils.getInnerHTML(node) || '';

let [find, key, htmlAs = '', content = ''] = m;
let replace = '';

if (htmlAs === 'markdown') {
replace = formatContent(
marked(cachedImports[getKey(key, '.md', CONTENT)] || '')
Expand All @@ -307,63 +277,43 @@ const compileImport = (body, pattern) => {

// Recurse
replace = compileTemplate(replace, slots);
body = body.replace(find, replace);
}
return replace;
});

return body;
};

const compileTemplate = (body, slots = { default: '' }) => {
const compileTemplate = (body_, slots = { default: '' }) => {
let body = prepareHTML(body_);
body = compileSlots(body, slots);

if (!hasImports(body)) {
return body;
}

body = compileImport(body, patterns.simpleImports);
body = compileImport(body, patterns.complexImports);

body = compileImport(body);
return body;
};

const compileLinks = (body, path) => {
let m;
let copy;
const compileLinks = (body_, path) => {
let body = body_;
body = changeTag.main({ html: body, selector: patterns.link }, (node) => {
const arrTo = ['to', 'href'].filter((i) => domutils.hasAttrib(node, i))[0];

if (!hasLinks(body)) {
return body;
}

copy = body;
while ((m = patterns.links.exec(body)) !== null) {
if (m.index === patterns.links.lastIndex) {
patterns.links.lastIndex++;
}

let [find, attr1 = '', to, attr2 = '', content] = m;
let replace = '';
let attributes = [`href="${to}"`, attr1, attr2]
.map(x => x.trim())
.filter(Boolean)
.join(' ');
const to = arrTo ? domutils.getAttributeValue(node, arrTo) : '';
arrTo && delete node.attribs[arrTo];

const isCurrent = isCurrentPage(to, path);
if (isCurrent || isParentPage(to, path)) {
if (attributes.includes('class="')) {
attributes = attributes.replace('class="', `class="${ACTIVE_CLASS} `);
} else {
attributes += ` class="${ACTIVE_CLASS}"`;
}
const currClass = domutils.getAttributeValue(node, 'class') || '';
node.attribs['class'] = `${ACTIVE_CLASS} ${currClass.trimLeft()}`.trim();

if (isCurrent) {
attributes += ' aria-current="page"';
node.attribs['aria-current'] = 'page';
}
}

replace = `<a ${attributes}>${content}</a>`;
copy = copy.replace(find, replace);
}
body = copy;
return domutils
.getOuterHTML(node)
.replace(/^<sergey-link/, '<a')
.replace('</sergey-link>', '</a>')
.replace(/^<a/, `<a href="${to}"`);
});

return body;
};
Expand Down
29 changes: 29 additions & 0 deletions src/lib/html/change-tag/change-items-by-html-fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const changeItemsByHTML = require('./change-items-by-html');

/**
* bug-fix for tags inside tags (e.g. <meta foo="<sergey-slot></sergey-slot>">)
* Extends `changeItemsByHTML`.
* Can match tags inside tags
* @author Gabriel Rodrigues <[email protected]>
*/
module.exports = (options) => {
let { html, selector } = options;

if (html.includes(`</${selector}>`)) {
const regexpRangeTag = selector.replace('-', '\\-');
const remaingTags =
html.match(
new RegExp(
`<${selector}[^<]*>[^<${regexpRangeTag}]*<\\/${selector}>`,
'g'
)
) || [];

const foundAs = remaingTags.join('');
html = html.replace(
foundAs,
changeItemsByHTML(Object.assign({}, options, { html: foundAs }))
);
}
return html;
};
48 changes: 48 additions & 0 deletions src/lib/html/change-tag/change-items-by-html-fallback.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const prepareHTML = require('../prepare-html');
const { changeItemsByHTML, changeItemsByHTMLFallback } = require('./index');
const getOptions = (option, html_) =>
Object.assign({}, option, { html: html_ });

const options = [
{
selector: 'get-title',
changeItem: () => {
return '...';
},
},
{
selector: 'get-text',
changeItem: () => {
return 'text';
},
},
];

const input = prepareHTML(
'<div><p title="<get-title />"><get-text /></p></div>'
);

test('Shoud fail to match tag with changeItemsByHTML', () => {
const expected = prepareHTML('<div><p title="<get-title/>">text</p></div>');

let output = input;
output = changeItemsByHTML(getOptions(options[0], output));
output = changeItemsByHTML(getOptions(options[1], output));

expect(output).toBe(expected);
});

test('Change HTML tags with changeItemsByHTMLFallback', () => {
const expected = prepareHTML('<div><p title="...">text</p></div>');

let output = input;

// get-title
output = changeItemsByHTML(getOptions(options[0], output));
output = changeItemsByHTMLFallback(getOptions(options[0], output));
// get-text
output = changeItemsByHTML(getOptions(options[1], output));
output = changeItemsByHTMLFallback(getOptions(options[1], output));

expect(output).toBe(expected);
});
33 changes: 33 additions & 0 deletions src/lib/html/change-tag/change-items-by-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const domutils = require('domutils');
const { queryNodesByHTML } = require('../nodes');

const defaultModes = {
innerHTML: domutils.getInnerHTML,
outerHTML: domutils.getOuterHTML,
};

/**
* Core function to change tags
* @author Gabriel Rodrigues <[email protected]>
*/
module.exports = ({
html,
selector,
changeItem,
mode = 'outerHTML',
modes = defaultModes,
base: base_,
}) => {
let base = html || base_ || '';

const { nodes: selectedNodes } = queryNodesByHTML({ html: base, selector });
selectedNodes.forEach((i) => {
const oldContent = modes[mode](i);
const newContent = changeItem(i, oldContent);

if(newContent !== false) {
base = base.replace(oldContent, newContent);
}
});
return base;
};
Loading