mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'main' into list-accordion
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list large>
|
||||
<v-list-item to="/activity" exact>
|
||||
<v-list-item-icon>
|
||||
<v-icon name="access_time" />
|
||||
@@ -9,7 +9,7 @@
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item :to="`/activity?action_by=${currentUserID}`" exact>
|
||||
<v-list-item :to="`/activity?user=${currentUserID}`" exact>
|
||||
<v-list-item-icon>
|
||||
<v-icon name="face" />
|
||||
</v-list-item-icon>
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
<template v-else>
|
||||
<!-- @TODO add final design -->
|
||||
<p class="type-label">User:</p>
|
||||
<user-popover v-if="item.action_by" :user="item.action_by.id">
|
||||
{{ item.action_by.first_name }} {{ item.action_by.last_name }}
|
||||
<user-popover v-if="item.user" :user="item.user.id">
|
||||
{{ item.user.first_name }} {{ item.user.last_name }}
|
||||
</user-popover>
|
||||
|
||||
<p class="type-label">Action:</p>
|
||||
<p>{{ item.action }}</p>
|
||||
|
||||
<p class="type-label">Date:</p>
|
||||
<p>{{ item.action_on }}</p>
|
||||
<p>{{ item.timestamp }}</p>
|
||||
|
||||
<p class="type-label">IP Address:</p>
|
||||
<p>{{ item.ip }}</p>
|
||||
@@ -56,12 +56,12 @@ type Values = {
|
||||
};
|
||||
|
||||
type ActivityRecord = {
|
||||
action_by: {
|
||||
user: {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
} | null;
|
||||
action: string;
|
||||
action_on: string;
|
||||
timestamp: string;
|
||||
ip: string;
|
||||
user_agent: string;
|
||||
collection: string;
|
||||
@@ -105,11 +105,11 @@ export default defineComponent({
|
||||
const response = await api.get(`/activity/${props.primaryKey}`, {
|
||||
params: {
|
||||
fields: [
|
||||
'action_by.id',
|
||||
'action_by.first_name',
|
||||
'action_by.last_name',
|
||||
'user.id',
|
||||
'user.first_name',
|
||||
'user.last_name',
|
||||
'action',
|
||||
'action_on',
|
||||
'timestamp',
|
||||
'ip',
|
||||
'user_agent',
|
||||
'collection',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-menu ref="contextMenu" show-arrow placement="bottom-start">
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-list-item @click="renameActive = true" :disabled="isMine === false">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="edit" outline />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list large>
|
||||
<template v-if="customNavItems && customNavItems.length > 0">
|
||||
<v-detail
|
||||
:active="group.accordion === 'always_open' || undefined"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
secondary
|
||||
exact
|
||||
v-tooltip.bottom="$t('back')"
|
||||
@click="$router.go(-1)"
|
||||
:to="'/collections/' + collection"
|
||||
>
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
|
||||
445
app/src/modules/docs/components/markdown.vue
Normal file
445
app/src/modules/docs/components/markdown.vue
Normal 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>
|
||||
@@ -1,24 +1,10 @@
|
||||
<template>
|
||||
<v-list-item v-if="section.children === undefined" :to="section.to" :dense="dense" :value="section.to">
|
||||
<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 :multiple="false" :value="section.to" disableGroupableParent>
|
||||
<v-divider v-if="section.divider" />
|
||||
<v-list-group v-else-if="section.children" :dense="dense" :multiple="false" :value="section.to">
|
||||
<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-text>{{ section.name }}</v-list-item-text>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
<navigation-list-item
|
||||
@@ -28,17 +14,24 @@
|
||||
dense
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-item v-else :to="`/docs${section.to}`" :dense="dense" :value="section.to">
|
||||
<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, computed } 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: {
|
||||
@@ -48,24 +41,3 @@ export default defineComponent({
|
||||
}
|
||||
});
|
||||
</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>
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
<template>
|
||||
<v-list nav :multiple="false" v-model="selection">
|
||||
<navigation-item v-for="item in sections" :key="item.to" :section="item"></navigation-item>
|
||||
<v-list large :multiple="false" v-model="selection">
|
||||
<navigation-item v-for="item in navSections" :key="item.name" :section="item" />
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref, watch } from '@vue/composition-api';
|
||||
import { spread } from 'lodash';
|
||||
import { defineComponent, PropType, computed, watch, ref } from '@vue/composition-api';
|
||||
import NavigationItem from './navigation-item.vue';
|
||||
import sections, {Section} from './sections';
|
||||
import { nav } from '@directus/docs';
|
||||
|
||||
function spreadPath(path: string) {
|
||||
const sections = path.substr(1).split('/')
|
||||
if(sections.length === 0) return []
|
||||
|
||||
const paths: string[] = ['/'+sections[0]]
|
||||
|
||||
for(let i = 1; i < sections.length; i++) {
|
||||
paths.push(paths[i - 1] + '/' + sections[i])
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { NavigationItem },
|
||||
props: {
|
||||
section: {
|
||||
type: Object as PropType<Section>,
|
||||
default: null,
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next((vm) => {
|
||||
(vm as any)._selection = spreadPath(to.path)
|
||||
})
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
this._selection = spreadPath(to.path)
|
||||
},
|
||||
setup(props) {
|
||||
const _selection = ref<string[]>(spreadPath(props.section.to))
|
||||
|
||||
watch(props.section, (newSection) => {
|
||||
if(newSection !== null)
|
||||
_selection.value = spreadPath(newSection.to)
|
||||
})
|
||||
const _selection = ref<string[]>([])
|
||||
|
||||
const selection = computed({
|
||||
get() {
|
||||
@@ -44,19 +52,7 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
function spreadPath(path: string) {
|
||||
const sections = path.substr(1).split('/')
|
||||
if(sections.length === 0) return []
|
||||
|
||||
const paths: string[] = ['/'+sections[0]]
|
||||
|
||||
for(let i = 1; i < sections.length; i++) {
|
||||
paths.push(paths[i - 1] + '/' + sections[i])
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
return { sections, selection };
|
||||
return { navSections: nav.app, selection };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,192 +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: 'Troubleshooting',
|
||||
to: '/docs/getting-started/troubleshooting',
|
||||
},
|
||||
{
|
||||
name: 'Contributing',
|
||||
to: '/docs/getting-started/contributing',
|
||||
},
|
||||
{
|
||||
name: 'Supporting Directus',
|
||||
to: '/docs/getting-started/supporting-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: '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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Collections',
|
||||
to: '/docs/guides/collections',
|
||||
},
|
||||
{
|
||||
name: 'Fields',
|
||||
to: '/docs/guides/fields',
|
||||
},
|
||||
{
|
||||
name: 'Presets & Bookmarks',
|
||||
to: '/docs/guides/managing-presets-and-bookmarks',
|
||||
},
|
||||
{
|
||||
name: 'Roles & Permissions',
|
||||
to: '/docs/guides/managing-roles-and-permissions',
|
||||
},
|
||||
{
|
||||
name: 'Webhooks',
|
||||
to: '/docs/guides/managing-webhooks',
|
||||
},
|
||||
{
|
||||
name: 'Creating a Project',
|
||||
to: '/docs/guides/creating-a-project',
|
||||
},
|
||||
{
|
||||
name: 'Configuring a Project',
|
||||
to: '/docs/guides/configuring-the-api',
|
||||
},
|
||||
{
|
||||
name: 'Setting up a Project',
|
||||
to: '/docs/guides/configuring-project-settings',
|
||||
},
|
||||
{
|
||||
name: 'White-Labeling a Project',
|
||||
to: '/docs/guides/white-labeling-a-project',
|
||||
},
|
||||
{
|
||||
name: 'Upgrading a Project',
|
||||
to: '/docs/guides/upgrading-a-project',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
name: 'Reference',
|
||||
to: '/docs/reference',
|
||||
default: 'readme',
|
||||
children: [
|
||||
{
|
||||
name: 'Command Line Interface',
|
||||
to: '/docs/reference/command-line-interface',
|
||||
},
|
||||
{
|
||||
name: 'Error Codes',
|
||||
to: '/docs/reference/error-codes',
|
||||
},
|
||||
{
|
||||
name: 'Project Env Variables',
|
||||
to: '/docs/reference/project-environment-variables',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default sections;
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 :section="section" />
|
||||
</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>
|
||||
@@ -1,403 +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';
|
||||
|
||||
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) => highlight.highlightAuto(code).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 {
|
||||
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>
|
||||
32
app/src/modules/docs/routes/not-found.vue
Normal file
32
app/src/modules/docs/routes/not-found.vue
Normal 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>
|
||||
96
app/src/modules/docs/routes/static.vue
Normal file
96
app/src/modules/docs/routes/static.vue
Normal 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>
|
||||
@@ -50,7 +50,7 @@ export default defineComponent({
|
||||
try {
|
||||
const newFolder = await api.post(`/folders`, {
|
||||
name: newFolderName.value,
|
||||
parent_folder: props.parent === 'root' ? null : props.parent,
|
||||
parent: props.parent === 'root' ? null : props.parent,
|
||||
});
|
||||
|
||||
await fetchFolders();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-skeleton-loader v-if="loading" />
|
||||
<div class="folder-picker" v-else>
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-item-group scope="folder-picker" multiple v-model="openFolders">
|
||||
<v-list-group
|
||||
disable-groupable-parent
|
||||
@@ -40,7 +40,7 @@ import FolderPickerListItem from './folder-picker-list-item.vue';
|
||||
type FolderRaw = {
|
||||
id: string;
|
||||
name: string;
|
||||
parent_folder: null | string;
|
||||
parent: null | string;
|
||||
};
|
||||
|
||||
type Folder = {
|
||||
@@ -67,7 +67,7 @@ export default defineComponent({
|
||||
const error = ref<any>(null);
|
||||
const tree = computed<Folder[]>(() => {
|
||||
return folders.value
|
||||
.filter((folder) => folder.parent_folder === null)
|
||||
.filter((folder) => folder.parent === null)
|
||||
.map((folder) => {
|
||||
return {
|
||||
...folder,
|
||||
@@ -78,7 +78,7 @@ export default defineComponent({
|
||||
function getChildFolders(folder: FolderRaw): Folder[] {
|
||||
return folders.value
|
||||
.filter((childFolder) => {
|
||||
return childFolder.parent_folder === folder.id;
|
||||
return childFolder.parent === folder.id;
|
||||
})
|
||||
.map((childFolder) => {
|
||||
return {
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
const shouldBeOpen: string[] = [];
|
||||
const folder = folders.value.find((folder) => folder.id === props.value);
|
||||
|
||||
if (folder && folder.parent_folder) parseFolder(folder.parent_folder);
|
||||
if (folder && folder.parent) parseFolder(folder.parent);
|
||||
|
||||
const startOpenFolders = ['root'];
|
||||
|
||||
@@ -137,8 +137,8 @@ export default defineComponent({
|
||||
|
||||
const folder = folders.value.find((folder) => folder.id === id);
|
||||
|
||||
if (folder && folder.parent_folder) {
|
||||
parseFolder(folder.parent_folder);
|
||||
if (folder && folder.parent) {
|
||||
parseFolder(folder.parent);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</v-list-group>
|
||||
|
||||
<v-menu ref="contextMenu" show-arrow placement="bottom-start">
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-list-item @click="renameActive = true">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="edit" outline />
|
||||
@@ -181,7 +181,7 @@ export default defineComponent({
|
||||
|
||||
function useMoveFolder() {
|
||||
const moveActive = ref(false);
|
||||
const moveValue = ref(props.folder.parent_folder);
|
||||
const moveValue = ref(props.folder.parent);
|
||||
const moveSaving = ref(false);
|
||||
|
||||
return { moveActive, moveValue, moveSave, moveSaving };
|
||||
@@ -191,7 +191,7 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
await api.patch(`/folders/${props.folder.id}`, {
|
||||
parent_folder: moveValue.value,
|
||||
parent: moveValue.value,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -216,7 +216,7 @@ export default defineComponent({
|
||||
const foldersToUpdate = await api.get('/folders', {
|
||||
params: {
|
||||
filter: {
|
||||
parent_folder: {
|
||||
parent: {
|
||||
_eq: props.folder.id,
|
||||
},
|
||||
},
|
||||
@@ -233,13 +233,13 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
const newParent = props.folder.parent_folder || null;
|
||||
const newParent = props.folder.parent || null;
|
||||
|
||||
const folderKeys = foldersToUpdate.data.data.map((folder: { id: string }) => folder.id);
|
||||
const fileKeys = filesToUpdate.data.data.map((file: { id: string }) => file.id);
|
||||
|
||||
if (folderKeys.length > 0) {
|
||||
await api.patch(`/folders/${folderKeys.join(',')}`, { parent_folder: newParent });
|
||||
await api.patch(`/folders/${folderKeys.join(',')}`, { parent: newParent });
|
||||
}
|
||||
|
||||
if (fileKeys.length > 0) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list large>
|
||||
<template v-if="loading && (nestedFolders === null || nestedFolders.length === 0)">
|
||||
<v-list-item v-for="n in 4" :key="n">
|
||||
<v-skeleton-loader type="list-item-icon" />
|
||||
@@ -79,7 +79,7 @@ export default defineComponent({
|
||||
const shouldBeOpen: string[] = [];
|
||||
const folder = folders.value.find((folder) => folder.id === props.currentFolder);
|
||||
|
||||
if (folder && folder.parent_folder) parseFolder(folder.parent_folder);
|
||||
if (folder && folder.parent) parseFolder(folder.parent);
|
||||
|
||||
const newOpenFolders = [...openFolders.value];
|
||||
|
||||
@@ -99,8 +99,8 @@ export default defineComponent({
|
||||
|
||||
const folder = folders.value.find((folder) => folder.id === id);
|
||||
|
||||
if (folder && folder.parent_folder) {
|
||||
parseFolder(folder.parent_folder);
|
||||
if (folder && folder.parent) {
|
||||
parseFolder(folder.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import { TranslateResult } from 'vue-i18n';
|
||||
type FolderRaw = {
|
||||
id: string;
|
||||
name: string;
|
||||
parent_folder: string | null;
|
||||
parent: string | null;
|
||||
};
|
||||
|
||||
export type Folder = {
|
||||
id: string | null;
|
||||
name: string | TranslateResult;
|
||||
parent_folder: string | null;
|
||||
parent: string | null;
|
||||
children?: Folder[];
|
||||
};
|
||||
|
||||
@@ -65,14 +65,14 @@ export default function useFolders() {
|
||||
export function nestFolders(rawFolders: FolderRaw[]) {
|
||||
return rawFolders
|
||||
.map((rawFolder) => nestChildren(rawFolder, rawFolders))
|
||||
.filter((folder) => folder.parent_folder === null);
|
||||
.filter((folder) => folder.parent === null);
|
||||
}
|
||||
|
||||
export function nestChildren(rawFolder: FolderRaw, rawFolders: FolderRaw[]) {
|
||||
const folder: FolderRaw & Folder = { ...rawFolder };
|
||||
|
||||
const children = rawFolders
|
||||
.filter((childFolder) => childFolder.parent_folder === rawFolder.id && childFolder.id !== rawFolder.id)
|
||||
.filter((childFolder) => childFolder.parent === rawFolder.id && childFolder.id !== rawFolder.id)
|
||||
.map((childRawFolder) => nestChildren(childRawFolder, rawFolders));
|
||||
|
||||
if (children.length > 0) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<files-not-found v-if="!loading && !item" />
|
||||
<private-view v-else :title="loading || !item ? $t('loading') : item.title">
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon secondary exact @click="$router.go(-1)">
|
||||
<v-button class="header-icon" rounded icon secondary exact :to="to">
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
</template>
|
||||
@@ -286,6 +286,11 @@ export default defineComponent({
|
||||
.filter((field: Field) => fieldsDenyList.includes(field.field) === false);
|
||||
});
|
||||
|
||||
const to = computed(() => {
|
||||
if(item.value && item.value?.folder) return `/files?folder=${item.value.folder}`
|
||||
else return '/files'
|
||||
})
|
||||
|
||||
const { formFields } = useFormFields(fieldsFiltered);
|
||||
|
||||
const confirmLeave = ref(false);
|
||||
@@ -327,6 +332,7 @@ export default defineComponent({
|
||||
selectedFolder,
|
||||
fileSrc,
|
||||
form,
|
||||
to
|
||||
};
|
||||
|
||||
function changeCacheBuster() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list large>
|
||||
<v-list-item v-for="item in navItems" :to="item.to" :key="item.to">
|
||||
<v-list-item-icon><v-icon :name="item.icon" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||
<v-list-item-text>{{ item.name }}</v-list-item-text>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
<v-list-item v-for="item in externalItems" :href="item.href" :key="item.href">
|
||||
<v-list-item-icon><v-icon :name="item.icon" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||
<v-list-item-text>{{ item.name }}</v-list-item-text>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item href="https://github.com/directus/directus/releases" class="version">
|
||||
<v-list-item-icon><v-icon name="directus" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="version">Directus {{ version }}</v-list-item-title>
|
||||
<v-list-item-text class="version">Directus {{ version }}</v-list-item-text>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<template #activator="{ toggle }">
|
||||
<v-icon name="more_vert" @click="toggle" class="ctx-toggle" />
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-list-item @click="deleteActive = true" class="danger">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="delete" outline />
|
||||
@@ -73,6 +73,7 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.v-button.delete {
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-125);
|
||||
}
|
||||
|
||||
.ctx-toggle {
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
:collection="collection"
|
||||
:field-data="fieldData"
|
||||
:relations="relations"
|
||||
:new-fields="newFields"
|
||||
:new-collections="newCollections"
|
||||
:is="`interface-options-${selectedInterface.id}`"
|
||||
v-else
|
||||
/>
|
||||
@@ -115,9 +117,9 @@ export default defineComponent({
|
||||
return interfaces.value.find((inter) => inter.id === state.fieldData.meta.interface);
|
||||
});
|
||||
|
||||
const { fieldData, relations } = toRefs(state);
|
||||
const { fieldData, relations, newCollections, newFields } = toRefs(state);
|
||||
|
||||
return { fieldData, relations, selectItems, selectedInterface };
|
||||
return { fieldData, relations, selectItems, selectedInterface, newCollections, newFields };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<v-icon name="list_alt" @click="toggle" v-tooltip="$t('select_existing')" :disabled="isExisting" />
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in items"
|
||||
:key="item.value"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="collection in availableCollections"
|
||||
:key="collection.collection"
|
||||
@@ -79,7 +79,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="collection in availableCollections"
|
||||
:key="collection.collection"
|
||||
@@ -130,7 +130,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in junctionFields"
|
||||
:key="item.value"
|
||||
@@ -166,7 +166,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in junctionFields"
|
||||
:key="item.value"
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="collection in availableCollections"
|
||||
:key="collection.collection"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="collection in availableCollections"
|
||||
:key="collection.collection"
|
||||
@@ -72,7 +72,7 @@
|
||||
<v-icon name="list_alt" @click="toggle" v-tooltip="$t('select_existing')" />
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="field in fields"
|
||||
:key="field.value"
|
||||
|
||||
@@ -125,9 +125,9 @@
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<div class="label type-label">{{ $t('translation') }}</div>
|
||||
<div class="label type-label">{{ $t('translations') }}</div>
|
||||
<interface-repeater
|
||||
v-model="fieldData.meta.translation"
|
||||
v-model="fieldData.meta.translations"
|
||||
:template="'{{ translation }} ({{ locale }})'"
|
||||
:fields="[
|
||||
{
|
||||
@@ -176,6 +176,69 @@ import { types } from '@/types';
|
||||
import i18n from '@/lang';
|
||||
import { state } from '../store';
|
||||
|
||||
export const fieldTypes = [
|
||||
{
|
||||
text: i18n.t('string'),
|
||||
value: 'string',
|
||||
},
|
||||
{
|
||||
text: i18n.t('text'),
|
||||
value: 'text',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('boolean'),
|
||||
value: 'boolean',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('integer'),
|
||||
value: 'integer',
|
||||
},
|
||||
{
|
||||
text: i18n.t('bigInteger'),
|
||||
value: 'bigInteger',
|
||||
},
|
||||
{
|
||||
text: i18n.t('float'),
|
||||
value: 'float',
|
||||
},
|
||||
{
|
||||
text: i18n.t('decimal'),
|
||||
value: 'decimal',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('timestamp'),
|
||||
value: 'timestamp',
|
||||
},
|
||||
{
|
||||
text: i18n.t('datetime'),
|
||||
value: 'dateTime',
|
||||
},
|
||||
{
|
||||
text: i18n.t('date'),
|
||||
value: 'date',
|
||||
},
|
||||
{
|
||||
text: i18n.t('time'),
|
||||
value: 'time',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('json'),
|
||||
value: 'json',
|
||||
},
|
||||
{
|
||||
text: i18n.t('csv'),
|
||||
value: 'csv',
|
||||
},
|
||||
{
|
||||
text: i18n.t('uuid'),
|
||||
value: 'uuid',
|
||||
},
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isExisting: {
|
||||
@@ -189,68 +252,7 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const typesWithLabels = computed(() => {
|
||||
return [
|
||||
{
|
||||
text: i18n.t('string'),
|
||||
value: 'string',
|
||||
},
|
||||
{
|
||||
text: i18n.t('text'),
|
||||
value: 'text',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('boolean'),
|
||||
value: 'boolean',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('integer'),
|
||||
value: 'integer',
|
||||
},
|
||||
{
|
||||
text: i18n.t('bigInteger'),
|
||||
value: 'bigInteger',
|
||||
},
|
||||
{
|
||||
text: i18n.t('float'),
|
||||
value: 'float',
|
||||
},
|
||||
{
|
||||
text: i18n.t('decimal'),
|
||||
value: 'decimal',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('timestamp'),
|
||||
value: 'timestamp',
|
||||
},
|
||||
{
|
||||
text: i18n.t('datetime'),
|
||||
value: 'dateTime',
|
||||
},
|
||||
{
|
||||
text: i18n.t('date'),
|
||||
value: 'date',
|
||||
},
|
||||
{
|
||||
text: i18n.t('time'),
|
||||
value: 'time',
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
text: i18n.t('json'),
|
||||
value: 'json',
|
||||
},
|
||||
{
|
||||
text: i18n.t('csv'),
|
||||
value: 'csv',
|
||||
},
|
||||
{
|
||||
text: i18n.t('uuid'),
|
||||
value: 'uuid',
|
||||
},
|
||||
];
|
||||
return fieldTypes
|
||||
});
|
||||
|
||||
const typeDisabled = computed(() => {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="collection in availableCollections"
|
||||
:key="collection.collection"
|
||||
@@ -79,7 +79,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="collection in availableCollections"
|
||||
:key="collection.collection"
|
||||
@@ -130,7 +130,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in junctionFields"
|
||||
:key="item.value"
|
||||
@@ -166,7 +166,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list dense class="monospace">
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in junctionFields"
|
||||
:key="item.value"
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -659,7 +659,7 @@ function initLocalStore(
|
||||
delete state.fieldData.schema;
|
||||
state.fieldData.type = null;
|
||||
|
||||
state.fieldData.meta.special = ['alias'];
|
||||
state.fieldData.meta.special = ['alias', 'no-data'];
|
||||
}
|
||||
|
||||
if (type === 'standard') {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-list-item :to="`/settings/data-model/${field.collection}/${field.field}`">
|
||||
<v-list-item-icon><v-icon name="edit" outline /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
@@ -82,7 +82,7 @@
|
||||
<v-icon @click.stop="toggle" name="more_vert" />
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-list-item :to="`/settings/data-model/${field.collection}/${field.field}`">
|
||||
<v-list-item-icon><v-icon name="edit" outline /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
@@ -184,7 +184,7 @@
|
||||
<v-card-title>{{ $t('delete_field_are_you_sure', { field: field.field }) }}</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-button @click="deleteActive = false" secondary>{{ $t('cancel') }}</v-button>
|
||||
<v-button :loading="deleting" @click="deleteField">{{ $t('delete') }}</v-button>
|
||||
<v-button :loading="deleting" @click="deleteField" class="delete">{{ $t('delete') }}</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
@@ -298,7 +298,7 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
async function saveDuplicate() {
|
||||
const newField = {
|
||||
const newField: any = {
|
||||
...props.field,
|
||||
field: duplicateName.value,
|
||||
collection: duplicateTo.value,
|
||||
@@ -500,4 +500,9 @@ export default defineComponent({
|
||||
|
||||
@include form-grid;
|
||||
}
|
||||
|
||||
.delete {
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-125);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<template v-for="(option, index) in addOptions">
|
||||
<v-divider v-if="option.divider === true" :key="index" />
|
||||
<v-list-item
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<private-view :title="collectionInfo && collectionInfo.name">
|
||||
<template #headline>{{ $t('settings_data_model') }}</template>
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon exact @click="$router.go(-1)">
|
||||
<v-button class="header-icon" rounded icon exact to="/settings/data-model">
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
@@ -28,14 +28,16 @@ export function getLocalTypeForField(
|
||||
return 'translations';
|
||||
}
|
||||
|
||||
const relationForCurrent = relations.find(
|
||||
(relation: Relation) =>
|
||||
const relationForCurrent = relations.find((relation: Relation) => {
|
||||
return (
|
||||
(relation.many_collection === collection && relation.many_field === field) ||
|
||||
(relation.one_collection === collection && relation.one_field === field)
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
if (relationForCurrent?.many_collection === collection && relationForCurrent?.many_field === field)
|
||||
if (relationForCurrent?.many_collection === collection && relationForCurrent?.many_field === field) {
|
||||
return 'm2o';
|
||||
}
|
||||
|
||||
if (relations[0].one_collection === 'directus_files' || relations[1].one_collection === 'directus_files') {
|
||||
return 'files';
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
<template #sidebar>
|
||||
<v-tabs vertical v-model="currentTab">
|
||||
<v-tab value="collection">{{ $t('collection_setup') }}</v-tab>
|
||||
<v-tab value="system">{{ $t('optional_system_fields') }}</v-tab>
|
||||
<v-tab value="system" :disabled="!collectionName">{{ $t('optional_system_fields') }}</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<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') }}</div>
|
||||
<div class="type-label">
|
||||
{{ $t('name') }}
|
||||
<v-icon class="required" v-tooltip="$t('required')" name="star" sup />
|
||||
</div>
|
||||
<v-input
|
||||
autofocus
|
||||
class="monospace"
|
||||
@@ -72,7 +75,7 @@
|
||||
<div class="grid system">
|
||||
<div class="field" v-for="(info, field) in systemFields" :key="field">
|
||||
<div class="type-label">{{ $t(info.label) }}</div>
|
||||
<v-input v-model="info.name" class="monospace">
|
||||
<v-input v-model="info.name" class="monospace" :class="{active: info.enabled}" @click.native="info.enabled = true">
|
||||
<template #prepend>
|
||||
<v-checkbox v-model="info.enabled" />
|
||||
</template>
|
||||
@@ -190,10 +193,12 @@ export default defineComponent({
|
||||
await api.post(`/collections`, {
|
||||
collection: collectionName.value,
|
||||
fields: [getPrimaryKeyField(), ...getSystemFields()],
|
||||
sort_field: sortField.value,
|
||||
archive_field: archiveField.value,
|
||||
archive_value: archiveValue.value,
|
||||
unarchive_value: unarchiveValue.value,
|
||||
meta: {
|
||||
sort_field: sortField.value,
|
||||
archive_field: archiveField.value,
|
||||
archive_value: archiveValue.value,
|
||||
unarchive_value: unarchiveValue.value,
|
||||
},
|
||||
});
|
||||
|
||||
await collectionsStore.hydrate();
|
||||
@@ -426,6 +431,16 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.system {
|
||||
::v-deep .v-input {
|
||||
.input {
|
||||
color: var(--foreground-subdued);
|
||||
}
|
||||
|
||||
&.active .input {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.v-icon {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
}
|
||||
@@ -438,4 +453,8 @@ export default defineComponent({
|
||||
.v-input.monospace {
|
||||
--v-input-font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.required {
|
||||
color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<v-list>
|
||||
<v-list-item @click="setFullAccess">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="check" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list large>
|
||||
<v-list-item to="/users" exact :active="currentRole === null">
|
||||
<v-list-item-icon><v-icon name="folder_shared" outline /></v-list-item-icon>
|
||||
<v-list-item-content>{{ $t('all_users') }}</v-list-item-content>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
<router-link :to="user.last_page">{{ user.last_page }}</router-link>
|
||||
</dd>
|
||||
</div>
|
||||
<div v-if="user.last_login">
|
||||
<dt>{{ $t('last_login') }}</dt>
|
||||
<dd>{{ user.last_login }}</dd>
|
||||
<div v-if="user.last_access">
|
||||
<dt>{{ $t('last_access') }}</dt>
|
||||
<dd>{{ lastAccessDate }}</dd>
|
||||
</div>
|
||||
<div v-if="user.created_on">
|
||||
<dt>{{ $t('created_on') }}</dt>
|
||||
@@ -36,8 +36,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, ref, watch } from '@vue/composition-api';
|
||||
import marked from 'marked';
|
||||
import localizedFormat from '../../../utils/localized-format';
|
||||
import i18n from '../../../lang';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -51,7 +53,21 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return { marked };
|
||||
const lastAccessDate = ref('');
|
||||
|
||||
watch(
|
||||
props,
|
||||
async () => {
|
||||
if (!props.user) return;
|
||||
lastAccessDate.value = await localizedFormat(
|
||||
new Date(props.user.last_access),
|
||||
String(i18n.t('date-fns_date_short'))
|
||||
);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return { marked, lastAccessDate };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<private-view :title="title">
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon secondary exact @click="$router.go(-1)">
|
||||
<v-button class="header-icon" rounded icon secondary exact to="/users">
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
</template>
|
||||
@@ -270,11 +270,11 @@ export default defineComponent({
|
||||
'id',
|
||||
'external_id',
|
||||
'last_page',
|
||||
'last_login',
|
||||
'created_on',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'modified_on',
|
||||
'last_access',
|
||||
];
|
||||
|
||||
const fieldsFiltered = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user