Merge branch 'release_v4.2.0'
Some checks failed
sync-2-gitee / sync-2-gitee (push) Failing after 3s
Some checks failed
sync-2-gitee / sync-2-gitee (push) Failing after 3s
This commit is contained in:
commit
0db1dac57f
10
.gitignore
vendored
10
.gitignore
vendored
@ -3,11 +3,15 @@
|
|||||||
exampleSite/*
|
exampleSite/*
|
||||||
|
|
||||||
# Exclude special files in content folder
|
# Exclude special files in content folder
|
||||||
|
exampleSite/content/**/
|
||||||
!exampleSite/content
|
!exampleSite/content
|
||||||
exampleSite/content/*
|
!exmapleSite/content/about.md
|
||||||
!exampleSite/content/archives
|
!exampleSite/content/archives
|
||||||
!exampleSite/content/post
|
!exampleSite/content/post
|
||||||
|
|
||||||
|
# Exclude special files in data folder
|
||||||
|
!exampleSite/data
|
||||||
|
|
||||||
# Exclude special files in static folder
|
# Exclude special files in static folder
|
||||||
!exampleSite/static
|
!exampleSite/static
|
||||||
|
|
||||||
@ -15,3 +19,7 @@ exampleSite/content/*
|
|||||||
!exampleSite/config.yaml
|
!exampleSite/config.yaml
|
||||||
!exampleSite/start.sh
|
!exampleSite/start.sh
|
||||||
|
|
||||||
|
data/*
|
||||||
|
!data/config.yaml
|
||||||
|
!data/resources.yaml
|
||||||
|
|
||||||
|
141
assets/css/_common/components/pages/flinks.scss
Normal file
141
assets/css/_common/components/pages/flinks.scss
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
.flinks-block {
|
||||||
|
|
||||||
|
.flinks-block-title {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-list-title {
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-lists > div:not(:first-child) {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-list-desc {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-list-items {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.flinks-list-item {
|
||||||
|
@include mobile() {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 15px 7px;
|
||||||
|
width: calc(100% / 3 - 18px);
|
||||||
|
height: 90px;
|
||||||
|
border-radius: 8px;
|
||||||
|
line-height: 17px;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
border: groove 1px var(--highlight-foreground);
|
||||||
|
box-shadow: .6rem .5rem 0.3rem var(--body-bg-color);
|
||||||
|
|
||||||
|
&:hover:before,
|
||||||
|
&:focus:before,
|
||||||
|
&:active:before {
|
||||||
|
-webkit-transform:scale(1);
|
||||||
|
-moz-transform:scale(1);
|
||||||
|
-o-transform:scale(1);
|
||||||
|
-ms-transform:scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&:hover .flinks-item-icon {
|
||||||
|
margin-left:-10px;
|
||||||
|
width:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
background: var(--body-bg-color);
|
||||||
|
content: ' ';
|
||||||
|
transition: transform .3s ease-out;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
//color: var(--font-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-item-icon {
|
||||||
|
float: left;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 15px 10px;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 35px;
|
||||||
|
border: solid 1px var(--body-bg-color);
|
||||||
|
-webkit-transition: width .3s ease-out;
|
||||||
|
-moz-transition: width .3s ease-out;
|
||||||
|
-o-transition: width .3s ease-out;
|
||||||
|
-ms-transition: width .3s ease-out;
|
||||||
|
transition: width .3s ease-out;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
-webkit-transition: filter 375ms ease-in .2s,-webkit-transform .3s;
|
||||||
|
-moz-transition: filter 375ms ease-in .2s,-moz-transform .3s;
|
||||||
|
-o-transition: filter 375ms ease-in .2s,-o-transform .3s;
|
||||||
|
-ms-transition: filter 375ms ease-in .2s,-ms-transform .3s;
|
||||||
|
transition: filter 375ms ease-in .2s,transform .3s;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-item-name, .flinks-item-desc {
|
||||||
|
overflow:hidden;
|
||||||
|
-o-text-overflow:ellipsis;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
white-space:nowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-item-name {
|
||||||
|
padding: 20px 0 0 0;
|
||||||
|
height: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
@include mobile() {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-item-desc {
|
||||||
|
padding: 10px 0;
|
||||||
|
font-size: .92em;
|
||||||
|
|
||||||
|
@include mobile() {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flinks-page-desc {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
@ -3,3 +3,4 @@
|
|||||||
@import 'schedule';
|
@import 'schedule';
|
||||||
@import 'breadcrumb';
|
@import 'breadcrumb';
|
||||||
@import 'tag-cloud';
|
@import 'tag-cloud';
|
||||||
|
@import 'flinks';
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
.use-motion {
|
.use-motion {
|
||||||
@if $motion_trans_post_block {
|
@if $motion_trans_post_block {
|
||||||
.post-block, .pagination, .comments, .post-comments {
|
.post-block, .flinks-block, .pagination, .comments, .post-comments {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,4 +76,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
max-width: 98%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// Fix issue #16
|
// Fix issue #16
|
||||||
// To do: use `gap` instead of `margin`
|
// To do: use `gap` instead of `margin`
|
||||||
// See https://caniuse.com/flexbox-gap
|
// See https://caniuse.com/flexbox-gap
|
||||||
.post-footer {
|
.post-footer, .flinks-list-footer {
|
||||||
@include flex-column();
|
@include flex-column();
|
||||||
|
|
||||||
hr{
|
hr{
|
||||||
|
@ -106,6 +106,13 @@
|
|||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mark.search-keyword {
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px dashed $red;
|
||||||
|
color: $red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@if $algolia_search_enable {
|
@if $algolia_search_enable {
|
||||||
@ -169,11 +176,4 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mark.search-keyword {
|
|
||||||
background: transparent;
|
|
||||||
border-bottom: 1px dashed $red;
|
|
||||||
color: $red;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,29 @@
|
|||||||
.highlight {
|
.highlight {
|
||||||
|
|
||||||
background: var(--highlight-background);
|
background: var(--highlight-background);
|
||||||
|
margin-bottom: 26px;
|
||||||
|
|
||||||
.table-container, pre {
|
//TODO Need fixed the copy button show position.
|
||||||
|
div:first-child {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container table tbody tr {
|
table tbody tr {
|
||||||
background: none;
|
//TODO Fixed the too long code line over
|
||||||
|
// layout background color.
|
||||||
|
background: #272822;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pre > code span {
|
||||||
|
white-space: break-spaces;
|
||||||
|
overflow: hidden;
|
||||||
|
word-break: break-all;
|
||||||
|
word-wrap: break-word;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
@if $codeblock_copy_btn_enable {
|
@if $codeblock_copy_btn_enable {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
padding: 10px;
|
padding: 18px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//@use 'sass:map';
|
//@use 'sass:map';
|
||||||
|
|
||||||
@if $note_style != 'disabled' {
|
@if $note_style != 'disabled' {
|
||||||
.post-body .note {
|
.main .note {
|
||||||
$note-icons : $note_icons;
|
$note-icons : $note_icons;
|
||||||
$note-style : $note_style;
|
$note-style : $note_style;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
// ==================================================
|
// ==================================================
|
||||||
// Post blocks.
|
// Post blocks.
|
||||||
.main-inner > {
|
.main-inner > {
|
||||||
.sub-menu, .post-block, .tabs-comment, .comments, .post-comments, .pagination {
|
.sub-menu, .post-block, .flinks-block, .tabs-comment, .comments, .post-comments, .pagination {
|
||||||
background: var(--content-bg-color);
|
background: var(--content-bg-color);
|
||||||
border-radius: $border-radius-inner;
|
border-radius: $border-radius-inner;
|
||||||
box-shadow: $box-shadow-inner;
|
box-shadow: $box-shadow-inner;
|
||||||
@ -47,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Post & Comments blocks.
|
// Post & Comments blocks.
|
||||||
.post-block, .comments, .post-comments {
|
.post-block, .flinks-block, .comments, .post-comments {
|
||||||
padding: $content-desktop-padding;
|
padding: $content-desktop-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-block {
|
.post-block, .flinks-block {
|
||||||
// Inside posts blocks content padding (default 40px).
|
// Inside posts blocks content padding (default 40px).
|
||||||
padding: ($content-tablet-padding * 2);
|
padding: ($content-tablet-padding * 2);
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-block {
|
.post-block, .flinks-block {
|
||||||
// Inside posts blocks content padding (default 40px).
|
// Inside posts blocks content padding (default 40px).
|
||||||
padding: $sidebar-offset;
|
padding: $sidebar-offset;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ NexT.motion.middleWares = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
animate(postblock, '.post-block, .pagination, .post-comments');
|
animate(postblock, '.post-block,.flinks-block, .pagination, .post-comments');
|
||||||
animate(collheader, '.collection-header');
|
animate(collheader, '.collection-header');
|
||||||
animate(postheader, '.post-header');
|
animate(postheader, '.post-header');
|
||||||
animate(postbody, '.post-body');
|
animate(postbody, '.post-body');
|
||||||
|
@ -4,7 +4,7 @@ NexT.boot = {};
|
|||||||
|
|
||||||
NexT.boot.registerEvents = function() {
|
NexT.boot.registerEvents = function() {
|
||||||
|
|
||||||
// NexT.utils.registerScrollPercent();
|
NexT.utils.registerScrollPercent();
|
||||||
// NexT.utils.registerCanIUseTag();
|
// NexT.utils.registerCanIUseTag();
|
||||||
|
|
||||||
// Mobile top menu bar.
|
// Mobile top menu bar.
|
||||||
|
144
assets/js/third-party/search/algolia.js
vendored
Normal file
144
assets/js/third-party/search/algolia.js
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/* global instantsearch, algoliasearch, CONFIG, pjax */
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
const algoiajs = NexT.utils.getCDNResource(NexT.CONFIG.algolia.js);
|
||||||
|
const instantschjs = NexT.utils.getCDNResource(NexT.CONFIG.algolia.instantjs);
|
||||||
|
|
||||||
|
NexT.utils.getScript(algoiajs, {});
|
||||||
|
NexT.utils.getScript(instantschjs, {}).then(() => {
|
||||||
|
|
||||||
|
const { indexname, appid, apikey, hits } = NexT.CONFIG.algolia.cfg;
|
||||||
|
const indexName = indexname;
|
||||||
|
|
||||||
|
const search = instantsearch({
|
||||||
|
indexName,
|
||||||
|
searchClient: algoliasearch(appid, apikey),
|
||||||
|
searchFunction: helper => {
|
||||||
|
if (document.querySelector('.search-input').value) {
|
||||||
|
helper.search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const markKeyWords = function(content) {
|
||||||
|
return content.replaceAll("<mark>", '<mark class="search-keyword">');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof pjax === 'object') {
|
||||||
|
search.on('render', () => {
|
||||||
|
pjax.refresh(document.querySelector('.algolia-hits'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registering Widgets
|
||||||
|
search.addWidgets([
|
||||||
|
instantsearch.widgets.configure({
|
||||||
|
hitsPerPage: hits.perpage || 10
|
||||||
|
}),
|
||||||
|
|
||||||
|
instantsearch.widgets.searchBox({
|
||||||
|
container: '.search-input-container',
|
||||||
|
placeholder: NexT.CONFIG.i18n.placeholder,
|
||||||
|
// Hide default icons of algolia search
|
||||||
|
showReset: false,
|
||||||
|
showSubmit: false,
|
||||||
|
showLoadingIndicator: true,
|
||||||
|
cssClasses: {
|
||||||
|
input: 'search-input'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
instantsearch.widgets.stats({
|
||||||
|
container: '.algolia-stats',
|
||||||
|
templates: {
|
||||||
|
text: data => {
|
||||||
|
const stats = NexT.CONFIG.i18n.hits_time
|
||||||
|
.replace('${hits}', data.nbHits)
|
||||||
|
.replace('${time}', data.processingTimeMS);
|
||||||
|
return `<span>${stats}</span><img src="/imgs/algolia-logo.svg" alt="Algolia">`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
text: 'search-stats'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
instantsearch.widgets.hits({
|
||||||
|
container: '.algolia-hits',
|
||||||
|
escapeHTML: true,
|
||||||
|
templates: {
|
||||||
|
item: data => {
|
||||||
|
const { title, content } = data._highlightResult;
|
||||||
|
let result = `<a href="${data.permalink}" class="search-result-title">${markKeyWords(title.value)}</a>`;
|
||||||
|
//const content = excerpt || excerptStrip || content;
|
||||||
|
if (content && content.value) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = markKeyWords(content.value);
|
||||||
|
result += `<a href="${data.permalink}"><p class="search-result">${div.innerHTML.substring(0, 200)}...</p></a>`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
empty: data => {
|
||||||
|
return `<div class="algolia-hits-empty">
|
||||||
|
${NexT.CONFIG.i18n.empty.replace('${query}', data.query)}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
list: 'search-result-list'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
instantsearch.widgets.pagination({
|
||||||
|
container: '.algolia-pagination',
|
||||||
|
scrollTo: false,
|
||||||
|
showFirst: true,
|
||||||
|
showLast: true,
|
||||||
|
templates: {
|
||||||
|
first: '<i class="fa fa-angle-double-left"></i>',
|
||||||
|
last: '<i class="fa fa-angle-double-right"></i>',
|
||||||
|
previous: '<i class="fa fa-angle-left"></i>',
|
||||||
|
next: '<i class="fa fa-angle-right"></i>'
|
||||||
|
},
|
||||||
|
cssClasses: {
|
||||||
|
list: ['pagination', 'algolia-pagination'],
|
||||||
|
item: 'pagination-item',
|
||||||
|
link: 'page-number',
|
||||||
|
selectedItem: 'current',
|
||||||
|
disabledItem: 'disabled-item'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
search.start();
|
||||||
|
|
||||||
|
// Handle and trigger popup window
|
||||||
|
document.querySelectorAll('.popup-trigger').forEach(element => {
|
||||||
|
element.addEventListener('click', () => {
|
||||||
|
document.body.classList.add('search-active');
|
||||||
|
setTimeout(() => document.querySelector('.search-input').focus(), 500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Monitor main search box
|
||||||
|
const onPopupClose = () => {
|
||||||
|
document.body.classList.remove('search-active');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelector('.search-pop-overlay').addEventListener('click', event => {
|
||||||
|
if (event.target === document.querySelector('.search-pop-overlay')) {
|
||||||
|
onPopupClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
||||||
|
document.addEventListener('pjax:success', onPopupClose);
|
||||||
|
window.addEventListener('keyup', event => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
onPopupClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});;
|
||||||
|
|
||||||
|
|
||||||
|
});
|
326
assets/js/third-party/search/local.js
vendored
Normal file
326
assets/js/third-party/search/local.js
vendored
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
/* global CONFIG, pjax, LocalSearch */
|
||||||
|
class LocalSearch {
|
||||||
|
constructor({
|
||||||
|
path = '',
|
||||||
|
unescape = false,
|
||||||
|
top_n_per_article = 1
|
||||||
|
}) {
|
||||||
|
this.path = path;
|
||||||
|
this.unescape = unescape;
|
||||||
|
this.top_n_per_article = top_n_per_article;
|
||||||
|
this.isfetched = false;
|
||||||
|
this.datas = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIndexByWord(words, text, caseSensitive = false) {
|
||||||
|
const index = [];
|
||||||
|
const included = new Set();
|
||||||
|
|
||||||
|
if (!caseSensitive) {
|
||||||
|
text = text.toLowerCase();
|
||||||
|
}
|
||||||
|
words.forEach(word => {
|
||||||
|
if (this.unescape) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerText = word;
|
||||||
|
word = div.innerHTML;
|
||||||
|
}
|
||||||
|
const wordLen = word.length;
|
||||||
|
if (wordLen === 0) return;
|
||||||
|
let startPosition = 0;
|
||||||
|
let position = -1;
|
||||||
|
if (!caseSensitive) {
|
||||||
|
word = word.toLowerCase();
|
||||||
|
}
|
||||||
|
while ((position = text.indexOf(word, startPosition)) > -1) {
|
||||||
|
index.push({ position, word });
|
||||||
|
included.add(word);
|
||||||
|
startPosition = position + wordLen;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Sort index by position of keyword
|
||||||
|
index.sort((left, right) => {
|
||||||
|
if (left.position !== right.position) {
|
||||||
|
return left.position - right.position;
|
||||||
|
}
|
||||||
|
return right.word.length - left.word.length;
|
||||||
|
});
|
||||||
|
return [index, included];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge hits into slices
|
||||||
|
mergeIntoSlice(start, end, index) {
|
||||||
|
let item = index[0];
|
||||||
|
let { position, word } = item;
|
||||||
|
const hits = [];
|
||||||
|
const count = new Set();
|
||||||
|
while (position + word.length <= end && index.length !== 0) {
|
||||||
|
count.add(word);
|
||||||
|
hits.push({
|
||||||
|
position,
|
||||||
|
length: word.length
|
||||||
|
});
|
||||||
|
const wordEnd = position + word.length;
|
||||||
|
|
||||||
|
// Move to next position of hit
|
||||||
|
index.shift();
|
||||||
|
while (index.length !== 0) {
|
||||||
|
item = index[0];
|
||||||
|
position = item.position;
|
||||||
|
word = item.word;
|
||||||
|
if (wordEnd > position) {
|
||||||
|
index.shift();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hits,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
count: count.size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight title and content
|
||||||
|
highlightKeyword(val, slice) {
|
||||||
|
let result = '';
|
||||||
|
let index = slice.start;
|
||||||
|
for (const { position, length } of slice.hits) {
|
||||||
|
result += val.substring(index, position);
|
||||||
|
index = position + length;
|
||||||
|
result += `<mark class="search-keyword">${val.substr(position, length)}</mark>`;
|
||||||
|
}
|
||||||
|
result += val.substring(index, slice.end);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
getResultItems(keywords) {
|
||||||
|
const resultItems = [];
|
||||||
|
this.datas.forEach(({ title, content, url }) => {
|
||||||
|
// The number of different keywords included in the article.
|
||||||
|
const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title);
|
||||||
|
const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content);
|
||||||
|
const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size;
|
||||||
|
|
||||||
|
// Show search results
|
||||||
|
const hitCount = indexOfTitle.length + indexOfContent.length;
|
||||||
|
if (hitCount === 0) return;
|
||||||
|
|
||||||
|
const slicesOfTitle = [];
|
||||||
|
if (indexOfTitle.length !== 0) {
|
||||||
|
slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle));
|
||||||
|
}
|
||||||
|
|
||||||
|
let slicesOfContent = [];
|
||||||
|
while (indexOfContent.length !== 0) {
|
||||||
|
const item = indexOfContent[0];
|
||||||
|
const { position } = item;
|
||||||
|
// Cut out 100 characters. The maxlength of .search-input is 80.
|
||||||
|
const start = Math.max(0, position - 20);
|
||||||
|
const end = Math.min(content.length, position + 80);
|
||||||
|
slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort slices in content by included keywords' count and hits' count
|
||||||
|
slicesOfContent.sort((left, right) => {
|
||||||
|
if (left.count !== right.count) {
|
||||||
|
return right.count - left.count;
|
||||||
|
} else if (left.hits.length !== right.hits.length) {
|
||||||
|
return right.hits.length - left.hits.length;
|
||||||
|
}
|
||||||
|
return left.start - right.start;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select top N slices in content
|
||||||
|
const upperBound = parseInt(this.top_n_per_article, 10);
|
||||||
|
if (upperBound >= 0) {
|
||||||
|
slicesOfContent = slicesOfContent.slice(0, upperBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultItem = '';
|
||||||
|
|
||||||
|
url = new URL(url, location.origin);
|
||||||
|
url.searchParams.append('highlight', keywords.join(' '));
|
||||||
|
|
||||||
|
if (slicesOfTitle.length !== 0) {
|
||||||
|
resultItem += `<li><a href="${url.href}" class="search-result-title">${this.highlightKeyword(title, slicesOfTitle[0])}</a>`;
|
||||||
|
} else {
|
||||||
|
resultItem += `<li><a href="${url.href}" class="search-result-title">${title}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
slicesOfContent.forEach(slice => {
|
||||||
|
resultItem += `<a href="${url.href}"><p class="search-result">${this.highlightKeyword(content, slice)}...</p></a>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultItem += '</li>';
|
||||||
|
resultItems.push({
|
||||||
|
item: resultItem,
|
||||||
|
id : resultItems.length,
|
||||||
|
hitCount,
|
||||||
|
includedCount
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return resultItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchData() {
|
||||||
|
const isXml = !this.path.endsWith('json');
|
||||||
|
fetch(this.path)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(res => {
|
||||||
|
// Get the contents from search data
|
||||||
|
this.isfetched = true;
|
||||||
|
this.datas = isXml ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({
|
||||||
|
title : element.querySelector('title').textContent,
|
||||||
|
content: element.querySelector('content').textContent,
|
||||||
|
url : element.querySelector('url').textContent
|
||||||
|
})) : JSON.parse(res);
|
||||||
|
// Only match articles with non-empty titles
|
||||||
|
this.datas = this.datas.filter(data => data.title).map(data => {
|
||||||
|
data.title = data.title.trim();
|
||||||
|
data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '';
|
||||||
|
data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
// Remove loading animation
|
||||||
|
window.dispatchEvent(new Event('search:loaded'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight by wrapping node in mark elements with the given class name
|
||||||
|
highlightText(node, slice, className) {
|
||||||
|
const val = node.nodeValue;
|
||||||
|
let index = slice.start;
|
||||||
|
const children = [];
|
||||||
|
for (const { position, length } of slice.hits) {
|
||||||
|
const text = document.createTextNode(val.substring(index, position));
|
||||||
|
index = position + length;
|
||||||
|
const mark = document.createElement('mark');
|
||||||
|
mark.className = className;
|
||||||
|
mark.appendChild(document.createTextNode(val.substr(position, length)));
|
||||||
|
children.push(text, mark);
|
||||||
|
}
|
||||||
|
node.nodeValue = val.substring(index, slice.end);
|
||||||
|
children.forEach(element => {
|
||||||
|
node.parentNode.insertBefore(element, node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the search words provided in the url in the text
|
||||||
|
highlightSearchWords(body) {
|
||||||
|
const params = new URL(location.href).searchParams.get('highlight');
|
||||||
|
const keywords = params ? params.split(' ') : [];
|
||||||
|
if (!keywords.length || !body) return;
|
||||||
|
const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null);
|
||||||
|
const allNodes = [];
|
||||||
|
while (walk.nextNode()) {
|
||||||
|
if (!walk.currentNode.parentNode.matches('button, select, textarea')) allNodes.push(walk.currentNode);
|
||||||
|
}
|
||||||
|
allNodes.forEach(node => {
|
||||||
|
const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue);
|
||||||
|
if (!indexOfNode.length) return;
|
||||||
|
const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode);
|
||||||
|
this.highlightText(node, slice, 'search-keyword');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
if (! NexT.CONFIG.localSearch.path) {
|
||||||
|
// Search DB path
|
||||||
|
console.warn('`search indexes file` is not configurate!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const localSearch = new LocalSearch({
|
||||||
|
path : NexT.CONFIG.localSearch.path,
|
||||||
|
top_n_per_article: NexT.CONFIG.localSearch.topnperarticle,
|
||||||
|
unescape : NexT.CONFIG.localSearch.unescape
|
||||||
|
});
|
||||||
|
|
||||||
|
const input = document.querySelector('.search-input');
|
||||||
|
|
||||||
|
const inputEventFunction = () => {
|
||||||
|
if (!localSearch.isfetched) return;
|
||||||
|
const searchText = input.value.trim().toLowerCase();
|
||||||
|
const keywords = searchText.split(/[-\s]+/);
|
||||||
|
const container = document.querySelector('.search-result-container');
|
||||||
|
let resultItems = [];
|
||||||
|
if (searchText.length > 0) {
|
||||||
|
// Perform local searching
|
||||||
|
resultItems = localSearch.getResultItems(keywords);
|
||||||
|
}
|
||||||
|
if (keywords.length === 1 && keywords[0] === '') {
|
||||||
|
container.classList.add('no-result');
|
||||||
|
container.innerHTML = '<div class="search-result-icon"><i class="fa fa-search fa-5x"></i></div>';
|
||||||
|
} else if (resultItems.length === 0) {
|
||||||
|
container.classList.add('no-result');
|
||||||
|
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||||
|
} else {
|
||||||
|
resultItems.sort((left, right) => {
|
||||||
|
if (left.includedCount !== right.includedCount) {
|
||||||
|
return right.includedCount - left.includedCount;
|
||||||
|
} else if (left.hitCount !== right.hitCount) {
|
||||||
|
return right.hitCount - left.hitCount;
|
||||||
|
}
|
||||||
|
return right.id - left.id;
|
||||||
|
});
|
||||||
|
const stats = NexT.CONFIG.i18n.hits.replace('${hits}', resultItems.length);
|
||||||
|
|
||||||
|
container.classList.remove('no-result');
|
||||||
|
container.innerHTML = `<div class="search-stats">${stats}</div>
|
||||||
|
<hr>
|
||||||
|
<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
||||||
|
if (typeof pjax === 'object') pjax.refresh(container);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
localSearch.highlightSearchWords(document.querySelector('.post-body'));
|
||||||
|
if (NexT.CONFIG.localSearch.preload) {
|
||||||
|
localSearch.fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NexT.CONFIG.localSearch.trigger === 'auto') {
|
||||||
|
input.addEventListener('input', inputEventFunction);
|
||||||
|
} else {
|
||||||
|
document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
|
||||||
|
input.addEventListener('keypress', event => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
inputEventFunction();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener('search:loaded', inputEventFunction);
|
||||||
|
|
||||||
|
// Handle and trigger popup window
|
||||||
|
document.querySelectorAll('.popup-trigger').forEach(element => {
|
||||||
|
element.addEventListener('click', () => {
|
||||||
|
document.body.classList.add('search-active');
|
||||||
|
// Wait for search-popup animation to complete
|
||||||
|
setTimeout(() => input.focus(), 500);
|
||||||
|
if (!localSearch.isfetched) localSearch.fetchData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Monitor main search box
|
||||||
|
const onPopupClose = () => {
|
||||||
|
document.body.classList.remove('search-active');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelector('.search-pop-overlay').addEventListener('click', event => {
|
||||||
|
if (event.target === document.querySelector('.search-pop-overlay')) {
|
||||||
|
onPopupClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
||||||
|
document.addEventListener('pjax:success', () => {
|
||||||
|
localSearch.highlightSearchWords(document.querySelector('.post-body'));
|
||||||
|
onPopupClose();
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', event => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
onPopupClose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
# Hugo NexT theme's custom config
|
# Hugo NexT theme's custom config
|
||||||
#
|
#
|
||||||
|
|
||||||
version: 4.1.0
|
version: 4.2.0
|
@ -39,8 +39,8 @@ analytics:
|
|||||||
addthis:
|
addthis:
|
||||||
js: https://s7.addthis.com/js/300/addthis_widget.js
|
js: https://s7.addthis.com/js/300/addthis_widget.js
|
||||||
|
|
||||||
# 评论组件资源
|
# 评论组件
|
||||||
# Comment component Resources
|
# Comment component
|
||||||
waline:
|
waline:
|
||||||
js:
|
js:
|
||||||
name: '@waline/client'
|
name: '@waline/client'
|
||||||
@ -71,3 +71,14 @@ utterances:
|
|||||||
|
|
||||||
livere:
|
livere:
|
||||||
js: https://cdn-city.livere.com/js/embed.dist.js
|
js: https://cdn-city.livere.com/js/embed.dist.js
|
||||||
|
|
||||||
|
# 全文搜索
|
||||||
|
# Full text search
|
||||||
|
algolia:
|
||||||
|
name: algoliasearch
|
||||||
|
version: 4.13.0
|
||||||
|
file: dist/algoliasearch-lite.umd.js
|
||||||
|
instant:
|
||||||
|
name: instantsearch.js
|
||||||
|
version: 4.40.5
|
||||||
|
file: dist/instantsearch.production.min.js
|
@ -55,6 +55,42 @@ markup:
|
|||||||
endLevel: 3
|
endLevel: 3
|
||||||
ordered: false
|
ordered: false
|
||||||
|
|
||||||
|
# 站点文章导航文件
|
||||||
|
# Site map of all articles
|
||||||
|
sitemap:
|
||||||
|
filename: "sitemap.xml"
|
||||||
|
changefreq: "weekly"
|
||||||
|
priority: 0.5
|
||||||
|
|
||||||
|
outputFormats:
|
||||||
|
RSS:
|
||||||
|
baseName: "rss"
|
||||||
|
# 自定义生成本地搜索文件
|
||||||
|
# Custom file of indexes for local search
|
||||||
|
LocalIndexes:
|
||||||
|
mediaType: application/xml
|
||||||
|
baseName: searchindexes
|
||||||
|
isPlainText: true
|
||||||
|
notAlternative: true
|
||||||
|
# 生成 Algolia 索引文件
|
||||||
|
# Build indexes of Aloglia
|
||||||
|
AlgoliaIndexes:
|
||||||
|
mediaType: application/json
|
||||||
|
baseName: algolia
|
||||||
|
isPlainText: true
|
||||||
|
notAlternative: true
|
||||||
|
|
||||||
|
# 默认使用本地搜索,如切换到 Algolia 搜索引擎,
|
||||||
|
# 请将下面的 LocalIndexes 替换成 AlgoliaIndexes
|
||||||
|
# By default use local search, before switch to Algolia engine
|
||||||
|
# Please replace LocalIndexes with AlgoliaIndexes
|
||||||
|
outputs:
|
||||||
|
home: ["HTML", "RSS", "LocalIndexes"]
|
||||||
|
|
||||||
|
# 输出 robots 文件支持爬虫扫描
|
||||||
|
# Enable robots with support crawler scan
|
||||||
|
enableRobotsTXT: true
|
||||||
|
|
||||||
#--------------------------------------
|
#--------------------------------------
|
||||||
# 菜单配置说明
|
# 菜单配置说明
|
||||||
# identifier : 唯一标识不可重复
|
# identifier : 唯一标识不可重复
|
||||||
@ -84,16 +120,21 @@ menus:
|
|||||||
pageref: /about.html
|
pageref: /about.html
|
||||||
pre: user
|
pre: user
|
||||||
weight: 2
|
weight: 2
|
||||||
|
- identifier: flinks
|
||||||
|
name: 站点示例
|
||||||
|
pageref: /flinks.html
|
||||||
|
pre: thumbs-up
|
||||||
|
weight: 3
|
||||||
- identifier: archives
|
- identifier: archives
|
||||||
name: 归档
|
name: 归档
|
||||||
pageref: /archives
|
pageref: /archives
|
||||||
pre: archive
|
pre: archive
|
||||||
weight: 3
|
weight: 4
|
||||||
- identifier: commonweal
|
- identifier: commonweal
|
||||||
name: 公益 404
|
name: 公益 404
|
||||||
url: /404.html
|
url: /404.html
|
||||||
pre: heartbeat
|
pre: heartbeat
|
||||||
weight: 4
|
weight: 5
|
||||||
|
|
||||||
#-----------------------------------------
|
#-----------------------------------------
|
||||||
# Hugo NexT 主题参数配置
|
# Hugo NexT 主题参数配置
|
||||||
@ -877,8 +918,7 @@ params:
|
|||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# TODO
|
# 内容搜索服务
|
||||||
# 内容搜索服务(暂时未实现)
|
|
||||||
# Search Services
|
# Search Services
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
@ -886,19 +926,32 @@ params:
|
|||||||
# For more information: https://www.algolia.com
|
# For more information: https://www.algolia.com
|
||||||
algoliaSearch:
|
algoliaSearch:
|
||||||
enable: false
|
enable: false
|
||||||
|
appId: #<algolia app id>
|
||||||
|
apiKey: #<algolia api key>
|
||||||
|
indexName: #<algolia index name>
|
||||||
hits:
|
hits:
|
||||||
perPage: 10
|
perPage: 10
|
||||||
|
|
||||||
|
# 本地搜索
|
||||||
# Local Search
|
# Local Search
|
||||||
localSearch:
|
localSearch:
|
||||||
|
# 是否开启搜索功能
|
||||||
|
# Enable search function
|
||||||
enable: true
|
enable: true
|
||||||
|
# 搜索索引文件路径
|
||||||
|
# Indexes file path for search
|
||||||
|
path: searchindexes.xml
|
||||||
|
# 是立即搜索当输入关键字时,可选值: auto | manual
|
||||||
# If auto, trigger search by changing input.
|
# If auto, trigger search by changing input.
|
||||||
# If manual, trigger search by pressing enter key or search button.
|
# If manual, trigger search by pressing enter key or search button.
|
||||||
trigger: auto
|
trigger: auto
|
||||||
|
# 显示头部的搜索记录,-1 表示显示所有搜索结果
|
||||||
# Show top n results per article, show all results by setting to -1
|
# Show top n results per article, show all results by setting to -1
|
||||||
top_n_per_article: 1
|
topNPerArticle: -1
|
||||||
|
# 将 html 字符串转换为可读字符串
|
||||||
# Unescape html strings to the readable one.
|
# Unescape html strings to the readable one.
|
||||||
unescape: false
|
unescape: false
|
||||||
|
# 页面加载时是否要重新载入索引文件
|
||||||
# Preload the search data when the page loads.
|
# Preload the search data when the page loads.
|
||||||
preload: false
|
preload: false
|
||||||
|
|
||||||
|
27
exampleSite/content/about.md
Normal file
27
exampleSite/content/about.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "关于 Hugo NexT 组织"
|
||||||
|
description: "Hugo NexT 组织介绍说明。"
|
||||||
|
|
||||||
|
date: 2022-06-09T20:12:52+08:00
|
||||||
|
lastmod: 2022-06-09T20:12:52+08:00
|
||||||
|
|
||||||
|
share: false
|
||||||
|
followme: false
|
||||||
|
nav: false
|
||||||
|
copyright: false
|
||||||
|
url: about.html
|
||||||
|
---
|
||||||
|
|
||||||
|
`Hugo NexT` 组织是由众多喜爱 `NexT` 主题及风格的世界各地友人共同组建而成,为的就是让这个主题继续在 `Hugo` 引擎中也能得到发扬光大,在此也欢迎你的加入!
|
||||||
|
|
||||||
|
# 我们的愿景
|
||||||
|
|
||||||
|
延续 `NexT` 经典的黑白调搭配,保持简单的易用性及强大的功能。
|
||||||
|
|
||||||
|
# 使用反馈
|
||||||
|
|
||||||
|
- 加入 [GitHub Discussions](https://github.com/hugo-next/hugo-theme-next/discussions) 或 [Gitter](https://gitter.im/hugo-next/community) 在线讨论 :beers:
|
||||||
|
- [GitHub Issues](https://github.com/hugo-next/hugo-theme-next/issues/new?labels=Bug&template=bug-report.md) 提交错误报告 :bug:
|
||||||
|
- [GitHub Feature](https://github.com/hugo-next/hugo-theme-next/issues/new?labels=Feature+Request&template=feature-request.md) 表新功能的想法 :sparkles:
|
||||||
|
|
||||||
|
> 同时国内用户也可加入 QQ 群交流: 604710815
|
16
exampleSite/content/flinks.md
Normal file
16
exampleSite/content/flinks.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "站点示例"
|
||||||
|
type: flinks
|
||||||
|
url: flinks.html
|
||||||
|
---
|
||||||
|
|
||||||
|
如想要交换友情链接,请在评论区留下你的站点信息,格式参考如下:
|
||||||
|
|
||||||
|
{{< note success no-icon >}}
|
||||||
|
|
||||||
|
**名称:** NexT 主题 <br/>
|
||||||
|
**说明:** 保持简单的易用性和强大的功能。 <br/>
|
||||||
|
**站标:** https://hugo-next.eu.org/imgs/hugo_next_avatar.png <br/>
|
||||||
|
**网址:** https://hugo-next.eu.org <br/>
|
||||||
|
|
||||||
|
{{< /note >}}
|
86
exampleSite/content/post/shortcodes.md
Normal file
86
exampleSite/content/post/shortcodes.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
title: "自定义短语示例"
|
||||||
|
description: "将常用的块引用、标签卡、按钮等信息设置成短代码,便于快速引用"
|
||||||
|
keywords: "shortcode,短代码"
|
||||||
|
|
||||||
|
date: 2022-08-06T14:41:50+08:00
|
||||||
|
lastmod: 2022-08-06T14:41:50+08:00
|
||||||
|
|
||||||
|
categories:
|
||||||
|
- 示例
|
||||||
|
tags:
|
||||||
|
- 短代码
|
||||||
|
- 语法
|
||||||
|
url: "post/shortcodes.html"
|
||||||
|
---
|
||||||
|
|
||||||
|
虽然 `Markdown` 语法已经非常丰富能够满足我们写文章的绝大部分需求,但是为更好的对文章内容进行更友好的排版,为引设计一套自定义的短语,便于在使用时能够快速引用。
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
# 块引用
|
||||||
|
|
||||||
|
在引用一些经典名言名句时,可以采用此短语,语法参考如下:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{</* quote */>}}
|
||||||
|
### block quote
|
||||||
|
写下你想表达的话语!
|
||||||
|
{{</* /quote */>}}
|
||||||
|
```
|
||||||
|
|
||||||
|
实际效果:
|
||||||
|
|
||||||
|
{{< quote >}}
|
||||||
|
|
||||||
|
希望是无所谓有,无所谓无的,这正如地上的路。
|
||||||
|
|
||||||
|
|
||||||
|
其实地上本没有路,走的人多了,也便成了路。
|
||||||
|
|
||||||
|
**鲁迅**
|
||||||
|
|
||||||
|
{{< /quote >}}
|
||||||
|
|
||||||
|
# 信息块
|
||||||
|
|
||||||
|
支持 `default`,`info`,`success`,`warning`,`danger` 等五种不同效果的展示,语法参考如下:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
{{</* note [class] [no-icon] */>}}
|
||||||
|
书写表达的信息
|
||||||
|
支持 Markdown 语法
|
||||||
|
{{</* /note */>}}
|
||||||
|
```
|
||||||
|
|
||||||
|
实际效果:
|
||||||
|
|
||||||
|
{{< note default no-icon >}}
|
||||||
|
### Default Header without icon
|
||||||
|
**Welcome** to [Hugo NexT!](https://preview.hugo-next.eu.org)
|
||||||
|
{{< /note >}}
|
||||||
|
|
||||||
|
{{< note default >}}
|
||||||
|
### Default Header
|
||||||
|
**Welcome** to [Hugo NexT!](https://preview.hugo-next.eu.org)
|
||||||
|
{{< /note >}}
|
||||||
|
|
||||||
|
{{< note info >}}
|
||||||
|
### Info Header
|
||||||
|
**Welcome** to [Hugo NexT!](https://preview.hugo-next.eu.org)
|
||||||
|
{{< /note >}}
|
||||||
|
|
||||||
|
{{< note success >}}
|
||||||
|
### Success Header
|
||||||
|
**Welcome** to [Hugo NexT!](https://preview.hugo-next.eu.org)
|
||||||
|
{{< /note >}}
|
||||||
|
|
||||||
|
{{< note warning >}}
|
||||||
|
### Warning Header
|
||||||
|
**Welcome** to [Hugo NexT!](https://preview.hugo-next.eu.org)
|
||||||
|
{{< /note >}}
|
||||||
|
|
||||||
|
{{< note danger >}}
|
||||||
|
### Danger Header
|
||||||
|
**Welcome** to [Hugo NexT!](https://preview.hugo-next.eu.org)
|
||||||
|
{{< /note >}}
|
29
exampleSite/data/flinks.yaml
Normal file
29
exampleSite/data/flinks.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 友情链接
|
||||||
|
# Friend's links
|
||||||
|
|
||||||
|
- FLinksTitle: 官方示例
|
||||||
|
FLinksDesc: 来自主题官方的示例内容。
|
||||||
|
FLinksList:
|
||||||
|
- name: Hugo
|
||||||
|
desc: 世界上最快的网站建设框架!
|
||||||
|
avatar: https://gohugo.io/favicon-32x32.png
|
||||||
|
link: https://gohugo.io
|
||||||
|
|
||||||
|
- name: Hugo-NexT
|
||||||
|
desc: Hugo NexT 官方预览网站。
|
||||||
|
avatar: https://hugo-next.eu.org/imgs/hugo_next_avatar.png
|
||||||
|
link: https://hugo-next.eu.org
|
||||||
|
|
||||||
|
- name: 凡梦星尘空间站
|
||||||
|
desc: 再平凡的人也有属于他的梦想!
|
||||||
|
avatar: https://lisenhui.cn/img/avatar.png
|
||||||
|
link: https://lisenhui.cn
|
||||||
|
|
||||||
|
|
||||||
|
- FLinksTitle: Hugo NexT 粉丝群体
|
||||||
|
FLinksDesc: 来自 Hugo NexT 主题爱好者们的精彩呈现!
|
||||||
|
FLinksList:
|
||||||
|
- name: 阿哈吉
|
||||||
|
desc: 全网首个 Hugo NexT 忠实粉丝用户
|
||||||
|
avatar: https://a.happy2008.top/imgs/stayhome-small.png
|
||||||
|
link: https://a.happy2008.top/
|
@ -20,4 +20,4 @@ EOT
|
|||||||
|
|
||||||
next `cat ../VERSION`
|
next `cat ../VERSION`
|
||||||
|
|
||||||
hugo server -D -t ../.. --port 1414 --panicOnWarning --config config.dev.yaml
|
hugo server -t ../.. --port 1414 --panicOnWarning --config config.yaml
|
||||||
|
@ -141,3 +141,12 @@ PageViewsLabel:
|
|||||||
|
|
||||||
FooterPowerby:
|
FooterPowerby:
|
||||||
other: "Power by %s"
|
other: "Power by %s"
|
||||||
|
|
||||||
|
SearchPh:
|
||||||
|
other: Searching...
|
||||||
|
SearchEmpty:
|
||||||
|
other: "We didn't find any results for the search: ${query}"
|
||||||
|
SearchHits:
|
||||||
|
hits: "${hits} results found"
|
||||||
|
SearchHitsTime:
|
||||||
|
other: "${hits} results found in ${time} ms"
|
@ -142,3 +142,12 @@ PageViewsLabel:
|
|||||||
|
|
||||||
FooterPowerby:
|
FooterPowerby:
|
||||||
other: 由 %s 强力驱动
|
other: 由 %s 强力驱动
|
||||||
|
|
||||||
|
SearchPh:
|
||||||
|
other: 搜索...
|
||||||
|
SearchEmpty:
|
||||||
|
other: "没有找到任何搜索结果:${query}"
|
||||||
|
SearchHits:
|
||||||
|
hits: "找到 ${hits} 个搜索结果"
|
||||||
|
SearchHitsTime:
|
||||||
|
other: "找到 ${hits} 个搜索结果(用时 ${time} 毫秒)"
|
12
layouts/_default/list.algoliaindexes.json
Normal file
12
layouts/_default/list.algoliaindexes.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{{- range $index, $entry := where .Site.RegularPages "Kind" "page" }}
|
||||||
|
{{- if $index }}, {{ end }}
|
||||||
|
{
|
||||||
|
"permalink": "{{ .Permalink | relURL }}",
|
||||||
|
"title": {{ .Title | jsonify }},
|
||||||
|
"content": {{ .Plain | jsonify }},
|
||||||
|
"date": {{ .Date.Format $.Site.Params.timeFormat | jsonify }},
|
||||||
|
"updated": {{ .Lastmod.Format $.Site.Params.timeFormat | jsonify }}
|
||||||
|
}
|
||||||
|
{{- end }}
|
||||||
|
]
|
@ -1,7 +1,3 @@
|
|||||||
{{- if and .Page.IsSection (eq .Section "archives") }}
|
{{ $paginator := .Paginator.Pages.GroupByDate "2006" }}
|
||||||
{{- $paginator := (.Paginate (where .Page.Site.RegularPages "Section" "in" .Site.Params.mainSections)).Pages.GroupByDate "2006" }}
|
{{ partial "list.html" $paginator }}
|
||||||
{{ partial "list.html" $paginator }}
|
|
||||||
{{ else }}
|
|
||||||
{{- $paginator := .Paginator.Pages.GroupByDate "2006" }}
|
|
||||||
{{ partial "list.html" $paginator }}
|
|
||||||
{{- end }}
|
|
||||||
|
18
layouts/_default/list.localindexes.xml
Normal file
18
layouts/_default/list.localindexes.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>" | safeHTML }}
|
||||||
|
<search>
|
||||||
|
{{range where .Site.RegularPages "Kind" "page"}}
|
||||||
|
<entry>
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
<url>{{ .Permalink }}</url>
|
||||||
|
<categories>
|
||||||
|
{{- range .Params.categories }}<category>{{ . }}</category>{{- end }}
|
||||||
|
</categories>
|
||||||
|
<tags>
|
||||||
|
{{- range .Params.tags }}
|
||||||
|
<tag>{{ . }}</tag>
|
||||||
|
{{- end }}
|
||||||
|
</tags>
|
||||||
|
<content type="html"><![CDATA[{{ .Content | plainify }}]]></content>
|
||||||
|
</entry>
|
||||||
|
{{ end }}
|
||||||
|
</search>
|
@ -1,39 +1,15 @@
|
|||||||
{{- define "title" }}
|
{{- define "title" }}
|
||||||
{{- .Params.Title | default (T .Section) | default .Section | dict "Some" | T "AllSome" }} - {{ .Site.Title -}}
|
{{- .Params.Title | default (T .Section) | default .Section }} - {{ .Site.Title -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{ define "main_inner_class" }}archive posts-collapse{{ end }}
|
{{ define "main_inner_class" }}index posts-expand{{ end }}
|
||||||
|
|
||||||
{{- define "main" }}
|
{{- define "main" }}
|
||||||
<div class="post-block">
|
|
||||||
<div class="post-content">
|
|
||||||
<div class="collection-title">
|
|
||||||
{{- $cheers := "Um" }}
|
|
||||||
{{- $posts := .Scratch.Get "postsCount" }}
|
|
||||||
{{- if and .Page.IsSection (ne .Section "archives") }}
|
|
||||||
{{- $posts = .Scratch.Get .Section }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if gt $posts 210 }}
|
|
||||||
{{- $cheers = "Excellent" }}
|
|
||||||
{{- else if gt $posts 130 }}
|
|
||||||
{{- $cheers = "Great" }}
|
|
||||||
{{- else if gt $posts 80 }}
|
|
||||||
{{- $cheers = "Good" }}
|
|
||||||
{{- else if gt $posts 50 }}
|
|
||||||
{{- $cheers = "Nice" }}
|
|
||||||
{{- else if gt $posts 30 }}
|
|
||||||
{{- $cheers = "Ok" }}
|
|
||||||
{{- end }}
|
|
||||||
<span class="collection-header">
|
|
||||||
{{- T (printf "PostArchiveCheers%s" $cheers) }}
|
|
||||||
{{- T "SymbolComma" }}
|
|
||||||
{{- T "ArchiveCounterTitle" $posts | safeHTML }}
|
|
||||||
{{- T "SymbolComma" }}
|
|
||||||
{{- T "PostArchiveKeepOn" }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ .Render "list" }}
|
{{ $paginator := .Paginate (where .Site.RegularPages "Section" "==" .Section) }}
|
||||||
|
{{- range $paginator.Pages }}
|
||||||
</div>
|
{{ partial "post.html" (dict "ctx" . "IsHome" true) }}
|
||||||
</div>
|
{{- end }}
|
||||||
{{- partial "pagination.html" . }}
|
|
||||||
|
{{- partial "partials/pagination.html" . }}
|
||||||
|
|
||||||
{{- end }}
|
{{- end }}
|
2
layouts/archives/list.html
Normal file
2
layouts/archives/list.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
{{ $paginator := (.Paginate (where .Page.Site.RegularPages "Section" "in" .Site.Params.mainSections)).Pages.GroupByDate "2006" }}
|
||||||
|
{{ partial "list.html" $paginator }}
|
36
layouts/archives/section.html
Normal file
36
layouts/archives/section.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{{- define "title" }}
|
||||||
|
{{- .Params.Title | default (T .Section) | default .Section | dict "Some" | T "AllSome" }} - {{ .Site.Title -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{ define "main_inner_class" }}archive posts-collapse{{ end }}
|
||||||
|
{{- define "main" }}
|
||||||
|
<div class="post-block">
|
||||||
|
<div class="post-content">
|
||||||
|
<div class="collection-title">
|
||||||
|
{{- $cheers := "Um" }}
|
||||||
|
{{- $posts := .Scratch.Get "postsCount" }}
|
||||||
|
{{- if gt $posts 210 }}
|
||||||
|
{{- $cheers = "Excellent" }}
|
||||||
|
{{- else if gt $posts 130 }}
|
||||||
|
{{- $cheers = "Great" }}
|
||||||
|
{{- else if gt $posts 80 }}
|
||||||
|
{{- $cheers = "Good" }}
|
||||||
|
{{- else if gt $posts 50 }}
|
||||||
|
{{- $cheers = "Nice" }}
|
||||||
|
{{- else if gt $posts 30 }}
|
||||||
|
{{- $cheers = "Ok" }}
|
||||||
|
{{- end }}
|
||||||
|
<span class="collection-header">
|
||||||
|
{{- T (printf "PostArchiveCheers%s" $cheers) }}
|
||||||
|
{{- T "SymbolComma" }}
|
||||||
|
{{- T "ArchiveCounterTitle" $posts | safeHTML }}
|
||||||
|
{{- T "SymbolComma" }}
|
||||||
|
{{- T "PostArchiveKeepOn" }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ .Render "list" }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- partial "pagination.html" . }}
|
||||||
|
{{- end }}
|
34
layouts/flinks/single.html
Normal file
34
layouts/flinks/single.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{{- define "title" }}{{- .Title }} - {{ .Site.Title -}}{{- end }}
|
||||||
|
|
||||||
|
{{- define "main_inner_class" }}flinks posts-expand{{- end }}
|
||||||
|
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="flinks-block">
|
||||||
|
<h1 class="flinks-block-title">{{ .Title }}</h1>
|
||||||
|
<div class="flinks-lists">
|
||||||
|
{{ range .Site.Data.flinks }}
|
||||||
|
<div class="flinks-list">
|
||||||
|
<div class="flinks-list-title">{{ .FLinksTitle | safeHTML }}</div>
|
||||||
|
<div class="flinks-list-desc">{{ .FLinksDesc | safeHTML }}</div>
|
||||||
|
<div class="flinks-list-items">
|
||||||
|
{{ range .FLinksList }}
|
||||||
|
<div class="flinks-list-item">
|
||||||
|
<a href="{{ .link }}" rel="external nofollow noreferrer" title=" {{ .name }}" target="_blank">
|
||||||
|
<div class="flinks-item-icon">
|
||||||
|
<img class="no-lightbox entered loaded" src="{{ .avatar }}" alt="{{ .name }}"/>
|
||||||
|
</div>
|
||||||
|
<div class="flinks-item-name">{{ .name | safeHTML }}</div>
|
||||||
|
<div class="flinks-item-desc" title="{{ .desc }}">{{ .desc | safeHTML }}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="flinks-list-footer"><hr/></div>
|
||||||
|
<div class="flinks-page-desc">{{ .Content }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{ end }}
|
@ -1,8 +1,11 @@
|
|||||||
{{- define "main_inner_class" }}index posts-expand{{- end }}
|
{{- define "main_inner_class" }}index posts-expand{{- end }}
|
||||||
|
|
||||||
{{- define "main" }}
|
{{- define "main" }}
|
||||||
|
|
||||||
{{ $paginator := .Paginate (where .Site.RegularPages "Section" "in" .Site.Params.mainSections) }}
|
{{ $paginator := .Paginate (where .Site.RegularPages "Section" "in" .Site.Params.mainSections) }}
|
||||||
{{- range $paginator.Pages }}
|
{{- range $paginator.Pages }}
|
||||||
{{ partial "post.html" (dict "ctx" . "IsHome" true) }}
|
{{ partial "post.html" (dict "ctx" . "IsHome" true) }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- partial "partials/pagination.html" . }}
|
{{- partial "partials/pagination.html" . }}
|
||||||
{{- end }}
|
{{- end }}
|
14
layouts/partials/_thirdparty/search/algolia.html
vendored
Normal file
14
layouts/partials/_thirdparty/search/algolia.html
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<div class="search-header">
|
||||||
|
<span class="search-icon">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<div class="search-input-container"></div>
|
||||||
|
<span class="popup-btn-close" role="button">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-result-container">
|
||||||
|
<div class="algolia-stats"><hr></div>
|
||||||
|
<div class="algolia-hits"></div>
|
||||||
|
<div class="algolia-pagination"></div>
|
||||||
|
</div>
|
18
layouts/partials/_thirdparty/search/local.html
vendored
Normal file
18
layouts/partials/_thirdparty/search/local.html
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<div class="search-header">
|
||||||
|
<span class="search-icon">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<div class="search-input-container">
|
||||||
|
<input autocomplete="off" autocapitalize="off" maxlength="80"
|
||||||
|
placeholder="{{ T "SearchPh" }}" spellcheck="false"
|
||||||
|
type="search" class="search-input">
|
||||||
|
</div>
|
||||||
|
<span class="popup-btn-close" role="button">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="search-result-container no-result">
|
||||||
|
<div class="search-result-icon">
|
||||||
|
<i class="fa fa-spinner fa-pulse fa-5x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -27,7 +27,7 @@
|
|||||||
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
|
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
|
||||||
{{- if .IsPage }}
|
{{- if .IsPage }}
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.post-footer hr:after {
|
.post-footer, .flinks-list-footer hr:after {
|
||||||
content: "{{ .Site.Params.postFooter.endLineTip }}";
|
content: "{{ .Site.Params.postFooter.endLineTip }}";
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
{{ partial "header/brand.html" . }}
|
{{ partial "header/brand.html" . }}
|
||||||
{{ partial "header/menus.html" . }}
|
{{ partial "header/menus.html" . }}
|
||||||
|
{{ partial "header/search.html" . }}
|
11
layouts/partials/header/search.html
Normal file
11
layouts/partials/header/search.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{{- if or .Site.Params.algoliaSearch.enable .Site.Params.localSearch.enable }}
|
||||||
|
<div class="search-pop-overlay">
|
||||||
|
<div class="popup search-popup">
|
||||||
|
{{- if .Site.Params.algoliaSearch.enable }}
|
||||||
|
{{ partial "_thirdparty/search/algolia.html" . }}
|
||||||
|
{{- else if .Site.Params.localSearch.enable }}
|
||||||
|
{{ partial "_thirdparty/search/local.html" . }}
|
||||||
|
{{- end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
@ -1,22 +1,20 @@
|
|||||||
{{/* Use to defind global variables */}}
|
{{/* Use to defind global variables */}}
|
||||||
|
|
||||||
{{ if not hugo.IsExtended }}
|
|
||||||
{{ warnf "Hugo NexT 主题使用了 SCSS 框架,请到官方地址下载 Hugo Extended 版本:https://github.com/gohugoio/hugo/releases" }}
|
|
||||||
{{ errorf "Because that use SCSS framework in Hugo NexT, Please download Hugo extended version on offical site: https://github.com/gohugoio/hugo/releases" }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ $version := int (index (split hugo.Version ".") 1) }}
|
{{ $version := int (index (split hugo.Version ".") 1) }}
|
||||||
{{ if lt $version 89 }}
|
{{ if lt $version 89 }}
|
||||||
{{ warnf "当前 Hugo 版本小于 0.89.0,请到官方地址下载 Hugo 最新版本:https://github.com/gohugoio/hugo/releases" }}
|
{{ warnf "当前 Hugo 版本小于 0.89.0,请到官方地址下载 Hugo 最新版本:https://github.com/gohugoio/hugo/releases" }}
|
||||||
{{ errorf "Current Hugo version is less then 0.89.0, Please download Hugo latest version on offical site: https://github.com/gohugoio/hugo/releases" }}
|
{{ errorf "Current Hugo version is less then 0.89.0, Please download Hugo latest version on offical site: https://github.com/gohugoio/hugo/releases" }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if not hugo.IsExtended }}
|
||||||
|
{{ warnf "Hugo NexT 主题使用了 SCSS 框架,请到官方地址下载 Hugo Extended 版本:https://github.com/gohugoio/hugo/releases" }}
|
||||||
|
{{ errorf "Because that use SCSS framework in Hugo NexT, Please download Hugo extended version on offical site: https://github.com/gohugoio/hugo/releases" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
{{ $globalVars := newScratch }}
|
{{ $globalVars := newScratch }}
|
||||||
|
|
||||||
{{ $globalVars.Set "postsCount" (len (where .Page.Site.RegularPages "Section" "in" .Site.Params.mainSections)) }}
|
{{ $globalVars.Set "postsCount" (len (where .Page.Site.RegularPages "Section" "in" .Site.Params.mainSections)) }}
|
||||||
{{ range .Site.Params.mainSections }}
|
|
||||||
{{ $globalVars.Set . (len (where $.Page.Site.RegularPages "Section" .))}}
|
|
||||||
{{ end }}
|
|
||||||
{{ $globalVars.Set "catsCount" (len .Site.Taxonomies.categories) }}
|
{{ $globalVars.Set "catsCount" (len .Site.Taxonomies.categories) }}
|
||||||
{{ $globalVars.Set "tagsCount" (len .Site.Taxonomies.tags) }}
|
{{ $globalVars.Set "tagsCount" (len .Site.Taxonomies.tags) }}
|
||||||
|
|
||||||
@ -25,20 +23,40 @@
|
|||||||
{{ $globalVars.Set "router" $router }}
|
{{ $globalVars.Set "router" $router }}
|
||||||
|
|
||||||
{{ $config := dict
|
{{ $config := dict
|
||||||
"hostname" .Site.BaseURL
|
"hostname" .Site.BaseURL
|
||||||
"root" "/"
|
"root" "/"
|
||||||
"lang" .Site.LanguageCode
|
"lang" .Site.LanguageCode
|
||||||
"vendor" (dict "plugins" $vendor "router" $router)
|
"vendor" (dict "plugins" $vendor "router" $router)
|
||||||
"darkmode" .Site.Params.darkmode
|
"darkmode" .Site.Params.darkmode
|
||||||
"version" .Site.Data.config.version
|
"version" .Site.Data.config.version
|
||||||
"scheme" .Site.Params.scheme
|
"scheme" .Site.Params.scheme
|
||||||
"sidebar" .Site.Params.sidebar
|
"sidebar" .Site.Params.sidebar
|
||||||
"copybtn" .Site.Params.codeblock.copyBtn
|
"copybtn" .Site.Params.codeblock.copyBtn
|
||||||
"bookmark" .Site.Params.bookmark
|
"bookmark" .Site.Params.bookmark
|
||||||
"lazyload" .Site.Params.lazyload
|
"lazyload" .Site.Params.lazyload
|
||||||
"motion" .Site.Params.motion
|
"motion" .Site.Params.motion
|
||||||
|
"i18n" (dict
|
||||||
|
"placeholder" (T "SearchPh")
|
||||||
|
"empty" (T "SearchEmpty")
|
||||||
|
"hits_time" (T "SearchHitsTime")
|
||||||
|
"hits" (T "SearchHits")
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
{{ if .Site.Params.localSearch.enable }}
|
||||||
|
{{ $localSearch := dict "localSearch" .Site.Params.localSearch }}
|
||||||
|
{{ $config = merge $config $localSearch }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if .Site.Params.algoliaSearch.enable }}
|
||||||
|
{{ $algoliaSearch := dict
|
||||||
|
"js" .Site.Data.resources.algolia
|
||||||
|
"instantjs" .Site.Data.resources.instant
|
||||||
|
"cfg" .Site.Params.algoliaSearch
|
||||||
|
}}
|
||||||
|
{{ $config = merge $config (dict "algolia" $algoliaSearch) }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{ with .Site.Params.waline }}
|
{{ with .Site.Params.waline }}
|
||||||
{{ $waline := dict
|
{{ $waline := dict
|
||||||
"js" $.Site.Data.resources.waline.js
|
"js" $.Site.Data.resources.waline.js
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<article itemscope itemtype="http://schema.org/Article">
|
<article itemscope itemtype="http://schema.org/Article">
|
||||||
<header class="post-header">
|
<header class="post-header">
|
||||||
<div class="post-meta-container">
|
<div class="post-meta-container">
|
||||||
{{ $month := .Date.Format "01-02" }}
|
{{ $month := .Date.Format .Site.Params.monthFormat }}
|
||||||
<time itemprop="dateCreated" datetime="{{ .Date.Format "2006-01-02T15:04:05-07:00" }}" content="{{ $month }}">
|
<time itemprop="dateCreated" datetime="{{ .Date.Format .Site.Params.timeFormat }}" content="{{ $month }}">
|
||||||
{{ $month }}
|
{{ $month }}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<i class="fa fa-external-link-alt"></i>
|
<i class="fa fa-external-link-alt"></i>
|
||||||
</a>
|
</a>
|
||||||
{{- else }}
|
{{- else }}
|
||||||
<a class="post-title-link" href="{{ .Permalink }}" itemprop="url">
|
<a class="post-title-link" href="{{ .RelPermalink | relLangURL }}" itemprop="url">
|
||||||
<span itemprop="name">{{ .Title }}</span>
|
<span itemprop="name">{{ .Title }}</span>
|
||||||
</a>
|
</a>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -25,15 +25,15 @@
|
|||||||
<div class="post-nav">
|
<div class="post-nav">
|
||||||
<div class="post-nav-next post-nav-item">
|
<div class="post-nav-next post-nav-item">
|
||||||
{{- with .NextInSection }}
|
{{- with .NextInSection }}
|
||||||
<a href="{{ .Permalink}}" rel="next" title="{{.Title}}">
|
<a href="{{ .RelPermalink | relLangURL }}" rel="next" title="{{ .Title }}">
|
||||||
<i class="fa fa-chevron-left"></i> {{.Title}}
|
<i class="fa fa-chevron-left"></i> {{ .Title }}
|
||||||
</a>
|
</a>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="post-nav-prev post-nav-item">
|
<div class="post-nav-prev post-nav-item">
|
||||||
{{- with .PrevInSection }}
|
{{- with .PrevInSection }}
|
||||||
<a href="{{ .Permalink}}" rel="prev" title="{{.Title}}">
|
<a href="{{ .RelPermalink | relLangURL }}" rel="prev" title="{{ .Title }}">
|
||||||
{{.Title}}
|
{{ .Title }}
|
||||||
<i class="fa fa-chevron-right"></i>
|
<i class="fa fa-chevron-right"></i>
|
||||||
</a>
|
</a>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -64,8 +64,17 @@
|
|||||||
{{- $utterancesjs := resources.Get "js/third-party/comments/utterances.js" }}
|
{{- $utterancesjs := resources.Get "js/third-party/comments/utterances.js" }}
|
||||||
{{- $nextjs = $nextjs | append $utterancesjs }}
|
{{- $nextjs = $nextjs | append $utterancesjs }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Site.Params.localSearch.enable }}
|
||||||
|
{{- $search := resources.Get "js/third-party/search/local.js" }}
|
||||||
|
{{- $nextjs = $nextjs | append $search }}
|
||||||
|
{{ end }}
|
||||||
|
{{- if .Site.Params.algoliaSearch.enable }}
|
||||||
|
{{- $search := resources.Get "js/third-party/search/algolia.js" }}
|
||||||
|
{{- $nextjs = $nextjs | append $search }}
|
||||||
|
{{ end }}
|
||||||
{{- $nextjs = $nextjs | resources.Concat "js/main.js"}}
|
{{- $nextjs = $nextjs | resources.Concat "js/main.js"}}
|
||||||
{{ if hugo.IsProduction }}
|
{{ if hugo.IsProduction }}
|
||||||
{{- $nextjs = $nextjs | minify | fingerprint }}
|
{{- $nextjs = $nextjs | minify | fingerprint }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<script type="text/javascript" src="{{ $nextjs.RelPermalink }}" defer></script>
|
<script type="text/javascript" src="{{ $nextjs.RelPermalink }}" defer></script>
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<div class="site-state-wrap site-overview-item animated">
|
<div class="site-state-wrap site-overview-item animated">
|
||||||
<nav class="site-state">
|
<nav class="site-state">
|
||||||
<div class="site-state-item site-state-posts">
|
<div class="site-state-item site-state-posts">
|
||||||
<a href="{{ "/posts/" | relLangURL }}">
|
<a href="{{ "/archives/" | relLangURL }}">
|
||||||
<span class="site-state-item-count">{{ .Scratch.Get "postsCount" }}</span>
|
<span class="site-state-item-count">{{ .Scratch.Get "postsCount" }}</span>
|
||||||
<span class="site-state-item-name">{{ T "SbPostsLable" }}</span>
|
<span class="site-state-item-name">{{ T "SbPostsLable" }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
3
layouts/shortcodes/note.html
Normal file
3
layouts/shortcodes/note.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="note {{ .Get 0 }} {{ .Get 1 }}">
|
||||||
|
{{ .Inner | markdownify }}
|
||||||
|
</div>
|
3
layouts/shortcodes/quote.html
Normal file
3
layouts/shortcodes/quote.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<blockquote class="blockquote-center">
|
||||||
|
{{ .Inner | markdownify }}
|
||||||
|
</blockquote>
|
1
static/imgs/algolia-logo.svg
Normal file
1
static/imgs/algolia-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.2 KiB |
130
static/js/third-party/search/algolia-search.js
vendored
130
static/js/third-party/search/algolia-search.js
vendored
@ -1,130 +0,0 @@
|
|||||||
/* global instantsearch, algoliasearch, CONFIG, pjax */
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const { indexName, appID, apiKey, hits } = CONFIG.algolia;
|
|
||||||
|
|
||||||
const search = instantsearch({
|
|
||||||
indexName,
|
|
||||||
searchClient : algoliasearch(appID, apiKey),
|
|
||||||
searchFunction: helper => {
|
|
||||||
if (document.querySelector('.search-input').value) {
|
|
||||||
helper.search();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof pjax === 'object') {
|
|
||||||
search.on('render', () => {
|
|
||||||
pjax.refresh(document.querySelector('.algolia-hits'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registering Widgets
|
|
||||||
search.addWidgets([
|
|
||||||
instantsearch.widgets.configure({
|
|
||||||
hitsPerPage: hits.per_page || 10
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantsearch.widgets.searchBox({
|
|
||||||
container : '.search-input-container',
|
|
||||||
placeholder : CONFIG.i18n.placeholder,
|
|
||||||
// Hide default icons of algolia search
|
|
||||||
showReset : false,
|
|
||||||
showSubmit : false,
|
|
||||||
showLoadingIndicator: false,
|
|
||||||
cssClasses : {
|
|
||||||
input: 'search-input'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantsearch.widgets.stats({
|
|
||||||
container: '.algolia-stats',
|
|
||||||
templates: {
|
|
||||||
text: data => {
|
|
||||||
const stats = CONFIG.i18n.hits_time
|
|
||||||
.replace('${hits}', data.nbHits)
|
|
||||||
.replace('${time}', data.processingTimeMS);
|
|
||||||
return `<span>${stats}</span>
|
|
||||||
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia">`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cssClasses: {
|
|
||||||
text: 'search-stats'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantsearch.widgets.hits({
|
|
||||||
container : '.algolia-hits',
|
|
||||||
escapeHTML: false,
|
|
||||||
templates : {
|
|
||||||
item: data => {
|
|
||||||
const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult;
|
|
||||||
let result = `<a href="${data.permalink}" class="search-result-title">${title.value}</a>`;
|
|
||||||
const content = excerpt || excerptStrip || contentStripTruncate;
|
|
||||||
if (content && content.value) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.innerHTML = content.value;
|
|
||||||
result += `<a href="${data.permalink}"><p class="search-result">${div.textContent.substring(0, 100)}...</p></a>`;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
empty: data => {
|
|
||||||
return `<div class="algolia-hits-empty">
|
|
||||||
${CONFIG.i18n.empty.replace('${query}', data.query)}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cssClasses: {
|
|
||||||
list: 'search-result-list'
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
instantsearch.widgets.pagination({
|
|
||||||
container: '.algolia-pagination',
|
|
||||||
scrollTo : false,
|
|
||||||
showFirst: false,
|
|
||||||
showLast : false,
|
|
||||||
templates: {
|
|
||||||
first : '<i class="fa fa-angle-double-left"></i>',
|
|
||||||
last : '<i class="fa fa-angle-double-right"></i>',
|
|
||||||
previous: '<i class="fa fa-angle-left"></i>',
|
|
||||||
next : '<i class="fa fa-angle-right"></i>'
|
|
||||||
},
|
|
||||||
cssClasses: {
|
|
||||||
list : ['pagination', 'algolia-pagination'],
|
|
||||||
item : 'pagination-item',
|
|
||||||
link : 'page-number',
|
|
||||||
selectedItem: 'current',
|
|
||||||
disabledItem: 'disabled-item'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
search.start();
|
|
||||||
|
|
||||||
// Handle and trigger popup window
|
|
||||||
document.querySelectorAll('.popup-trigger').forEach(element => {
|
|
||||||
element.addEventListener('click', () => {
|
|
||||||
document.body.classList.add('search-active');
|
|
||||||
setTimeout(() => document.querySelector('.search-input').focus(), 500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Monitor main search box
|
|
||||||
const onPopupClose = () => {
|
|
||||||
document.body.classList.remove('search-active');
|
|
||||||
};
|
|
||||||
|
|
||||||
document.querySelector('.search-pop-overlay').addEventListener('click', event => {
|
|
||||||
if (event.target === document.querySelector('.search-pop-overlay')) {
|
|
||||||
onPopupClose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
|
||||||
document.addEventListener('pjax:success', onPopupClose);
|
|
||||||
window.addEventListener('keyup', event => {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
onPopupClose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
99
static/js/third-party/search/local-search.js
vendored
99
static/js/third-party/search/local-search.js
vendored
@ -1,99 +0,0 @@
|
|||||||
/* global CONFIG, pjax, LocalSearch */
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
if (!CONFIG.path) {
|
|
||||||
// Search DB path
|
|
||||||
console.warn('`hexo-generator-searchdb` plugin is not installed!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const localSearch = new LocalSearch({
|
|
||||||
path : CONFIG.path,
|
|
||||||
top_n_per_article: CONFIG.localsearch.top_n_per_article,
|
|
||||||
unescape : CONFIG.localsearch.unescape
|
|
||||||
});
|
|
||||||
|
|
||||||
const input = document.querySelector('.search-input');
|
|
||||||
|
|
||||||
const inputEventFunction = () => {
|
|
||||||
if (!localSearch.isfetched) return;
|
|
||||||
const searchText = input.value.trim().toLowerCase();
|
|
||||||
const keywords = searchText.split(/[-\s]+/);
|
|
||||||
const container = document.querySelector('.search-result-container');
|
|
||||||
let resultItems = [];
|
|
||||||
if (searchText.length > 0) {
|
|
||||||
// Perform local searching
|
|
||||||
resultItems = localSearch.getResultItems(keywords);
|
|
||||||
}
|
|
||||||
if (keywords.length === 1 && keywords[0] === '') {
|
|
||||||
container.classList.add('no-result');
|
|
||||||
container.innerHTML = '<div class="search-result-icon"><i class="fa fa-search fa-5x"></i></div>';
|
|
||||||
} else if (resultItems.length === 0) {
|
|
||||||
container.classList.add('no-result');
|
|
||||||
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
|
||||||
} else {
|
|
||||||
resultItems.sort((left, right) => {
|
|
||||||
if (left.includedCount !== right.includedCount) {
|
|
||||||
return right.includedCount - left.includedCount;
|
|
||||||
} else if (left.hitCount !== right.hitCount) {
|
|
||||||
return right.hitCount - left.hitCount;
|
|
||||||
}
|
|
||||||
return right.id - left.id;
|
|
||||||
});
|
|
||||||
const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length);
|
|
||||||
|
|
||||||
container.classList.remove('no-result');
|
|
||||||
container.innerHTML = `<div class="search-stats">${stats}</div>
|
|
||||||
<hr>
|
|
||||||
<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
|
||||||
if (typeof pjax === 'object') pjax.refresh(container);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
localSearch.highlightSearchWords(document.querySelector('.post-body'));
|
|
||||||
if (CONFIG.localsearch.preload) {
|
|
||||||
localSearch.fetchData();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CONFIG.localsearch.trigger === 'auto') {
|
|
||||||
input.addEventListener('input', inputEventFunction);
|
|
||||||
} else {
|
|
||||||
document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
|
|
||||||
input.addEventListener('keypress', event => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
inputEventFunction();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
window.addEventListener('search:loaded', inputEventFunction);
|
|
||||||
|
|
||||||
// Handle and trigger popup window
|
|
||||||
document.querySelectorAll('.popup-trigger').forEach(element => {
|
|
||||||
element.addEventListener('click', () => {
|
|
||||||
document.body.classList.add('search-active');
|
|
||||||
// Wait for search-popup animation to complete
|
|
||||||
setTimeout(() => input.focus(), 500);
|
|
||||||
if (!localSearch.isfetched) localSearch.fetchData();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Monitor main search box
|
|
||||||
const onPopupClose = () => {
|
|
||||||
document.body.classList.remove('search-active');
|
|
||||||
};
|
|
||||||
|
|
||||||
document.querySelector('.search-pop-overlay').addEventListener('click', event => {
|
|
||||||
if (event.target === document.querySelector('.search-pop-overlay')) {
|
|
||||||
onPopupClose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
|
||||||
document.addEventListener('pjax:success', () => {
|
|
||||||
localSearch.highlightSearchWords(document.querySelector('.post-body'));
|
|
||||||
onPopupClose();
|
|
||||||
});
|
|
||||||
window.addEventListener('keyup', event => {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
onPopupClose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,4 +1,4 @@
|
|||||||
name = "Hugo Next"
|
name = "Hugo NexT"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
licenselink = "https://github.com/hugo-next/hugo-theme-next/blob/main/LICENSE"
|
licenselink = "https://github.com/hugo-next/hugo-theme-next/blob/main/LICENSE"
|
||||||
description = "Easily & powerful theme for Hugo engine."
|
description = "Easily & powerful theme for Hugo engine."
|
||||||
|
Loading…
Reference in New Issue
Block a user