Merge branch 'main' into add-modified_on

This commit is contained in:
Rijk van Zanten
2020-10-11 18:53:42 -04:00
committed by GitHub
142 changed files with 31727 additions and 2875 deletions

View File

@@ -0,0 +1,445 @@
<template>
<div class="md" v-html="html" @click="onClick" />
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch, PropType, onMounted, onUpdated } from '@vue/composition-api';
import marked, { Renderer } from 'marked';
import highlight from 'highlight.js';
export default defineComponent({
setup(props, { slots }) {
const html = ref('');
onMounted(generateHTML);
onUpdated(generateHTML);
return { html, onClick };
async function onClick($event: Event) {
if ($event.target instanceof HTMLElement && $event.target.classList.contains('copy')) {
await navigator.clipboard.writeText(
window.location.href.split('#')[0] + $event.target.getAttribute('href')
);
}
}
function generateHTML() {
if (slots.default === null || !slots.default()?.[0]?.text) {
html.value = '';
return;
}
let htmlString = slots.default()[0].text!;
const hintRegex = /<p>:::(.*?) (.*?)\r?\n((\s|.)*?):::<\/p>/gm;
const renderer: Partial<Renderer> = {
heading(text, level) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `
<h${level} id="${escapedText}">
<a class="heading-link copy" href="#${escapedText}">#</a>
${text}
</h${level}>`;
},
};
// Marked merges it's default rendered with our extension. It's typed as a full rendered however
marked.use({ renderer } as any);
htmlString = marked(htmlString, {
highlight: (code, lang) => {
return highlight.highlightAuto(code, [lang]).value;
},
});
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;
}
.md {
::v-deep {
font-weight: 400;
font-size: 16px;
line-height: 27px;
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
a {
color: var(--primary);
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin: 40px 0 8px;
padding: 0;
font-weight: 600;
cursor: text;
a {
position: absolute;
right: 100%;
padding-right: 4px;
opacity: 0;
&:hover {
text-decoration: underline;
}
}
&:hover a {
opacity: 1;
}
}
h1 {
margin-bottom: 40px;
font-size: 35px;
line-height: 44px;
}
h2 {
margin-top: 60px;
margin-bottom: 20px;
padding-bottom: 12px;
font-size: 26px;
line-height: 33px;
border-bottom: 2px solid var(--border-subdued);
}
h3 {
margin-bottom: 0px;
font-size: 19px;
line-height: 24px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: var(--foreground-normal);
font-size: 14px;
}
.heading-link {
color: var(--foreground-subdued);
font-size: 16px;
&:hover {
color: var(--primary);
text-decoration: none;
}
}
pre {
padding: 16px 20px;
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 1px;
padding: 0 4px;
font-size: 15px;
font-family: var(--family-monospace);
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;
}
p,
blockquote,
ul,
ol,
dl,
li,
table,
pre {
margin: 8px 0;
}
p {
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
h3 + p {
margin-block-start: 0.5em;
}
& > 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 {
margin: 20px 0;
padding-left: 20px;
li {
margin: 8px 0;
line-height: 24px;
}
ul,
ol {
margin: 4px 0;
li {
margin: 4px 0;
line-height: 24px;
}
}
}
blockquote {
padding: 0 20px;
color: var(--foreground-subdued);
font-size: 18px;
border-left: 2px solid var(--background-normal);
}
blockquote > :first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 0;
}
table {
min-width: 100%;
margin: 40px 0;
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: 8px 20px;
font-weight: bold;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr td {
margin: 0;
padding: 8px 20px;
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%;
margin: 20px 0;
&.no-margin {
margin: 0;
}
&.full {
width: 100%;
}
&.shadow {
box-shadow: 0px 5px 10px 0px rgba(23, 41, 64, 0.1), 0px 2px 40px 0px rgba(23, 41, 64, 0.05);
}
}
.highlight pre {
padding: 8px 20px;
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: 40px auto;
border: none;
border-top: 2px solid var(--background-normal);
}
b,
strong {
font-weight: 600;
}
.hint {
display: inline-block;
width: 100%;
margin: 20px 0;
padding: 0 20px;
background-color: var(--background-subdued);
border-left: 2px solid var(--primary);
&-title {
margin-bottom: 0.5em;
font-weight: bold;
}
&-body {
margin-top: 0.5em;
}
&.tip {
border-left: 2px solid var(--success);
}
&.warning {
background-color: var(--warning-10);
border-left: 2px solid var(--warning);
}
&.danger {
background-color: var(--danger-10);
border-left: 2px solid var(--danger);
}
}
}
}
</style>

View File

@@ -1,20 +1,6 @@
<template>
<v-list-item v-if="section.children === undefined" :to="section.to" :dense="dense" :subdued="subdued">
<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-text>{{ section.name }}</v-list-item-text>
</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>
<v-divider v-if="section.divider" />
<v-list-group v-else-if="section.children" :dense="dense">
<template #activator>
<v-list-item-icon v-if="section.icon !== undefined"><v-icon :name="section.icon" /></v-list-item-icon>
<v-list-item-content>
@@ -28,48 +14,30 @@
dense
/>
</v-list-group>
<v-list-item v-else :to="`/docs${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-text>{{ section.name }}</v-list-item-text>
</v-list-item-content>
</v-list-item>
</template>
<script lang="ts">
import { defineComponent, PropType } from '@vue/composition-api';
import { Section } from './sections';
import { Link, Group } from '@directus/docs';
export default defineComponent({
name: 'navigation-list-item',
props: {
section: {
type: Object as PropType<Section>,
type: Object as PropType<Link | Group>,
default: null,
},
dense: {
type: Boolean,
default: false,
},
subdued: {
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>

View File

@@ -1,18 +1,18 @@
<template>
<v-list large>
<navigation-item v-for="item in sections" :key="item.to" :section="item"></navigation-item>
<navigation-item v-for="item in navSections" :key="item.name" :section="item" />
</v-list>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { defineComponent, PropType, computed } from '@vue/composition-api';
import NavigationItem from './navigation-item.vue';
import sections from './sections';
import { nav } from '@directus/docs';
export default defineComponent({
components: { NavigationItem },
setup() {
return { sections };
return { navSections: nav.app };
},
});
</script>

View File

@@ -1,188 +0,0 @@
export type Section = {
name: string;
to: string;
description?: string;
icon?: string;
sectionIcon?: string;
sectionName?: string;
children?: Section[];
default?: string;
flat?: boolean;
};
export const defaultSection = '/docs/getting-started/introduction';
const sections: Section[] = [
{
icon: 'play_arrow',
name: 'Getting Started',
to: '/docs/getting-started',
default: '',
children: [
{
name: 'Introduction',
to: '/docs/getting-started/introduction',
},
{
name: 'Support & FAQ',
to: '/docs/getting-started/support',
},
{
name: 'Contributing',
to: '/docs/getting-started/contributing',
},
{
name: 'Backing Directus',
to: '/docs/getting-started/backing-directus',
},
],
},
{
icon: 'school',
name: 'Concepts',
to: '/docs/concepts',
default: 'readme',
children: [
{
name: 'Platform Overview',
to: '/docs/concepts/platform-overview',
},
{
name: 'App Overview',
to: '/docs/concepts/app-overview',
},
{
name: 'App Extensions',
to: '/docs/concepts/app-extensions',
},
{
name: 'Activity & Versions',
to: '/docs/concepts/activity-and-versions',
},
{
name: 'Files & Thumbnails',
to: '/docs/concepts/files-and-thumbnails',
},
{
name: 'Internationalization',
to: '/docs/concepts/internationalization',
},
{
name: 'Relationships',
to: '/docs/concepts/relationships',
},
{
name: 'Users, Roles & Permissions',
to: '/docs/concepts/users-roles-and-permissions',
},
],
},
{
icon: 'article',
name: 'Guides',
to: '/docs/guides',
default: 'readme',
children: [
{
name: 'Collections',
to: '/docs/guides/collections',
},
{
name: 'Fields',
to: '/docs/guides/fields',
},
{
name: 'Presets',
to: '/docs/guides/presets',
},
{
name: 'Projects',
to: '/docs/guides/projects',
},
{
name: 'Roles & Permissions',
to: '/docs/guides/roles-and-permissions',
},
{
name: 'Users',
to: '/docs/guides/users',
},
{
name: 'Webhooks',
to: '/docs/guides/webhooks',
},
{
name: 'White-Labeling',
to: '/docs/guides/white-labeling',
},
{
name: 'Extensions',
to: '/docs/guides/extensions',
children: [
{
name: 'Custom Displays',
to: '/docs/guides/extensions/creating-a-custom-display',
},
{
name: 'Custom Interfaces',
to: '/docs/guides/extensions/creating-a-custom-interface',
},
{
name: 'Custom Layouts',
to: '/docs/guides/extensions/creating-a-custom-layout',
},
{
name: 'Custom Modules',
to: '/docs/guides/extensions/creating-a-custom-module',
},
{
name: 'Custom API Endpoints',
to: '/docs/guides/extensions/creating-a-custom-api-endpoint',
},
{
name: 'Custom API Hooks',
to: '/docs/guides/extensions/creating-a-custom-api-hook',
},
{
name: 'Custom Email Templates',
to: '/docs/guides/extensions/creating-a-custom-email-template',
},
{
name: 'Custom Storage Adapters',
to: '/docs/guides/extensions/creating-a-custom-storage-adapter',
},
{
name: 'Accessing Data',
to: '/docs/guides/extensions/accessing-data',
},
],
},
],
},
{
icon: 'code',
name: 'Reference',
to: '/docs/reference',
default: 'readme',
children: [
{
name: 'Environment Variables',
to: '/docs/reference/environment-variables',
},
{
name: 'Command Line Interface',
to: '/docs/reference/command-line-interface',
},
{
name: 'Error Codes',
to: '/docs/reference/error-codes',
},
{
name: 'Item Rules',
to: '/docs/reference/item-rules',
},
],
},
];
export default sections;

View File

@@ -1,71 +1,49 @@
import { defineModule } from '@/modules/define';
import Docs from './routes/docs.vue';
import sections, { Section, defaultSection } from './components/sections';
import { Route } from 'vue-router';
import { cloneDeep } from 'lodash';
import { RouteConfig } from 'vue-router';
import { files, Directory } from '@directus/docs';
import StaticDocs from './routes/static.vue';
import NotFound from './routes/not-found.vue';
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 {
let section = sections.find((s) => urlSplitter(s.to).pop() === urlSections[0]);
if (section === undefined) {
return null;
}
section = cloneDeep(section);
if (urlSections.length === 1) {
let finalSection = section;
while (finalSection.children !== undefined) {
finalSection = finalSection.children[0];
}
if (section.icon) finalSection.icon = section.icon;
if (finalSection.sectionName === undefined) finalSection.sectionName = section.name;
return finalSection;
}
if (section.children === undefined) return null;
const sectionDeep = urlToSection(urlSections.slice(1), section.children);
if (sectionDeep !== null && sectionDeep.sectionName === undefined) {
sectionDeep.sectionName = section.name;
}
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: [
export default defineModule(({ i18n }) => {
const routes: RouteConfig[] = [
{
path: '/',
component: StaticDocs,
},
...parseRoutes(files),
{
path: '/*',
beforeEnter: (to, from, next) => {
if (to.path === '/docs/') next(defaultSection);
else next();
},
component: Docs,
props: props,
component: NotFound,
},
],
order: 20,
}));
];
return {
id: 'docs',
name: i18n.t('documentation'),
icon: 'info',
routes,
order: 20,
};
function parseRoutes(directory: Directory): RouteConfig[] {
const routes: RouteConfig[] = [];
for (const doc of directory.children) {
if (doc.type === 'file') {
routes.push({
path: '/' + doc.path.replace('.md', ''),
component: StaticDocs,
});
} else if (doc.type === 'directory') {
routes.push({
path: '/' + doc.path,
redirect: '/' + doc.children![0].path.replace('.md', ''),
});
routes.push(...parseRoutes(doc));
}
}
return routes;
}
});

View File

@@ -1,95 +0,0 @@
<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.sectionName;
});
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>

View File

@@ -1,407 +0,0 @@
<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';
import 'highlight.js/styles/github.css'
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 = slots.default()[0].text!;
const hintRegex = /<p>:::(.*?) (.*?)\r?\n((\s|.)*?):::<\/p>/gm;
htmlString = marked(htmlString, {
highlight: (code, lang) => {
return highlight.highlightAuto(code, [lang]).value
},
});
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: 0 var(--content-padding) var(--content-padding-bottom);
.md {
max-width: 740px;
::v-deep {
font-weight: 400;
font-size: 16px;
line-height: 27px;
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
a {
color: var(--primary);
text-decoration: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin: 40px 0 8px;
padding: 0;
font-weight: 600;
cursor: text;
}
h1 {
margin-bottom: 40px;
font-size: 35px;
line-height: 44px;
}
h2 {
margin-top: 60px;
margin-bottom: 20px;
padding-bottom: 12px;
font-size: 26px;
line-height: 33px;
border-bottom: 2px solid var(--border-subdued);
}
h3 {
margin-bottom: 0px;
font-size: 19px;
line-height: 24px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: var(--foreground-normal);
font-size: 14px;
}
pre {
padding: 16px 20px;
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 1px;
padding: 0 4px;
font-family: var(--family-monospace);
font-size: 15px;
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;
}
p,
blockquote,
ul,
ol,
dl,
li,
table,
pre {
margin: 8px 0;
}
p {
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
}
h3 + p {
margin-block-start: 0.5em;
}
& > 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 {
margin: 20px 0;
padding-left: 20px;
li {
margin: 8px 0;
line-height: 24px;
}
ul,
ol {
margin: 4px 0;
li {
margin: 4px 0;
line-height: 24px;
}
}
}
blockquote {
font-size: 18px;
padding: 0 20px;
color: var(--foreground-subdued);
border-left: 2px solid var(--background-normal);
}
blockquote > :first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 0;
}
table {
min-width: 100%;
margin: 40px 0;
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: 8px 20px;
font-weight: bold;
text-align: left;
border: 1px solid var(--background-normal);
}
table tr td {
margin: 0;
padding: 8px 20px;
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%;
margin: 20px 0;
&.no-margin {
margin: 0;
}
&.full {
width: 100%;
}
&.shadow {
box-shadow: 0px 5px 10px 0px rgba(23,41,64,0.1),
0px 2px 40px 0px rgba(23,41,64,0.05);
}
}
.highlight pre {
padding: 8px 20px;
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: 40px auto;
border: none;
border-top: 2px solid var(--background-normal);
}
b,
strong {
font-weight: 600;
}
.hint {
display: inline-block;
margin: 20px 0;
padding: 0 20px;
background-color: var(--background-subdued);
border-left: 2px solid var(--primary);
width: 100%;
&-title {
font-weight: bold;
margin-bottom: 0.5em;
}
&-body {
margin-top: 0.5em;
}
&.tip {
border-left: 2px solid var(--success);
}
&.warning {
background-color: var(--warning-10);
border-left: 2px solid var(--warning);
}
&.danger {
background-color: var(--danger-10);
border-left: 2px solid var(--danger);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<private-view :title="$t('page_not_found')">
<template #navigation>
<docs-navigation />
</template>
<div class="not-found">
<v-info :title="$t('page_not_found')" icon="not_interested">
{{ $t('page_not_found_body') }}
</v-info>
</div>
</private-view>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import DocsNavigation from '../components/navigation.vue';
export default defineComponent({
name: 'NotFound',
components: { DocsNavigation },
});
</script>
<style lang="scss" scoped>
.not-found {
display: flex;
align-items: center;
justify-content: center;
padding: 20vh 0;
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<private-view :title="title" ref="view">
<template #headline>Documentation</template>
<template #title-outer:prepend>
<v-button rounded disabled icon>
<v-icon name="info" />
</v-button>
</template>
<template #navigation>
<docs-navigation />
</template>
<div class="docs-content selectable">
<markdown>{{ markdownWithoutTitle }}</markdown>
</div>
<template #drawer>
<drawer-detail icon="info_outline" :title="$t('information')" close>
<div class="page-description" v-html="marked($t('page_help_docs_global'))" />
</drawer-detail>
</template>
</private-view>
</template>
<script lang="ts">
import { defineComponent, ref, computed, inject, onUpdated } from '@vue/composition-api';
import DocsNavigation from '../components/navigation.vue';
import Markdown from '../components/markdown.vue';
import marked from 'marked';
async function getMarkdownForPath(path: string) {
const pathParts = path.split('/');
while (pathParts.includes('docs')) {
pathParts.shift();
}
let docsPath = pathParts.join('/');
// Home
if (!docsPath) {
docsPath = 'readme';
}
const mdModule = await import('raw-loader!@directus/docs/' + docsPath + '.md');
return mdModule.default;
}
export default defineComponent({
name: 'StaticDocs',
components: { DocsNavigation, Markdown },
async beforeRouteEnter(to, from, next) {
const md = await getMarkdownForPath(to.path);
next((vm) => {
(vm as any).markdown = md;
});
},
async beforeRouteUpdate(to, from, next) {
this.markdown = await getMarkdownForPath(to.path);
next();
},
setup() {
const markdown = ref('');
const view = ref<Vue>();
const title = computed(() => {
const firstLine = markdown.value.split('\n').shift();
return firstLine?.substring(2).trim();
});
const markdownWithoutTitle = computed(() => {
const lines = markdown.value.split('\n');
lines.shift();
return lines.join('\n');
});
onUpdated(() => {
view.value?.$data.contentEl?.scrollTo({ top: 0 });
});
return { markdown, title, markdownWithoutTitle, view, marked };
},
});
</script>
<style lang="scss" scoped>
.docs-content {
max-width: 740px;
padding: 0 var(--content-padding) var(--content-padding-bottom);
}
</style>

View File

@@ -65,13 +65,13 @@ export default defineComponent({
{
icon: 'bug_report',
name: i18n.t('report_bug'),
href: 'https://github.com/directus/directus/issues/new/choose',
href: 'https://github.com/directus/next/issues/new?body=%23%23%23+Project+Details%0A%60%60%60%0ADirectus+Version:+'+version+'%0AEnvironment:+Development%0AOS:+Mac%0ADatabase:+MySQL+5.2%0A%60%60%60',
outline: true,
},
{
icon: 'new_releases',
name: i18n.t('request_feature'),
href: 'https://github.com/directus/directus/discussions/new',
href: 'https://github.com/directus/next/discussions/new',
outline: true,
},
];

View File

@@ -291,21 +291,21 @@ export default defineComponent({
}
await Promise.all(
state.newCollections.map((newCollection: Partial<Collection> & { $type: string }) => {
state.newCollections.map((newCollection: Partial<Collection> & { $type?: string }) => {
delete newCollection.$type;
return api.post(`/collections`, newCollection);
})
);
await Promise.all(
state.newFields.map((newField: Partial<Field> & { $type: string }) => {
state.newFields.map((newField: Partial<Field> & { $type?: string }) => {
delete newField.$type;
return api.post(`/fields/${newField.collection}`, newField);
})
);
await Promise.all(
state.updateFields.map((updateField: Partial<Field> & { $type: string }) => {
state.updateFields.map((updateField: Partial<Field> & { $type?: string }) => {
delete updateField.$type;
return api.post(`/fields/${updateField.collection}/${updateField.field}`, updateField);
})

View File

@@ -298,7 +298,7 @@ export default defineComponent({
};
async function saveDuplicate() {
const newField = {
const newField: any = {
...props.field,
field: duplicateName.value,
collection: duplicateTo.value,

View File

@@ -26,19 +26,25 @@
<v-tabs-items v-model="currentTab">
<v-tab-item value="collection">
<h2 class="type-title">{{ $t('creating_collection_info') }}</h2>
<div class="type-label">
{{ $t('name') }}
<v-icon class="required" v-tooltip="$t('required')" name="star" sup />
</div>
<v-input
autofocus
class="monospace"
v-model="collectionName"
db-safe
:placeholder="$t('a_unique_table_name')"
/>
<v-divider />
<div class="grid">
<div>
<div class="type-label">
{{ $t('name') }}
<v-icon class="required" v-tooltip="$t('required')" name="star" sup />
</div>
<v-input
autofocus
class="monospace"
v-model="collectionName"
db-safe
:placeholder="$t('a_unique_table_name')"
/>
</div>
<div>
<div class="type-label">{{ $t('singleton') }}</div>
<v-checkbox block :label="$t('singleton_label')" v-model="singleton" />
</div>
<v-divider class="full" />
<div>
<div class="type-label">{{ $t('primary_key_field') }}</div>
<v-input
@@ -73,7 +79,7 @@
<v-tab-item value="system">
<h2 class="type-title">{{ $t('creating_collection_system') }}</h2>
<div class="grid system">
<div class="field" v-for="(info, field) in systemFields" :key="field">
<div v-for="(info, field) in systemFields" :key="field">
<div class="type-label">{{ $t(info.label) }}</div>
<v-input v-model="info.name" class="monospace" :class="{active: info.enabled}" @click.native="info.enabled = true">
<template #prepend>
@@ -124,6 +130,7 @@ export default defineComponent({
const currentTab = ref(['collection']);
const collectionName = ref(null);
const singleton = ref(false);
const primaryKeyFieldName = ref('id');
const primaryKeyFieldType = ref<'auto_int' | 'uuid' | 'manual'>('auto_int');
@@ -184,6 +191,7 @@ export default defineComponent({
collectionName,
saveError,
saving,
singleton
};
async function save() {
@@ -198,6 +206,7 @@ export default defineComponent({
archive_field: archiveField.value,
archive_value: archiveValue.value,
unarchive_value: unarchiveValue.value,
singleton: singleton.value
},
});
@@ -412,22 +421,14 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
@import '@/styles/mixins/form-grid';
.type-title {
margin-bottom: 48px;
}
.type-label {
margin-bottom: 12px;
}
.v-divider {
margin: 48px 0;
}
.grid {
display: grid;
grid-gap: 48px 36px;
grid-template-columns: repeat(2, 1fr);
@include form-grid;
}
.system {

View File

@@ -106,7 +106,7 @@ import PresetsInfoDrawerDetail from './components/presets-info-drawer-detail.vue
type PresetRaw = {
id: number;
title: null | string;
bookmark: null | string;
user: null | { first_name: string; last_name: string };
role: null | { name: string };
collection: string;
@@ -184,7 +184,7 @@ export default defineComponent({
scope: scope,
collection: collection,
layout: layout,
name: preset.title,
name: preset.bookmark,
} as Preset;
});
});
@@ -199,7 +199,7 @@ export default defineComponent({
params: {
fields: [
'id',
'title',
'bookmark',
'user.first_name',
'user.last_name',
'role.name',

View File

@@ -40,7 +40,7 @@ export default defineComponent({
try {
const response = await api.get(`/presets`, {
params: {
[`filter[title][nnull]`]: 1,
[`filter[bookmark][_nnull]`]: 1,
fields: ['id'],
meta: 'filter_count,total_count',
},