Update in-app docs (#3989)

* Fix root redirect

* Fix html in headings

* Add graphql syntax mode

* Install markdown-it + plugins

* Fix file copying on mac

* Replace md rendering engine

* Fix wrong links in guides

* Scroll to top on every route change

* Update in-app links

* Fix home redirect

* Fix deep-linking paths

* Add to reference

* Fix nested group scoping

* Fix open path reference
This commit is contained in:
Rijk van Zanten
2021-02-09 18:31:25 -05:00
committed by GitHub
parent e1d36706aa
commit df22a58c6a
13 changed files with 622 additions and 234 deletions

View File

@@ -18,7 +18,7 @@
"access": "public"
},
"scripts": {
"copy-docs-images": "rimraf public/img/docs && copyfiles -u 3 ../docs/assets/* public/img/docs --verbose",
"copy-docs-images": "rimraf public/img/docs && copyfiles -u 3 \"../docs/assets/**/*\" \"public/img/docs\" --verbose",
"predev": "npm run copy-docs-images",
"prebuild": "npm run copy-docs-images",
"dev": "vue-cli-service serve",
@@ -34,8 +34,8 @@
},
"gitHead": "4476da28dbbc2824e680137aa28b2b91b5afabec",
"dependencies": {
"@directus/format-title": "file:../packages/format-title",
"@directus/docs": "file:../docs"
"@directus/docs": "file:../docs",
"@directus/format-title": "file:../packages/format-title"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.8",

View File

@@ -2,93 +2,183 @@
icon: play_circle_outline
to: '/getting-started'
children:
- name: Introduction
to: '/getting-started/introduction'
- name: Support & FAQ
to: '/getting-started/support'
- name: Contributing
to: '/getting-started/contributing'
- name: Backing Directus
to: '/getting-started/backing-directus'
- to: '/getting-started/introduction'
name: Introduction
- to: '/getting-started/support'
name: Help & Support
- to: '/getting-started/backing-directus'
name: Backing Directus
- name: Concepts
icon: school
to: '/concepts'
children:
- name: Platform Overview
to: '/concepts/platform-overview'
- name: App Overview
to: '/concepts/app-overview'
- name: App Extensions
to: '/concepts/app-extensions'
- name: Activity & Versions
to: '/concepts/activity-and-versions'
- name: Files & Thumbnails
to: '/concepts/files-and-thumbnails'
- name: Internationalization
to: '/concepts/internationalization'
- name: Relationships
to: '/concepts/relationships'
- name: Users, Roles & Permissions
to: '/concepts/users-roles-and-permissions'
- to: '/concepts/activity'
name: Activity
- to: '/concepts/application'
name: Application
- to: '/concepts/collections'
name: Collections
- to: '/concepts/databases'
name: Databases
- to: '/concepts/displays'
name: Displays
- to: '/concepts/extensions'
name: Extensions
- to: '/concepts/fields'
name: Fields
- to: '/concepts/files'
name: Files
- to: '/concepts/interfaces'
name: Interfaces
- to: '/concepts/items'
name: Items
- to: '/concepts/layouts'
name: Layouts
- to: '/concepts/modules'
name: Modules
- to: '/concepts/permissions'
name: Permissions
- to: '/concepts/projects'
name: Projects
- to: '/concepts/relationships'
name: Relationships
- to: '/concepts/revisions'
name: Revisions
- to: '/concepts/roles'
name: Roles
- to: '/concepts/translations'
name: Translations
- to: '/concepts/types'
name: Types
- to: '/concepts/users'
name: Users
- name: Guides
icon: article
to: '/guides'
icon: article
children:
- name: Collections
to: '/guides/collections'
- name: Items
to: '/guides/items'
- name: Fields
to: '/guides/fields'
- name: Files
to: '/guides/files'
- name: Presets
to: '/guides/presets'
- name: Projects
to: '/guides/projects'
- name: Roles & Permissions
to: '/guides/roles-and-permissions'
- name: Users
to: '/guides/users'
- name: Webhooks
to: '/guides/webhooks'
- name: White-Labeling
to: '/guides/white-labeling'
- name: Extensions
to: '/guides/extensions'
children:
- name: Accessing Data
to: '/guides/extensions/accessing-data'
- name: API Endpoints
to: '/guides/extensions/api-endpoints'
- name: API Hooks
to: '/guides/extensions/api-hooks'
- name: Displays
to: '/guides/extensions/displays'
- name: Email Templates
to: '/guides/extensions/email-templates'
- name: Interfaces
to: '/guides/extensions/interfaces'
- name: Layouts
to: '/guides/extensions/layouts'
- name: Modules
to: '/guides/extensions/modules'
- name: Storage Adapters
to: '/guides/extensions/storage-adapters'
- to: '/guides/api-config'
name: API Config
- to: '/guides/api-endpoints'
name: API Endpoints
- to: '/guides/api-hooks'
name: API Hooks
- to: '/guides/collections'
name: Collections
- to: '/guides/displays'
name: Displays
- to: '/guides/fields'
name: Fields
- to: '/guides/files'
name: Files
- to: '/guides/interfaces'
name: Interfaces
- to: '/guides/items'
name: Items
- to: '/guides/layouts'
name: Layouts
- to: '/guides/migrations'
name: Migrations
- to: '/guides/modules'
name: Modules
- to: '/guides/permissions'
name: Permissions
- to: '/guides/presets'
name: Presets
- to: '/guides/projects'
name: Projects
- to: '/guides/roles'
name: Roles
- to: '/guides/styles'
name: Styles
- to: '/guides/users'
name: Users
- to: '/guides/webhooks'
name: Webhooks
- name: Reference
icon: code
to: '/reference'
children:
- name: Command Line Interface
to: '/reference/command-line-interface'
- name: Environment Variables
to: '/reference/environment-variables'
- name: Error Codes
to: '/reference/error-codes'
- name: Filter Rules
to: '/reference/filter-rules'
- name: Item Objects
to: '/reference/item-objects'
- to: '/reference/command-line-interface'
name: Command Line Interface
- to: '/reference/environment-variables'
name: Environment Variables
- to: '/reference/error-codes'
name: Error Codes
- to: '/reference/field-transforms'
name: Field Transforms
- to: '/reference/filter-rules'
name: Filter Rules
- to: '/reference/sdk-js'
name: JavaScript SDK
- name: API Reference
icon: api
to: '/reference/api'
children:
- to: '/reference/api/introduction'
name: Introduction
- to: '/reference/api/authentication'
name: Authentication
- name: Global Query Parameters
to: '/reference/api/query'
- name: REST API
to: '/reference/api/rest'
children:
- to: '/reference/api/rest/introduction'
name: Introduction
- to: '/reference/api/rest/activity'
name: Activity
- to: '/reference/api/rest/authentication'
name: Authentication
- to: '/reference/api/rest/collections'
name: Collections
- to: '/reference/api/rest/extensions'
name: Extensions
- to: '/reference/api/rest/fields'
name: Fields
- to: '/reference/api/rest/files'
name: Files
- to: '/reference/api/rest/folders'
name: Folders
- to: '/reference/api/rest/items'
name: Items
- to: '/reference/api/rest/permissions'
name: Permissions
- to: '/reference/api/rest/presets'
name: Presets
- to: '/reference/api/rest/relations'
name: Relations
- to: '/reference/api/rest/revisions'
name: Revisions
- to: '/reference/api/rest/roles'
name: Roles
- to: '/reference/api/rest/server'
name: Server
- to: '/reference/api/rest/settings'
name: Settings
- to: '/reference/api/rest/users'
name: Users
- to: '/reference/api/rest/utilities'
name: Utilities
- to: '/reference/api/rest/webhooks'
name: Webhooks
- to: '/reference/api/graphql'
name: GraphQL API
- to: '/reference/api/assets'
name: Assets
- to: '/reference/api/health'
name: Health Check
# - name: Contributing
# to: '/contributing'
# children:
# - to: '/contributing/introduction'
# name: Introduction
# - to: '/contributing/codebase-overview'
# name: Codebase Overview
# - to: '/contributing/running-locally'
# name: Running Locally
# - to: '/contributing/translations'
# name: Translating the App

View File

@@ -1,38 +1,68 @@
<template>
<div class="md" v-html="html" @click="onClick" />
<div class="md" :class="pageClass" v-html="html" @click="onClick" />
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onUpdated } from '@vue/composition-api';
import { defineComponent, ref, onMounted, onUpdated, inject } from '@vue/composition-api';
import marked, { Renderer } from 'marked';
import highlight from 'highlight.js';
import MarkdownIt from 'markdown-it';
import fm from 'front-matter';
import hljs from 'highlight.js';
import hljsGraphQL from '../../../utils/hljs-graphql';
import { getRootPath } from '../../../utils/get-root-path';
import router from '@/router';
hljs.registerLanguage('graphql', hljsGraphQL);
const md = new MarkdownIt({
html: true,
highlight(str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(lang, str).value;
} catch {
console.warn('There was an error highlighting in Markdown');
}
}
return '';
},
});
md.use(require('markdown-it-table-of-contents'), { includeLevel: [2] });
md.use(require('markdown-it-anchor').default, { permalink: true, permalinkSymbol: '#' });
function hintRenderer(type: string) {
return (tokens: any[], idx: number) => {
const token = tokens[idx];
let title = token.info.trim().slice(type.length).trim() || '';
if (title) title = `<div class="hint-title">${title}</div>`;
if (token.nesting === 1) {
return `<div class="${type} hint">${title}\n`;
} else {
return '</div>\n';
}
};
}
md.use(require('markdown-it-container'), 'tip', { render: hintRenderer('tip') });
md.use(require('markdown-it-container'), 'warning', { render: hintRenderer('warning') });
md.use(require('markdown-it-container'), 'danger', { render: hintRenderer('danger') });
export default defineComponent({
setup(props, { slots }) {
const html = ref('');
const pageClass = ref<string>();
onMounted(generateHTML);
onUpdated(generateHTML);
return { html, onClick };
async function onClick($event: Event) {
if ($event.target instanceof HTMLElement) {
const href = $event.target.getAttribute('href');
if (href && $event.target.classList.contains('copy')) {
await navigator.clipboard.writeText(window.location.href.split('#')[0] + href);
}
if (href && href.indexOf('/admin/docs/') === 0) {
$event.preventDefault();
await router.push(href.replace('/admin/docs/', '/docs/'));
}
}
}
return { html, onClick, pageClass };
function generateHTML() {
if (slots.default === null || !slots.default()?.[0]?.text) {
@@ -40,65 +70,78 @@ export default defineComponent({
return;
}
let htmlString = slots.default()[0].text!;
const source = slots.default()[0].text!;
const { attributes, body } = fm<{ pageClass?: string }>(source);
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}>`;
},
link(href, title, text) {
let classname = 'body-link link';
if (href && href.indexOf('/') === 0 && href.indexOf('/admin/docs/') === -1) href = `/admin/docs${href}`;
if (href && href.indexOf('#') === 0) classname = `${classname} copy`;
return `<a href="${href}" class="${classname}">${text}</a>`;
},
image(href, title, text) {
if (!href) return '';
let paths = href.split('/');
let markdown = body;
while ((paths[0] === 'assets') === false) {
paths = paths.slice(1);
}
const rawImages = body.matchAll(/!\[[^\]]*\]\((?<filename>.*?)(?=\"|\))(?<optionalpart>\".*\")?\)/g) ?? [];
const rootPath = getRootPath();
const path = `/img/docs/${paths.slice(1).join('/')}`;
for (const rawImage of rawImages) {
const filenameParts = rawImage.groups!.filename.split('/');
const classname = 'body-image';
while (filenameParts.includes('assets')) {
filenameParts.shift();
}
return `<img src="${path}" class="${classname}" alt="${text}">`;
},
};
const newFilename = `${rootPath}img/docs/${filenameParts.join('/')}`;
const newImage = rawImage[0].replace(rawImage.groups!.filename, newFilename);
markdown = markdown.replace(rawImage[0], newImage);
}
// Marked merges it's default rendered with our extension. It's typed as a full rendered however
marked.use({ renderer } as any);
pageClass.value = attributes?.pageClass;
htmlString = htmlString.replace(/::: ([^\s]+) ([^\n]+)([\s\S]*?)\n:::/gm, (match, type, title, body) => {
body = marked(body);
return `<div class="hint ${type}"><p class="hint-title">${title}</p><p class="hint-body">${body}</p></div>`;
});
htmlString = marked(htmlString, {
highlight: (code, lang) => {
return highlight.highlightAuto(code, [lang]).value;
},
});
const htmlString = md.render(markdown);
html.value = htmlString;
// The Markdown is fetched async on page transition, which means the # link already exists before the markdown does
// This will force the main el to scroll down to the targetted element on updates of the content
const mainElement = inject('main-element', ref<Element | null>(null));
if (router.currentRoute.hash) {
const linkedEl = document.querySelector(router.currentRoute.hash) as HTMLElement;
if (linkedEl) {
mainElement.value?.scrollTo({ top: linkedEl.offsetTop - 100 });
}
}
}
function onClick(event: MouseEvent) {
if (
event.target &&
(event.target as HTMLElement).tagName.toLowerCase() === 'a' &&
(event.target as HTMLAnchorElement).href
) {
const link = (event.target as HTMLAnchorElement).getAttribute('href')!;
if (link.startsWith('http') || link.startsWith('#')) return;
event.preventDefault();
const parts = link.split('#');
parts[0] = parts[0].endsWith('/') ? parts[0].slice(0, -1) : parts[0];
router.push({
path: `/docs${parts[0]}`,
hash: parts[1],
});
}
}
},
});
</script>
<style lang="scss" scoped>
// stylelint-disable
.error {
padding: 20vh 0;
}
.md {
max-width: 740px;
::v-deep {
font-weight: 400;
font-size: 16px;
@@ -448,14 +491,13 @@ export default defineComponent({
border-left: 2px solid var(--primary);
&-title {
margin-bottom: 0.5em;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
font-weight: bold;
}
&-body {
margin-top: 0.5em;
}
&.tip {
border-left: 2px solid var(--success);
}
@@ -470,6 +512,102 @@ export default defineComponent({
border-left: 2px solid var(--danger);
}
}
.two-up {
margin-top: 3rem;
}
.two-up .right {
margin-top: 50px;
}
.table-of-contents {
margin-top: -20px;
}
pre,
pre[class*='language-'] {
margin-top: 0;
}
@media (min-width: 1000px) {
.two-up {
display: grid;
grid-template-columns: minmax(0, 4fr) minmax(0, 3fr);
grid-gap: 40px;
align-items: start;
}
.two-up .left,
.two-up .right {
> *:first-child {
margin-top: 0 !important;
}
}
.two-up .right {
margin-top: 0;
position: sticky;
top: 100px;
}
}
}
&.page-reference {
max-width: 1200px;
::v-deep {
hr {
width: calc(100% + 5rem);
left: -2.5rem;
position: relative;
margin: 3rem 0;
}
h2 {
border-bottom: 0;
margin-top: 3rem;
font-size: 2rem;
}
h3 {
margin-top: 3rem;
margin-bottom: 0.5rem;
font-size: 1.2rem;
}
h4 {
margin-top: 2rem;
margin-bottom: 0;
}
.definitions {
font-size: 0.9rem;
> p {
border-bottom: 2px solid var(--border-subdued);
padding: 0.8rem 0;
margin: 0;
&:first-child {
border-top: 2px solid var(--border-subdued);
}
> code:first-child {
background: transparent;
padding: 0;
margin-right: 0.2rem;
font-weight: 700;
font-size: 0.9rem;
border: 0;
}
> strong {
color: #aaa;
}
}
}
}
}
}
</style>

View File

@@ -1,13 +1,22 @@
<template>
<v-divider v-if="section.divider" />
<v-list-group v-else-if="section.children" :dense="dense" :multiple="false" :value="section.to">
<v-list-group :scope="scope" 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-text>{{ section.name }}</v-list-item-text>
</v-list-item-content>
</template>
<navigation-list-item v-for="(child, index) in section.children" :key="index" :section="child" dense />
<v-item-group :scope="section.to">
<navigation-list-item
v-for="(child, index) in section.children"
:key="index"
:section="child"
dense
:scope="section.to"
/>
</v-item-group>
</v-list-group>
<v-list-item v-else :to="`/docs${section.to}`" :dense="dense" :value="section.to">
@@ -19,7 +28,7 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from '@vue/composition-api';
import { defineComponent, PropType } from '@vue/composition-api';
import { Link, Group } from '@directus/docs';
export default defineComponent({
@@ -33,6 +42,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
scope: {
type: String,
default: null,
},
},
});
</script>

View File

@@ -18,6 +18,11 @@ function spreadPath(path: string) {
for (let i = 1; i < sections.length; i++) {
paths.push(paths[i - 1] + '/' + sections[i]);
}
if (paths[0] === '/reference' && paths[1] === '/reference/api') {
paths.shift();
}
return paths;
}
@@ -30,35 +35,16 @@ export default defineComponent({
},
},
setup(props) {
const _selection = ref<string[] | null>(null);
const selection = ref<string[] | null>(null);
watch(
() => props.path,
(newPath) => {
if (newPath === null) return;
_selection.value = spreadPath(newPath.replace('/docs', ''));
selection.value = spreadPath(newPath.replace('/docs', ''));
}
);
const selection = computed({
get() {
if (_selection.value === null && props.path !== null)
_selection.value = spreadPath(props.path.replace('/docs', ''));
return _selection.value || [];
},
set(newSelection: string[]) {
if (newSelection.length === 0) {
_selection.value = [];
} else {
if (_selection.value && _selection.value.includes(newSelection[0])) {
_selection.value = _selection.value.filter((s) => s !== newSelection[0]);
} else {
_selection.value = spreadPath(newSelection[0]);
}
}
},
});
return { navSections: navLinks, selection };
},
});

View File

@@ -8,7 +8,7 @@ export default defineModule(({ i18n }) => {
const routes: RouteConfig[] = [
{
path: '/',
component: StaticDocs,
redirect: '/getting-started/introduction/',
},
...parseRoutes(files),
{

View File

@@ -1,11 +1,15 @@
<template>
<private-view :title="title" ref="view">
<private-view :title="title">
<template #title-outer:prepend>
<v-button rounded disabled icon>
<v-icon name="info" />
</v-button>
</template>
<template #title>
<h1 v-html="title" class="type-title"></h1>
</template>
<template #navigation>
<docs-navigation :path="path" />
</template>
@@ -23,7 +27,7 @@
</template>
<script lang="ts">
import { defineComponent, ref, computed, onUpdated } from '@vue/composition-api';
import { defineComponent, ref, computed } from '@vue/composition-api';
import DocsNavigation from '../components/navigation.vue';
import Markdown from '../components/markdown.vue';
import marked from 'marked';
@@ -35,7 +39,11 @@ async function getMarkdownForPath(path: string) {
pathParts.shift();
}
const docsPath = pathParts.join('/');
let docsPath = pathParts.join('/');
if (docsPath.endsWith('/')) {
docsPath = docsPath.slice(0, -1);
}
const mdModule = await import('raw-loader!@directus/docs/' + docsPath + '.md');
@@ -62,31 +70,36 @@ export default defineComponent({
setup() {
const path = ref<string | null>(null);
const markdown = ref('');
const view = ref<Vue>();
const title = computed(() => {
const firstLine = markdown.value.split('\n').shift();
return firstLine?.substring(2).trim();
const lines = markdown.value.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('# ')) {
return lines[i].substring(2).trim();
}
}
});
const markdownWithoutTitle = computed(() => {
const lines = markdown.value.split('\n');
lines.shift();
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('# ')) {
lines.splice(i, 1);
break;
}
}
return lines.join('\n');
});
onUpdated(() => {
view.value?.$data.contentEl?.scrollTo({ top: 0 });
});
return { markdown, title, markdownWithoutTitle, view, marked, path };
return { markdown, title, markdownWithoutTitle, marked, path };
},
});
</script>
<style lang="scss" scoped>
.docs-content {
max-width: 740px;
padding: 0 var(--content-padding) var(--content-padding-bottom);
}
</style>

View File

@@ -0,0 +1,45 @@
import { HASH_COMMENT_MODE, QUOTE_STRING_MODE, NUMBER_MODE } from 'highlight.js';
export default () => ({
aliases: ['gql'],
keywords: {
keyword:
'query mutation subscription|10 type input schema directive interface union scalar fragment|10 enum on ...',
literal: 'true false null',
},
contains: [
HASH_COMMENT_MODE,
QUOTE_STRING_MODE,
NUMBER_MODE,
{
className: 'type',
begin: '[^\\w][A-Z][a-z]',
end: '\\W',
excludeEnd: !0,
},
{
className: 'literal',
begin: '[^\\w][A-Z][A-Z]',
end: '\\W',
excludeEnd: !0,
},
{
className: 'variable',
begin: '\\$',
end: '\\W',
excludeEnd: !0,
},
{
className: 'keyword',
begin: '[.]{2}',
end: '\\.',
},
{
className: 'meta',
begin: '@',
end: '\\W',
excludeEnd: !0,
},
],
illegal: /([;<']|BEGIN)/,
});

View File

@@ -61,7 +61,7 @@
</template>
<script lang="ts">
import { defineComponent, ref, provide, toRefs, computed } from '@vue/composition-api';
import { defineComponent, ref, provide, toRefs, computed, onUpdated, nextTick } from '@vue/composition-api';
import ModuleBar from './components/module-bar/';
import SidebarDetailGroup from './components/sidebar-detail-group/';
import HeaderBar from './components/header-bar';
@@ -71,8 +71,7 @@ import NotificationsGroup from './components/notifications-group/';
import NotificationsPreview from './components/notifications-preview/';
import NotificationDialogs from './components/notification-dialogs/';
import { useUserStore, useAppStore } from '../../stores';
import i18n from '../../lang';
import emitter, { Events } from '../../events';
import router from '../../router';
export default defineComponent({
components: {
@@ -112,6 +111,22 @@ export default defineComponent({
provide('main-element', contentEl);
router.afterEach(async (to, from) => {
contentEl.value?.scrollTo({ top: 0 });
// await nextTick();
// const hash = to.hash;
// if (hash) {
// const linkedEl = document.querySelector(hash) as HTMLElement;
// if (linkedEl) {
// contentEl.value?.scrollTo({ top: linkedEl.offsetTop - 100, behavior: 'smooth' });
// }
// }
});
return {
navOpen,
contentEl,
@@ -195,21 +210,21 @@ export default defineComponent({
}
.content {
--border-radius: 6px;
--input-height: 60px;
--input-padding: 16px; // (60 - 4 - 24) / 2
--form-vertical-gap: 52px;
position: relative;
flex-grow: 1;
width: 100%;
height: 100%;
overflow: auto;
scroll-padding-top: 100px;
scroll-behavior: smooth;
// Page Content Spacing (Could be converted to Project Setting toggle)
font-size: 15px;
line-height: 24px;
--border-radius: 6px;
--input-height: 60px;
--input-padding: 16px; // (60 - 4 - 24) / 2
--form-vertical-gap: 52px;
main {
display: contents;

View File

@@ -1,14 +1,15 @@
# Projects
> A Project is a complete instance of Directus, including its **Database**, config file, asset storage, and any custom extensions. [Learn more about Projects](/concepts/projects/).
> A Project is a complete instance of Directus, including its **Database**, config file, asset storage, and any custom
> extensions. [Learn more about Projects](/concepts/projects/).
## Creating a Project
To install Directus, choose one of the following methods.
- [Command Line Interface (CLI)](/guides/installation/cli.md)
- [Docker](/guides/installation/docker.md)
- [Manually](/guides/installation/manual.md)
- [Command Line Interface (CLI)](/guides/installation/cli)
- [Docker](/guides/installation/docker)
- [Manually](/guides/installation/manual)
## Configuring a Project
@@ -22,14 +23,14 @@ environment variables, each is explained in the following reference:
1. Navigate to **Settings > Project Settings**
2. Configure any of the following **branding fields**
- **Project Name** — The name used at the top of the [Navigation Bar](/concepts/application/#_2-navigation-bar) and on the login/public
pages
- **Project Name** — The name used at the top of the [Navigation Bar](/concepts/application/#_2-navigation-bar) and on
the login/public pages
- **Project URL** — The URL when clicking the logo at the top of the [Module Bar](/concepts/application/#_1-module-bar)
- **Project Color** — The color used behind the logo at the top of the [Module Bar](/concepts/application/#_1-module-bar), on the
login/public pages, and for the browser's FavIcon
- **Project Logo** — A 40x40 pixel logo at the top of the [Module Bar](/concepts/application/#_1-module-bar) and on the login/public
pages. The image is _inset_ within the 64x64 pixel square filled with the Project Color, so we recommend using a SVG
or PNG logo with transparency to avoid a "boxy" look.
- **Project Color** — The color used behind the logo at the top of the
[Module Bar](/concepts/application/#_1-module-bar), on the login/public pages, and for the browser's FavIcon
- **Project Logo** — A 40x40 pixel logo at the top of the [Module Bar](/concepts/application/#_1-module-bar) and on the
login/public pages. The image is _inset_ within the 64x64 pixel square filled with the Project Color, so we recommend
using a SVG or PNG logo with transparency to avoid a "boxy" look.
::: tip Browser FavIcon & Title

View File

@@ -7,8 +7,9 @@ pageClass: page-reference
<div class="two-up">
<div class="left">
> Files are the objects that contain information about the file managed through Directus. If you're looking for the actual file
data, including thumbnails, see the [/assets](/reference/api/assets/) endpoint. [Learn more about Files](/concepts/files/).
> Files are the objects that contain information about the file managed through Directus. If you're looking for the
> actual file data, including thumbnails, see the [/assets](/reference/api/assets/) endpoint.
> [Learn more about Files](/concepts/files/).
</div>
<div class="right">
@@ -45,16 +46,16 @@ Title for the file.
Mimetype of the file.
`folder` **many-to-one**\
What (virtual) folder the file is in. Many-to-one to [folders](/reference/api/folders/).
What (virtual) folder the file is in. Many-to-one to [folders](/reference/api/rest/folders/).
`uploaded_by` **many-to-one**\
Who uploaded the file. Many-to-one to [users](/reference/api/users/).
Who uploaded the file. Many-to-one to [users](/reference/api/rest/users/).
`uploaded_on` **datetime**\
When the file was uploaded.
`modified_by` **many-to-one**\
Who updated the file last. Many-to-one to [users](/reference/api/users/).
Who updated the file last. Many-to-one to [users](/reference/api/rest/users/).
`filesize` **number**\
Size of the file in bytes.

134
package-lock.json generated
View File

@@ -18683,12 +18683,29 @@
"@types/node": "*"
}
},
"@types/linkify-it": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.0.tgz",
"integrity": "sha512-x9OaQQTb1N2hPZ/LWJsqushexDvz7NgzuZxiRmZio44WPuolTZNHDBCrOxCzRVOMwamJRO2dWax5NbygOf1OTQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.168",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
"integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
"dev": true
},
"@types/markdown-it": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.0.1.tgz",
"integrity": "sha512-mHfT8j/XkPb1uLEfs0/C3se6nd+webC2kcqcy8tgcVr0GDEONv/xaQzAN+aQvkxQXk/jC0Q6mPS+0xhFwRF35g==",
"dev": true,
"requires": {
"@types/highlight.js": "^9.7.0",
"@types/linkify-it": "*",
"@types/mdurl": "*"
}
},
"@types/marked": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-1.2.2.tgz",
@@ -18704,6 +18721,12 @@
"@types/unist": "*"
}
},
"@types/mdurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz",
"integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==",
"dev": true
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -20790,6 +20813,42 @@
"markdown-it-emoji": "^1.4.0",
"markdown-it-table-of-contents": "^0.4.0",
"prismjs": "^1.13.0"
},
"dependencies": {
"linkify-it": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
"dev": true,
"requires": {
"uc.micro": "^1.0.1"
}
},
"markdown-it": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
"integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"entities": "~1.1.1",
"linkify-it": "^2.0.0",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
}
},
"markdown-it-anchor": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
"dev": true
},
"markdown-it-table-of-contents": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz",
"integrity": "sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw==",
"dev": true
}
}
},
"@vuepress/markdown-loader": {
@@ -29225,6 +29284,14 @@
"integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
"dev": true
},
"front-matter": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz",
"integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==",
"requires": {
"js-yaml": "^3.13.1"
}
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -32985,10 +33052,9 @@
"dev": true
},
"linkify-it": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
"dev": true,
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz",
"integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==",
"requires": {
"uc.micro": "^1.0.1"
}
@@ -33714,23 +33780,33 @@
"dev": true
},
"markdown-it": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz",
"integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==",
"dev": true,
"version": "12.0.4",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.0.4.tgz",
"integrity": "sha512-34RwOXZT8kyuOJy25oJNJoulO8L0bTHYWXcdZBYZqFnjIy3NgjeoM3FmPXIOFQ26/lSHYMr8oc62B6adxXcb3Q==",
"requires": {
"argparse": "^1.0.7",
"entities": "~1.1.1",
"linkify-it": "^2.0.0",
"argparse": "^2.0.1",
"entities": "~2.1.0",
"linkify-it": "^3.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
}
}
},
"markdown-it-anchor": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
"dev": true
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-7.0.2.tgz",
"integrity": "sha512-UtYFAkce16mJlixXUMXUf14ZmOWK2YHLGKUpkZUn98w3qP8nVhb7k5sCBZmVHGMq3SpYtrxQ5XOdHQHRuLR40Q=="
},
"markdown-it-chain": {
"version": "1.3.0",
@@ -33766,10 +33842,9 @@
}
},
"markdown-it-container": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-2.0.0.tgz",
"integrity": "sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=",
"dev": true
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz",
"integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw=="
},
"markdown-it-emoji": {
"version": "1.4.0",
@@ -33778,10 +33853,9 @@
"dev": true
},
"markdown-it-table-of-contents": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz",
"integrity": "sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw==",
"dev": true
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.5.2.tgz",
"integrity": "sha512-6o+rxSwzXmXCUn1n8QGTSpgbcnHBG6lUU8x7A5Cssuq5vbfzTfitfGPvQ5PZkp+gP1NGS/DR2rkYqJPn0rbZ1A=="
},
"markdown-table": {
"version": "1.1.3",
@@ -33911,8 +33985,7 @@
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
"dev": true
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
},
"media-typer": {
"version": "0.3.0",
@@ -44630,8 +44703,7 @@
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
"dev": true
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"uglify-js": {
"version": "3.4.10",
@@ -45597,6 +45669,14 @@
"requires": {
"@vuepress/shared-utils": "^1.2.0",
"markdown-it-container": "^2.0.0"
},
"dependencies": {
"markdown-it-container": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-2.0.0.tgz",
"integrity": "sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=",
"dev": true
}
}
},
"vuepress-plugin-smooth-scroll": {

View File

@@ -65,6 +65,7 @@
"@types/jsonwebtoken": "^8.5.0",
"@types/keyv": "^3.1.1",
"@types/lodash": "^4.14.159",
"@types/markdown-it": "^12.0.1",
"@types/marked": "^1.1.0",
"@types/mime-types": "^2.1.0",
"@types/mocha": "^8.0.4",
@@ -194,7 +195,12 @@
"create-directus-project": "file:packages/create-directus-project",
"directus": "file:api",
"dompurify": "^2.2.6",
"escape-string-regexp": "^4.0.0"
"escape-string-regexp": "^4.0.0",
"front-matter": "^4.0.2",
"markdown-it": "^12.0.4",
"markdown-it-anchor": "^7.0.2",
"markdown-it-container": "^3.0.0",
"markdown-it-table-of-contents": "^0.5.2"
},
"husky": {
"hooks": {