Merge branch 'develop' into spacy.io

This commit is contained in:
Ines Montani 2019-02-25 20:11:30 +01:00
commit 0a7a2c73e2
17 changed files with 603 additions and 93 deletions

View File

@ -9,18 +9,19 @@ from ...compat import unicode_
class RussianLemmatizer(Lemmatizer):
_morph = None
def __init__(self, pymorphy2_lang='ru'):
def __init__(self):
super(RussianLemmatizer, self).__init__()
try:
from pymorphy2 import MorphAnalyzer
except ImportError:
raise ImportError(
"The Russian lemmatizer requires the pymorphy2 library: "
'try to fix it with "pip install pymorphy2==0.8"'
'try to fix it with "pip install pymorphy2==0.8" '
'or "pip install git+https://github.com/kmike/pymorphy2.git pymorphy2-dicts-uk"'
"if you need Ukrainian too"
)
if RussianLemmatizer._morph is None:
RussianLemmatizer._morph = MorphAnalyzer(lang=pymorphy2_lang)
RussianLemmatizer._morph = MorphAnalyzer()
def __call__(self, string, univ_pos, morphology=None):
univ_pos = self.normalize_univ_pos(univ_pos)

View File

@ -1,15 +1,207 @@
# coding: utf8
from __future__ import unicode_literals
from ..ru.lemmatizer import RussianLemmatizer
from ...symbols import ADJ, DET, NOUN, NUM, PRON, PROPN, PUNCT, VERB, POS
from ...lemmatizer import Lemmatizer
class UkrainianLemmatizer(RussianLemmatizer):
def __init__(self, pymorphy2_lang="ru"):
class UkrainianLemmatizer(Lemmatizer):
_morph = None
def __init__(self):
super(UkrainianLemmatizer, self).__init__()
try:
super(UkrainianLemmatizer, self).__init__(pymorphy2_lang="uk")
except ImportError:
from pymorphy2 import MorphAnalyzer
if UkrainianLemmatizer._morph is None:
UkrainianLemmatizer._morph = MorphAnalyzer(lang="uk")
except (ImportError, TypeError):
raise ImportError(
"The Ukrainian lemmatizer requires the pymorphy2 library and dictionaries: "
'try to fix it with "pip install git+https://github.com/kmike/pymorphy2.git pymorphy2-dicts-uk"'
"The Ukrainian lemmatizer requires the pymorphy2 library and
'dictionaries: try to fix it with "pip uninstall pymorphy2" and'
'"pip install git+https://github.com/kmike/pymorphy2.git pymorphy2-dicts-uk"'
)
def __call__(self, string, univ_pos, morphology=None):
univ_pos = self.normalize_univ_pos(univ_pos)
if univ_pos == "PUNCT":
return [PUNCT_RULES.get(string, string)]
if univ_pos not in ("ADJ", "DET", "NOUN", "NUM", "PRON", "PROPN", "VERB"):
# Skip unchangeable pos
return [string.lower()]
analyses = self._morph.parse(string)
filtered_analyses = []
for analysis in analyses:
if not analysis.is_known:
# Skip suggested parse variant for unknown word for pymorphy
continue
analysis_pos, _ = oc2ud(str(analysis.tag))
if analysis_pos == univ_pos or (
analysis_pos in ("NOUN", "PROPN") and univ_pos in ("NOUN", "PROPN")
):
filtered_analyses.append(analysis)
if not len(filtered_analyses):
return [string.lower()]
if morphology is None or (len(morphology) == 1 and POS in morphology):
return list(set([analysis.normal_form for analysis in filtered_analyses]))
if univ_pos in ("ADJ", "DET", "NOUN", "PROPN"):
features_to_compare = ["Case", "Number", "Gender"]
elif univ_pos == "NUM":
features_to_compare = ["Case", "Gender"]
elif univ_pos == "PRON":
features_to_compare = ["Case", "Number", "Gender", "Person"]
else: # VERB
features_to_compare = [
"Aspect",
"Gender",
"Mood",
"Number",
"Tense",
"VerbForm",
"Voice",
]
analyses, filtered_analyses = filtered_analyses, []
for analysis in analyses:
_, analysis_morph = oc2ud(str(analysis.tag))
for feature in features_to_compare:
if (
feature in morphology
and feature in analysis_morph
and morphology[feature] != analysis_morph[feature]
):
break
else:
filtered_analyses.append(analysis)
if not len(filtered_analyses):
return [string.lower()]
return list(set([analysis.normal_form for analysis in filtered_analyses]))
@staticmethod
def normalize_univ_pos(univ_pos):
if isinstance(univ_pos, str):
return univ_pos.upper()
symbols_to_str = {
ADJ: "ADJ",
DET: "DET",
NOUN: "NOUN",
NUM: "NUM",
PRON: "PRON",
PROPN: "PROPN",
PUNCT: "PUNCT",
VERB: "VERB",
}
if univ_pos in symbols_to_str:
return symbols_to_str[univ_pos]
return None
def is_base_form(self, univ_pos, morphology=None):
# TODO
raise NotImplementedError
def det(self, string, morphology=None):
return self(string, "det", morphology)
def num(self, string, morphology=None):
return self(string, "num", morphology)
def pron(self, string, morphology=None):
return self(string, "pron", morphology)
def lookup(self, string):
analyses = self._morph.parse(string)
if len(analyses) == 1:
return analyses[0].normal_form
return string
def oc2ud(oc_tag):
gram_map = {
"_POS": {
"ADJF": "ADJ",
"ADJS": "ADJ",
"ADVB": "ADV",
"Apro": "DET",
"COMP": "ADJ", # Can also be an ADV - unchangeable
"CONJ": "CCONJ", # Can also be a SCONJ - both unchangeable ones
"GRND": "VERB",
"INFN": "VERB",
"INTJ": "INTJ",
"NOUN": "NOUN",
"NPRO": "PRON",
"NUMR": "NUM",
"NUMB": "NUM",
"PNCT": "PUNCT",
"PRCL": "PART",
"PREP": "ADP",
"PRTF": "VERB",
"PRTS": "VERB",
"VERB": "VERB",
},
"Animacy": {"anim": "Anim", "inan": "Inan"},
"Aspect": {"impf": "Imp", "perf": "Perf"},
"Case": {
"ablt": "Ins",
"accs": "Acc",
"datv": "Dat",
"gen1": "Gen",
"gen2": "Gen",
"gent": "Gen",
"loc2": "Loc",
"loct": "Loc",
"nomn": "Nom",
"voct": "Voc",
},
"Degree": {"COMP": "Cmp", "Supr": "Sup"},
"Gender": {"femn": "Fem", "masc": "Masc", "neut": "Neut"},
"Mood": {"impr": "Imp", "indc": "Ind"},
"Number": {"plur": "Plur", "sing": "Sing"},
"NumForm": {"NUMB": "Digit"},
"Person": {"1per": "1", "2per": "2", "3per": "3", "excl": "2", "incl": "1"},
"Tense": {"futr": "Fut", "past": "Past", "pres": "Pres"},
"Variant": {"ADJS": "Brev", "PRTS": "Brev"},
"VerbForm": {
"GRND": "Conv",
"INFN": "Inf",
"PRTF": "Part",
"PRTS": "Part",
"VERB": "Fin",
},
"Voice": {"actv": "Act", "pssv": "Pass"},
"Abbr": {"Abbr": "Yes"},
}
pos = "X"
morphology = dict()
unmatched = set()
grams = oc_tag.replace(" ", ",").split(",")
for gram in grams:
match = False
for categ, gmap in sorted(gram_map.items()):
if gram in gmap:
match = True
if categ == "_POS":
pos = gmap[gram]
else:
morphology[categ] = gmap[gram]
if not match:
unmatched.add(gram)
while len(unmatched) > 0:
gram = unmatched.pop()
if gram in ("Name", "Patr", "Surn", "Geox", "Orgn"):
pos = "PROPN"
elif gram == "Auxt":
pos = "AUX"
elif gram == "Pltm":
morphology["Number"] = "Ptan"
return pos, morphology
PUNCT_RULES = {"«": '"', "»": '"'}

View File

@ -6,8 +6,10 @@ STOP_WORDS = set(
"""а
або
адже
аж
але
алло
б
багато
без
безперервно
@ -16,6 +18,7 @@ STOP_WORDS = set(
більше
біля
близько
бо
був
буває
буде
@ -29,22 +32,27 @@ STOP_WORDS = set(
були
було
бути
бывь
в
важлива
важливе
важливий
важливі
вам
вами
вас
ваш
ваша
ваше
вашим
вашими
ваших
ваші
вашій
вашого
вашої
вашому
вашою
вашу
вгорі
вгору
вдалині
весь
вже
ви
від
@ -59,7 +67,15 @@ STOP_WORDS = set(
вони
воно
восьмий
все
всею
всі
всім
всіх
всього
всьому
всю
вся
втім
г
геть
@ -95,16 +111,15 @@ STOP_WORDS = set(
досить
другий
дуже
дякую
е
є
ж
же
життя
з
за
завжди
зазвичай
зайнята
зайнятий
зайняті
зайнято
занадто
зараз
зате
@ -112,22 +127,28 @@ STOP_WORDS = set(
звідси
звідусіль
здається
зі
значить
знову
зовсім
ім'я
і
із
її
їй
їм
іноді
інша
інше
інший
інших
інші
її
їй
їх
й
його
йому
каже
ким
кілька
кого
кожен
кожна
@ -136,13 +157,13 @@ STOP_WORDS = set(
коли
кому
краще
крейдуючи
кругом
крім
куди
ласка
ледве
лише
люди
людина
м
має
майже
мало
мати
@ -157,20 +178,27 @@ STOP_WORDS = set(
мій
мільйонів
мною
мого
могти
моє
мож
моєї
моєму
моєю
може
можна
можно
можуть
можхо
мої
мор
моїй
моїм
моїми
моїх
мою
моя
на
навіть
навіщо
навколо
навкруги
нагорі
над
@ -183,10 +211,21 @@ STOP_WORDS = set(
наш
наша
наше
нашим
нашими
наших
наші
нашій
нашого
нашої
нашому
нашою
нашу
не
небагато
небудь
недалеко
неї
немає
нерідко
нещодавно
@ -199,17 +238,22 @@ STOP_WORDS = set(
них
ні
ніби
ніж
ній
ніколи
нікуди
нім
нічого
ну
нх
нього
ньому
о
обидва
обоє
один
одинадцятий
одинадцять
однак
однієї
одній
одного
@ -218,11 +262,16 @@ STOP_WORDS = set(
он
особливо
ось
п'ятий
п'ятнадцятий
п'ятнадцять
п'ять
перед
перший
під
пізніше
пір
після
по
повинно
подів
@ -233,18 +282,14 @@ STOP_WORDS = set(
потім
потрібно
почала
прекрасне
прекрасно
початку
при
про
просто
проте
проти
п'ятий
п'ятнадцятий
п'ятнадцять
п'ять
раз
разу
раніше
рано
раптом
@ -252,6 +297,7 @@ STOP_WORDS = set(
роки
років
року
році
сам
сама
саме
@ -264,15 +310,15 @@ STOP_WORDS = set(
самого
самому
саму
світу
свого
своє
своєї
свої
своїй
своїх
свою
сеаой
себе
сих
сім
сімнадцятий
сімнадцять
@ -300,7 +346,17 @@ STOP_WORDS = set(
також
там
твій
твого
твоє
твоєї
твоєму
твоєю
твої
твоїй
твоїм
твоїми
твоїх
твою
твоя
те
тебе
@ -312,15 +368,19 @@ STOP_WORDS = set(
тисяч
тих
ті
тієї
тією
тій
тільки
тім
то
тобі
тобою
того
тоді
той
том
тому
тою
треба
третій
три
@ -338,7 +398,6 @@ STOP_WORDS = set(
усім
усіма
усіх
усію
усього
усьому
усю
@ -356,6 +415,7 @@ STOP_WORDS = set(
цими
цих
ці
цієї
цій
цього
цьому
@ -368,11 +428,24 @@ STOP_WORDS = set(
через
четвертий
чи
чиє
чиєї
чиєму
чиї
чиїй
чиїм
чиїми
чиїх
чий
чийого
чийому
чим
численна
численне
численний
численні
чию
чия
чого
чому
чотири
@ -385,6 +458,8 @@ STOP_WORDS = set(
ще
що
щоб
щодо
щось
я
як
яка
@ -393,6 +468,6 @@ STOP_WORDS = set(
які
якій
якого
якщо
""".split()
якої
якщо""".split()
)

View File

@ -573,7 +573,7 @@ class Language(object):
proc._rehearsal_model = deepcopy(proc.model)
return self._optimizer
def evaluate(self, docs_golds, verbose=False):
def evaluate(self, docs_golds, verbose=False, batch_size=256):
scorer = Scorer()
docs, golds = zip(*docs_golds)
docs = list(docs)
@ -582,7 +582,7 @@ class Language(object):
if not hasattr(pipe, "pipe"):
docs = (pipe(doc) for doc in docs)
else:
docs = pipe.pipe(docs, batch_size=256)
docs = pipe.pipe(docs, batch_size=batch_size)
for doc, gold in zip(docs, golds):
if verbose:
print(doc)

View File

@ -179,6 +179,7 @@ def tt_tokenizer():
@pytest.fixture(scope="session")
def uk_tokenizer():
pytest.importorskip("pymorphy2")
pytest.importorskip("pymorphy2.lang")
return get_lang_class("uk").Defaults.create_tokenizer()

View File

@ -92,6 +92,7 @@ def test_uk_tokenizer_splits_open_appostrophe(uk_tokenizer, text):
assert tokens[0].text == "'"
@pytest.mark.xfail(reason="See #3327")
@pytest.mark.parametrize("text", ["Тест''"])
def test_uk_tokenizer_splits_double_end_quote(uk_tokenizer, text):
tokens = uk_tokenizer(text)

View File

@ -0,0 +1,21 @@
# coding: utf-8
from __future__ import unicode_literals
import pytest
from spacy.matcher import Matcher
from spacy.tokens import Doc
@pytest.mark.xfail
def test_issue3328(en_vocab):
doc = Doc(en_vocab, words=["Hello", ",", "how", "are", "you", "doing", "?"])
matcher = Matcher(en_vocab)
patterns = [
[{"LOWER": {"IN": ["hello", "how"]}}],
[{"LOWER": {"IN": ["you", "doing"]}}],
]
matcher.add("TEST", None, *patterns)
matches = matcher(doc)
assert len(matches) == 4
matched_texts = [doc[start:end].text for _, start, end in matches]
assert matched_texts == ["Hello", "how", "you", "doing"]

View File

@ -22,6 +22,10 @@
"id": "83b0498b1e7fa3c91ce68c3f1",
"list": "89ad33e698"
},
"docSearch": {
"apiKey": "371e26ed49d29a27bd36273dfdaf89af",
"indexName": "spacy"
},
"spacyVersion": "2.1",
"binderUrl": "ines/spacy-io-binder",
"binderBranch": "nightly",

View File

@ -18,6 +18,7 @@ import { ReactComponent as YesIcon } from '../images/icons/yes.svg'
import { ReactComponent as NoIcon } from '../images/icons/no.svg'
import { ReactComponent as NeutralIcon } from '../images/icons/neutral.svg'
import { ReactComponent as OfflineIcon } from '../images/icons/offline.svg'
import { ReactComponent as SearchIcon } from '../images/icons/search.svg'
import classes from '../styles/icon.module.sass'
@ -39,6 +40,7 @@ const icons = {
no: NoIcon,
neutral: NeutralIcon,
offline: OfflineIcon,
search: SearchIcon,
}
const Icon = ({ name, width, height, inline, variant, className }) => {

View File

@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { navigate } from 'gatsby'
import Link from './link'
import Icon from './icon'
@ -8,14 +9,39 @@ import { github } from './util'
import { ReactComponent as Logo } from '../images/logo.svg'
import classes from '../styles/navigation.module.sass'
const Navigation = ({ title, items, section, children }) => (
const Dropdown = ({ items, section }) => {
const active = items.find(({ text }) => text.toLowerCase() === section)
const defaultValue = active ? active.url : 'title'
return (
<select
defaultValue={defaultValue}
className={classes.dropdown}
onChange={({ target }) => navigate(target.value)}
>
<option value="title" disabled>
Menu
</option>
{items.map(({ text, url }, i) => (
<option key={i} value={url}>
{text}
</option>
))}
</select>
)
}
const Navigation = ({ title, items, section, search, children }) => {
return (
<nav className={classes.root}>
<Link to="/" aria-label={title} hidden>
<h1 className={classes.title}>{title}</h1>
<Logo className={classes.logo} width={300} height={96} />
</Link>
<ul className={classes.menu}>
<div className={classes.menu}>
<Dropdown items={items} section={section} />
<ul className={classes.list}>
{items.map(({ text, url }, i) => {
const isActive = section && text.toLowerCase() === section
const itemClassNames = classNames(classes.item, {
@ -35,9 +61,12 @@ const Navigation = ({ title, items, section, children }) => (
</Link>
</li>
</ul>
{search && <div className={classes.search}>{search}</div>}
</div>
{children}
</nav>
)
)
}
Navigation.defaultProps = {
items: [],
@ -52,6 +81,7 @@ Navigation.propTypes = {
})
),
section: PropTypes.string,
search: PropTypes.node,
}
export default Navigation

View File

@ -0,0 +1,52 @@
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { window } from 'browser-monads'
import Icon from './icon'
import classes from '../styles/search.module.sass'
const Search = ({ id, placeholder, settings }) => {
const { apiKey, indexName } = settings
const [isInitialized, setIsInitialized] = useState(false)
useEffect(() => {
if (!isInitialized) {
setIsInitialized(true)
window.docsearch({
apiKey,
indexName,
inputSelector: `#${id}`,
debug: false,
})
}
}, window.docsearch)
return (
<form className={classes.root}>
<label htmlFor={id} className={classes.icon}>
<Icon name="search" width={24} />
</label>
<input
id={id}
className={classes.input}
type="search"
placeholder={placeholder}
aria-label={placeholder}
/>
</form>
)
}
Search.defaultProps = {
id: 'docsearch',
placeholder: 'Search docs',
}
Search.propTypes = {
settings: PropTypes.shape({
apiKey: PropTypes.string.isRequired,
indexName: PropTypes.string.isRequired,
}).isRequired,
id: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
}
export default Search

View File

@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import Button from './button'
import Tag from './tag'
@ -34,7 +35,7 @@ const Title = ({ title, tag, version, teaser, source, image, children, ...props
</Tag>
)}
{teaser && <div className={classes.teaser}>{teaser}</div>}
{teaser && <div className={classNames('heading-teaser', classes.teaser)}>{teaser}</div>}
{children}
</header>

43
website/src/html.js Normal file
View File

@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
export default function HTML(props) {
return (
<html {...props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{props.headComponents}
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
/>
</head>
<body {...props.bodyAttributes}>
{props.preBodyComponents}
<noscript key="noscript" id="gatsby-noscript">
This app works best with JavaScript enabled.
</noscript>
<div key={`body`} id="___gatsby" dangerouslySetInnerHTML={{ __html: props.body }} />
{props.postBodyComponents}
</body>
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"
/>
</html>
)
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M9.516 14.016c2.484 0 4.5-2.016 4.5-4.5s-2.016-4.5-4.5-4.5-4.5 2.016-4.5 4.5 2.016 4.5 4.5 4.5zM15.516 14.016l4.969 4.969-1.5 1.5-4.969-4.969v-0.797l-0.281-0.281c-1.125 0.984-2.625 1.547-4.219 1.547-3.609 0-6.516-2.859-6.516-6.469s2.906-6.516 6.516-6.516 6.469 2.906 6.469 6.516c0 1.594-0.563 3.094-1.547 4.219l0.281 0.281h0.797z"></path>
</svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@ -18,6 +18,7 @@
box-shadow: var(--box-shadow)
.logo
min-width: 95px
width: 95px
height: 30px
color: var(--color-theme) !important
@ -26,23 +27,12 @@
.title
display: none
.menu
.list
display: flex
height: 100%
flex-flow: row nowrap
border-color: inherit
overflow-x: auto
overflow-y: hidden
-webkit-overflow-scrolling: touch
@include breakpoint(max, sm)
.menu
@include scroll-shadow-base(var(--color-shadow))
margin-left: 1rem
@include breakpoint(min, md)
.menu
justify-content: flex-end
margin-bottom: 0.25rem
.item
display: inline-flex
@ -58,13 +48,40 @@
margin-left: 2em
&:last-child
@include scroll-shadow-cover(right, var(--color-back))
padding-right: 2rem
&:first-child
@include scroll-shadow-cover(left, var(--color-back))
padding-left: 2rem
.menu
display: flex
flex-flow: row nowrap
margin-left: 0.5rem
.dropdown
font-family: var(--font-secondary)
font-size: 1.6rem
font-weight: bold
color: var(--color-theme)
text-transform: uppercase
background: transparent
border: 0
margin-right: 0.5rem
.is-active
color: var(--color-dark)
pointer-events: none
.search
display: inline-flex
align-items: center
height: 100%
padding-right: 2rem
@include breakpoint(max, xs)
.list
display: none
@include breakpoint(min, sm)
.dropdown
display: none

View File

@ -0,0 +1,57 @@
@import base
.root
font: var(--font-size-sm)/var(--line-height-md) var(--font-primary)
border: 1px solid var(--color-subtle)
border-radius: 2em
max-width: 100%
display: flex
flex-flow: row nowrap
&:focus-within
border-color: var(--color-theme)
box-shadow: 0 0 0 1px var(--color-theme)
.input
width: 100%
max-width: 100%
font: inherit
padding: 0.5rem 1rem 0.5rem 0.5rem
.icon
display: inline-block
color: var(--color-subtle-dark)
padding-left: 0.5rem
position: relative
top: 0.25rem
cursor: pointer
/* Algolia DocSearch */
@include breakpoint(max, xs)
\:global(.algolia-autocomplete .ds-dropdown-menu)
max-width: 90vw
min-width: 90vw
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--category-header)
display: block
font: bold var(--font-size-lg)/var(--line-height-md) var(--font-secondary)
text-transform: uppercase
color: var(--color-theme)
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column)
color: var(--color-dark)
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--title)
font-size: var(--font-size-sm)
\:global(.algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content)
background: var(--color-subtle-opaque) !important
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--text)
font-size: var(--font-size-sm)
\:global(.algolia-autocomplete .algolia-docsearch-suggestion--highlight)
box-shadow: none !important
background: var(--color-theme-opaque) !important
color: var(--color-theme-dark) !important

View File

@ -30,6 +30,7 @@ import Tag from '../components/tag'
import Grid from '../components/grid'
import { YouTube, SoundCloud, Iframe, Image } from '../components/embed'
import Alert from '../components/alert'
import Search from '../components/search'
const mdxComponents = {
a: Link,
@ -119,7 +120,12 @@ class Layout extends React.Component {
bodyClass={bodyClass}
/>
<AlertSpace />
<Navigation title={meta.title} items={meta.navigation} section={section}>
<Navigation
title={meta.title}
items={meta.navigation}
section={section}
search={<Search settings={meta.docSearch} />}
>
<Progress key={location.href} />
</Navigation>
{isDocs ? (
@ -184,6 +190,10 @@ export const pageQuery = graphql`
text
url
}
docSearch {
apiKey
indexName
}
}
}
file(fields: { slug: { eq: $slug } }) {