mirror of
https://github.com/directus/directus.git
synced 2026-01-30 08:47:57 -05:00
@@ -7,3 +7,7 @@ charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{package.json,*.yml,*.yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
27721
app/package-lock.json
generated
Normal file
27721
app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@directus/docs": "^9.0.0-beta.2",
|
||||
"@directus/format-title": "^3.2.0",
|
||||
"@popperjs/core": "^2.4.3",
|
||||
"@sindresorhus/slugify": "^1.0.0",
|
||||
@@ -53,6 +54,7 @@
|
||||
"csslint": "^1.0.5",
|
||||
"date-fns": "^2.14.0",
|
||||
"diff": "^4.0.2",
|
||||
"highlight.js": "^10.2.0",
|
||||
"htmlhint": "^0.14.1",
|
||||
"joi": "^17.2.1",
|
||||
"js-yaml": "^3.14.0",
|
||||
@@ -94,6 +96,7 @@
|
||||
"@types/base-64": "^0.1.3",
|
||||
"@types/bytes": "^3.1.0",
|
||||
"@types/diff": "^4.0.2",
|
||||
"@types/highlight.js": "^9.12.4",
|
||||
"@types/jest": "^26.0.5",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
@@ -126,6 +129,7 @@
|
||||
"lint-staged": "^10.2.11",
|
||||
"mockdate": "^3.0.2",
|
||||
"prettier": "^2.0.5",
|
||||
"raw-loader": "^4.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-is": "^16.13.1",
|
||||
|
||||
@@ -545,6 +545,8 @@
|
||||
"user_directory": "User Directory",
|
||||
"help_and_docs": "Help & Docs",
|
||||
|
||||
"documentation": "Documentation",
|
||||
|
||||
"card_size": "Card Size",
|
||||
"sort_field": "Sort Field",
|
||||
|
||||
@@ -688,6 +690,8 @@
|
||||
"color": "Color",
|
||||
"circle": "Circle",
|
||||
|
||||
|
||||
|
||||
"empty_item": "Empty Item",
|
||||
|
||||
"log_in_with": "Log In with {provider}",
|
||||
|
||||
71
app/src/modules/docs/components/navigation-item.vue
Normal file
71
app/src/modules/docs/components/navigation-item.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<v-list-item v-if="section.children === undefined" :to="section.to" :dense="dense">
|
||||
<v-list-item-icon v-if="section.icon !== undefined"><v-icon :name="section.icon" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ section.name }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<div v-else-if="section.flat === true">
|
||||
<v-divider></v-divider>
|
||||
<navigation-list-item
|
||||
v-for="(childSection, index) in section.children"
|
||||
:key="index"
|
||||
:section="childSection"
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
<v-list-group v-else>
|
||||
<template #activator>
|
||||
<v-list-item-icon v-if="section.icon !== undefined"><v-icon :name="section.icon" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ section.name }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
<navigation-list-item
|
||||
v-for="(childSection, index) in section.children"
|
||||
:key="index"
|
||||
:section="childSection"
|
||||
dense
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from '@vue/composition-api';
|
||||
import { Section } from './sections';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'navigation-list-item',
|
||||
props: {
|
||||
section: {
|
||||
type: Object as PropType<Section>,
|
||||
default: null,
|
||||
},
|
||||
dense: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.version {
|
||||
.v-icon {
|
||||
color: var(--foreground-subdued);
|
||||
transition: color var(--fast) var(--transition);
|
||||
}
|
||||
::v-deep .type-text {
|
||||
color: var(--foreground-subdued);
|
||||
transition: color var(--fast) var(--transition);
|
||||
}
|
||||
&:hover {
|
||||
.v-icon {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
::v-deep .type-text {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
18
app/src/modules/docs/components/navigation.vue
Normal file
18
app/src/modules/docs/components/navigation.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<navigation-item v-for="item in sections" :key="item.to" :section="item"></navigation-item>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import NavigationItem from './navigation-item.vue';
|
||||
import sections from './sections';
|
||||
|
||||
export default defineComponent({
|
||||
components: { NavigationItem },
|
||||
setup() {
|
||||
return { sections };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
60
app/src/modules/docs/components/sections.ts
Normal file
60
app/src/modules/docs/components/sections.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
export type Section = {
|
||||
name: string;
|
||||
to: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
sectionIcon?: string;
|
||||
children?: Section[];
|
||||
default?: string;
|
||||
flat?: boolean;
|
||||
};
|
||||
|
||||
const sections: Section[] = [
|
||||
{
|
||||
icon: 'bubble_chart',
|
||||
name: 'Getting Started',
|
||||
to: '/docs/getting-started',
|
||||
children: [
|
||||
{
|
||||
name: 'Introduction',
|
||||
to: '/docs/getting-started/introduction',
|
||||
},
|
||||
{
|
||||
name: 'Installation',
|
||||
to: '/docs/getting-started/installation',
|
||||
},
|
||||
{
|
||||
name: 'Contributing',
|
||||
to: '/docs/getting-started/contributing',
|
||||
},
|
||||
{
|
||||
name: 'Troubleshooting',
|
||||
to: '/docs/getting-started/trouble',
|
||||
children: [
|
||||
{
|
||||
name: 'Technical Support',
|
||||
to: '/docs/getting-started/trouble/tech-support',
|
||||
},
|
||||
{
|
||||
name: 'Premium Support',
|
||||
to: '/docs/getting-started/trouble/prem-support',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'school',
|
||||
name: 'Concepts',
|
||||
to: '/docs/concepts',
|
||||
default: 'readme',
|
||||
},
|
||||
{
|
||||
icon: 'format_list_numbered',
|
||||
name: 'Guides',
|
||||
to: '/docs/guides',
|
||||
default: 'readme',
|
||||
},
|
||||
];
|
||||
|
||||
export default sections;
|
||||
59
app/src/modules/docs/index.ts
Normal file
59
app/src/modules/docs/index.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { defineModule } from '@/modules/define';
|
||||
import Docs from './routes/docs.vue';
|
||||
import sections, { Section } from './components/sections';
|
||||
import { Route } from 'vue-router';
|
||||
|
||||
function urlSplitter(url: string) {
|
||||
if (url.startsWith('/docs')) url = url.replace('/docs', '');
|
||||
if (url.startsWith('/')) url = url.substr(1);
|
||||
return url.split('/');
|
||||
}
|
||||
|
||||
function urlToSection(urlSections: string[], sections: Section[]): Section | null {
|
||||
const section = sections.find((s) => urlSplitter(s.to).pop() === urlSections[0]);
|
||||
|
||||
if (section === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (urlSections.length === 1) {
|
||||
let finalSection = section;
|
||||
while (finalSection.children !== undefined) {
|
||||
finalSection = finalSection.children[0];
|
||||
}
|
||||
if (section.icon) finalSection.icon = section.icon;
|
||||
return finalSection;
|
||||
}
|
||||
|
||||
if (section.children === undefined) return null;
|
||||
|
||||
const sectionDeep = urlToSection(urlSections.slice(1), section.children);
|
||||
|
||||
if (
|
||||
sectionDeep !== null &&
|
||||
sectionDeep.icon === undefined &&
|
||||
sectionDeep.sectionIcon === undefined &&
|
||||
section.icon !== undefined
|
||||
)
|
||||
sectionDeep.sectionIcon = section.icon;
|
||||
return sectionDeep;
|
||||
}
|
||||
|
||||
function props(route: Route) {
|
||||
const section = urlToSection(urlSplitter(route.path), sections);
|
||||
return { section };
|
||||
}
|
||||
|
||||
export default defineModule(({ i18n }) => ({
|
||||
id: 'docs',
|
||||
name: i18n.t('documentation'),
|
||||
icon: 'info',
|
||||
routes: [
|
||||
{
|
||||
path: '/*',
|
||||
component: Docs,
|
||||
props: props,
|
||||
},
|
||||
],
|
||||
order: 20,
|
||||
}));
|
||||
95
app/src/modules/docs/routes/docs.vue
Normal file
95
app/src/modules/docs/routes/docs.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<private-view :title="notFound ? $t('page_not_found') : title">
|
||||
<template #headline>{{ $t('documentation') }}</template>
|
||||
|
||||
<template #title-outer:prepend>
|
||||
<v-button rounded disabled icon>
|
||||
<v-icon :name="isAPIReference ? 'code' : section.icon || section.sectionIcon" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #navigation>
|
||||
<docs-navigation />
|
||||
</template>
|
||||
|
||||
<div v-if="notFound" class="not-found">
|
||||
<v-info :title="$t('page_not_found')" icon="not_interested">
|
||||
{{ $t('page_not_found_body') }}
|
||||
</v-info>
|
||||
</div>
|
||||
|
||||
<markdown v-else>{{ mdString }}</markdown>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, PropType } from '@vue/composition-api';
|
||||
import DocsNavigation from '../components/navigation.vue';
|
||||
import { Section } from '../components/sections';
|
||||
import Markdown from './markdown.vue';
|
||||
import i18n from '@/lang';
|
||||
|
||||
declare module '*.md';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DocsNavigation, Markdown },
|
||||
props: {
|
||||
section: {
|
||||
type: Object as PropType<Section>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const mdString = ref<string | null>(null);
|
||||
const loading = ref(true);
|
||||
const error = ref(null);
|
||||
|
||||
const isAPIReference = computed(() => props.section && props.section.to.startsWith('/docs/api-reference'));
|
||||
const notFound = computed(() => {
|
||||
return props.section === null || error.value !== null;
|
||||
});
|
||||
|
||||
const title = computed(() => {
|
||||
return isAPIReference.value ? i18n.t('api_reference') : props.section.name;
|
||||
});
|
||||
|
||||
watch(() => props.section, loadMD, { immediate: true });
|
||||
|
||||
return { isAPIReference, notFound, title, mdString };
|
||||
|
||||
async function loadMD() {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
if (props.section === null) {
|
||||
mdString.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const loadString = props.section.to.replace('/docs', '');
|
||||
|
||||
try {
|
||||
const md = await import(`raw-loader!@directus/docs${loadString}.md`);
|
||||
mdString.value = md.default;
|
||||
} catch (err) {
|
||||
mdString.value = null;
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.v-info {
|
||||
padding: 20vh 0;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20vh 0;
|
||||
}
|
||||
</style>
|
||||
352
app/src/modules/docs/routes/markdown.vue
Normal file
352
app/src/modules/docs/routes/markdown.vue
Normal file
@@ -0,0 +1,352 @@
|
||||
<template>
|
||||
<div class="docs selectable">
|
||||
<div class="md" v-html="html" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, PropType, onMounted, onUpdated } from '@vue/composition-api';
|
||||
import marked from 'marked';
|
||||
import highlight from 'highlight.js';
|
||||
|
||||
export default defineComponent({
|
||||
setup(props, { slots }) {
|
||||
const html = ref('');
|
||||
|
||||
onMounted(generateHTML);
|
||||
onUpdated(generateHTML);
|
||||
|
||||
return { html };
|
||||
|
||||
function generateHTML() {
|
||||
if (slots.default === null || !slots.default()?.[0]?.text) {
|
||||
html.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let htmlString = marked(slots.default()[0].text!, {
|
||||
highlight: (code) => highlight.highlightAuto(code).value,
|
||||
});
|
||||
|
||||
const hintRegex = /:::(.*?) (.*?)\n((\s|.)*?):::/gm;
|
||||
|
||||
htmlString = htmlString.replaceAll(
|
||||
hintRegex,
|
||||
(match: string, type: string, title: string, body: string) => {
|
||||
return `<div class="hint ${type}"><p class="hint-title">${title}</p><p class="hint-body">${body}</p></div>`;
|
||||
}
|
||||
);
|
||||
|
||||
html.value = htmlString;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.error {
|
||||
padding: 20vh 0;
|
||||
}
|
||||
|
||||
.docs {
|
||||
padding: var(--content-padding);
|
||||
padding-bottom: var(--content-padding-bottom);
|
||||
|
||||
.md {
|
||||
max-width: 740px;
|
||||
|
||||
::v-deep {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
|
||||
& > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
& > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
position: relative;
|
||||
margin: 20px 0 10px;
|
||||
padding: 0;
|
||||
font-weight: 600;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 6px 10px;
|
||||
overflow: auto;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
background-color: var(--background-page);
|
||||
border: 1px solid var(--background-normal);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
code,
|
||||
tt {
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
white-space: nowrap;
|
||||
background-color: var(--background-page);
|
||||
border: 1px solid var(--background-normal);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
pre code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: pre;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre code,
|
||||
pre tt {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
h1 tt,
|
||||
h1 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
h2 tt,
|
||||
h2 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
h3 tt,
|
||||
h3 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
h4 tt,
|
||||
h4 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
h5 tt,
|
||||
h5 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
h6 tt,
|
||||
h6 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
color: var(--foreground-normal);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
li,
|
||||
table,
|
||||
pre {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
& > h2:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
& > h1:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
& > h3:first-child,
|
||||
& > h4:first-child,
|
||||
& > h5:first-child,
|
||||
& > h6:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
a:first-child h1,
|
||||
a:first-child h2,
|
||||
a:first-child h3,
|
||||
a:first-child h4,
|
||||
a:first-child h5,
|
||||
a:first-child h6 {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
h1 p,
|
||||
h2 p,
|
||||
h3 p,
|
||||
h4 p,
|
||||
h5 p,
|
||||
h6 p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
li p.first {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 30px;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul :first-child,
|
||||
ol :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul :last-child,
|
||||
ol :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 0 15px;
|
||||
color: var(--foreground-normal);
|
||||
border-left: 4px solid var(--background-normal);
|
||||
}
|
||||
|
||||
blockquote > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
padding: 0;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
table tr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
border-top: 1px solid var(--background-normal);
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
table tr th {
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
border: 1px solid var(--background-normal);
|
||||
}
|
||||
|
||||
table tr td {
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
text-align: left;
|
||||
border: 1px solid var(--background-normal);
|
||||
}
|
||||
|
||||
table tr th :first-child,
|
||||
table tr td :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
table tr th :last-child,
|
||||
table tr td :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.highlight pre {
|
||||
padding: 6px 10px;
|
||||
overflow: auto;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
background-color: var(--background-page);
|
||||
border: 1px solid var(--background-normal);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 20px auto;
|
||||
border: none;
|
||||
border-top: 1px solid var(--background-normal);
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hint {
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
background-color: var(--background-subdued);
|
||||
border-left: 8px solid var(--primary);
|
||||
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.tip {
|
||||
border-left: 8px solid var(--success);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-left: 8px solid var(--warning);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
border-left: 8px solid var(--danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user