255 lines
8.3 KiB
Vue
255 lines
8.3 KiB
Vue
<template>
|
|
<Head>
|
|
<Title>d[any] - Modification</Title>
|
|
</Head>
|
|
<div class="flex flex-1 flex-col xl:-mx-12 xl:-my-8 lg:-mx-8 lg:-my-6 -mx-6 -my-3 overflow-hidden">
|
|
<div class="z-50 flex w-full items-center justify-between border-b border-light-35 dark:border-dark-35 px-2">
|
|
<div class="flex items-center px-2 gap-4">
|
|
<!-- <CollapsibleTrigger asChild>
|
|
<Button icon class="!bg-transparent group md:hidden">
|
|
<Icon class="group-data-[state=open]:hidden" icon="radix-icons:hamburger-menu" />
|
|
<Icon class="group-data-[state=closed]:hidden" icon="radix-icons:cross-1" />
|
|
</Button>
|
|
</CollapsibleTrigger> -->
|
|
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-opacity-70 m-2 flex items-center gap-4" aria-label="Accueil" :to="{ path: '/', force: true }">
|
|
<Avatar src="/logo.dark.svg" class="dark:block hidden" />
|
|
<Avatar src="/logo.light.svg" class="block dark:hidden" />
|
|
<span class="text-xl max-md:hidden">d[any]</span>
|
|
</NuxtLink>
|
|
</div>
|
|
<div class="flex items-center px-2 gap-4">
|
|
<NuxtLink class="text-light-100 dark:text-dark-100 hover:text-light-70 dark:hover:text-dark-70" :to="{ name: 'user-login' }">{{ user!.username }}</NuxtLink>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-1 flex-row relative h-screen overflow-hidden">
|
|
<div class="bg-light-0 dark:bg-dark-0 z-40 w-screen md:w-[18rem] border-r border-light-30 dark:border-dark-30 flex flex-col justify-between my-2 max-md:data-[state=closed]:hidden">
|
|
<div class="flex-1 px-2 max-w-full max-h-full overflow-y-auto overflow-x-hidden" ref="tree"></div>
|
|
<div class="xl:px-12 px-6 pt-4 pb-2 text-center text-xs text-light-60 dark:text-dark-60">
|
|
<NuxtLink class="hover:underline italic" :to="{ name: 'roadmap' }">Roadmap</NuxtLink> - <NuxtLink class="hover:underline italic" :to="{ name: 'legal' }">Mentions légales</NuxtLink>
|
|
<p>Copyright Peaceultime - 2025</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-1 flex-row max-h-full overflow-hidden" ref="container"></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { Content, Editor } from '#shared/content.util';
|
|
import { loading } from '#shared/proses';
|
|
|
|
definePageMeta({
|
|
rights: ['admin', 'editor'],
|
|
layout: 'null',
|
|
});
|
|
|
|
const { user } = useUserSession();
|
|
const tree = useTemplateRef('tree'), container = useTemplateRef('container');
|
|
let editor: Editor;
|
|
|
|
onMounted(async () => {
|
|
if(tree.value && container.value && await Content.ready)
|
|
{
|
|
const load = loading('normal');
|
|
tree.value.appendChild(load);
|
|
|
|
editor = new Editor();
|
|
|
|
tree.value.replaceChild(editor.tree.container, load);
|
|
container.value.appendChild(editor.container);
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
editor?.unmount();
|
|
})
|
|
/* import { Icon } from '@iconify/vue/dist/iconify.js';
|
|
import type { Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/dist/types/tree-item';
|
|
import type { FileType, LocalContent, TreeItem } from '#shared/content.util';
|
|
import { DEFAULT_CONTENT, iconByType, Content, getPath } from '#shared/content.util';
|
|
import type { Preferences } from '~/types/general';
|
|
import { fakeA as proseA } from '#shared/proses';
|
|
import { parsePath } from '~/shared/general.util';
|
|
import type { CanvasContent } from '~/types/canvas';
|
|
|
|
export type TreeItemEditable = LocalContent &
|
|
{
|
|
parent?: string;
|
|
name?: string;
|
|
customPath?: boolean;
|
|
children?: TreeItemEditable[];
|
|
}
|
|
|
|
definePageMeta({
|
|
rights: ['admin', 'editor'],
|
|
layout: 'null',
|
|
});
|
|
|
|
const { user } = useUserSession();
|
|
|
|
const router = useRouter();
|
|
const open = ref(true), topOpen = ref(true);
|
|
|
|
const toaster = useToast();
|
|
const saveStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle');
|
|
|
|
let navigation = Content.tree as TreeItemEditable[];
|
|
const selected = ref<TreeItemEditable>();
|
|
|
|
const preferences = useCookie<Preferences>('preferences', { default: () => ({ markdown: { editing: 'split' }, canvas: { gridSnap: true, neighborSnap: true, spacing: 32 } }), watch: true, maxAge: 60*60*24*31 });
|
|
|
|
watch(selected, async (value, old) => {
|
|
if(selected.value)
|
|
{
|
|
if(!selected.value.content && selected.value.path)
|
|
{
|
|
selected.value = await Content.content(selected.value.path);
|
|
}
|
|
|
|
router.replace({ hash: '#' + encodeURIComponent(selected.value!.path || getPath(selected.value!)) });
|
|
}
|
|
else
|
|
{
|
|
router.replace({ hash: '' });
|
|
}
|
|
})
|
|
const debouncedSave = useDebounceFn(save, 60000, { maxWait: 180000 });
|
|
|
|
useShortcuts({
|
|
//meta_s: { usingInput: true, handler: () => save(), prevent: true },
|
|
meta_n: { usingInput: true, handler: () => add('markdown'), prevent: true },
|
|
meta_shift_n: { usingInput: true, handler: () => add('folder'), prevent: true },
|
|
meta_shift_z: { usingInput: true, handler: () => router.push({ name: 'explore-path', params: { path: 'index' } }), prevent: true }
|
|
})
|
|
|
|
function add(type: FileType): void
|
|
{
|
|
if(!navigation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const news = [...tree.search(navigation, 'title', 'Nouveau')].filter((e, i, a) => a.indexOf(e) === i);
|
|
const title = `Nouveau${news.length > 0 ? ' (' + news.length +')' : ''}`;
|
|
const item: TreeItemEditable = { navigable: true, private: false, parent: '', path: '', title: title, name: parsePath(title), type: type, order: 0, children: [], customPath: false, content: DEFAULT_CONTENT[type], owner: -1, timestamp: new Date(), visit: 0 };
|
|
|
|
if(!selected.value)
|
|
{
|
|
navigation = [...navigation, item];
|
|
}
|
|
else if(selected.value?.children)
|
|
{
|
|
item.parent = getPath(selected.value);
|
|
navigation = tree.insertChild(navigation, item.parent, item);
|
|
}
|
|
else
|
|
{
|
|
navigation = tree.insertAfter(navigation, getPath(selected.value), item);
|
|
}
|
|
}
|
|
function updateTree(instruction: Instruction, itemId: string, targetId: string) : TreeItemEditable[] | undefined {
|
|
if(!navigation)
|
|
return;
|
|
|
|
const item = tree.find(navigation, itemId);
|
|
const target = tree.find(navigation, targetId);
|
|
|
|
if(!item)
|
|
return;
|
|
|
|
if (instruction.type === 'reparent') {
|
|
const path = tree.getPathToItem({
|
|
current: navigation,
|
|
targetId: targetId,
|
|
});
|
|
if (!path) {
|
|
console.error(`missing ${path}`);
|
|
return;
|
|
}
|
|
|
|
const desiredId = path[instruction.desiredLevel];
|
|
let result = tree.remove(navigation, itemId);
|
|
result = tree.insertAfter(result, desiredId, item);
|
|
return result;
|
|
}
|
|
|
|
// the rest of the actions require you to drop on something else
|
|
if (itemId === targetId)
|
|
return navigation;
|
|
|
|
if (instruction.type === 'reorder-above') {
|
|
let result = tree.remove(navigation, itemId);
|
|
result = tree.insertBefore(result, targetId, item);
|
|
return result;
|
|
}
|
|
|
|
if (instruction.type === 'reorder-below') {
|
|
let result = tree.remove(navigation, itemId);
|
|
result = tree.insertAfter(result, targetId, item);
|
|
return result;
|
|
}
|
|
|
|
if (instruction.type === 'make-child') {
|
|
if(!target || target.type !== 'folder')
|
|
return;
|
|
|
|
let result = tree.remove(navigation, itemId);
|
|
result = tree.insertChild(result, targetId, item);
|
|
rebuildPath([item], targetId);
|
|
return result;
|
|
}
|
|
|
|
return navigation;
|
|
}
|
|
function transform(items: TreeItem[] | undefined): TreeItemEditable[] | undefined
|
|
{
|
|
return items?.map(e => ({
|
|
...e,
|
|
parent: e.path.substring(0, e.path.lastIndexOf('/')),
|
|
name: e.path.substring(e.path.lastIndexOf('/') + 1),
|
|
customPath: e.path.substring(e.path.lastIndexOf('/') + 1) !== parsePath(e.title),
|
|
children: transform(e.children)
|
|
})) as TreeItemEditable[] | undefined;
|
|
}
|
|
function flatten(items: TreeItemEditable[] | undefined): TreeItemEditable[]
|
|
{
|
|
return items?.flatMap(e => [e, ...flatten(e.children)]) ?? [];
|
|
}
|
|
function drop(instruction: Instruction, itemId: string, targetId: string)
|
|
{
|
|
navigation = updateTree(instruction, itemId, targetId) ?? navigation ?? [];
|
|
}
|
|
function rebuildPath(tree: TreeItemEditable[] | null | undefined, parentPath: string)
|
|
{
|
|
if(!tree)
|
|
return;
|
|
|
|
tree.forEach(e => {
|
|
e.parent = parentPath;
|
|
rebuildPath(e.children, getPath(e));
|
|
});
|
|
}
|
|
function save()
|
|
{
|
|
if(selected.value && selected.value.content)
|
|
{
|
|
selected.value.path = getPath(selected.value);
|
|
Content.save(selected.value);
|
|
}
|
|
}
|
|
|
|
const defaultExpanded = computed(() => {
|
|
if(router.currentRoute.value.hash)
|
|
{
|
|
const split = router.currentRoute.value.hash.substring(1).split('/');
|
|
split.forEach((e, i) => { if(i !== 0) split[i] = split[i - 1] + '/' + e });
|
|
return split;
|
|
}
|
|
});
|
|
watch(router.currentRoute, (value) => {
|
|
if(value && value.hash && navigation)
|
|
selected.value = tree.find(navigation, decodeURIComponent(value.hash.substring(1)));
|
|
else
|
|
selected.value = undefined;
|
|
}, { immediate: true }); */
|
|
</script> |