diff --git a/src/components/v-button/readme.md b/src/components/v-button/readme.md index b1e68df056..c3d4ce50ac 100644 --- a/src/components/v-button/readme.md +++ b/src/components/v-button/readme.md @@ -60,7 +60,8 @@ The loading slot is rendered _on top_ of the content that was there before. Make | `small` | Render small | `false` | | `large` | Render large | `false` | | `x-large` | Render extra large | `false` | -| `to` | Render as vue router-link | `false` | +| `to` | Render as vue router-link | `null` | +| `href` | Render as anchor | `null` | | `align` | Align content in button. One of `left | center | right` | `'center'` | | `dashed` | Render the border dashed. Meant to be used with `outlined`. | `false` | | `tile` | Render without border radius | `false` | diff --git a/src/components/v-button/v-button.vue b/src/components/v-button/v-button.vue index 4a5fe36483..ff1ff11b3e 100644 --- a/src/components/v-button/v-button.vue +++ b/src/components/v-button/v-button.vue @@ -23,6 +23,9 @@ :type="type" :disabled="disabled" :to="to" + :href="href" + :target="component === 'a' ? '_blank' : null" + :ref="component === 'a' ? 'noopener noreferer' : null" @click="onClick" > @@ -43,6 +46,7 @@ import { defineComponent, computed, PropType } from '@vue/composition-api'; import { Location } from 'vue-router'; import useSizeClass, { sizeProps } from '@/compositions/size-class'; import { useGroupable } from '@/compositions/groupable'; +import { notEmpty } from '@/utils/is-empty'; export default defineComponent({ props: { @@ -78,6 +82,10 @@ export default defineComponent({ type: [String, Object] as PropType, default: null, }, + href: { + type: String, + default: null, + }, exact: { type: Boolean, default: false, @@ -108,7 +116,11 @@ export default defineComponent({ setup(props, { emit }) { const sizeClass = useSizeClass(props); - const component = computed(() => (props.to ? 'router-link' : 'button')); + const component = computed<'a' | 'router-link' | 'button'>(() => { + if (notEmpty(props.href)) return 'a'; + if (notEmpty(props.to)) return 'router-link'; + return 'button'; + }); const { active, toggle } = useGroupable(props.value, 'button-group'); return { sizeClass, onClick, component, active, toggle }; diff --git a/src/lang/en-US/index.json b/src/lang/en-US/index.json index ac65ad2705..409a3edbfc 100644 --- a/src/lang/en-US/index.json +++ b/src/lang/en-US/index.json @@ -124,6 +124,7 @@ "webhooks": "Webhooks", "roles": "User Roles", + "help_and_docs": "Help & Docs", "about_directus": "About Directus", @@ -390,7 +391,6 @@ "forgot_password": "Forgot Password", "greater_than": "Greater than", "greater_than_equal": "Greater than or equal to", - "help_and_docs": "Help & Docs", "hidden": "Hidden", "hidden_browse": "Hidden on Browse", "hidden_detail": "Hidden on Detail", diff --git a/src/modules/define.ts b/src/modules/define.ts index 4fd8525e33..4e8ab32028 100644 --- a/src/modules/define.ts +++ b/src/modules/define.ts @@ -13,17 +13,19 @@ export function defineModule( options = config; } - options.routes = options.routes.map((route) => { - if (route.path) { - route.path = `/:project/${options.id}${route.path}`; - } + if (options.routes !== undefined) { + options.routes = options.routes.map((route) => { + if (route.path) { + route.path = `/:project/${options.id}${route.path}`; + } - if (route.redirect) { - route.redirect = `/:project/${options.id}${route.redirect}`; - } + if (route.redirect) { + route.redirect = `/:project/${options.id}${route.redirect}`; + } - return route; - }); + return route; + }); + } return options; } diff --git a/src/modules/docs/index.ts b/src/modules/docs/index.ts new file mode 100644 index 0000000000..d26939d315 --- /dev/null +++ b/src/modules/docs/index.ts @@ -0,0 +1,8 @@ +import { defineModule } from '@/modules/define'; + +export default defineModule(({ i18n }) => ({ + id: 'docs', + name: i18n.t('help_and_docs'), + icon: 'help', + link: 'https://docs.directus.io', +})); diff --git a/src/modules/index.ts b/src/modules/index.ts index 232a8265c2..c27f78da5a 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -2,6 +2,7 @@ import CollectionsModule from './collections/'; import FilesModule from './files/'; import UsersModule from './users/'; import ActivityModule from './activity/'; +import DocsModule from './docs/'; import SettingsModule from './settings/'; export const modules = [ @@ -9,6 +10,8 @@ export const modules = [ CollectionsModule, UsersModule, FilesModule, + DocsModule, SettingsModule, ]; + export default modules; diff --git a/src/modules/register.ts b/src/modules/register.ts index 5ae2c02347..b1184df969 100644 --- a/src/modules/register.ts +++ b/src/modules/register.ts @@ -2,7 +2,10 @@ import { RouteConfig } from 'vue-router'; import { replaceRoutes } from '@/router'; import modules from './index'; -const moduleRoutes: RouteConfig[] = modules.map((module) => module.routes).flat(); +const moduleRoutes: RouteConfig[] = modules + .map((module) => module.routes) + .filter((r) => r) + .flat(); replaceRoutes((routes) => insertBeforeProjectWildcard(routes, moduleRoutes)); diff --git a/src/modules/types.ts b/src/modules/types.ts index ae10086125..d4eda02379 100644 --- a/src/modules/types.ts +++ b/src/modules/types.ts @@ -7,7 +7,8 @@ export type ModuleConfig = { hidden?: boolean | Ref; icon: string; name: string | VueI18n.TranslateResult; - routes: RouteConfig[]; + routes?: RouteConfig[]; + link?: string; }; export type ModuleContext = { i18n: VueI18n }; diff --git a/src/views/private/components/module-bar/module-bar.vue b/src/views/private/components/module-bar/module-bar.vue index a46df32f70..29efe94998 100644 --- a/src/views/private/components/module-bar/module-bar.vue +++ b/src/views/private/components/module-bar/module-bar.vue @@ -2,7 +2,15 @@
- +
@@ -29,7 +37,8 @@ export default defineComponent({ const _modules = modules .map((module) => ({ ...module, - to: `/${currentProjectKey}/${module.id}/`, + href: module.link || null, + to: module.link === undefined ? `/${currentProjectKey}/${module.id}/` : null, })) .filter((module) => { if (module.hidden !== undefined) {