Added emoji index to icon search

This commit is contained in:
squidfunk
2021-02-15 15:57:44 +01:00
parent a776d0018f
commit 356cde2ad8
18 changed files with 120 additions and 64 deletions

View File

@@ -14,7 +14,7 @@ and used in `mkdocs.yml`, documents and templates.
<div class="mdx-icon-search" data-mdx-component="icon-search"> <div class="mdx-icon-search" data-mdx-component="icon-search">
<input <input
class="md-input md-input--stretch mdx-icon-search__input" class="md-input md-input--stretch mdx-icon-search__input"
placeholder="Search the icon database" placeholder="Search the icon and emoji database"
data-mdx-component="icon-search-query" data-mdx-component="icon-search-query"
/> />
<div class="mdx-icon-search-result" data-mdx-component="icon-search-result"> <div class="mdx-icon-search-result" data-mdx-component="icon-search-result">
@@ -24,8 +24,8 @@ and used in `mkdocs.yml`, documents and templates.
</div> </div>
<small> <small>
:octicons-light-bulb-16: :octicons-light-bulb-16:
**Tip:** Enter some keywords to find the perfect icon and click on the **Tip:** Enter some keywords to find the perfect icon or emoji and click on
shortcode to copy it to your clipboard. the shortcode to copy it to your clipboard.
</small> </small>
## Configuration ## Configuration

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{ {
"assets/javascripts/bundle.js": "assets/javascripts/bundle.fbcb1fc3.min.js", "assets/javascripts/bundle.js": "assets/javascripts/bundle.21e94a31.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.fbcb1fc3.min.js.map", "assets/javascripts/bundle.js.map": "assets/javascripts/bundle.21e94a31.min.js.map",
"assets/javascripts/vendor.js": "assets/javascripts/vendor.00ecb175.min.js", "assets/javascripts/vendor.js": "assets/javascripts/vendor.00ecb175.min.js",
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.00ecb175.min.js.map", "assets/javascripts/vendor.js.map": "assets/javascripts/vendor.00ecb175.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.3f4c5856.min.js", "assets/javascripts/worker/search.js": "assets/javascripts/worker/search.3f4c5856.min.js",
@@ -9,8 +9,8 @@
"assets/stylesheets/main.css.map": "assets/stylesheets/main.45122f27.min.css.map", "assets/stylesheets/main.css.map": "assets/stylesheets/main.45122f27.min.css.map",
"assets/stylesheets/palette.css": "assets/stylesheets/palette.e03a20ad.min.css", "assets/stylesheets/palette.css": "assets/stylesheets/palette.e03a20ad.min.css",
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.e03a20ad.min.css.map", "assets/stylesheets/palette.css.map": "assets/stylesheets/palette.e03a20ad.min.css.map",
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.759c98a8.min.js", "overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.f4aeaef7.min.js",
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.759c98a8.min.js.map", "overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.f4aeaef7.min.js.map",
"overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.c2cc92d1.min.css", "overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.c2cc92d1.min.css",
"overrides/assets/stylesheets/main.css.map": "overrides/assets/stylesheets/main.c2cc92d1.min.css.map" "overrides/assets/stylesheets/main.css.map": "overrides/assets/stylesheets/main.c2cc92d1.min.css.map"
} }

View File

@@ -217,7 +217,7 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/vendor.00ecb175.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/vendor.00ecb175.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.fbcb1fc3.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.21e94a31.min.js' | url }}"></script>
{% for path in config["extra_javascript"] %} {% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script> <script src="{{ path | url }}"></script>
{% endfor %} {% endfor %}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -53,5 +53,5 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script src="{{ 'overrides/assets/javascripts/bundle.759c98a8.min.js' | url }}"></script> <script src="{{ 'overrides/assets/javascripts/bundle.f4aeaef7.min.js' | url }}"></script>
{% endblock %} {% endblock %}

View File

@@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { Observable, ObservableInput, merge } from "rxjs" import { NEVER, Observable, ObservableInput, merge } from "rxjs"
import { filter, sample, take } from "rxjs/operators" import { filter, sample, take } from "rxjs/operators"
import { configuration } from "~/_" import { configuration } from "~/_"
@@ -99,6 +99,10 @@ function fetchSearchIndex(url: string): ObservableInput<SearchIndex> {
export function mountSearch( export function mountSearch(
el: HTMLElement, { keyboard$ }: MountOptions el: HTMLElement, { keyboard$ }: MountOptions
): Observable<Component<Search>> { ): Observable<Component<Search>> {
if (location.protocol === "file:")
return NEVER
/* Set up search worker */
const config = configuration() const config = configuration()
const worker = setupSearchWorker(config.search, fetchSearchIndex( const worker = setupSearchWorker(config.search, fetchSearchIndex(
`${config.base}/search/search_index.json` `${config.base}/search/search_index.json`

View File

@@ -184,7 +184,7 @@ export function setupInstantLoading(
const el = ev.target.closest("a") const el = ev.target.closest("a")
if (el && !el.target && urls.includes(el.href)) { if (el && !el.target && urls.includes(el.href)) {
ev.preventDefault() ev.preventDefault()
return of<HistoryState>({ return of({
url: new URL(el.href) url: new URL(el.href)
}) })
} }
@@ -193,7 +193,7 @@ export function setupInstantLoading(
}) })
) )
), ),
share() share<HistoryState>()
) )
/* Intercept history back and forward */ /* Intercept history back and forward */
@@ -203,8 +203,8 @@ export function setupInstantLoading(
map(ev => ({ map(ev => ({
url: new URL(location.href), url: new URL(location.href),
offset: ev.state offset: ev.state
} as HistoryState)), })),
share() share<HistoryState>()
) )
/* Emit location change */ /* Emit location change */

View File

@@ -39,10 +39,33 @@ import {
* Types * Types
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/**
* Icon
*/
export interface Icon {
shortcode: string /* Icon shortcode */
url: string /* Icon URL */
}
/* ------------------------------------------------------------------------- */
/**
* Icon search database
*/
export interface IconSearchDatabase {
base: string /* Category base URL */
data: Record<string, string> /* Category data */
}
/** /**
* Icon search index * Icon search index
*/ */
export type IconSearchIndex = string[] export interface IconSearchIndex {
icons: IconSearchDatabase /* Icon database */
emojis: IconSearchDatabase /* Emoji database */
}
/* ------------------------------------------------------------------------- */
/** /**
* Icon search * Icon search
@@ -67,7 +90,7 @@ export function mountIconSearch(
): Observable<Component<IconSearch>> { ): Observable<Component<IconSearch>> {
const config = configuration() const config = configuration()
const index$ = requestJSON<IconSearchIndex>( const index$ = requestJSON<IconSearchIndex>(
`${config.base}/overrides/assets/javascripts/icons.json` `${config.base}/overrides/assets/javascripts/icon_search_index.json`
) )
/* Retrieve nested components */ /* Retrieve nested components */

View File

@@ -55,7 +55,7 @@ import {
import { renderIconSearchResult } from "../../../templates" import { renderIconSearchResult } from "../../../templates"
import { Component } from "../../_" import { Component } from "../../_"
import { IconSearchIndex } from "../_" import { Icon, IconSearchIndex } from "../_"
import { IconSearchQuery } from "../query" import { IconSearchQuery } from "../query"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@@ -66,7 +66,7 @@ import { IconSearchQuery } from "../query"
* Icon search result * Icon search result
*/ */
export interface IconSearchResult { export interface IconSearchResult {
data: string[] /* Search result data */ data: Icon[] /* Search result data */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@@ -139,11 +139,35 @@ export function mountIconSearchResult(
/* Create and return component */ /* Create and return component */
return combineLatest([ return combineLatest([
index$, query$.pipe(distinctUntilKeyChanged("value")),
query$.pipe(distinctUntilKeyChanged("value")) index$
.pipe(
map(({ icons, emojis }) => [
...Object.keys(icons.data),
...Object.keys(emojis.data)
])
)
]) ])
.pipe( .pipe(
map(([index, { value }]) => ({ data: search(index, value) })), withLatestFrom(index$),
map(([[{ value }, data], index]) => {
const results = search(data, value)
return {
data: results.map(name => {
if (name in index.icons.data) {
return {
shortcode: name,
url: `${index.icons.base}${index.icons.data[name]}`
}
} else {
return {
shortcode: name,
url: `${index.emojis.base}${index.emojis.data[name]}`
}
}
})
}
}),
tap(internal$), tap(internal$),
finalize(() => internal$.complete()), finalize(() => internal$.complete()),
map(state => ({ ref: el, ...state })) map(state => ({ ref: el, ...state }))

View File

@@ -25,33 +25,12 @@ import { wrap } from "fuzzaldrin-plus"
import { translation } from "~/_" import { translation } from "~/_"
import { h } from "~/utilities" import { h } from "~/utilities"
/* ---------------------------------------------------------------------------- import { Icon } from "../../components"
* Data
* ------------------------------------------------------------------------- */
/**
* Icon CDN URL
*/
const base =
"https://raw.githubusercontent.com/" +
"squidfunk/mkdocs-material/" +
"master/material/.icons/"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Helper functions * Helper functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/**
* Convert icon search result to shortcode
*
* @param value - Icon search result
*
* @returns Shortcode
*/
function shortcode(value: string): string {
return `:${value.replace(/\.svg$/, "").replace(/\//g, "-")}:`
}
/** /**
* Highlight an icon search result * Highlight an icon search result
* *
@@ -61,7 +40,7 @@ function shortcode(value: string): string {
* @returns Highlighted result * @returns Highlighted result
*/ */
function highlight(value: string, query: string) { function highlight(value: string, query: string) {
return wrap(shortcode(value), query, { return wrap(value, query, {
wrap: { wrap: {
tagOpen: "<b>", tagOpen: "<b>",
tagClose: "</b>" tagClose: "</b>"
@@ -76,25 +55,25 @@ function highlight(value: string, query: string) {
/** /**
* Render an icon search result * Render an icon search result
* *
* @param value - Icon search result * @param icon - Icon search result
* @param query - Icon search query * @param query - Icon search query
* *
* @returns Element * @returns Element
*/ */
export function renderIconSearchResult( export function renderIconSearchResult(
value: string, query: string icon: Icon, query: string
): HTMLElement { ): HTMLElement {
return ( return (
<li class="mdx-icon-search-result__item"> <li class="mdx-icon-search-result__item">
<span class="twemoji"> <span class="twemoji">
<img src={base + value} /> <img src={icon.url} />
</span> </span>
<button <button
class="md-clipboard--inline" class="md-clipboard--inline"
title={translation("clipboard.copy")} title={translation("clipboard.copy")}
data-clipboard-text={shortcode(value)} data-clipboard-text={`:${icon.shortcode}:`}
> >
<code>{highlight(value, query)}</code> <code>{`:${highlight(icon.shortcode, query)}:`}</code>
</button> </button>
</li> </li>
) )

View File

@@ -345,14 +345,40 @@ export default (_env: never, args: Configuration): Configuration[] => {
/* Save template with replaced assets */ /* Save template with replaced assets */
fs.writeFileSync(file, template, "utf8") fs.writeFileSync(file, template, "utf8")
} }
}
/* Build search index for bundled icons */ /* Build search index for bundled icons */
const index = await glob("**/*.svg", { cwd: "material/.icons" }) const icons: Record<string, string> = {}
fs.writeFileSync( for (const file of await glob("**/*.svg", {
"material/overrides/assets/javascripts/icons.json", cwd: "material/.icons"
JSON.stringify(index) })) {
) const name = file.replace(/\.svg$/, "").replace(/\//g, "-")
icons[name] = file
}
/* Build search index for emojis (based on Twemoji) */
const emojis: Record<string, string> = {}
const [database] = await glob("venv/**/twemoji_db.py")
if (typeof database !== "undefined") {
const contents = fs.readFileSync(database, "utf8")
const [, content] = contents.match(/^emoji = ({.*})$.alias/ms)!
for (const [name, data] of toPairs(JSON.parse(content))) {
emojis[name.replace(/(^:|:$)/g, "")] = `${data.unicode}.svg`
}
}
fs.writeFileSync(
"material/overrides/assets/javascripts/icon_search_index.json",
JSON.stringify({
icons: {
base: "https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/material/.icons/",
data: icons
},
emojis: {
base: "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/",
data: emojis
}
})
)
}
} }
}), }),