mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-31 15:29:48 +02:00
site: fix site on mobile (#597)
Signed-off-by: Bobby DeSimone <bobbydesimone@gmail.com>
This commit is contained in:
parent
8111a3d1b5
commit
ad56322c7e
31 changed files with 233 additions and 2324 deletions
|
@ -1,213 +0,0 @@
|
|||
<template>
|
||||
<form id="search-form" class="algolia-search-wrapper search-box" role="search">
|
||||
<input id="algolia-search-input" class="search-query" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["options"],
|
||||
|
||||
mounted() {
|
||||
this.initialize(this.options, this.$lang);
|
||||
},
|
||||
|
||||
methods: {
|
||||
initialize(userOptions, lang) {
|
||||
Promise.all([
|
||||
import(
|
||||
/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.js"
|
||||
),
|
||||
import(
|
||||
/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.css"
|
||||
)
|
||||
]).then(([docsearch]) => {
|
||||
docsearch = docsearch.default;
|
||||
const { algoliaOptions = {} } = userOptions;
|
||||
docsearch(
|
||||
Object.assign({}, userOptions, {
|
||||
inputSelector: "#algolia-search-input",
|
||||
// #697 Make docsearch work well at i18n mode.
|
||||
algoliaOptions: Object.assign(
|
||||
{
|
||||
facetFilters: [`lang:${lang}`].concat(
|
||||
algoliaOptions.facetFilters || []
|
||||
)
|
||||
},
|
||||
algoliaOptions
|
||||
),
|
||||
handleSelected: (input, event, suggestion) => {
|
||||
const { pathname, hash } = new URL(suggestion.url);
|
||||
this.$router.push(`${pathname}${hash}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
update(options, lang) {
|
||||
this.$el.innerHTML =
|
||||
'<input id="algolia-search-input" class="search-query">';
|
||||
this.initialize(options, lang);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
$lang(newValue) {
|
||||
this.update(this.options, newValue);
|
||||
},
|
||||
|
||||
options(newValue) {
|
||||
this.update(newValue, this.$lang);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.algolia-search-wrapper {
|
||||
& > span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.algolia-autocomplete {
|
||||
line-height: normal;
|
||||
|
||||
.ds-dropdown-menu {
|
||||
background-color: #fff;
|
||||
border: 1px solid #999;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
margin: 6px 0 0;
|
||||
padding: 4px;
|
||||
text-align: left;
|
||||
|
||||
&:before {
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
[class*=ds-dataset-] {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ds-suggestions {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ds-suggestion {
|
||||
border-bottom: 1px solid $borderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
color: #2c815b;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion {
|
||||
border-color: $borderColor;
|
||||
padding: 0;
|
||||
|
||||
.algolia-docsearch-suggestion--category-header {
|
||||
padding: 5px 10px;
|
||||
margin-top: 0;
|
||||
background: $accentColor;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0;
|
||||
color: $textColor;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--subcategory-column {
|
||||
vertical-align: top;
|
||||
padding: 5px 7px 5px 5px;
|
||||
border-color: $borderColor;
|
||||
background: #f1f3f5;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--subcategory-column-text {
|
||||
color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
.algolia-docsearch-footer {
|
||||
border-color: $borderColor;
|
||||
}
|
||||
|
||||
.ds-cursor .algolia-docsearch-suggestion--content {
|
||||
background-color: #e7edf3 !important;
|
||||
color: $textColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $MQMobile) {
|
||||
.algolia-search-wrapper {
|
||||
.algolia-autocomplete {
|
||||
.algolia-docsearch-suggestion {
|
||||
.algolia-docsearch-suggestion--subcategory-column {
|
||||
float: none;
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--content {
|
||||
float: none;
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.ds-dropdown-menu {
|
||||
min-width: 515px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.algolia-search-wrapper {
|
||||
.ds-dropdown-menu {
|
||||
min-width: calc(100vw - 4rem) !important;
|
||||
max-width: calc(100vw - 4rem) !important;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--wrapper {
|
||||
padding: 5px 7px 5px 5px !important;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--subcategory-column {
|
||||
padding: 0 !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
.algolia-docsearch-suggestion--subcategory-column-text:after {
|
||||
content: ' > ';
|
||||
font-size: 10px;
|
||||
line-height: 14.4px;
|
||||
display: inline-block;
|
||||
width: 5px;
|
||||
margin: -3px 3px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,208 +0,0 @@
|
|||
<template>
|
||||
<div class="dropdown-wrapper" :class="{ open }">
|
||||
<a class="dropdown-title" @click="toggle">
|
||||
<span class="title">{{ item.text }}</span>
|
||||
<span class="arrow" :class="open ? 'down' : 'right'"></span>
|
||||
</a>
|
||||
|
||||
<DropdownTransition>
|
||||
<ul class="nav-dropdown" v-show="open">
|
||||
<li
|
||||
class="dropdown-item"
|
||||
:key="subItem.link || index"
|
||||
v-for="(subItem, index) in item.items"
|
||||
>
|
||||
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
|
||||
|
||||
<ul class="dropdown-subitem-wrapper" v-if="subItem.type === 'links'">
|
||||
<li
|
||||
class="dropdown-subitem"
|
||||
:key="childSubItem.link"
|
||||
v-for="childSubItem in subItem.items"
|
||||
>
|
||||
<NavLink :item="childSubItem" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<NavLink v-else :item="subItem" />
|
||||
</li>
|
||||
</ul>
|
||||
</DropdownTransition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavLink from "@theme/components/NavLink.vue";
|
||||
import DropdownTransition from "@theme/components/DropdownTransition.vue";
|
||||
|
||||
export default {
|
||||
components: { NavLink, DropdownTransition },
|
||||
|
||||
data() {
|
||||
return {
|
||||
open: false
|
||||
};
|
||||
},
|
||||
|
||||
props: {
|
||||
item: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
this.open = !this.open;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.dropdown-wrapper {
|
||||
cursor: pointer;
|
||||
|
||||
.dropdown-title {
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
vertical-align: middle;
|
||||
margin-top: -1px;
|
||||
margin-left: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
.dropdown-item {
|
||||
color: inherit;
|
||||
line-height: 1.7rem;
|
||||
|
||||
h4 {
|
||||
margin: 0.45rem 0 0;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 0.45rem 1.5rem 0 1.25rem;
|
||||
}
|
||||
|
||||
.dropdown-subitem-wrapper {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
.dropdown-subitem {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
line-height: 1.7rem;
|
||||
position: relative;
|
||||
border-bottom: none;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
padding: 0 1.5rem 0 1.25rem;
|
||||
color: $accentColor;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&.router-link-active {
|
||||
color: $accentColor;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid $accentColor;
|
||||
border-top: 3px solid transparent;
|
||||
border-bottom: 3px solid transparent;
|
||||
position: absolute;
|
||||
top: calc(50% - 2px);
|
||||
left: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child h4 {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.dropdown-wrapper {
|
||||
&.open .dropdown-title {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
transition: height 0.1s ease-out;
|
||||
overflow: hidden;
|
||||
|
||||
.dropdown-item {
|
||||
h4 {
|
||||
border-top: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
h4, & > a {
|
||||
font-size: 15px;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.dropdown-subitem {
|
||||
font-size: 14px;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $MQMobile) {
|
||||
.dropdown-wrapper {
|
||||
height: 1.8rem;
|
||||
|
||||
&:hover .nav-dropdown {
|
||||
// override the inline style.
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.dropdown-title .arrow {
|
||||
// make the arrow always down at desktop
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 6px solid $arrowBgColor;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
display: none;
|
||||
// Avoid height shaked by clicking
|
||||
height: auto !important;
|
||||
box-sizing: border-box;
|
||||
max-height: calc(100vh - 2.7rem);
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
padding: 0.6rem 0;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom-color: #ccc;
|
||||
text-align: left;
|
||||
border-radius: 0.25rem;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,33 +0,0 @@
|
|||
<template>
|
||||
<transition
|
||||
name="dropdown"
|
||||
@enter="setHeight"
|
||||
@after-enter="unsetHeight"
|
||||
@before-leave="setHeight"
|
||||
>
|
||||
<slot/>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DropdownTransition',
|
||||
|
||||
methods: {
|
||||
setHeight (items) {
|
||||
// explicitly set height so that it can be transitioned
|
||||
items.style.height = items.scrollHeight + 'px'
|
||||
},
|
||||
|
||||
unsetHeight (items) {
|
||||
items.style.height = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.dropdown-enter, .dropdown-leave-to
|
||||
height 0 !important
|
||||
|
||||
</style>
|
|
@ -109,7 +109,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import NavLink from "@theme/components/NavLink.vue";
|
||||
import NavLink from "@parent-theme/components/NavLink.vue";
|
||||
|
||||
export default {
|
||||
components: { NavLink },
|
||||
|
@ -122,10 +122,10 @@ export default {
|
|||
actionLink() {
|
||||
return {
|
||||
link: this.data.actionLink,
|
||||
text: this.data.actionText
|
||||
text: this.data.actionText,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<template>
|
||||
<router-link class="nav-link" :to="link" v-if="!isExternal(link)" :exact="exact">{{ item.text }}</router-link>
|
||||
<a
|
||||
v-else
|
||||
:href="link"
|
||||
class="nav-link external"
|
||||
:target="isMailto(link) || isTel(link) ? null : '_blank'"
|
||||
:rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
|
||||
>
|
||||
{{ item.text }}
|
||||
<OutboundLink />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isExternal, isMailto, isTel, ensureExt } from "../util";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
link() {
|
||||
return ensureExt(this.item.link);
|
||||
},
|
||||
|
||||
exact() {
|
||||
if (this.$site.locales) {
|
||||
return Object.keys(this.$site.locales).some(
|
||||
rootLink => rootLink === this.link
|
||||
);
|
||||
}
|
||||
return this.link === "/";
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
isExternal,
|
||||
isMailto,
|
||||
isTel
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,119 +0,0 @@
|
|||
<template>
|
||||
<nav class="nav-links" v-if="userLinks.length || repoLink">
|
||||
<!-- user links -->
|
||||
<div class="nav-item" v-for="item in userLinks" :key="item.link">
|
||||
<DropdownLink v-if="item.type === 'links'" :item="item" />
|
||||
<NavLink v-else :item="item" />
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DropdownLink from "@theme/components/DropdownLink.vue";
|
||||
import { resolveNavLinkItem } from "../util";
|
||||
import NavLink from "@theme/components/NavLink.vue";
|
||||
|
||||
export default {
|
||||
components: { NavLink, DropdownLink },
|
||||
|
||||
computed: {
|
||||
userNav() {
|
||||
return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || [];
|
||||
},
|
||||
|
||||
nav() {
|
||||
const { locales } = this.$site;
|
||||
if (locales && Object.keys(locales).length > 1) {
|
||||
const currentLink = this.$page.path;
|
||||
const routes = this.$router.options.routes;
|
||||
const themeLocales = this.$site.themeConfig.locales || {};
|
||||
const languageDropdown = {
|
||||
text: this.$themeLocaleConfig.selectText || "Languages",
|
||||
items: Object.keys(locales).map(path => {
|
||||
const locale = locales[path];
|
||||
const text =
|
||||
(themeLocales[path] && themeLocales[path].label) || locale.lang;
|
||||
let link;
|
||||
// Stay on the current page
|
||||
if (locale.lang === this.$lang) {
|
||||
link = currentLink;
|
||||
} else {
|
||||
// Try to stay on the same page
|
||||
link = currentLink.replace(this.$localeConfig.path, path);
|
||||
// fallback to homepage
|
||||
if (!routes.some(route => route.path === link)) {
|
||||
link = path;
|
||||
}
|
||||
}
|
||||
return { text, link };
|
||||
})
|
||||
};
|
||||
return [...this.userNav, languageDropdown];
|
||||
}
|
||||
return this.userNav;
|
||||
},
|
||||
|
||||
userLinks() {
|
||||
return (this.nav || []).map(link => {
|
||||
return Object.assign(resolveNavLinkItem(link), {
|
||||
items: (link.items || []).map(resolveNavLinkItem)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.nav-links {
|
||||
display: inline-block;
|
||||
color: $navbar-text-color;
|
||||
|
||||
a {
|
||||
line-height: 1.4rem;
|
||||
color: $navbar-text-color;
|
||||
|
||||
&:hover, &.router-link-active {
|
||||
color: $navbar-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 1.5rem;
|
||||
line-height: 2rem;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.repo-link {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.nav-links {
|
||||
.nav-item, .repo-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $MQMobile) {
|
||||
.nav-links a {
|
||||
&:hover, &.router-link-active {
|
||||
color: $navbar-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item > a:not(.external) {
|
||||
&:hover, &.router-link-active {
|
||||
margin-bottom: -2px;
|
||||
border-bottom: 2px solid darken($navbar-text-color, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,142 +0,0 @@
|
|||
<template>
|
||||
<header class="navbar">
|
||||
<SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
|
||||
|
||||
<router-link :to="$localePath" class="home-link">
|
||||
<img
|
||||
class="logo"
|
||||
v-if="$site.themeConfig.logo"
|
||||
:src="$withBase($site.themeConfig.logo)"
|
||||
:alt="$siteTitle"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<div
|
||||
class="links"
|
||||
:style="linksWrapMaxWidth ? {
|
||||
'max-width': linksWrapMaxWidth + 'px'
|
||||
} : {}"
|
||||
>
|
||||
<NavLinks class="can-hide" />
|
||||
<AlgoliaSearchBox v-if="isAlgoliaSearch" :options="algolia" />
|
||||
<SearchBox
|
||||
v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AlgoliaSearchBox from "@AlgoliaSearchBox";
|
||||
import SearchBox from "@SearchBox";
|
||||
import SidebarButton from "@theme/components/SidebarButton.vue";
|
||||
import NavLinks from "@theme/components/NavLinks.vue";
|
||||
|
||||
export default {
|
||||
components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
|
||||
|
||||
data() {
|
||||
return {
|
||||
linksWrapMaxWidth: null
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const MOBILE_DESKTOP_BREAKPOINT = 719; // refer to config.styl
|
||||
const NAVBAR_VERTICAL_PADDING =
|
||||
parseInt(css(this.$el, "paddingLeft")) +
|
||||
parseInt(css(this.$el, "paddingRight"));
|
||||
const handleLinksWrapWidth = () => {
|
||||
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
|
||||
this.linksWrapMaxWidth = null;
|
||||
} else {
|
||||
this.linksWrapMaxWidth =
|
||||
this.$el.offsetWidth -
|
||||
NAVBAR_VERTICAL_PADDING -
|
||||
((this.$refs.siteName && this.$refs.siteName.offsetWidth) || 0);
|
||||
}
|
||||
};
|
||||
handleLinksWrapWidth();
|
||||
window.addEventListener("resize", handleLinksWrapWidth, false);
|
||||
},
|
||||
|
||||
computed: {
|
||||
algolia() {
|
||||
return (
|
||||
this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
|
||||
);
|
||||
},
|
||||
|
||||
isAlgoliaSearch() {
|
||||
return this.algolia && this.algolia.apiKey && this.algolia.indexName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function css(el, property) {
|
||||
// NOTE: Known bug, will return 'auto' if style value is 'auto'
|
||||
const win = el.ownerDocument.defaultView;
|
||||
// null means not to return pseudo styles
|
||||
return win.getComputedStyle(el, null)[property];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
$navbar-vertical-padding = 0.7rem;
|
||||
$navbar-horizontal-padding = 1.5rem;
|
||||
|
||||
.navbar {
|
||||
padding: $navbar-vertical-padding $navbar-horizontal-padding;
|
||||
line-height: $navbarHeight - 1.4rem;
|
||||
background: $navbar-background;
|
||||
|
||||
a, span, img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: $navbarHeight - 3.1rem;
|
||||
margin-right: 0.2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: $textColor;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.links {
|
||||
// padding-left: 1.5rem;
|
||||
box-sizing: border-box;
|
||||
background-color: $navbar-background;
|
||||
white-space: nowrap;
|
||||
font-size: 0.95rem;
|
||||
position: absolute;
|
||||
right: $navbar-horizontal-padding;
|
||||
top: $navbar-vertical-padding;
|
||||
display: flex;
|
||||
|
||||
.search-box {
|
||||
padding-left: 1.5rem;
|
||||
flex: 0 0 auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.navbar {
|
||||
padding-left: 4rem;
|
||||
|
||||
.can-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.links {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,241 +0,0 @@
|
|||
<template>
|
||||
<main class="page">
|
||||
<slot name="top" />
|
||||
|
||||
<Content class="theme-default-content" />
|
||||
|
||||
<footer class="page-edit">
|
||||
<div class="edit-link" v-if="editLink">
|
||||
<a :href="editLink" target="_blank" rel="noopener noreferrer">{{ editLinkText }}</a>
|
||||
<OutboundLink />
|
||||
</div>
|
||||
|
||||
<div class="last-updated" v-if="lastUpdated">
|
||||
<span class="prefix">{{ lastUpdatedText }}:</span>
|
||||
<span class="time">{{ lastUpdated }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="page-nav" v-if="prev || next">
|
||||
<p class="inner">
|
||||
<span v-if="prev" class="prev">
|
||||
←
|
||||
<router-link v-if="prev" class="prev" :to="prev.path">{{ prev.title || prev.path }}</router-link>
|
||||
</span>
|
||||
|
||||
<span v-if="next" class="next">
|
||||
<router-link v-if="next" :to="next.path">{{ next.title || next.path }}</router-link>→
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<slot name="bottom" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { resolvePage, outboundRE, endingSlashRE } from "../util";
|
||||
|
||||
export default {
|
||||
props: ["sidebarItems"],
|
||||
|
||||
computed: {
|
||||
lastUpdated() {
|
||||
return this.$page.lastUpdated;
|
||||
},
|
||||
|
||||
lastUpdatedText() {
|
||||
if (typeof this.$themeLocaleConfig.lastUpdated === "string") {
|
||||
return this.$themeLocaleConfig.lastUpdated;
|
||||
}
|
||||
if (typeof this.$site.themeConfig.lastUpdated === "string") {
|
||||
return this.$site.themeConfig.lastUpdated;
|
||||
}
|
||||
return "Last Updated";
|
||||
},
|
||||
|
||||
prev() {
|
||||
const prev = this.$page.frontmatter.prev;
|
||||
if (prev === false) {
|
||||
return;
|
||||
} else if (prev) {
|
||||
return resolvePage(this.$site.pages, prev, this.$route.path);
|
||||
} else {
|
||||
return resolvePrev(this.$page, this.sidebarItems);
|
||||
}
|
||||
},
|
||||
|
||||
next() {
|
||||
const next = this.$page.frontmatter.next;
|
||||
if (next === false) {
|
||||
return;
|
||||
} else if (next) {
|
||||
return resolvePage(this.$site.pages, next, this.$route.path);
|
||||
} else {
|
||||
return resolveNext(this.$page, this.sidebarItems);
|
||||
}
|
||||
},
|
||||
|
||||
editLink() {
|
||||
if (this.$page.frontmatter.editLink === false) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
repo,
|
||||
editLinks,
|
||||
docsDir = "",
|
||||
docsBranch = "master",
|
||||
docsRepo = repo
|
||||
} = this.$site.themeConfig;
|
||||
|
||||
if (docsRepo && editLinks && this.$page.relativePath) {
|
||||
return this.createEditLink(
|
||||
repo,
|
||||
docsRepo,
|
||||
docsDir,
|
||||
docsBranch,
|
||||
this.$page.relativePath
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
editLinkText() {
|
||||
return (
|
||||
this.$themeLocaleConfig.editLinkText ||
|
||||
this.$site.themeConfig.editLinkText ||
|
||||
`Edit this page`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
createEditLink(repo, docsRepo, docsDir, docsBranch, path) {
|
||||
const bitbucket = /bitbucket.org/;
|
||||
if (bitbucket.test(repo)) {
|
||||
const base = outboundRE.test(docsRepo) ? docsRepo : repo;
|
||||
return (
|
||||
base.replace(endingSlashRE, "") +
|
||||
`/src` +
|
||||
`/${docsBranch}/` +
|
||||
(docsDir ? docsDir.replace(endingSlashRE, "") + "/" : "") +
|
||||
path +
|
||||
`?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
|
||||
);
|
||||
}
|
||||
|
||||
const base = outboundRE.test(docsRepo)
|
||||
? docsRepo
|
||||
: `https://github.com/${docsRepo}`;
|
||||
return (
|
||||
base.replace(endingSlashRE, "") +
|
||||
`/edit` +
|
||||
`/${docsBranch}/` +
|
||||
(docsDir ? docsDir.replace(endingSlashRE, "") + "/" : "") +
|
||||
path
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function resolvePrev(page, items) {
|
||||
return find(page, items, -1);
|
||||
}
|
||||
|
||||
function resolveNext(page, items) {
|
||||
return find(page, items, 1);
|
||||
}
|
||||
|
||||
function find(page, items, offset) {
|
||||
const res = [];
|
||||
flatten(items, res);
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const cur = res[i];
|
||||
if (cur.type === "page" && cur.path === decodeURIComponent(page.path)) {
|
||||
return res[i + offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flatten(items, res) {
|
||||
for (let i = 0, l = items.length; i < l; i++) {
|
||||
if (items[i].type === "group") {
|
||||
flatten(items[i].children || [], res);
|
||||
} else {
|
||||
res.push(items[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@require '../styles/wrapper.styl';
|
||||
|
||||
.page {
|
||||
padding-bottom: 2rem;
|
||||
display: block;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.page-edit {
|
||||
@extend $wrapper;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
overflow: auto;
|
||||
|
||||
.edit-link {
|
||||
display: inline-block;
|
||||
|
||||
a {
|
||||
color: lighten($textColor, 25%);
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
float: right;
|
||||
font-size: 0.9em;
|
||||
|
||||
.prefix {
|
||||
font-weight: 500;
|
||||
color: lighten($textColor, 25%);
|
||||
}
|
||||
|
||||
.time {
|
||||
font-weight: 400;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-nav {
|
||||
@extend $wrapper;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 0;
|
||||
|
||||
.inner {
|
||||
min-height: 2rem;
|
||||
margin-top: 0;
|
||||
border-top: 1px solid $borderColor;
|
||||
padding-top: 1rem;
|
||||
overflow: auto; // clear float
|
||||
}
|
||||
|
||||
.next {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.page-edit {
|
||||
.edit-link {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
font-size: 0.8em;
|
||||
float: none;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<aside class="sidebar">
|
||||
<NavLinks />
|
||||
<slot name="top" />
|
||||
<SidebarLinks :depth="0" :items="items" />
|
||||
<slot name="bottom" />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarLinks from "@theme/components/SidebarLinks.vue";
|
||||
import NavLinks from "@theme/components/NavLinks.vue";
|
||||
|
||||
export default {
|
||||
name: "Sidebar",
|
||||
|
||||
components: { SidebarLinks, NavLinks },
|
||||
|
||||
props: ["items"]
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar {
|
||||
background-color: $sidebarColor;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: none;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
padding: 0.5rem 0 0.75rem 0;
|
||||
|
||||
a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-item, .repo-link {
|
||||
display: block;
|
||||
line-height: 1.15rem;
|
||||
font-size: 1.1em;
|
||||
padding: 0.5rem 0 0.5rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
& > .sidebar-links {
|
||||
padding: 1.5rem 0;
|
||||
|
||||
& > li > a.sidebar-link {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
& > li:not(:first-child) {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobileNarrow) {
|
||||
.sidebar {
|
||||
background-color: #6E43E8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.sidebar {
|
||||
background-color: #6E43E8;
|
||||
|
||||
.nav-links {
|
||||
display: block;
|
||||
|
||||
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after {
|
||||
top: calc(1rem - 2px);
|
||||
}
|
||||
}
|
||||
|
||||
& > .sidebar-links {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,42 +0,0 @@
|
|||
<template>
|
||||
<div class="sidebar-button" @click="$emit('toggle-sidebar')">
|
||||
<svg
|
||||
class="icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
viewBox="0 0 448 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
|
||||
class
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar-button {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
position: absolute;
|
||||
padding: 0.6rem;
|
||||
top: 0.6rem;
|
||||
left: 1rem;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.sidebar-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,136 +0,0 @@
|
|||
<template>
|
||||
<section
|
||||
class="sidebar-group"
|
||||
:class="[
|
||||
{
|
||||
collapsable,
|
||||
'is-sub-group': depth !== 0
|
||||
},
|
||||
`depth-${depth}`
|
||||
]"
|
||||
>
|
||||
<router-link
|
||||
v-if="item.path"
|
||||
class="sidebar-heading clickable"
|
||||
:class="{
|
||||
open,
|
||||
'active': isActive($route, item.path)
|
||||
}"
|
||||
:to="item.path"
|
||||
@click.native="$emit('toggle')"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
|
||||
</router-link>
|
||||
|
||||
<p v-else class="sidebar-heading" :class="{ open }" @click="$emit('toggle')">
|
||||
<span>{{ item.title }}</span>
|
||||
<span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
|
||||
</p>
|
||||
|
||||
<DropdownTransition>
|
||||
<SidebarLinks
|
||||
class="sidebar-group-items"
|
||||
:items="item.children"
|
||||
v-if="open || !collapsable"
|
||||
:sidebarDepth="item.sidebarDepth"
|
||||
:depth="depth + 1"
|
||||
/>
|
||||
</DropdownTransition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isActive } from "../util";
|
||||
import DropdownTransition from "@theme/components/DropdownTransition.vue";
|
||||
|
||||
export default {
|
||||
name: "SidebarGroup",
|
||||
props: ["item", "open", "collapsable", "depth"],
|
||||
components: { DropdownTransition },
|
||||
// ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
|
||||
beforeCreate() {
|
||||
this.$options.components.SidebarLinks = require("./SidebarLinks.vue").default;
|
||||
},
|
||||
methods: { isActive }
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar-group {
|
||||
.sidebar-group {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
&:not(.collapsable) {
|
||||
.sidebar-heading:not(.clickable) {
|
||||
cursor: auto;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// refine styles of nested sidebar groups
|
||||
&.is-sub-group {
|
||||
padding-left: 0;
|
||||
|
||||
& > .sidebar-heading {
|
||||
font-size: 0.95em;
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
padding-left: 2rem;
|
||||
|
||||
&:not(.clickable) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
& > .sidebar-group-items {
|
||||
padding-left: 1rem;
|
||||
|
||||
& > li > .sidebar-link {
|
||||
font-size: 0.95em;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.depth-2 {
|
||||
& > .sidebar-heading {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
color: lighten($textColor, 50%);
|
||||
transition: color 0.15s ease;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
// text-transform uppercase
|
||||
padding: 0.35rem 1.5rem 0.35rem 1.25rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
border-left: 0.25rem solid transparent;
|
||||
|
||||
.arrow {
|
||||
position: relative;
|
||||
top: -0.12em;
|
||||
left: 0.5em;
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-group-items {
|
||||
transition: height 0.1s ease-out;
|
||||
font-size: 0.95em;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -1,153 +0,0 @@
|
|||
<script>
|
||||
import { isActive, hashRE, groupHeaders } from "../util";
|
||||
|
||||
export default {
|
||||
functional: true,
|
||||
|
||||
props: ["item", "sidebarDepth"],
|
||||
|
||||
render(
|
||||
h,
|
||||
{
|
||||
parent: { $page, $site, $route, $themeConfig, $themeLocaleConfig },
|
||||
props: { item, sidebarDepth }
|
||||
}
|
||||
) {
|
||||
// use custom active class matching logic
|
||||
// due to edge case of paths ending with / + hash
|
||||
const selfActive = isActive($route, item.path);
|
||||
// for sidebar: auto pages, a hash link should be active if one of its child
|
||||
// matches
|
||||
const active =
|
||||
item.type === "auto"
|
||||
? selfActive ||
|
||||
item.children.some(c =>
|
||||
isActive($route, item.basePath + "#" + c.slug)
|
||||
)
|
||||
: selfActive;
|
||||
const link =
|
||||
item.type === "external"
|
||||
? renderExternal(h, item.path, item.title || item.path)
|
||||
: renderLink(h, item.path, item.title || item.path, active);
|
||||
|
||||
const configDepth =
|
||||
$page.frontmatter.sidebarDepth ||
|
||||
sidebarDepth ||
|
||||
$themeLocaleConfig.sidebarDepth ||
|
||||
$themeConfig.sidebarDepth;
|
||||
|
||||
const maxDepth = configDepth == null ? 1 : configDepth;
|
||||
|
||||
const displayAllHeaders =
|
||||
$themeLocaleConfig.displayAllHeaders || $themeConfig.displayAllHeaders;
|
||||
|
||||
if (item.type === "auto") {
|
||||
return [
|
||||
link,
|
||||
renderChildren(h, item.children, item.basePath, $route, maxDepth)
|
||||
];
|
||||
} else if (
|
||||
(active || displayAllHeaders) &&
|
||||
item.headers &&
|
||||
!hashRE.test(item.path)
|
||||
) {
|
||||
const children = groupHeaders(item.headers);
|
||||
return [link, renderChildren(h, children, item.path, $route, maxDepth)];
|
||||
} else {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function renderLink(h, to, text, active) {
|
||||
return h(
|
||||
"router-link",
|
||||
{
|
||||
props: {
|
||||
to,
|
||||
activeClass: "",
|
||||
exactActiveClass: ""
|
||||
},
|
||||
class: {
|
||||
active,
|
||||
"sidebar-link": true
|
||||
}
|
||||
},
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function renderChildren(h, children, path, route, maxDepth, depth = 1) {
|
||||
if (!children || depth > maxDepth) return null;
|
||||
return h(
|
||||
"ul",
|
||||
{ class: "sidebar-sub-headers" },
|
||||
children.map(c => {
|
||||
const active = isActive(route, path + "#" + c.slug);
|
||||
return h("li", { class: "sidebar-sub-header" }, [
|
||||
renderLink(h, path + "#" + c.slug, c.title, active),
|
||||
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function renderExternal(h, to, text) {
|
||||
return h(
|
||||
"a",
|
||||
{
|
||||
attrs: {
|
||||
href: to,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
},
|
||||
class: {
|
||||
"sidebar-link": true
|
||||
}
|
||||
},
|
||||
[text, h("OutboundLink")]
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar .sidebar-sub-headers {
|
||||
padding-left: 1rem;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
a.sidebar-link {
|
||||
// font-size: 0.95em;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
color: $textColor;
|
||||
border-left: 0.25rem solid transparent;
|
||||
padding: 0.35rem 1rem 0.35rem 1.25rem;
|
||||
line-height: 1.3;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
.sidebar-group & {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.sidebar-sub-headers & {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border-left: none;
|
||||
|
||||
&.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,79 +0,0 @@
|
|||
<template>
|
||||
<ul class="sidebar-links" v-if="items.length">
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<SidebarGroup
|
||||
v-if="item.type === 'group'"
|
||||
:item="item"
|
||||
:open="i === openGroupIndex"
|
||||
:collapsable="item.collapsable || item.collapsible"
|
||||
:depth="depth"
|
||||
@toggle="toggleGroup(i)"
|
||||
/>
|
||||
<SidebarLink v-else :sidebarDepth="sidebarDepth" :item="item" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarGroup from "@theme/components/SidebarGroup.vue";
|
||||
import SidebarLink from "@theme/components/SidebarLink.vue";
|
||||
import { isActive } from "../util";
|
||||
|
||||
export default {
|
||||
name: "SidebarLinks",
|
||||
|
||||
components: { SidebarGroup, SidebarLink },
|
||||
|
||||
props: [
|
||||
"items",
|
||||
"depth", // depth of current sidebar links
|
||||
"sidebarDepth" // depth of headers to be extracted
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
openGroupIndex: 0
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.refreshIndex();
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route() {
|
||||
this.refreshIndex();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
refreshIndex() {
|
||||
const index = resolveOpenGroupIndex(this.$route, this.items);
|
||||
if (index > -1) {
|
||||
this.openGroupIndex = index;
|
||||
}
|
||||
},
|
||||
|
||||
toggleGroup(index) {
|
||||
this.openGroupIndex = index === this.openGroupIndex ? -1 : index;
|
||||
},
|
||||
|
||||
isActive(page) {
|
||||
return isActive(this.$route, page.regularPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function resolveOpenGroupIndex(route, items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (
|
||||
item.type === "group" &&
|
||||
item.children.some(c => c.type === "page" && isActive(route, c.path))
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue