MkDocs(Material)图标和图标搜索
记录实现搜索mkdocs material
中提供的 icon 和 emoji 的功能,开始自己实现 js,之后发现官方仓库已经有生成的 js 和 css,直接使用,很方便了。
Emoji 和 Icon 搜索实现¶
实现目标:Material for MkDocs 官方文档中 Icons, Emojis 搜索
基本思想:从iconsearch_index.json
读取图标名和对应图片位置信息,通过 js 搜索并展示响应结果。
实现示例: hantang.github.io/search
方式 1:使用官方资源【推荐】¶
更新:新建对应项目,定时更新发布 hantang/search
直接使用官方提供的 js 和 css:
- 下载 assets 目录( material/overrides/assets),放到 overrides 目录。
- 修改配置文件
mkdocs.yml
,添加 javascript 和 css 路径。 - 新建
search.md
,内容截取自官方文档 icons-emojis.md.
# Icons, Emojis
## Search
<div class="mdx-iconsearch" data-mdx-component="iconsearch">
<input
class="md-input md-input--stretch mdx-iconsearch__input"
placeholder="Search the icon and emoji database"
data-mdx-component="iconsearch-query"
/>
<div class="mdx-iconsearch-result" data-mdx-component="iconsearch-result">
<div class="mdx-iconsearch-result__meta"></div>
<ol class="mdx-iconsearch-result__list"></ol>
</div>
</div>
<small>
:octicons-light-bulb-16:
**Tip:** Enter some keywords to find icons and emojis and click on the
shortcode to copy it to your clipboard.
</small>
方式 2:自己实现 JS¶
iconsearch_index.json
文件从官网页面的源代码中抽取下载,search.md
中增加了 3 个 HTML id,方便 js 中元素定位。之后同样在mkdocs.yml
中添加 css 和 javascript(js 也可以在search.md
中通过<script src="iconsearch.js"></script>
的方式引入)。
Example
/* icon emoji search*/
/* from: https://squidfunk.github.io/mkdocs-material/assets/stylesheets/custom.14cc6f30.min.css */
.md-typeset .mdx-iconsearch {
background-color: var(--md-default-bg-color);
border-radius: 0.1rem;
box-shadow: var(--md-shadow-z1);
position: relative;
transition: box-shadow 125ms;
}
.md-typeset .mdx-iconsearch:focus-within,
.md-typeset .mdx-iconsearch:hover {
box-shadow: var(--md-shadow-z2);
}
.md-typeset .mdx-iconsearch .md-input {
background: var(--md-default-bg-color);
box-shadow: none;
}
[data-md-color-scheme="slate"] .md-typeset .mdx-iconsearch .md-input {
background: var(--md-code-bg-color);
}
.md-typeset .mdx-iconsearch-result {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
max-height: 50vh;
overflow-y: auto;
scrollbar-color: var(--md-default-fg-color--lighter) #0000;
scrollbar-width: thin;
touch-action: pan-y;
}
.md-tooltip .md-typeset .mdx-iconsearch-result {
max-height: 10.25rem;
}
.md-typeset .mdx-iconsearch-result::-webkit-scrollbar {
height: 0.2rem;
width: 0.2rem;
}
.md-typeset .mdx-iconsearch-result::-webkit-scrollbar-thumb {
background-color: var(--md-default-fg-color--lighter);
}
.md-typeset .mdx-iconsearch-result::-webkit-scrollbar-thumb:hover {
background-color: var(--md-accent-fg-color);
}
.md-typeset .mdx-iconsearch-result__meta {
color: var(--md-default-fg-color--lighter);
font-size: 0.64rem;
position: absolute;
right: 0.6rem;
top: 0.4rem;
}
[dir="ltr"] .md-typeset .mdx-iconsearch-result__list {
margin-left: 0;
}
[dir="rtl"] .md-typeset .mdx-iconsearch-result__list {
margin-right: 0;
}
.md-typeset .mdx-iconsearch-result__list {
list-style: none;
margin: 0;
padding: 0;
}
[dir="ltr"] .md-typeset .mdx-iconsearch-result__item {
margin-left: 0;
}
[dir="rtl"] .md-typeset .mdx-iconsearch-result__item {
margin-right: 0;
}
.md-typeset .mdx-iconsearch-result__item {
border-bottom: 0.05rem solid var(--md-default-fg-color--lightest);
margin: 0;
padding: 0.2rem 0.6rem;
}
.md-typeset .mdx-iconsearch-result__item:last-child {
border-bottom: none;
}
.md-typeset .mdx-iconsearch-result__item > * {
margin-right: 0.6rem;
}
.md-typeset .mdx-iconsearch-result__item img {
height: 0.9rem;
width: 0.9rem;
}
[data-md-color-scheme="slate"]
.md-typeset
.mdx-iconsearch-result__item
img[src*="squidfunk"] {
filter: invert(1);
}
.md-typeset ol:not([hidden]),
.md-typeset ul:not([hidden]) {
display: flow-root;
}
Example
// const searchInput = document.querySelector('.mdx-iconsearch__input');
// const searchResultList = document.querySelector('.mdx-iconsearch-result__meta');
// const searchResultMeta = document.querySelector('.mdx-iconsearch-result__list');
const searchInput = document.getElementById("searchInput");
const searchResultList = document.getElementById("searchResultList");
const searchResultMeta = document.getElementById("searchResultMeta");
const datafile = "../assets/js/iconsearch_index.json";
const metaWords = "Type to start searching";
const displayStep = 20;
let searchData = null;
let searchResults = [];
let displayedResultCount = 0;
searchResultMeta.textContent = metaWords;
searchResultList.style.overflowY = "scroll";
searchResultList.style.maxHeight = "300px";
fetch(datafile)
.then((response) => response.json())
.then((data) => {
searchData = data;
})
.catch((error) => console.error("Error loading icon data:", error));
searchInput.addEventListener("input", function () {
const query = this.value.trim().toLowerCase().replace(/\s+/g, "");
searchResults = [];
// match results by letters
for (const category in searchData) {
const base = searchData[category].base;
for (const key in searchData[category].data) {
const lowerCaseKey = key;
let matchIndex = 0;
let isMatch = true;
for (const char of query) {
const index = lowerCaseKey.indexOf(char, matchIndex);
if (index === -1) {
isMatch = false;
break;
}
matchIndex = index + 1;
}
if (isMatch) {
const value = searchData[category].data[key];
const matchedText = key.substring(0, matchIndex);
const highlightedKey = key.replace(
new RegExp(matchedText, "i"),
`<b>${matchedText}</b>`
);
const resultText = `
<li class="mdx-iconsearch-result__item">
<span class="twemoji"><img src="${base}/${value}"></span>
<button class="md-clipboard--inline" title="Copy to clipboard" data-clipboard-text=":${key}:">
<code>${highlightedKey}</code>
</button>
</li>
`;
searchResults.push(resultText);
}
}
}
console.log("sort data", searchResults.length);
// show results
searchResults.sort((a, b) => {
const textA = a.replace(/<\/?b>/g, "").toLowerCase();
const textB = b.replace(/<\/?b>/g, "").toLowerCase();
return textA.localeCompare(textB);
});
// clear then display
displayedResultCount = 0;
searchResultList.innerHTML = "";
displaySearchResults();
});
searchInput.addEventListener("keyup", function (event) {
if (event.key === "Backspace" && this.value.trim() === "") {
searchResults = [];
displayedResultCount = 0;
displaySearchResults();
searchResultMeta.textContent = metaWords;
}
});
function displaySearchResults() {
console.log(displayedResultCount);
const totalResults = searchResults.length;
let html = "";
let resultCount = "";
if (totalResults > 0) {
resultCount =
totalResults > 1000
? (totalResults / 1000).toFixed(1) + "k"
: totalResults;
const results = searchResults.slice(
displayedResultCount,
displayedResultCount + displayStep
);
html = searchResultList.innerHTML + `${results.join("")}`;
displayedResultCount += displayStep;
} else {
resultCount = 0;
}
searchResultMeta.textContent = `${resultCount} matches`;
searchResultList.innerHTML = html;
}
searchResultList.addEventListener("scroll", function () {
// const scrollTop = this.scrollTop;
// const scrollHeight = this.scrollHeight;
// const clientHeight = this.clientHeight;
// if (scrollTop + clientHeight >= scrollHeight) {
if (displayedResultCount <= searchResults.length) {
displaySearchResults();
}
});
附录¶
- 相关 issue/discussion: A simple way to include the Icon search? #2822。
- 主题的源代码: iconsearch/query.