refactor(layout): common layout comps to jsx

pull/588/head
ppoffice 2019-12-23 01:49:48 -05:00
parent 7d544c5c3b
commit 467636b50a
35 changed files with 892 additions and 442 deletions

View File

@ -28,7 +28,9 @@
"inferno",
"inferno-create-element",
"hexo-util",
"cheerio"
"hexo-log",
"cheerio",
"moment"
]
}
]

View File

@ -1,44 +0,0 @@
<footer class="footer">
<div class="container">
<div class="level">
<div class="level-start has-text-centered-mobile">
<a class="footer-logo is-block has-mb-6" href="<%- url_for('/') %>">
<% if (logo && logo.text) { %>
<%= logo.text %>
<% } else { %>
<img src="<%- url_for(logo) %>" alt="<%= title %>" height="28">
<% } %>
</a>
<p class="is-size-7">
&copy; <%= date(new Date(), 'YYYY') %> <%= author || title %>&nbsp;
Powered by <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a> & <a
href="https://github.com/ppoffice/hexo-theme-icarus" target="_blank" rel="noopener">Icarus</a>
<% if (busuanzi) { %>
<br>
<span id="busuanzi_container_site_uv">
<%- _p('plugin.visitor', '<span id="busuanzi_value_site_uv">0</span>') %>
</span>
<% } %>
</p>
</div>
<div class="level-end">
<% if (Object.keys(links).length) { %>
<div class="field has-addons is-flex-center-mobile has-mt-5-mobile is-flex-wrap is-flex-middle">
<% for (let name in links) {
let link = links[name]; %>
<p class="control">
<a class="button is-white <%= typeof(link) !== 'string' ? 'is-large' : '' %>" target="_blank" rel="noopener" title="<%= name %>" href="<%= url_for(typeof(link) === 'string' ? link : link.url) %>">
<% if (typeof(link) === 'string') { %>
<%= name %>
<% } else { %>
<i class="<%= link.icon %>"></i>
<% } %>
</a>
</p>
<% } %>
</div>
<% } %>
</div>
</div>
</div>
</footer>

81
layout/common/footer.jsx Normal file
View File

@ -0,0 +1,81 @@
'use strict';
const { Component } = require('inferno');
const { cacheComponent } = require('../util/cache');
class Footer extends Component {
render() {
const {
logo,
logoUrl,
siteUrl,
siteTitle,
siteYear,
author,
links,
showVisitorCounter,
visitorCounterTitle
} = this.props;
return <footer className="footer">
<div className="container">
<div className="level">
<div className="level-start has-text-centered-mobile">
<a className="footer-logo is-block has-mb-6" href={siteUrl}>
{logo && logo.text ? logo.text : <img src={logoUrl} alt={siteTitle} height="28" />}
</a>
<p className="is-size-7">
<span dangerouslySetInnerHTML={{ __html: `&copy; ${siteYear} ${author || siteTitle}` }}></span>
Powered by <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a> &
<a href="https://github.com/ppoffice/hexo-theme-icarus" target="_blank" rel="noopener">Icarus</a>
{showVisitorCounter ? <br /> : null}
{showVisitorCounter ? <span id="busuanzi_container_site_uv"
dangerouslySetInnerHTML={{ __html: visitorCounterTitle }}></span> : null}
</p>
</div>
<div className="level-end">
{Object.keys(links).length ? <div className="field has-addons is-flex-center-mobile has-mt-5-mobile is-flex-wrap is-flex-middle">
{Object.keys(links).map(name => {
const link = links[name];
return <p className="control">
<a className={`button is-white ${link.icon ? 'is-large' : ''}`} target="_blank" rel="noopener" title={name} href={link.url}>
{link.icon ? name : <i className={link.icon}></i>}
</a>
</p>;
})}
</div> : null}
</div>
</div>
</div>
</footer>;
}
}
module.exports = cacheComponent(Footer, 'common.footer', props => {
const { config, helper } = this.props;
const { url_for, _p, date } = helper;
const { logo, title, author, footer, plugins } = config;
const links = {};
if (footer && footer.links) {
Object.keys(footer.links).forEach(name => {
const link = footer.links[name];
links[name] = {
url: url_for(typeof link === 'string' ? link : link.url),
icon: link.icon
};
});
}
return {
logo,
logoUrl: url_for(logo),
siteUrl: url_for('/'),
siteTitle: title,
siteYear: date(new Date(), 'YYYY'),
author,
links,
showVisitorCounter: plugins && plugins.busuanzi === true,
visitorCounterTitle: _p('plugin.visitor', '<span id="busuanzi_value_site_uv">0</span>')
};
});

View File

@ -1,10 +0,0 @@
module.exports = (ctx, locals) => {
const { get_config } = ctx;
return Object.assign(locals, {
title: get_config('title'),
author: get_config('author'),
logo: get_config('logo'),
busuanzi: get_config('plugins.busuanzi', false),
links: get_config('footer.links', [])
});
}

View File

@ -1,43 +0,0 @@
<meta charset="utf-8" />
<% if (get_config('meta_generator', true)) { %>
<meta name="generator" content="Hexo <%= hexo_version() %>" />
<% } %>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<%- meta() %>
<title><%= page_title() %></title>
<% if (has_config('open_graph')) { %>
<%- open_graph({
twitter_id: get_config('open_graph.twitter_id'),
twitter_site: get_config('open_graph.twitter_site'),
google_plus: get_config('open_graph.google_plus'),
fb_admins: get_config('open_graph.fb_admins'),
fb_app_id: get_config('open_graph.fb_app_id'),
image: get_og_image(page)
}) %>
<% } %>
<% if (has_config('canonical_url')) { %>
<link rel="canonical" href="<%- get_config('canonical_url') %>" />
<% } %>
<% if (has_config('rss')) { %>
<link rel="alternative" href="<%- get_config('rss') %>" title="<%= get_config('title') %>" type="application/atom+xml">
<% } %>
<% if (has_config('favicon')) { %>
<link rel="icon" href="<%- url_for(get_config('favicon')) %>">
<% } %>
<%- _css(cdn('bulma', '0.7.2', 'css/bulma.css')) %>
<%- _css(iconcdn()) %>
<%- _css(fontcdn('Ubuntu:400,600|Source+Code+Pro')) %>
<%- _css(cdn('highlight.js', '9.12.0', 'styles/' + get_config('article.highlight.theme') + '.css')) %>
<% if (has_config('plugins')) { %>
<% for (let plugin in get_config('plugins')) { %>
<%- _partial('plugin/' + plugin, { head: true, plugin: get_config('plugins')[plugin] }) %>
<% } %>
<% } %>
<%- _css('css/style') %>

76
layout/common/head.jsx Normal file
View File

@ -0,0 +1,76 @@
'use strict';
const { Component } = require('inferno');
const MetaTags = require('../misc/meta');
const OpenGraph = require('../misc/open_graph');
const Plugins = require('./plugins');
module.exports = class extends Component {
render() {
const { env, site, config, helper, page } = this.props;
const { is_post, url_for, cdn, iconcdn, fontcdn } = helper;
const {
url,
meta_generator = true,
meta = [],
open_graph,
canonical_url,
rss,
favicon,
article,
highlight
} = config;
let hlTheme;
if (highlight && highlight.enable === false) {
hlTheme = null;
} else if (article && article.highlight && article.hightlight.theme) {
hlTheme = article.hightlight.theme;
} else {
hlTheme = 'atom-one-light';
}
const images = [];
if (page.content && page.content.includes('<img')) {
let img;
const imgPattern = /<img [^>]*src=['"]([^'"]+)([^>]*>)/gi;
while ((img = imgPattern.exec(page.content)) !== null) {
images.push(img[1]);
}
}
return <head>
<meta charset="utf-8" />
{meta_generator ? <meta name="generator" content={`Hexo ${env.version}`} /> : null}
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<MetaTags meta={meta} />
{open_graph ? <OpenGraph
type={is_post() ? 'article' : 'website'}
title={page.title || config.title}
date={page.date}
updated={page.updated}
author={config.author}
description={page.description || page.excerpt || page.content || config.description}
keywords={page.keywords || (page.tags && page.tags.length ? page.tags : undefined) || config.keywords}
url={url}
images={page.photos || images}
siteName={config.title}
language={page.lang || page.language || config.language}
twitterId={open_graph.twitter}
googlePlus={open_graph.google_plus}
facebookAdmins={open_graph.fb_admins}
facebookAppId={open_graph.fb_app_id} /> : null}
{canonical_url ? <link rel="canonical" href={canonical_url} /> : null}
{rss ? <link rel="alternative" href={url_for(rss)} title={config.title} type="application/atom+xml" /> : null}
{favicon ? <link rel="icon" href={url_for(favicon)} /> : null}
<link rel="stylesheet" href={cdn('bulma', '0.7.2', 'css/bulma.css')} />
<link rel="stylesheet" href={iconcdn()} />
<link rel="stylesheet" href={fontcdn('Ubuntu:400,600|Source+Code+Pro')} />
{hlTheme ? <link rel="stylesheet" href={cdn('highlight.js', '9.12.0', 'styles/' + hlTheme + '.css')} /> : null}
<Plugins site={site} config={config} helper={helper} page={page} head={true} />
<link rel="stylesheet" href={url_for('/css/style')} />
</head>;
}
};

View File

@ -1,47 +0,0 @@
<nav class="navbar navbar-main">
<div class="container">
<div class="navbar-brand is-flex-center">
<a class="navbar-item navbar-logo" href="<%- url_for('/') %>">
<% if (logo && logo.text) { %>
<%= logo.text %>
<% } else { %>
<img src="<%- url_for(logo) %>" alt="<%= title %>" height="28">
<% } %>
</a>
</div>
<div class="navbar-menu">
<% if (Object.keys(menus).length) { %>
<div class="navbar-start">
<% for (let i in menus) { let menu = menus[i]; %>
<a class="navbar-item<% if (actives[i]) { %> is-active<% } %>"
href="<%- url_for(menu) %>"><%= i %></a>
<% } %>
</div>
<% } %>
<div class="navbar-end">
<% if (Object.keys(links).length) { %>
<% for (let name in links) {
let link = links[name]; %>
<a class="navbar-item" target="_blank" rel="noopener" title="<%= name %>" href="<%= url_for(typeof(link) === 'string' ? link : link.url) %>">
<% if (typeof(link) === 'string') { %>
<%= name %>
<% } else { %>
<i class="<%= link.icon %>"></i>
<% } %>
</a>
<% } %>
<% } %>
<% if (hasToc) { %>
<a class="navbar-item is-hidden-tablet catalogue" title="<%= _p('widget.catalogue', Infinity) %>" href="javascript:;">
<i class="fas fa-list-ul"></i>
</a>
<% } %>
<% if (search) { %>
<a class="navbar-item search" title="<%= __('search.search') %>" href="javascript:;">
<i class="fas fa-search"></i>
</a>
<% } %>
</div>
</div>
</div>
</nav>

109
layout/common/navbar.jsx Normal file
View File

@ -0,0 +1,109 @@
'use strict';
const { Component, Fragment } = require('inferno');
const { cacheComponent } = require('../util/cache');
function isSameLink(a, b) {
function santize(url) {
let paths = url.replace(/(^\w+:|^)\/\//, '').split('#')[0].split('/').filter(p => p.trim() !== '');
if (paths.length > 0 && paths[paths.length - 1].trim() === 'index.html') {
paths = paths.slice(0, paths.length - 1);
}
return paths.join('/');
}
return santize(a) === santize(b);
}
class Navbar extends Component {
render() {
const {
logo,
logoUrl,
siteUrl,
siteTitle,
menus,
links,
showToc,
tocTitle,
showSearch,
searchTitle
} = this.props;
return <nav className="navbar navbar-main">
<div className="container">
<div className="navbar-brand is-flex-center">
<a className="navbar-item navbar-logo" href={siteUrl}>
{logo && logo.text ? logo.text : <img src={logoUrl} alt={siteTitle} height="28" />}
</a>
</div>
<div className="navbar-menu">
{Object.keys(menus).length ? <div className="navbar-start">
{Object.keys(menus).map(name => {
const menu = menus[name];
return <a className={{ 'navbar-item': true, 'is-active': menu.active }} href={menu.url}>{name}</a>;
})}
</div> : null}
<div className="navbar-end">
{Object.keys(links).length ? <Fragment>
{Object.keys(links).forEach(name => {
const link = links[name];
return <a className="navbar-item" target="_blank" rel="noopener" title={name} href={link.url}>
{link.icon ? <i className={link.icon}></i> : name}
</a>;
})}
</Fragment> : null}
{showToc ? <a className="navbar-item is-hidden-tablet catalogue" title={tocTitle} href="javascript:;">
<i className="fas fa-list-ul"></i>
</a> : null}
{showSearch ? <a className="navbar-item search" title={searchTitle} href="javascript:;">
<i className="fas fa-search"></i>
</a> : null}
</div>
</div>
</div>
</nav>;
}
}
module.exports = cacheComponent(Navbar, 'common.navbar', props => {
const { config, helper, page } = this.props;
const { url_for, _p, __ } = helper;
const { logo, title, navbar, widgets, search } = config;
const hasTocWidget = Array.isArray(widgets) && widgets.find(widget => widget.type === 'toc');
const showToc = (config.toc === true || page.toc) && hasTocWidget && ['page', 'post'].includes(page.layout);
const menus = {};
if (navbar && navbar.menus) {
const pageUrl = typeof page.path !== 'undefined' ? url_for(page.path) : '';
Object.keys(navbar.menus).forEach(name => {
const url = url_for(navbar.menus[name]);
const active = isSameLink(url, pageUrl);
menus[name] = { url, active };
});
}
const links = {};
if (navbar && navbar.links) {
Object.keys(navbar.links).forEach(name => {
const link = navbar.links[name];
links[name] = {
url: url_for(typeof link === 'string' ? link : link.url),
icon: link.icon
};
});
}
return {
logo,
logoUrl: url_for(logo),
siteUrl: url_for('/'),
siteTitle: title,
menus,
links,
showToc,
tocTitle: _p('widget.catalogue', Infinity),
showSearch: search && search.type,
searchTitle: __('search.search')
};
});

View File

@ -1,18 +0,0 @@
module.exports = (ctx, locals) => {
const { get_config, has_config, has_widget, is_same_link, page } = ctx;
const menus = get_config('navbar.menu', {});
const actives = {};
Object.keys(menus).forEach(i => {
actives[i] = typeof page.path !== 'undefined' && is_same_link(menus[i], page.path)
});
const hasToc = get_config('toc') === true && has_widget('toc') && (page.layout === 'page' || page.layout === 'post');
return Object.assign(locals, {
hasToc,
menus,
actives,
title: get_config('title'),
logo: get_config('logo'),
links: get_config('navbar.links', []),
search: has_config('search.type')
});
}

View File

@ -1,48 +0,0 @@
<% function link_url(i) {
return url_for(i === 1 ? page.base : page.base + get_config('pagination_dir') + '/' + i + '/');
}
function pagination(c, m) {
var current = c,
last = m,
delta = 1,
left = current - delta,
right = current + delta + 1,
range = [],
elements = [],
l;
for (let i = 1; i <= last; i++) {
if (i == 1 || i == last || (i >= left && i < right)) {
range.push(i);
}
}
for (let i of range) {
if (l) {
if (i - l === 2) {
elements.push(`<li><a class="pagination-link has-text-black-ter" href="${ link_url(l + 1) }">${ l + 1 }</a></li>`);
} else if (i - l !== 1) {
elements.push(`<li><span class="pagination-ellipsis has-text-black-ter">&hellip;</span></li>`);
}
}
elements.push(`<li><a class="pagination-link${ c === i ? ' is-current' : ' has-text-black-ter'}" href="${ link_url(i) }">${ i }</a></li>`);
l = i;
}
return elements;
} %>
<div class="card card-transparent">
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
<div class="pagination-previous<%= page.current > 1 ? '' : ' is-invisible is-hidden-mobile' %>">
<a class="is-flex-grow has-text-black-ter" href="<%= link_url(page.current - 1) %>"><%= __('common.prev') %></a>
</div>
<div class="pagination-next<%= page.current < page.total ? '' : ' is-invisible is-hidden-mobile' %>">
<a class="is-flex-grow has-text-black-ter" href="<%= link_url(page.current + 1) %>"><%= __('common.next') %></a>
</div>
<ul class="pagination-list is-hidden-mobile">
<% pagination(page.current, page.total).forEach(element => { %>
<%- element %>
<% }) %>
</ul>
</nav>
</div>

27
layout/common/plugins.jsx Normal file
View File

@ -0,0 +1,27 @@
'use strict';
const logger = require('hexo-log');
const { Component, Fragment } = require('inferno');
module.exports = class extends Component {
render() {
const { site, config, page, helper, head } = this.props;
const { plugins = [] } = config;
return <Fragment>
{Object.keys(plugins).map(name => {
// plugin is not enabled
if (!plugins[name]) {
return null;
}
try {
const Plugin = require('../plugin/' + name);
return <Plugin site={site} config={config} page={page} helper={helper} plugin={plugins[name]} head={head} />;
} catch (e) {
logger.warn(`Icarus cannot load plugin "${name}"`);
}
return null;
})}
</Fragment>;
}
};

View File

@ -1,41 +0,0 @@
<%- _js(cdn('jquery', '3.3.1', 'dist/jquery.min.js')) %>
<%- _js(cdn('moment', '2.22.2', 'min/moment-with-locales.min.js')) %>
<script>moment.locale("<%= get_config('language', 'en') %>");</script>
<%
let externalLink = get_config('external_link');
if (typeof externalLink === 'boolean') {
externalLink = { enable: externalLink, exclude: [] };
} else {
externalLink = {
enable: typeof externalLink.enable === 'boolean' ? externalLink.enable : true,
exclude: externalLink.exclude || []
};
}
%>
<script>
var IcarusThemeSettings = {
site: {
url: '<%= config.url %>',
external_link: <%- JSON.stringify(externalLink) %>
},
article: {
highlight: {
clipboard: <%= get_config('article.highlight.clipboard', true) %>,
fold: '<%= get_config('article.highlight.fold', true) %>'
}
}
};
</script>
<% if (get_config('article.highlight.clipboard')) { %>
<%- _js(cdn('clipboard', '2.0.4', 'dist/clipboard.min.js'), true) %>
<% } %>
<% if (has_config('plugins')) { %>
<% for (let plugin in get_config('plugins')) { %>
<%- _partial('plugin/' + plugin, { head: false, plugin: get_config('plugins')[plugin] }) %>
<% } %>
<% } %>
<%- _js('js/main', true) %>

57
layout/common/scripts.jsx Normal file
View File

@ -0,0 +1,57 @@
'use strict';
const { Component, Fragment } = require('inferno');
const Plugins = require('./plugins');
module.exports = class extends Component {
render() {
const { site, config, helper, page } = this.props;
const { url_for, cdn } = helper;
const { external_link, article } = config;
const language = page.lang || page.language || config.language || 'en';
let externalLink;
if (typeof external_link === 'boolean') {
externalLink = { enable: external_link, exclude: [] };
} else {
externalLink = {
enable: typeof external_link.enable === 'boolean' ? external_link.enable : true,
exclude: external_link.exclude || []
};
}
let fold = 'unfolded';
let clipboard = true;
if (article && article.highlight) {
if (typeof article.highlight.clipboard !== 'undefined') {
clipboard = !!article.highlight.clipboard;
}
if (typeof article.highlight.fold === 'string') {
fold = article.highlight.fold;
}
}
const embeddedConfig = `var IcarusThemeSettings = {
site: {
url: '${config.url}',
external_link: ${JSON.stringify(externalLink)}
},
article: {
highlight: {
clipboard: ${clipboard},
fold: '${fold}'
}
}
};`;
return <Fragment>
<script src={cdn('jquery', '3.3.1', 'dist/jquery.min.js')}></script>
<script src={cdn('moment', '2.22.2', 'min/moment-with-locales.min.js')}></script>
<script dangerouslySetInnerHTML={{ __html: `moment.locale("${language}");` }}></script>
<script dangerouslySetInnerHTML={{ __html: embeddedConfig }}></script>
{clipboard ? <script src={cdn('clipboard', '2.0.4', 'dist/clipboard.min.js')} defer={true}></script> : null}
<Plugins site={site} config={config} page={page} helper={helper} head={false} />
<script src={url_for('/js/main.js')} defer={true}></script>
</Fragment>;
}
};

View File

@ -1,35 +0,0 @@
<% if (get_widgets(position).length) { %>
<% function side_column_class() {
switch (column_count()) {
case 2:
return 'is-4-tablet is-4-desktop is-4-widescreen';
case 3:
return 'is-4-tablet is-4-desktop is-3-widescreen';
}
return '';
} %>
<% function visibility_class() {
if (column_count() === 3 && position === 'right') {
return 'is-hidden-touch is-hidden-desktop-only';
}
return '';
} %>
<% function order_class() {
return position === 'left' ? 'has-order-1' : 'has-order-3';
} %>
<% function sticky_class(position) {
return get_config('sidebar.' + position + '.sticky', false) ? 'is-sticky' : '';
} %>
<div class="column <%= side_column_class() %> <%= visibility_class() %> <%= order_class() %> column-<%= position %> <%= sticky_class(position) %>">
<% get_widgets(position).forEach(widget => {%>
<%- _partial('widget/' + widget.type, { widget }) %>
<% }) %>
<% if (position === 'left') { %>
<div class="column-right-shadow is-hidden-widescreen <%= sticky_class('right') %>">
<% get_widgets('right').forEach(widget => {%>
<%- _partial('widget/' + widget.type, { widget }) %>
<% }) %>
</div>
<% } %>
</div>
<% } %>

102
layout/common/widgets.jsx Normal file
View File

@ -0,0 +1,102 @@
'use strict';
const logger = require('hexo-log');
const { Component } = require('inferno');
function formatWidgets(widgets) {
const result = {};
if (Array.isArray(widgets)) {
widgets.forEach(widget => {
if (Object.prototype.hasOwnProperty.call(widget, 'position')
&& (widget.position === 'left' || widget.position === 'right')) {
if (!Object.prototype.hasOwnProperty.call(result, widget.position)) {
result[widget.position] = [widget];
} else {
result[widget.position].push(widget);
}
}
});
}
return result;
}
function getColumnCount(widgets) {
let count = 1;
const w = formatWidgets(widgets);
if (Object.prototype.hasOwnProperty.call(w, 'left') && w.left.length) {
count++;
}
if (Object.prototype.hasOwnProperty.call(w, 'right') && w.left.length) {
count++;
}
return count;
}
function getColumnSizeClass(columnCount) {
switch (columnCount) {
case 2:
return 'is-4-tablet is-4-desktop is-4-widescreen';
case 3:
return 'is-4-tablet is-4-desktop is-3-widescreen';
}
return '';
}
function getColumnVisibilityClass(columnCount, position) {
if (columnCount === 3 && position === 'right') {
return 'is-hidden-touch is-hidden-desktop-only';
}
return '';
}
function getColumnOrderClass(position) {
return position === 'left' ? 'has-order-1' : 'has-order-3';
}
function isColumnSticky(config, position) {
return config.sidebar && config.sidebar[position] && config.sidebar[position].sticky === true;
}
class Widgets extends Component {
render() {
const { site, config, helper, page, position } = this.props;
const widgets = formatWidgets(config.widgets)[position] || [];
const columnCount = getColumnCount(config.widgets);
if (!widgets.length) {
return null;
}
return <div className={{
'column': true,
['column-' + position]: true,
[getColumnSizeClass(columnCount)]: true,
[getColumnVisibilityClass(columnCount, position)]: true,
[getColumnOrderClass(position)]: true,
'is-sticky': isColumnSticky(config, position)
}}>
{widgets[position].map(widget => {
// widget type is not defined
if (!widget.type) {
return null;
}
try {
const Widget = require('../widget/' + widget.type);
return <Widget site={site} helper={helper} config={config} page={page} widget={widget} />;
} catch (e) {
logger.warn(`Icarus cannot load widget "${widget.type}"`);
}
return null;
})}
{position === 'left' ? <div className={{
'column-right-shadow': true,
'is-hidden-widescreen': true,
'is-sticky': isColumnSticky(config, position)
}}></div> : null}
</div>;
}
}
Widgets.getColumnCount = getColumnCount;
module.exports = Widgets;

43
layout/misc/meta.jsx Normal file
View File

@ -0,0 +1,43 @@
'use strict';
const { Component, Fragment } = require('inferno');
function trim(str) {
return str.trim().replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1');
}
function split(str, sep) {
const result = [];
let matched = null;
while ((matched = sep.exec(str)) !== null) {
result.push(matched[0]);
}
return result;
}
module.exports = class extends Component {
render() {
let { meta = [] } = this.props;
if (!Array.isArray(meta)) {
meta = [meta];
}
const tags = meta.filter(entry => typeof entry === 'string')
.map(entry => {
const props = split(entry, /(?:[^\\;]+|\\.)+/g)
.map(property => {
const entry = split(property, /(?:[^\\=]+|\\.)+/g);
if (entry.length < 2) {
return null;
}
return { [trim(entry[0])]: trim(entry[1]) };
}).filter(property => {
return property !== null;
}).reduce((prev, current) => {
return Object.assign(prev, current);
}, {});
return <meta {...props} />;
});
return <Fragment>{tags}</Fragment>;
}
};

147
layout/misc/open_graph.jsx Normal file
View File

@ -0,0 +1,147 @@
'use strict';
// adapted from hexo/lib/plugins/helper/open_graph.js
const urlFn = require('url');
const moment = require('moment');
const { Component, Fragment } = require('inferno');
const { encodeURL, stripHTML, escapeHTML } = require('hexo-util');
const localeMap = {
'en': 'en_US',
'de': 'de_DE',
'es': 'es_ES',
'fr': 'fr_FR',
'hu': 'hu_HU',
'id': 'id_ID',
'it': 'it_IT',
'ja': 'ja_JP',
'ko': 'ko_KR',
'nl': 'nl_NL',
'ru': 'ru_RU',
'th': 'th_TH',
'tr': 'tr_TR',
'vi': 'vi_VN'
};
const localeRegex = new RegExp(Object.keys(localeMap).join('|'), 'i');
module.exports = class extends Component {
render() {
const {
type,
title,
date,
updated,
author,
url,
siteName,
twitterCard,
twitterSite,
googlePlus,
facebookAdmins,
facebookAppId
} = this.props;
let {
description,
language,
images,
keywords,
twitterId
} = this.props;
const htmlTags = [];
if (description) {
description = escapeHTML(stripHTML(description).substring(0, 200).trim())
.replace(/\n/g, ' ');
htmlTags.push(<meta description={description} />);
}
htmlTags.push(<meta property='og:type' content={type || 'website'} />);
htmlTags.push(<meta property='og:title' content={title} />);
htmlTags.push(<meta property='og:url' content={encodeURL(url)} />);
htmlTags.push(<meta property='og:site_name' content={siteName} />);
if (description) {
htmlTags.push(<meta property='og:description' content={description} />);
}
if (language) {
if (language.length === 2) {
language = language.replace(localeRegex, str => localeMap[str]);
htmlTags.push(<meta property='og:locale' content={language} />);
} else if (language.length === 5) {
const territory = language.slice(-2);
const territoryRegex = new RegExp(territory.concat('$'));
language = language.replace('-', '_').replace(territoryRegex, territory.toUpperCase());
htmlTags.push(<meta property='og:locale' content={language} />);
}
}
if (!Array.isArray(images)) {
images = [images];
}
images = images.map(path => {
if (!urlFn.parse(path).host) {
// resolve `path`'s absolute path relative to current page's url
// `path` can be both absolute (starts with `/`) or relative.
return urlFn.resolve(url, path);
}
htmlTags.push(<meta property='og:image' content={path} />);
return path;
});
if (date && (moment.isMoment(date) || moment.isDate(date)) && !isNaN(date.valueOf())) {
htmlTags.push(<meta property='article:published_time' content={date.toISOString()} />);
}
if (updated && (moment.isMoment(updated) || moment.isDate(updated)) && !isNaN(updated.valueOf())) {
htmlTags.push(<meta property='article:modified_time' content={updated.toISOString()} />);
}
if (author) {
htmlTags.push(<meta property='article:author' content={author} />);
}
if (keywords) {
if (typeof keywords === 'string') {
keywords = keywords.split(',');
}
keywords.map(tag => {
return tag.name ? tag.name : tag;
}).filter(Boolean).forEach(keyword => {
htmlTags.push(<meta property='article:tag' content={keyword} />);
});
}
htmlTags.push(<meta property='twitter:card' content={twitterCard || 'summary'} />);
if (images.length) {
htmlTags.push(<meta property='twitter:image' content={images[0]} />);
}
if (twitterId) {
if (twitterId[0] !== '@') {
twitterId = `@${twitterId}`;
}
htmlTags.push(<meta property='twitter:creator' content={twitterId} />);
}
if (twitterSite) {
htmlTags.push(<meta property='twitter:site' content={twitterSite} />);
}
if (googlePlus) {
htmlTags.push(<link rel="publisher" href={googlePlus} />);
}
if (facebookAdmins) {
htmlTags.push(<meta property='fb:admins' content={facebookAdmins} />);
}
if (facebookAppId) {
htmlTags.push(<meta property='fb:app_id' content={facebookAppId} />);
}
return <Fragment>{htmlTags}</Fragment>;
}
};

57
layout/misc/paginator.jsx Normal file
View File

@ -0,0 +1,57 @@
'use strict';
const { Component } = require('inferno');
module.exports = class extends Component {
render() {
const { current, total, baseUrl, path, urlFor, prevTitle, nextTitle } = this.props;
function getPageUrl(i) {
return urlFor(i === 1 ? baseUrl : baseUrl + path + '/' + i + '/');
}
function pagination(c, m) {
const current = c;
const last = m;
const delta = 1;
const left = current - delta;
const right = current + delta + 1;
const range = [];
const elements = [];
let l;
for (let i = 1; i <= last; i++) {
if (i === 1 || i === last || (i >= left && i < right)) {
range.push(i);
}
}
for (const i of range) {
if (l) {
if (i - l === 2) {
elements.push(<li><a className="pagination-link has-text-black-ter" href={getPageUrl(l + 1)}>{l + 1}</a></li>);
} else if (i - l !== 1) {
elements.push(<li><span className="pagination-ellipsis has-text-black-ter" dangerouslySetInnerHTML={{ __html: '&hellip;' }}></span></li>);
}
}
elements.push(<li><a className={`pagination-link${c === i ? ' is-current' : ' has-text-black-ter'}`} href={getPageUrl(i)}>{i}</a></li>);
l = i;
}
return elements;
}
return <div className="card card-transparent">
<nav className="pagination is-centered" role="navigation" aria-label="pagination">
<div className={`pagination-previous${current > 1 ? '' : ' is-invisible is-hidden-mobile'}`}>
<a className="is-flex-grow has-text-black-ter" href={getPageUrl(current - 1)}>{prevTitle}</a>
</div>
<div className={`pagination-next${current < total ? '' : ' is-invisible is-hidden-mobile'}`}>
<a className="is-flex-grow has-text-black-ter" href={getPageUrl(current + 1)}>{nextTitle}</a>
</div>
<ul className="pagination-list is-hidden-mobile">
{pagination(current, total)}
</ul>
</nav>
</div>;
}
};

View File

@ -14,8 +14,9 @@ class AnimeJs extends Component {
}
module.exports = cacheComponent(AnimeJs, 'plugin.animejs', props => {
const { helper, head } = props;
return {
head: props.head,
url_for: props.url_for
head,
url_for: helper.url_for
};
});

View File

@ -21,9 +21,10 @@ class BackToTop extends Component {
}
module.exports = cacheComponent(BackToTop, 'plugin.backtotop', props => {
const { helper, head } = props;
return {
head: props.head,
title: props.__('plugin.backtotop'),
url_for: props.url_for
head,
title: helper.__('plugin.backtotop'),
url_for: helper.url_for
};
});

View File

@ -20,10 +20,11 @@ class BaiduAnalytics extends Component {
}
module.exports = cacheComponent(BaiduAnalytics, 'plugin.baiduanalytics', props => {
if (!props.head || !props.tracking_id) {
const { head, plugin } = props;
if (!head || !plugin.tracking_id) {
return null;
}
return {
trackingId: props.tracking_id
trackingId: plugin.tracking_id
};
});

View File

@ -22,16 +22,17 @@ class Gallery extends Component {
}
module.exports = cacheComponent(Gallery, 'plugin.gallery', props => {
const { head, helper } = props;
return {
head: props.head,
url_for: props.url_for,
head,
url_for: helper.url_for,
lightGallery: {
jsUrl: props.cdn('lightgallery', '1.6.8', 'dist/js/lightgallery.min.js'),
cssUrl: props.cdn('lightgallery', '1.6.8', 'dist/css/lightgallery.min.css')
jsUrl: helper.cdn('lightgallery', '1.6.8', 'dist/js/lightgallery.min.js'),
cssUrl: helper.cdn('lightgallery', '1.6.8', 'dist/css/lightgallery.min.css')
},
justifiedGallery: {
jsUrl: props.cdn('justifiedGallery', '3.7.0', 'dist/js/jquery.justifiedGallery.min.js'),
cssUrl: props.cdn('justifiedGallery', '3.7.0', 'dist/css/justifiedGallery.min.css')
jsUrl: helper.cdn('justifiedGallery', '3.7.0', 'dist/js/jquery.justifiedGallery.min.js'),
cssUrl: helper.cdn('justifiedGallery', '3.7.0', 'dist/css/justifiedGallery.min.css')
}
};
});

View File

@ -21,10 +21,11 @@ class GoogleAnalytics extends Component {
}
module.exports = cacheComponent(GoogleAnalytics, 'plugin.googleanalytics', props => {
if (!props.head || !props.tracking_id) {
const { head, plugin } = props;
if (!head || !plugin.tracking_id) {
return null;
}
return {
trackingId: props.tracking_id
trackingId: plugin.tracking_id
};
});

View File

@ -23,10 +23,11 @@ class Hotjar extends Component {
}
module.exports = cacheComponent(Hotjar, 'plugin.hotjar', props => {
if (!props.head || !props.site_id) {
const { head, plugin } = props;
if (!head || !plugin.site_id) {
return null;
}
return {
siteId: props.site_id
siteId: plugin.site_id
};
});

View File

@ -35,10 +35,11 @@ class Mathjax extends Component {
}
module.exports = cacheComponent(Mathjax, 'plugin.mathjax', props => {
if (props.head) {
const { head, helper } = props;
if (head) {
return null;
}
return {
jsUrl: props.cdn('mathjax', '2.7.5', 'unpacked/MathJax.js?config=TeX-MML-AM_CHTML')
jsUrl: helper.cdn('mathjax', '2.7.5', 'unpacked/MathJax.js?config=TeX-MML-AM_CHTML')
};
});

View File

@ -35,9 +35,10 @@ class OutdatedBrowser extends Component {
}
module.exports = cacheComponent(OutdatedBrowser, 'plugin.outdatedbrowser', props => {
const { head, helper } = props;
return {
head: props.head,
cssUrl: props.cdn('outdatedbrowser', '1.1.5', 'outdatedbrowser/outdatedbrowser.min.css'),
jsUrl: props.cdn('outdatedbrowser', '1.1.5', 'outdatedbrowser/outdatedbrowser.min.js')
head,
cssUrl: helper.cdn('outdatedbrowser', '1.1.5', 'outdatedbrowser/outdatedbrowser.min.css'),
jsUrl: helper.cdn('outdatedbrowser', '1.1.5', 'outdatedbrowser/outdatedbrowser.min.js')
};
});

View File

@ -15,11 +15,12 @@ class ProgressBar extends Component {
}
module.exports = cacheComponent(ProgressBar, 'plugin.progressbar', props => {
if (!props.head) {
const { head, helper } = props;
if (!head) {
return null;
}
return {
url_for: props.url_for,
jsUrl: props.cdn('pace-js', '1.0.2', 'pace.min.js')
url_for: helper.url_for,
jsUrl: helper.cdn('pace-js', '1.0.2', 'pace.min.js')
};
});

View File

@ -12,17 +12,17 @@ class Archives extends Component {
} = this.props;
return <div className="card widget">
<div class="card-content">
<div class="menu">
<h3 class="menu-label">{title}</h3>
<ul class="menu-list">
<div className="card-content">
<div className="menu">
<h3 className="menu-label">{title}</h3>
<ul className="menu-list">
{items.map(archive => <li>
<a class="level is-marginless" href={archive.url}>
<span class="level-start">
<span class="level-item">{archive.name}</span>
<a className="level is-marginless" href={archive.url}>
<span className="level-start">
<span className="level-item">{archive.name}</span>
</span>
{showCount ? <span class="level-end">
<span class="level-item tag">{archive.count}</span>
{showCount ? <span className="level-end">
<span className="level-item tag">{archive.count}</span>
</span> : null}
</a>
</li>)}
@ -35,15 +35,23 @@ class Archives extends Component {
module.exports = cacheComponent(Archives, 'widget.archives', props => {
// adapted from hexo/lib/plugins/helper/list_archives.js
const { config, page, type = 'monthly', order = -1, url_for, _p } = props;
const posts = props.site.posts.sort('date', order);
const {
site,
config,
page,
helper,
type = 'monthly',
order = -1,
show_count = true,
format = null
} = props;
const { url_for, _p } = helper;
const posts = site.posts.sort('date', order);
if (!posts.length) {
return null;
}
const language = page.lang || page.language || config.language;
const format = props.format || type === 'monthly' ? 'MMMM YYYY' : 'YYYY';
const showCount = Object.prototype.hasOwnProperty.call(props, 'show_count') ? props.show_count : true;
const data = [];
let length = 0;
@ -61,7 +69,7 @@ module.exports = cacheComponent(Archives, 'widget.archives', props => {
const year = date.year();
const month = date.month() + 1;
const name = date.format(format);
const name = date.format(format || type === 'monthly' ? 'MMMM YYYY' : 'YYYY');
const lastData = data[length - 1];
if (!lastData || lastData.name !== name) {
@ -94,6 +102,6 @@ module.exports = cacheComponent(Archives, 'widget.archives', props => {
url: link(item)
})),
title: _p('common.archive', Infinity),
showCount
showCount: show_count
};
});

View File

@ -6,12 +6,12 @@ const { cacheComponent } = require('../util/cache');
class Categories extends Component {
renderList(categories, showCount) {
return categories.map(category => <li>
<a class="level is-marginless" href={category.url}>
<span class="level-start">
<span class="level-item">{category.name}</span>
<a className="level is-marginless" href={category.url}>
<span className="level-start">
<span className="level-item">{category.name}</span>
</span>
{showCount ? <span class="level-end">
<span class="level-item tag">{category.count}</span>
{showCount ? <span className="level-end">
<span className="level-item tag">{category.count}</span>
</span> : null}
</a>
{category.children.length ? <ul>{this.renderList(category.children)}</ul> : null}
@ -26,10 +26,10 @@ class Categories extends Component {
} = this.props;
return <div className="card widget">
<div class="card-content">
<div class="menu">
<h3 class="menu-label">{title}</h3>
<ul class="menu-list">
<div className="card-content">
<div className="menu">
<h3 className="menu-label">{title}</h3>
<ul className="menu-list">
{this.renderList(categories, showCount)}
</ul>
</div>
@ -40,14 +40,25 @@ class Categories extends Component {
module.exports = cacheComponent(Categories, 'widget.categories', props => {
// adapted from hexo/lib/plugins/helper/list_categories.js
const categories = props.categories || props.site.categories;
if (!categories.length) {
const {
page,
helper,
categories = props.site.categories,
orderBy = 'name',
order = 1,
show_current = false,
show_count = true
} = props;
const { url_for, _p } = helper;
if (!categories || !categories.length) {
return null;
}
const { orderBy = 'name', order = 1, page, url_for, _p } = props;
const depth = props.depth ? parseInt(props.depth, 10) : 0;
const showCurrent = props.show_current || false;
const showCount = Object.prototype.hasOwnProperty.call(props, 'show_count') ? props.show_count : true;
let depth = 0;
try {
depth = parseInt(props.depth, 10);
} catch (e) { }
function prepareQuery(parent) {
const query = {};
@ -69,7 +80,7 @@ module.exports = cacheComponent(Categories, 'widget.categories', props => {
}
let isCurrent = false;
if (showCurrent && page) {
if (show_current && page) {
for (let j = 0; j < cat.length; j++) {
const post = cat.posts.data[j];
if (post && post._id === page._id) {
@ -92,7 +103,7 @@ module.exports = cacheComponent(Categories, 'widget.categories', props => {
}
return {
showCount,
showCount: show_count,
categories: hierarchicalList(0),
title: _p('common.category', Infinity)
};

View File

@ -7,23 +7,23 @@ const { cacheComponent } = require('../util/cache');
class Links extends Component {
render() {
const { title, links } = this.props;
return <div class="card widget">
<div class="card-content">
<div class="menu">
<h3 class="menu-label">{title}</h3>
<ul class="menu-list">
return <div className="card widget">
<div className="card-content">
<div className="menu">
<h3 className="menu-label">{title}</h3>
<ul className="menu-list">
{Object.keys(links).map(i => {
let hostname = links[i];
try {
hostname = new URL(hostname).hostname;
} catch (e) { }
return <li>
<a class="level is-mobile" href="<%- links[i] %>" target="_blank" rel="noopener">
<span class="level-left">
<span class="level-item">{i}</span>
<a className="level is-mobile" href="<%- links[i] %>" target="_blank" rel="noopener">
<span className="level-left">
<span className="level-item">{i}</span>
</span>
<span class="level-right">
<span class="level-item tag">{hostname}</span>
<span className="level-right">
<span className="level-item tag">{hostname}</span>
</span>
</a>
</li>;
@ -36,11 +36,12 @@ class Links extends Component {
}
module.exports = cacheComponent(Links, 'widget.links', props => {
if (!Object.keys(props.links).length) {
const { helper, widget } = props;
if (!Object.keys(widget.links).length) {
return null;
}
return {
title: props.__('widget.links'),
links: props.links
title: helper.__('widget.links'),
links: widget.links
};
});

View File

@ -9,11 +9,11 @@ class Profile extends Component {
if (!links.length) {
return null;
}
return <div class="level is-mobile">
return <div className="level is-mobile">
{links.map(link => {
return <a class="level-item button is-white is-marginless"
return <a className="level-item button is-white is-marginless"
target="_blank" rel="noopener" title={link.name} href={link.url}>
{Object.prototype.hasOwnProperty.call(link, 'icon') ? <i class={link.icon}></i> : link.name}
{Object.prototype.hasOwnProperty.call(link, 'icon') ? <i className={link.icon}></i> : link.name}
</a>;
})}
</div>;
@ -31,51 +31,51 @@ class Profile extends Component {
followTitle,
socialLinks
} = this.props;
return <div class="card widget">
<div class="card-content">
<nav class="level">
<div class="level-item has-text-centered" style="flex-shrink: 1">
return <div className="card widget">
<div className="card-content">
<nav className="level">
<div className="level-item has-text-centered" style="flex-shrink: 1">
<div>
<figure class="image is-128x128 has-mb-6">
<img class={avatarRounded ? 'is-rounded' : ''} src={avatar} alt={author} />
<figure className="image is-128x128 has-mb-6">
<img className={avatarRounded ? 'is-rounded' : ''} src={avatar} alt={author} />
</figure>
{author ? <p class="is-size-4 is-block">{author}</p> : null}
{authorTitle ? <p class="is-size-6 is-block">{authorTitle}</p> : null}
{location ? <p class="is-size-6 is-flex is-flex-center has-text-grey">
<i class="fas fa-map-marker-alt has-mr-7"></i>
{author ? <p className="is-size-4 is-block">{author}</p> : null}
{authorTitle ? <p className="is-size-6 is-block">{authorTitle}</p> : null}
{location ? <p className="is-size-6 is-flex is-flex-center has-text-grey">
<i className="fas fa-map-marker-alt has-mr-7"></i>
<span>{location}</span>
</p> : null}
</div>
</div>
</nav>
<nav class="level is-mobile">
<div class="level-item has-text-centered is-marginless">
<nav className="level is-mobile">
<div className="level-item has-text-centered is-marginless">
<div>
<p class="heading">{counter.post.title}</p>
<p className="heading">{counter.post.title}</p>
<a href={counter.post.url}>
<p class="title has-text-weight-normal">{counter.post.count}</p>
<p className="title has-text-weight-normal">{counter.post.count}</p>
</a>
</div>
</div>
<div class="level-item has-text-centered is-marginless">
<div className="level-item has-text-centered is-marginless">
<div>
<p class="heading">{counter.category.title}</p>
<p className="heading">{counter.category.title}</p>
<a href={counter.category.url}>
<p class="title has-text-weight-normal">{counter.category.count}</p>
<p className="title has-text-weight-normal">{counter.category.count}</p>
</a>
</div>
</div>
<div class="level-item has-text-centered is-marginless">
<div className="level-item has-text-centered is-marginless">
<div>
<p class="heading">{counter.tag.title}</p>
<p className="heading">{counter.tag.title}</p>
<a href={counter.tag.url}>
<p class="title has-text-weight-normal">{counter.tag.count}</p>
<p className="title has-text-weight-normal">{counter.tag.count}</p>
</a>
</div>
</div>
</nav>
{followLink ? <div class="level">
<a class="level-item button is-link is-rounded" href={followLink} target="_blank" rel="noopener">{followTitle}</a>
{followLink ? <div className="level">
<a className="level-item button is-link is-rounded" href={followLink} target="_blank" rel="noopener">{followTitle}</a>
</div> : null}
{this.renderSocialLinks(socialLinks)}
</div>
@ -84,6 +84,7 @@ class Profile extends Component {
}
module.exports = cacheComponent(Profile, 'widget.profile', props => {
const { site, helper, widget } = props;
const {
avatar,
gravatar,
@ -92,12 +93,9 @@ module.exports = cacheComponent(Profile, 'widget.profile', props => {
author_title,
location,
follow_link,
social_links,
site,
url_for,
_p,
__
} = props;
social_links
} = widget;
const { url_for, _p, __ } = helper;
function getAvatar() {
if (gravatar) {
@ -126,7 +124,6 @@ module.exports = cacheComponent(Profile, 'widget.profile', props => {
url: url_for(link.name),
icon: link.icon
};
});
return {

View File

@ -7,28 +7,28 @@ class RecentPosts extends Component {
render() {
const { title, thumbnail, posts } = this.props;
return <div class="card widget">
<div class="card-content">
<h3 class="menu-label">{title}</h3>
return <div className="card widget">
<div className="card-content">
<h3 className="menu-label">{title}</h3>
{posts.map(post => {
const categories = [];
post.categories.forEach((category, i) => {
categories.push(<a class="has-link-grey" href={category.url}>{category.name}</a>);
categories.push(<a className="has-link-grey" href={category.url}>{category.name}</a>);
if (i < post.categories.length - 1) {
categories.push('/');
}
});
return <article class="media">
{thumbnail ? <a href={post.url} class="media-left">
<p class="image is-64x64">
<img class="thumbnail" src={post.thumbnail} alt={post.title} />
return <article className="media">
{thumbnail ? <a href={post.url} className="media-left">
<p className="image is-64x64">
<img className="thumbnail" src={post.thumbnail} alt={post.title} />
</p>
</a> : null}
<div class="media-content">
<div class="content">
<div><time class="has-text-grey is-size-7 is-uppercase" datetime={post.dateXml}>{post.date}</time></div>
<a href={post.url} class="title has-link-black-ter is-size-6 has-text-weight-normal">{post.title}</a>
<p class="is-size-7 is-uppercase">{categories}</p>
<div className="media-content">
<div className="content">
<div><time className="has-text-grey is-size-7 is-uppercase" datetime={post.dateXml}>{post.date}</time></div>
<a href={post.url} className="title has-link-black-ter is-size-6 has-text-weight-normal">{post.title}</a>
<p className="is-size-7 is-uppercase">{categories}</p>
</div>
</div>
</article>;
@ -39,7 +39,8 @@ class RecentPosts extends Component {
}
module.exports = cacheComponent(RecentPosts, 'widget.recentposts', props => {
const { site, config, get_thumbnail, url_for, __, date_xml, date } = props;
const { site, config, helper } = props;
const { get_thumbnail, url_for, __, date_xml, date } = helper;
if (!site.posts.length) {
return null;
}

View File

@ -7,27 +7,27 @@ class SubscribeEmail extends Component {
render() {
const { title, description, feedburnerId, buttonTitle } = this.props;
return <div class="card widget">
<div class="card-content">
<div class="menu">
<h3 class="menu-label">{title}</h3>
return <div className="card widget">
<div className="card-content">
<div className="menu">
<h3 className="menu-label">{title}</h3>
<div>
<form action="https://feedburner.google.com/fb/a/mailverify" method="post" target="popupwindow"
onsubmit={`window.open('https://feedburner.google.com/fb/a/mailverify?uri=${feedburnerId}','popupwindow','scrollbars=yes,width=550,height=520');return true`}>
<input type="hidden" value={feedburnerId} name="uri" />
<input type="hidden" name="loc" value="en_US" />
<div class="field">
<div class="control has-icons-left">
<input class="input" name="email" type="email" />
<span class="icon is-small is-left">
<i class="fas fa-envelope"></i>
<div className="field">
<div className="control has-icons-left">
<input className="input" name="email" type="email" />
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
</div>
<p class="help">{description}</p>
<p className="help">{description}</p>
</div>
<div class="field is-grouped is-grouped-right">
<div class="control">
<input class="button is-link" type="submit" value={buttonTitle} />
<div className="field is-grouped is-grouped-right">
<div className="control">
<input className="button is-link" type="submit" value={buttonTitle} />
</div>
</div>
</form>
@ -39,12 +39,13 @@ class SubscribeEmail extends Component {
}
module.exports = cacheComponent(SubscribeEmail, 'widget.subscribeemail', props => {
const { feedburner_id, description, __ } = props;
const { helper, widget } = props;
const { feedburner_id, description } = widget;
return {
title: __('widget.email.title'),
feedburnerId: feedburner_id,
description,
buttonTitle: __('widget.email.button')
feedburnerId: feedburner_id,
title: helper.__('widget.email.title'),
buttonTitle: helper.__('widget.email.button')
};
});

View File

@ -12,14 +12,14 @@ class Tags extends Component {
} = this.props;
return <div className="card widget">
<div class="card-content">
<div class="menu">
<h3 class="menu-label">{title}</h3>
<div class="field is-grouped is-grouped-multiline">
{tags.map(tag => <div class="control">
<a class="tags has-addons" href={tag.url}>
<span class="tag">{tag.name}</span>
{showCount ? <span class="tag is-grey">{tag.count}</span> : null}
<div className="card-content">
<div className="menu">
<h3 className="menu-label">{title}</h3>
<div className="field is-grouped is-grouped-multiline">
{tags.map(tag => <div className="control">
<a className="tags has-addons" href={tag.url}>
<span className="tag">{tag.name}</span>
{showCount ? <span className="tag is-grey">{tag.count}</span> : null}
</a>
</div>)}
</div>
@ -31,12 +31,19 @@ class Tags extends Component {
module.exports = cacheComponent(Tags, 'widget.tags', props => {
// adapted from hexo/lib/plugins/helper/list_tags.js
const {
helper,
orderBy = 'name',
order = 1,
amount,
show_count = true
} = props;
let tags = props.tags || props.site.tags;
if (!tags.length) {
const { url_for, _p } = helper;
if (!tags || !tags.length) {
return null;
}
const { orderBy = 'name', order = 1, amount, url_for, _p } = props;
const showCount = Object.prototype.hasOwnProperty.call(props, 'show_count') ? props.show_count : true;
tags = tags.sort(orderBy, order).filter(tag => tag.length);
if (amount) {
@ -44,7 +51,7 @@ module.exports = cacheComponent(Tags, 'widget.tags', props => {
}
return {
showCount,
showCount: show_count,
title: _p('common.tag', Infinity),
tags: tags.map(tag => ({
name: tag.name,

View File

@ -85,7 +85,7 @@ class Toc extends Component {
.sort((a, b) => a - b);
if (keys.length > 0) {
result = <ul class="menu-list">
result = <ul className="menu-list">
{keys.map(i => this.renderToc(toc[i]))}
</ul>;
}
@ -93,8 +93,8 @@ class Toc extends Component {
&& Object.prototype.hasOwnProperty.call(toc, 'index')
&& Object.prototype.hasOwnProperty.call(toc, 'text')) {
result = <li>
<a class="is-flex" href={'#' + toc.id}>
<span class="has-mr-6">{toc.index}</span>
<a className="is-flex" href={'#' + toc.id}>
<span className="has-mr-6">{toc.index}</span>
<span>{toc.text}</span>
</a>
{result}
@ -109,10 +109,10 @@ class Toc extends Component {
return null;
}
return <div class="card widget" id="toc">
<div class="card-content">
<div class="menu">
<h3 class="menu-label">{this.props.title}</h3>
return <div className="card widget" id="toc">
<div className="card-content">
<div className="menu">
<h3 className="menu-label">{this.props.title}</h3>
{this.renderToc(toc)}
</div>
</div>
@ -121,15 +121,15 @@ class Toc extends Component {
}
module.exports = cacheComponent(Toc, 'widget.toc', props => {
const { toc, page, _p } = props;
const { config, page, helper } = props;
const { layout, content } = page;
if (toc !== true || (layout !== 'page' && layout !== 'post')) {
if (config.toc !== true || (layout !== 'page' && layout !== 'post')) {
return null;
}
return {
title: _p('widget.catalogue', Infinity),
title: helper._p('widget.catalogue', Infinity),
content
};
});