Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/build-search.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: build-search-indices

on:
pull_request:
types: [closed]
branches:
- main

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: write
pages: write
pull-requests: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
buildSearch:
runs-on: ubuntu-latest
if: ${{ ! contains(github.event.head_commit.message, 'Automate update') }}
steps:
- uses: actions/checkout@v4
# Uses the private access token from above link
with:
fetch-depth: 0
token: ${{ secrets.GH_SUBMODULE_SECRET }}

- name: Restore timestamps
uses: chetan/git-restore-mtime-action@v1

- name: Pull latest changes
run: git pull origin main -X theirs

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Build search
run: npm run search

- name: Commit & push changes
run: |
git config --global user.email "actions@github.com"
git config --global user.name "GitHub Actions - build search index"
git status
git add .
git commit -m "Automate update site-search-data.json"
git push origin main
7 changes: 7 additions & 0 deletions docs/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
<button class="navbar-toggler" aria-label="Toggle navigation" aria-expanded="false" aria-controls="js-header__menu" data-target="#js-header__menu" data-toggle="collapse" type="button"><span class="navbar-toggler-icon"></span></button>
<div class="c-header__menu collapse navbar-collapse" id="js-header__menu">
<ul class="navbar-nav mr-auto">{% include {{ include.content_to_render }} %}</ul>
<div class="c-header__search js-app--search">
<form>
<input class="is-hidden" id="js-searchInput" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit" aria-label="Search" aria-controls="js-searchInput"> <i class="fa fa-search" aria-hidden="true"></i></button>
</form>
<div class="list-group"></div>
</div>
</div>
</div>
</header>
Expand Down
7 changes: 7 additions & 0 deletions docs/_includes/headers/_layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
<button class="navbar-toggler" aria-label="Toggle navigation" aria-expanded="false" aria-controls="js-header__menu" data-target="#js-header__menu" data-toggle="collapse" type="button"><span class="navbar-toggler-icon"></span></button>
<div class="c-header__menu collapse navbar-collapse" id="js-header__menu">
<ul class="navbar-nav mr-auto">{% include {{ include.content_to_render }} %}</ul>
<div class="c-header__search js-app--search">
<form>
<input class="is-hidden" id="js-searchInput" type="search" placeholder="Search" aria-label="Search"/>
<button class="btn btn-outline-success my-2 my-sm-0" type="submit" aria-label="Search" aria-controls="js-searchInput"> <i class="fa fa-search" aria-hidden="true"></i></button>
</form>
<div class="list-group"></div>
</div>
</div>
</div>
</header>
4 changes: 2 additions & 2 deletions docs/css/main.css

Large diffs are not rendered by default.

322 changes: 221 additions & 101 deletions docs/js/main.js

Large diffs are not rendered by default.

5,119 changes: 5,119 additions & 0 deletions docs/js/site-search-data.json

Large diffs are not rendered by default.

114 changes: 114 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,117 @@ exports.default = function () {
gulp.watch('src/js/*.js', js)
gulp.watch('src/styles/**/*.styl', touch)
}


// Site search
const fs = require('fs');
const path = require('path');
const { DOMParser } = require('xmldom')
let marked = {}
let searchDict = {}

function writeToFile(content, method = 'appendFile') {
fs[method]('./docs/js/site-search-data.json', content, (err) => {
if (err) {
console.error('Error writing file:', err);
return;
}
})
}

function createIndex(path, statsSync) {
const urlPath = path.replace('docs/', '')
console.log(path)
fs.readFile(path, 'utf8', (err, data) => {
try {
let htmlString = data
if (path.indexOf('.md') > -1) {
htmlString = marked.parse(data)
}

const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');

const headerTags = ['h1', 'h2', 'h3', 'h4']
for (let tag of headerTags) {
const headerCollection = doc.getElementsByTagName(tag)
const headers = Array.from(headerCollection);
if (headers.length) {
let content = ''
for (let h of headers) {
let title = h.textContent.replaceAll('"', "'").trim()
if (h.textContent != 'Table of Contents' && !searchDict[title+urlPath]) {
content += `{
"title": "${title}",
"mod": "${statsSync.trim()}",
"tag": "${tag}",
"tagId": "${h.getAttribute('id')}",
"path": "/${urlPath.replace(new RegExp(/(index\.(html|md)|\.(html|md))/), '')}"
},`
searchDict[title+urlPath] = true
}

}
writeToFile(content)
}
}
} catch(e) {
console.error('Index error: ', path, e)
}
});
}

function getAllFilesRecursively(directoryPath) {
let filePaths = [];
const filesAndFolders = fs.readdirSync(directoryPath);
const excludedNames = ['_site', '_layouts', '.sass-cache', 'js', ];
const filteredContents = filesAndFolders.filter(item => !excludedNames.includes(item));

for (const item of filteredContents) {
const fullPath = path.join(directoryPath, item);
const stats = fs.statSync(fullPath);

if (stats.isFile()) {
filePaths.push(fullPath);
} else if (stats.isDirectory()) {
filePaths = filePaths.concat(getAllFilesRecursively(fullPath));
}
}

for (let f of filePaths) {
if ( f.indexOf('.html') > -1 || f.indexOf('.md') > -1 ) {
try {
exec('git log -1 --format="%ad" -- '+f, (error, stdout, stderr) => {
createIndex(f, stdout)
})
}
catch (err) {
console.error('Error getting file stats synchronously:', err);
}
}
}

return filePaths;
}

function buildSearchIndicies(done) {

import('marked')
.then(module => {
marked = module
writeToFile('[', 'writeFile')
getAllFilesRecursively('./docs')
done()
})
.catch(error => {
console.error('Failed to load module:', error);
})
}

function endBuildSearchIndicies(done) {
writeToFile('{}]')
done()
}

gulp.task('search', buildSearchIndicies)
gulp.task('searchEnd', endBuildSearchIndicies)
33 changes: 32 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"html": "concurrently \"gulp html-layouts\" \"gulp html-pages\" ",
"css": "gulp css && cp docs/css/main.css docs/_site/css/main.css",
"start": "concurrently \"serve -l 3005 ./docs\" \"gulp\"",
"build": "concurrently \"npm:js\" \"npm:css\" \"npm:html\""
"search": "gulp search && gulp searchEnd",
"build": "concurrently \"npm:js\" \"npm:css\" \"npm:html\" \"npm:search\""
},
"prettier": {
"singleQuote": true,
Expand All @@ -36,6 +37,8 @@
},
"dependencies": {
"gulp-header": "^2.0.9",
"gulp-stylus": "^3.0.0"
"gulp-stylus": "^3.0.0",
"marked": "^17.0.1",
"xmldom": "^0.6.0"
}
}
30 changes: 10 additions & 20 deletions src/js/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,14 @@ class App {
static async loadLanguageFile() {
try {
if (window.apps.locale) return true
const res = await Rest.get(`/lang/${LocalStore.getLanguage()}.json`)
let res = await Rest.get(`/lang/${LocalStore.getLanguage()}.json`)
window.apps.locale = await res.json()

res = await Rest.get('/js/site-search-data.json')
if (res.ok) {
window.apps.searchData = await res.json()
window.apps.searchData.pop()
}
} catch (e) {
console.error(e)
}
Expand All @@ -112,27 +118,11 @@ class App {
}

static applyStyles(args) {
let css = ''
$('img').each(function( i ) {
let width = $(this).attr('width')
if (width && parseInt(width) > 0 && !$(this).hasClass('w-fixed')) {
let cls = `imgw--${i}`
$(this).addClass(`${cls}`)
width = parseInt(width).toString() === width ? width + 'px' : width
css += `.${cls} {max-width: ${width};}`
$('table').each(function( i ) {
if (!$(this).parent().hasClass('c-table--scrollable')) {
$(this).wrap('<div class="c-table c-table--scrollable"></div>')
}
})
if (css.length) {
$('body').append(`<style>${css}</style>`)
}

new CodeCopy(document, {app: 'codeCopy', ...args})

$('pre').each(function( i ) {
new Pre(this, {app: '<pre>', ...args})
})


}

static applyTheme() {
Expand Down
28 changes: 22 additions & 6 deletions src/js/FileMeta.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ class FileMeta extends App {
this.addDate()
}

toDate(lastMod) {
let date = new Date(lastMod)
let formattedDate = date.toLocaleDateString('en-US')
let formattedTime = date.toLocaleTimeString('en-US')

this.$.date.html(formattedDate + ' @ ' + formattedTime)
}

async addDate() {
let lastMod = null
try {
Expand All @@ -16,21 +24,29 @@ class FileMeta extends App {
path += 'index'
}
if (!path) return

// Find file stat from search indicies
let p = path

for (let d of window.apps.searchData) {
if (p.toLowerCase() == d.path.toLowerCase() && d.mod.length) {
this.toDate(d.mod)
this.$.label.addClass(this.classNames.active)
return
}
}

// If not found, use the meta from the server
let paths = []
if (path.split('.').pop() === path) {
paths = [`${path}.html`, `${path}.md`]
}
for (let p of paths) {
if (this.$.date.html() && this.$.date.html().length) return

let r = await Rest.get(p, 'text/plain')
if (r.ok) {
lastMod = r.headers.get('last-modified')
let date = new Date(lastMod)
let formattedDate = date.toLocaleDateString('en-US')
let formattedTime = date.toLocaleTimeString('en-US')

this.$.date.html(formattedDate + ' @ ' + formattedTime)
this.toDate(lastMod)
this.$.label.addClass(this.classNames.active)
} else {
this.$.label.removeClass(this.classNames.active)
Expand Down
Loading