Skip to content

Commit

Permalink
Search with indexing nested sidebars (#18755)
Browse files Browse the repository at this point in the history
* Search with indexing nested sidebars

* Refactor of search plugin
  • Loading branch information
pbochynski committed Sep 30, 2024
1 parent 1576ad2 commit 384308e
Show file tree
Hide file tree
Showing 4 changed files with 677 additions and 20 deletions.
57 changes: 37 additions & 20 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,24 @@
style: 'flat'
},
logo: 'assets/logo_icon.svg height="64px"',
search: 'auto',
search: {
maxAge: 3600000, // Expiration time - 1 hour
paths: 'auto',
sidebars: [
'/',
'/istio/user/',
'/btp-manager/user/',
'/application-connector-manager/user/',
'/keda-manager/user/',
'/serverless-manager/user/',
'/telemetry-manager/user/',
'/nats-manager/user/',
'/eventing-manager/user/',
'/api-gateway/user/',
'/cloud-manager/user/',
'/docker-registry/user/'
]
},
name: 'Kyma Project',
repo: '',
loadSidebar: true,
Expand All @@ -50,38 +67,38 @@
'/api-gateway/(.*)': 'https://raw.githubusercontent.com/kyma-project/api-gateway/main/docs/$1',
'/cloud-manager/(.*)': 'https://raw.githubusercontent.com/kyma-project/cloud-manager/main/docs/$1',
'/docker-registry/(.*)': 'https://raw.githubusercontent.com/kyma-project/docker-registry/main/docs/$1',
},
},
plugins: [
function (hook, vm) {
// edit on GitHub link
hook.beforeEach(function (html) {
if (/githubusercontent\.com/.test(vm.route.file)) {
url = vm.route.file
.replace('raw.githubusercontent.com', 'github.com')
.replace(/\/main/, '/blob/main')
.replace(/\/release-\d+\.\d+/, '/blob$&');
} else {
url = 'https://github.com/kyma-project/kyma/blob/main/docs/' + vm.route.file
}
var editHtml = '[:memo: Edit on GitHub](' + url + ')\n'
// edit on GitHub link
hook.beforeEach(function (html) {
if (/githubusercontent\.com/.test(vm.route.file)) {
url = vm.route.file
.replace('raw.githubusercontent.com', 'github.com')
.replace(/\/main/, '/blob/main')
.replace(/\/release-\d+\.\d+/, '/blob$&');
} else {
url = 'https://github.com/kyma-project/kyma/blob/main/docs/' + vm.route.file
}
var editHtml = '[:memo: Edit on GitHub](' + url + ')\n'

return editHtml
+ html
})
return editHtml
+ html
})
},
function noAliasesForImages(hook, vm) {

// remove title metadata section
hook.beforeEach(function (markdown) {
markdown = markdown.replaceAll(/---.*\ntitle: (.*)\n---.*\n/gm, "# $1");
markdown = markdown.replace(/(<div tabs.*?>)((.|\n)*?)(<\/div>)/gm, function(match, g1, g2, g3, g4) {
markdown = markdown.replace(/(<div tabs.*?>)((.|\n)*?)(<\/div>)/gm, function (match, g1, g2, g3, g4) {
let tab = g2.replace(/<details.*?>((.|\n)*?)<summary.*?>(\n|\s)*(.*?)(\n|\s)*<\/summary>((.|\n)*?).*?<\/details>/gm,
"#### **$4**\n$6\n");
return '<!-- tabs:start -->\n' + tab + '<!-- tabs:end -->\n';
});
return markdown
});

// replace aliases with absolute urls for images
hook.afterEach(function (html) {
let path = vm.route.path.split('/')
Expand All @@ -106,7 +123,7 @@
<script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/js/docsify-themeable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-tabs@1"></script>

<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="./search/index.js" type="module"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/zoom-image.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>

Expand Down
250 changes: 250 additions & 0 deletions docs/search/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/* eslint-disable no-unused-vars */
import { search } from './search.js';

let NO_DATA_TEXT = '';
let options;

function style() {
const code = `
.sidebar {
padding-top: 0;
}
.search {
margin-bottom: 20px;
padding: 6px;
border-bottom: 1px solid #eee;
}
.search .input-wrap {
display: flex;
align-items: center;
}
.search .results-panel {
display: none;
}
.search .results-panel.show {
display: block;
}
.search input {
outline: none;
border: none;
width: 100%;
padding: 0.6em 7px;
font-size: inherit;
border: 1px solid transparent;
}
.search input:focus {
box-shadow: 0 0 5px var(--theme-color, #42b983);
border: 1px solid var(--theme-color, #42b983);
}
.search input::-webkit-search-decoration,
.search input::-webkit-search-cancel-button,
.search input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
.search input::-ms-clear {
display: none;
height: 0;
width: 0;
}
.search .clear-button {
cursor: pointer;
width: 36px;
text-align: right;
display: none;
}
.search .clear-button.show {
display: block;
}
.search .clear-button svg {
transform: scale(.5);
}
.search h2 {
font-size: 17px;
margin: 10px 0;
}
.search a {
text-decoration: none;
color: inherit;
}
.search .matching-post {
border-bottom: 1px solid #eee;
}
.search .matching-post:last-child {
border-bottom: 0;
}
.search p {
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.search p.empty {
text-align: center;
}
.app-name.hide, .sidebar-nav.hide {
display: none;
}`;

Docsify.dom.style(code);
}

function tpl(defaultValue = '') {
const html = `<div class="input-wrap">
<input type="search" value="${defaultValue}" aria-label="Search text" />
<div class="clear-button">
<svg width="26" height="24">
<circle cx="12" cy="12" r="11" fill="#ccc" />
<path stroke="white" stroke-width="2" d="M8.25,8.25,15.75,15.75" />
<path stroke="white" stroke-width="2"d="M8.25,15.75,15.75,8.25" />
</svg>
</div>
</div>
<div class="results-panel"></div>
</div>`;
const el = Docsify.dom.create('div', html);
const aside = Docsify.dom.find('aside');

Docsify.dom.toggleClass(el, 'search');
Docsify.dom.before(aside, el);
}

function doSearch(value) {
const $search = Docsify.dom.find('div.search');
const $panel = Docsify.dom.find($search, '.results-panel');
const $clearBtn = Docsify.dom.find($search, '.clear-button');
const $sidebarNav = Docsify.dom.find('.sidebar-nav');
const $appName = Docsify.dom.find('.app-name');

if (!value) {
$panel.classList.remove('show');
$clearBtn.classList.remove('show');
$panel.innerHTML = '';

if (options.hideOtherSidebarContent) {
$sidebarNav && $sidebarNav.classList.remove('hide');
$appName && $appName.classList.remove('hide');
}

return;
}

const matchs = search(value);

let html = '';
matchs.forEach(post => {
html += `<div class="matching-post">
<a href="${post.url}">
<h2>${post.title}</h2>
<p>${post.content}</p>
</a>
</div>`;
});

$panel.classList.add('show');
$clearBtn.classList.add('show');
$panel.innerHTML = html || `<p class="empty">${NO_DATA_TEXT}</p>`;
if (options.hideOtherSidebarContent) {
$sidebarNav && $sidebarNav.classList.add('hide');
$appName && $appName.classList.add('hide');
}
}

function bindEvents() {
const $search = Docsify.dom.find('div.search');
const $input = Docsify.dom.find($search, 'input');
const $inputWrap = Docsify.dom.find($search, '.input-wrap');

let timeId;

/**
Prevent to Fold sidebar.
When searching on the mobile end,
the sidebar is collapsed when you click the INPUT box,
making it impossible to search.
*/
Docsify.dom.on(
$search,
'click',
e =>
['A', 'H2', 'P', 'EM'].indexOf(e.target.tagName) === -1 &&
e.stopPropagation()
);
Docsify.dom.on($input, 'input', e => {
clearTimeout(timeId);
timeId = setTimeout(_ => doSearch(e.target.value.trim()), 100);
});
Docsify.dom.on($inputWrap, 'click', e => {
// Click input outside
if (e.target.tagName !== 'INPUT') {
$input.value = '';
doSearch();
}
});
}

function updatePlaceholder(text, path) {
const $input = Docsify.dom.getNode('.search input[type="search"]');

if (!$input) {
return;
}

if (typeof text === 'string') {
$input.placeholder = text;
} else {
const match = Object.keys(text).filter(key => path.indexOf(key) > -1)[0];
$input.placeholder = text[match];
}
}

function updateNoData(text, path) {
if (typeof text === 'string') {
NO_DATA_TEXT = text;
} else {
const match = Object.keys(text).filter(key => path.indexOf(key) > -1)[0];
NO_DATA_TEXT = text[match];
}
}

function updateOptions(opts) {
options = opts;
}

export function init(opts, vm) {
const keywords = vm.router.parse().query.s;

updateOptions(opts);
style();
tpl(keywords);
bindEvents();
keywords && setTimeout(_ => doSearch(keywords), 500);
}

export function update(opts, vm) {
updateOptions(opts);
updatePlaceholder(opts.placeholder, vm.route.path);
updateNoData(opts.noData, vm.route.path);
}
49 changes: 49 additions & 0 deletions docs/search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable no-unused-vars */
import { init as initComponent, update as updateComponent } from './component.js';
import { init as initSearch } from './search.js';

const CONFIG = {
placeholder: 'Type to search',
noData: 'No Results!',
paths: 'auto',
depth: 2,
maxAge: 86400000, // 1 day
hideOtherSidebarContent: false,
namespace: undefined,
pathNamespaces: undefined,
pathsWithSidebars: undefined
};

const install = function (hook, vm) {
const { util } = Docsify;
const opts = vm.config.search || CONFIG;

if (Array.isArray(opts)) {
CONFIG.paths = opts;
} else if (typeof opts === 'object') {
CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';
CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;
CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;
CONFIG.noData = opts.noData || CONFIG.noData;
CONFIG.depth = opts.depth || CONFIG.depth;
CONFIG.hideOtherSidebarContent =
opts.hideOtherSidebarContent || CONFIG.hideOtherSidebarContent;
CONFIG.namespace = opts.namespace || CONFIG.namespace;
CONFIG.pathNamespaces = opts.pathNamespaces || CONFIG.pathNamespaces;
CONFIG.sidebars = opts.sidebars
}

const isAuto = CONFIG.paths === 'auto';

hook.mounted(_ => {
initComponent(CONFIG, vm);
!isAuto && initSearch(CONFIG, vm);
});
hook.doneEach(_ => {
updateComponent(CONFIG, vm);
isAuto && initSearch(CONFIG, vm);
});
};

window.$docsify = window.$docsify || {};
$docsify.plugins = [].concat(install, $docsify.plugins);
Loading

0 comments on commit 384308e

Please sign in to comment.