Compare commits
20 Commits
bb7a84259c
...
05d11e0ea5
Author | SHA1 | Date | |
---|---|---|---|
05d11e0ea5 | |||
d030346dde | |||
6016813f4c | |||
4838b7b624 | |||
5c6cb84383 | |||
25b7e723f6 | |||
609562b7fa | |||
a7b8c07952 | |||
e28f6b6974 | |||
af1e08227d | |||
abbdc61e79 | |||
1a3135c6c4 | |||
05a20ff94e | |||
c68762ceac | |||
bed12e2eee | |||
bb6d05be72 | |||
7932152025 | |||
a88f418901 | |||
0d6acdf174 | |||
25582dd1f1 |
@ -24,7 +24,8 @@
|
||||
<template>
|
||||
<i
|
||||
:style="{
|
||||
fontSize: `${size}${propUnit}`,
|
||||
...$attrs.style,
|
||||
fontSize: pixi,
|
||||
color: icolor || 'currentcolor',
|
||||
}"
|
||||
:class="`fa${styleMap[istyle]} fa-${name}`"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { Grid } from "ant-design-vue";
|
||||
import { Grid, ItemType } from "ant-design-vue";
|
||||
const bp = Grid.useBreakpoint();
|
||||
const { data, status } = useAuth();
|
||||
const itemMap = ref({
|
||||
@ -22,6 +22,7 @@
|
||||
let selected: string[] = [cur.value];
|
||||
|
||||
const clickFn = (minfo) => {
|
||||
console.log("clicky", minfo);
|
||||
if (itemMap.value[minfo.key] === undefined) return;
|
||||
cur.value = itemMap.value[minfo.key];
|
||||
selected = [cur.value];
|
||||
@ -43,12 +44,13 @@
|
||||
}"
|
||||
@click="clickFn"
|
||||
:active-key="cur"
|
||||
:key="data?.user?._id"
|
||||
>
|
||||
<a-menu-item key="home"> Home </a-menu-item>
|
||||
<a-menu-item key="bands"> Bands </a-menu-item>
|
||||
<a-menu-item key="authors"> Authors </a-menu-item>
|
||||
<a-menu-item key="forum"> Message Board</a-menu-item>
|
||||
<a-sub-menu title="My Stuff" v-if="!!data?.user" key="group/my-stuff">
|
||||
<a-sub-menu :disabled="!data?.user" v-if="!!data?.user" title="My Stuff" key="group/my-stuff">
|
||||
<a-menu-item key="account"> Account </a-menu-item>
|
||||
<a-menu-item key="edit-profile"> Edit Profile </a-menu-item>
|
||||
<a-menu-item key="profile"> View Profile </a-menu-item>
|
||||
@ -60,9 +62,9 @@
|
||||
<a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> Admin </a-menu-item>
|
||||
<a-menu-item key="logout" v-if="!!data?.user"> Logout </a-menu-item>
|
||||
</a-menu>
|
||||
<div>
|
||||
<div v-if="data?.user">
|
||||
<nuxt-link to="/new-story">
|
||||
<a-button v-if="data?.user" type="primary" tooltip="Post a New Story">
|
||||
<a-button type="primary" tooltip="Post a New Story">
|
||||
<!-- <template #icon>
|
||||
</template> -->
|
||||
<icon istyle="regular" name="file-plus" />
|
||||
@ -70,7 +72,7 @@
|
||||
</a-button>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<div class="acbut" v-if="!data">
|
||||
<div class="acbut" v-if="!data?.user">
|
||||
<a-button size="large" @click="() => navigateTo('/auth/login')"> Login </a-button>
|
||||
<a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register </a-button>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { ItemType, theme } from "ant-design-vue";
|
||||
import Icon from "../icon.vue";
|
||||
import { ISidebarItem } from "@models/sidebarEntry";
|
||||
import { NuxtLink } from "#components";
|
||||
|
||||
const { useToken } = theme;
|
||||
const { token } = useToken();
|
||||
@ -10,8 +11,8 @@
|
||||
const selState = ref<string>("");
|
||||
|
||||
const { data: injecto } = await useApiFetch<ISidebarItem[]>("/sidebar");
|
||||
|
||||
let items = reactive<ItemType[]>([
|
||||
let custItems = computed(() => (injecto.value || ([] as ISidebarItem[])).sort((a, b) => a.index - b.index));
|
||||
/*let items = ref<ItemType[]>([
|
||||
{
|
||||
key: "important",
|
||||
label: h("span", { class: "smallcaps" }, ["Pinned"]),
|
||||
@ -57,27 +58,24 @@
|
||||
name: "sparkles",
|
||||
size: 19,
|
||||
}),
|
||||
children: (injecto.value || ([] as ISidebarItem[]))
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map((b) => ({
|
||||
key: b.url,
|
||||
label: h(
|
||||
"span",
|
||||
{
|
||||
"data-color": b.color,
|
||||
class: "custom-side-item",
|
||||
},
|
||||
[h(NuxtLink, { to: b.url }, [b.linkTitle])],
|
||||
),
|
||||
})),
|
||||
children: custItems.value.map((b) => ({
|
||||
key: b.url,
|
||||
label: h(
|
||||
"span",
|
||||
{
|
||||
"data-color": b.color,
|
||||
class: "custom-side-item",
|
||||
},
|
||||
[h(NuxtLink, { to: b.url }, [b.linkTitle])],
|
||||
),
|
||||
})),
|
||||
} as SubMenuType,
|
||||
]);
|
||||
|
||||
// console.log("wtf", items)
|
||||
]);*/
|
||||
</script>
|
||||
<template>
|
||||
<!-- <client-only>-->
|
||||
<a-menu
|
||||
id="sidebar-menu"
|
||||
mode="inline"
|
||||
@select="
|
||||
({ item, key, selectedKeys }) => {
|
||||
@ -89,37 +87,41 @@
|
||||
"
|
||||
:trigger-sub-menu-action="'click'"
|
||||
v-model:active-key="selState"
|
||||
:items="items"
|
||||
:inline-indent="16"
|
||||
>
|
||||
<!-- <a-sub-menu>
|
||||
<template #title>
|
||||
<sidebar-icon>
|
||||
<template #icon>
|
||||
<Icon name="sparkles" istyle="regular" :size="19"/>
|
||||
</template>
|
||||
<template #rest>
|
||||
<div class="smallcaps">
|
||||
fun features
|
||||
</div>
|
||||
</template>
|
||||
</sidebar-icon>
|
||||
<a-sub-menu key="important">
|
||||
<template #icon>
|
||||
<icon istyle="regular" name="thumbtack" :size="19" />
|
||||
</template>
|
||||
<template #title>
|
||||
<span class="smallcaps">Pinned</span>
|
||||
</template>
|
||||
<a-menu-item key="/submission-rules">
|
||||
<template #icon>
|
||||
<icon istyle="regular" name="memo" :size="15" />
|
||||
</template>
|
||||
<a-menu-item>
|
||||
<sidebar-icon>
|
||||
<template #icon>
|
||||
<icon name="memo" :icolor="token.colorInfo" istyle="regular" :size="13"/>
|
||||
</template>
|
||||
<template #rest>
|
||||
<nuxt-link to="/submission-rules">
|
||||
<b :style="{ color: token.colorInfo }">
|
||||
submission rules
|
||||
</b>
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</sidebar-icon>
|
||||
</a-menu-item>
|
||||
</a-sub-menu> -->
|
||||
<b :style="{ color: token.colorInfo }">SUBMISSION RULES</b>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/terms">
|
||||
<template #icon>
|
||||
<icon istyle="regular" name="globe" :size="15" />
|
||||
</template>
|
||||
<b>Terms of Service</b>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-sub-menu key="fun-features">
|
||||
<template #icon>
|
||||
<Icon name="sparkles" istyle="regular" :size="19" />
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="smallcaps">fun features</div>
|
||||
</template>
|
||||
<a-menu-item v-for="item in custItems" :key="item.url">
|
||||
<span :style="{ color: item.color }">
|
||||
<nuxt-link :to="item.url">{{ item.linkTitle }}</nuxt-link>
|
||||
</span>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
<!-- </client-only>-->
|
||||
</template>
|
||||
@ -127,4 +129,7 @@
|
||||
.smallcaps {
|
||||
font-variant: small-caps;
|
||||
}
|
||||
#sidebar-menu ul {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -52,8 +52,8 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.ant-list-items > * + * {
|
||||
<style>
|
||||
ul.ant-list-items > * + * {
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
import { Field, FieldArray, useForm, useFieldArray } from "vee-validate";
|
||||
import { ASpin } from "#components";
|
||||
import { storySchema } from "@client/storyFormSchema";
|
||||
import { FormStory, defaultChapter } from "@client/types/form/story";
|
||||
import { FormStory, defaultChapter, FormChapter } from "@client/types/form/story";
|
||||
import { autoEdit, autoSave, debouncedAutoEdit, debouncedAutoSave } from "@client/utils";
|
||||
|
||||
import findUser from "~/components/findUser.vue";
|
||||
@ -20,17 +20,16 @@
|
||||
endpoint: string;
|
||||
endpointMethod: "put" | "post";
|
||||
submitText?: string;
|
||||
draftData?: {
|
||||
endpoint: string;
|
||||
endpointMethod: "put" | "post";
|
||||
};
|
||||
}>();
|
||||
let w;
|
||||
onMounted(() => {
|
||||
w = window;
|
||||
});
|
||||
const dc = defaultChapter;
|
||||
// data: FormStory;
|
||||
const sdata = defineModel<FormStory>("data", {
|
||||
required: true,
|
||||
});
|
||||
let drag = false;
|
||||
let drag: boolean = false;
|
||||
const expandos = ref<string[]>([]);
|
||||
|
||||
function logSubmit(values, actions) {
|
||||
@ -46,27 +45,32 @@
|
||||
otherBtnInvoked.value = false;
|
||||
await autoSave(values);
|
||||
} else {
|
||||
const { data: dat } = await useApiFetch(`/story/new`, {
|
||||
const { data: dat } = await useApiFetch<any>(`/story/new`, {
|
||||
method: "post",
|
||||
body: values,
|
||||
});
|
||||
if (dat.success) {
|
||||
await router.push(`/story/${dat.story._id}/1`);
|
||||
if (dat.value.success) {
|
||||
await router.push(`/story/${dat.value.story._id}/1`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await autoEdit(values, props.endpoint, props.endpointMethod);
|
||||
}
|
||||
}
|
||||
function inval({ values, errors, results }) {
|
||||
logSubmit(values, undefined);
|
||||
}
|
||||
|
||||
const { values, setFieldValue, handleSubmit } = useForm<FormStory>({
|
||||
keepValuesOnUnmount: true,
|
||||
validationSchema: storySchema,
|
||||
initialValues: sdata.value,
|
||||
});
|
||||
// const { push, remove, move, fields } = useFieldArray<FormChapter>("chapters");
|
||||
function renumber(update: (idx: number, value: FormChapter) => void) {
|
||||
for (let i = 0; i < values.chapters.length; i++) {
|
||||
const nv = values.chapters[i];
|
||||
nv.index = i + 1;
|
||||
update(i, nv);
|
||||
sdata.value.chapters[i].index = i + 1;
|
||||
}
|
||||
}
|
||||
const subCb = handleSubmit(onSubmit);
|
||||
|
||||
const pushHOF = (push) => (e) => {
|
||||
@ -96,7 +100,18 @@
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<form data-testid="storyform" @submit="subCb" @change="() => (canDraft ? debouncedAutoSave(values) : debouncedAutoEdit(values, endpoint, endpointMethod))">
|
||||
<form
|
||||
data-testid="storyform"
|
||||
@submit="subCb"
|
||||
@change="
|
||||
() =>
|
||||
canDraft
|
||||
? draftData
|
||||
? debouncedAutoSave(values, draftData.endpoint, draftData.endpointMethod)
|
||||
: debouncedAutoSave(values)
|
||||
: debouncedAutoEdit(values, endpoint, endpointMethod)
|
||||
"
|
||||
>
|
||||
<!-- <a-form v-bind:model="acData"> -->
|
||||
|
||||
<Field name="title" v-slot="{ value, field, errorMessage }">
|
||||
@ -112,30 +127,26 @@
|
||||
</Field>
|
||||
<a-divider />
|
||||
<!-- <test1/> -->
|
||||
<field-array name="chapters" v-slot="{ fields, push, remove, move }">
|
||||
<field-array name="chapters" v-slot="{ fields, push, remove, move, update }">
|
||||
<client-only :fallback="h(ASpin)">
|
||||
<div>
|
||||
<div v-for="(element, index) in data.chapters">
|
||||
<div v-for="(element, index) in values.chapters">
|
||||
<client-only>
|
||||
<Teleport :to="`#chapter-\\[${element.uuidKey}\\]`">
|
||||
<a-collapse v-model:active-key="expandos" collapsible="icon">
|
||||
<template #expandIcon="{ isActive }">
|
||||
<span :data-testid="`storyform.chapters[${index}].collapse`"> <RightOutlined :rotate="isActive ? 90 : undefined" /></span>
|
||||
</template>
|
||||
<a-collapse-panel :key="`${element.uuidKey}`">
|
||||
<a-collapse-panel :key="`${element.uuidKey}`" :data-testid="`storyform.chapters[${index}].outer`">
|
||||
<template #header>
|
||||
<div :data-testid="`storyform.chapters[${index}].header`" style="display: flex; justify-content: space-between">
|
||||
<div :data-testid="`storyform.chapters[${index}].header`" style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span :data-testid="`storyform.chapters[${index}].titleEl`">{{ values.chapters[index]?.chapterTitle || "Untitled" }}</span>
|
||||
<a-button
|
||||
@click="
|
||||
(e) => {
|
||||
// let localFields = toRaw(fields);
|
||||
// log.debug(`${index} | ${element.index}`);
|
||||
// log.debug('fields->', localFields);
|
||||
data.chapters.splice(index, 1);
|
||||
remove(index);
|
||||
// todo renumber
|
||||
// renumber()
|
||||
renumber(update);
|
||||
}
|
||||
"
|
||||
>
|
||||
@ -172,6 +183,7 @@
|
||||
console.debug(e.moved);
|
||||
move(e.moved.oldIndex, e.moved.newIndex);
|
||||
data.chapters = lmove(data.chapters, e.moved.oldIndex, e.moved.newIndex);
|
||||
renumber(update);
|
||||
// w.tinymce.remove();
|
||||
// log.debug(toRaw(acData.chapters.map((a) => toRaw(a))));
|
||||
}
|
||||
@ -191,3 +203,11 @@
|
||||
<a-button html-type="submit" v-if="canDraft" @click="() => (otherBtnInvoked = true)"> Save for Later </a-button>
|
||||
</form>
|
||||
</template>
|
||||
<style>
|
||||
.ant-collapse-item > .ant-collapse-header {
|
||||
align-items: center !important;
|
||||
}
|
||||
.ant-collapse-header.ant-collapse-icon-collapsible-only {
|
||||
align-items: center !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -39,7 +39,7 @@
|
||||
console.log(fileField.value);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('not yet');
|
||||
console.warn('not yet');
|
||||
}
|
||||
}
|
||||
"
|
||||
|
@ -50,67 +50,76 @@
|
||||
<i> Nothing here but crickets. </i>
|
||||
</template>
|
||||
<a-layout class="ylayout">
|
||||
<a-layout-header class="alayhead">
|
||||
<div style="display: flex; align-items: center; flex-wrap: wrap">
|
||||
<div class="siteTitle">Rockfic</div>
|
||||
<div class="stat-block">
|
||||
<div>
|
||||
<a-typography-text> Band fiction that rocks </a-typography-text>
|
||||
<a-typography-text type="secondary"> With {{ totals?.stories || 0 }} stories by {{ totals?.authors || 0 }} authors </a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<navbar v-if="!!bp['md']" :inline="false" />
|
||||
</div>
|
||||
<a-button class="mobileTrigger" type="primary" @click="() => (collapsed = !collapsed)" v-if="!bp['md']">
|
||||
<menu-unfold-outlined v-if="nav" />
|
||||
<menu-fold-outlined v-else />
|
||||
</a-button>
|
||||
</a-layout-header>
|
||||
<a-layout class="mlayout" has-sider style="padding-top: 1em">
|
||||
<a-layout-sider :trigger="null" :collapsed="true" :collapsed-width="0" :collapsible="true" v-model:collapsed="collapsed" v-if="!bp['md']">
|
||||
<navbar inline />
|
||||
</a-layout-sider>
|
||||
<a-layout-content style="flex-grow: 1">
|
||||
<slot />
|
||||
</a-layout-content>
|
||||
<a-layout-sider
|
||||
:zero-width-trigger-style="{
|
||||
background: '#e92662',
|
||||
padding: '1.2em',
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
borderRadius: '15%',
|
||||
color: 'white',
|
||||
border: '2.4px solid #fffFFF80',
|
||||
top: '75vh',
|
||||
'z-index': 99999999,
|
||||
}"
|
||||
:theme="darko ? 'dark' : 'light'"
|
||||
:breakpoint="'lg'"
|
||||
v-model:collapsed="collapsed"
|
||||
:collapsible="true"
|
||||
:collapsed-width="0"
|
||||
:style="{
|
||||
color: col,
|
||||
height: '100%',
|
||||
position: 'fixed',
|
||||
right: '0px',
|
||||
borderLeft: `2px solid ${darko ? '#fff' : '#ccc'}`,
|
||||
}"
|
||||
>
|
||||
<sidebar-thing />
|
||||
<template #trigger>
|
||||
<div class="outerst" @click="() => (collapsed = !collapsed)">
|
||||
<div :class="sideTriggerVal">
|
||||
<icon istyle="solid" name="chevron-right" :size="30" />
|
||||
<a-layout>
|
||||
<a-layout-header class="alayhead" :style="{ backgroundColor: darkBool ? '#141414' : '#f5f5f5' }">
|
||||
<div style="display: flex; align-items: center; flex-wrap: wrap">
|
||||
<div class="siteTitle">Rockfic</div>
|
||||
<div class="stat-block">
|
||||
<div>
|
||||
<a-typography-text> Band fiction that rocks </a-typography-text>
|
||||
<a-typography-text type="secondary"> With {{ totals?.stories || 0 }} stories by {{ totals?.authors || 0 }} authors </a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-layout-sider>
|
||||
<navbar v-if="!!bp['md']" :inline="false" />
|
||||
</div>
|
||||
<a-button class="mobileTrigger" type="primary" @click="() => (nav = !nav)" v-if="!bp['md']">
|
||||
<menu-unfold-outlined v-if="nav" />
|
||||
<menu-fold-outlined v-else />
|
||||
</a-button>
|
||||
</a-layout-header>
|
||||
<a-layout class="mlayout" has-sider style="padding-top: 1em">
|
||||
<a-layout-sider
|
||||
:trigger="null"
|
||||
:theme="darkBool ? 'dark' : 'light'"
|
||||
:collapsed="nav"
|
||||
:collapsed-width="0"
|
||||
:collapsible="true"
|
||||
v-model:collapsed="collapsed"
|
||||
v-if="!bp['md']"
|
||||
>
|
||||
<navbar inline />
|
||||
</a-layout-sider>
|
||||
<a-layout-content style="flex-grow: 1">
|
||||
<slot />
|
||||
</a-layout-content>
|
||||
<a-layout-sider
|
||||
:zero-width-trigger-style="{
|
||||
background: '#e92662',
|
||||
padding: '1.2em',
|
||||
position: 'fixed',
|
||||
right: 0,
|
||||
borderRadius: '15%',
|
||||
color: 'white',
|
||||
border: '2.4px solid #fffFFF80',
|
||||
top: '75vh',
|
||||
'z-index': 99999999,
|
||||
}"
|
||||
:theme="darko ? 'dark' : 'light'"
|
||||
:breakpoint="'lg'"
|
||||
v-model:collapsed="collapsed"
|
||||
:collapsible="true"
|
||||
:collapsed-width="0"
|
||||
:style="{
|
||||
color: col,
|
||||
right: '-3px',
|
||||
alignSelf: 'stretch',
|
||||
borderLeft: `2px solid ${darko ? '#fff' : '#ccc'}`,
|
||||
}"
|
||||
>
|
||||
<sidebar-thing />
|
||||
<template #trigger>
|
||||
<div class="outerst" @click="() => (collapsed = !collapsed)">
|
||||
<div :class="sideTriggerVal">
|
||||
<icon istyle="solid" name="chevron-right" :size="30" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-layout-sider>
|
||||
</a-layout>
|
||||
<a-layout-footer style="bottom: 100%">
|
||||
<cfooter />
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
<a-layout-footer style="bottom: 100%">
|
||||
<cfooter />
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
<!-- <div class="mlayout">
|
||||
<a-skeleton/>
|
||||
@ -137,7 +146,7 @@
|
||||
|
||||
.sideWrap {
|
||||
height: 100vh;
|
||||
right: -1rem;
|
||||
right: -3rem;
|
||||
width: inherit;
|
||||
/* display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -4,14 +4,14 @@ import { useRoute, useRouter } from "#app";
|
||||
|
||||
const base = `/user/me`;
|
||||
|
||||
export const favourites = (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => {
|
||||
export const favourites = async (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => {
|
||||
values?.splice(
|
||||
values!.findIndex((a) => a._id == id),
|
||||
1,
|
||||
);
|
||||
const key = type === "story" ? "stories" : "authors";
|
||||
const todo = [id];
|
||||
useApiFetch(`${base}/favs`, {
|
||||
await useApiFetch(`${base}/favs`, {
|
||||
method: "put",
|
||||
body: {
|
||||
[key]: {
|
||||
@ -22,7 +22,7 @@ export const favourites = (values: (any & { _id: number })[], id: number, remove
|
||||
});
|
||||
};
|
||||
|
||||
export const subscriptions = (
|
||||
export const subscriptions = async (
|
||||
values: ((any & { _id: number }) | number)[],
|
||||
id: number,
|
||||
action: "hide" | "subscribe" | "unsubscribe",
|
||||
@ -36,7 +36,7 @@ export const subscriptions = (
|
||||
values!.findIndex((a) => a._id == id || a == id),
|
||||
1,
|
||||
);
|
||||
useApiFetch(`${base}/${action}`, {
|
||||
await useApiFetch(`${base}/${action}`, {
|
||||
body: {
|
||||
push: {
|
||||
[type]: [id],
|
||||
@ -46,7 +46,7 @@ export const subscriptions = (
|
||||
method: "put",
|
||||
});
|
||||
} else if (action == "subscribe") {
|
||||
useApiFetch(`${base}/subscriptions`, {
|
||||
await useApiFetch(`${base}/subscriptions`, {
|
||||
body: {
|
||||
push: {
|
||||
[type]: [id],
|
||||
@ -56,7 +56,7 @@ export const subscriptions = (
|
||||
method: "put",
|
||||
});
|
||||
} else if (action == "unsubscribe") {
|
||||
useApiFetch(`${base}/subscriptions`, {
|
||||
await useApiFetch(`${base}/subscriptions`, {
|
||||
body: {
|
||||
pull: {
|
||||
[type]: [id],
|
||||
|
@ -142,3 +142,4 @@ export const rc: RuntimeConfig & any = {
|
||||
},
|
||||
app: {},
|
||||
};
|
||||
export const doNotSelect = ["password", "auth", "ipLog"].map((b) => `-${b}`).join(" ");
|
||||
|
@ -4,7 +4,7 @@ export interface IChapter {
|
||||
title: string;
|
||||
summary: string;
|
||||
id?: number;
|
||||
// index: number;
|
||||
index: number;
|
||||
words?: number;
|
||||
notes: string;
|
||||
genre: string[];
|
||||
@ -25,6 +25,9 @@ export const Chapter = new Schema<IChapter>({
|
||||
id: {
|
||||
type: Number,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
},
|
||||
summary: {
|
||||
type: String,
|
||||
},
|
||||
|
29
package.json
29
package.json
@ -4,8 +4,9 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"dev": "nuxt dev --host 127.0.0.1",
|
||||
"generate": "nuxt generate",
|
||||
"postinstall": "nuxt prepare",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
@ -13,17 +14,27 @@
|
||||
"@nuxt/devtools": "latest",
|
||||
"@nuxtjs/i18n": "^8.0.0-rc.9",
|
||||
"@types/uuid": "^9.0.4",
|
||||
"@vue/language-server": "^1.8.27",
|
||||
"nuxt": "^3.9.0",
|
||||
"@vitejs/plugin-vue": "^5.0.2",
|
||||
"@vitest/browser": "^1.1.2",
|
||||
"@vitest/ui": "^1.1.3",
|
||||
"@vue/language-server": "latest",
|
||||
"@vue/test-utils": "^2.4.3",
|
||||
"happy-dom": "^12.10.3",
|
||||
"jsdom": "^23.0.1",
|
||||
"nuxt": "^3.12.3",
|
||||
"playwright": "^1.40.1",
|
||||
"playwright-core": "^1.40.1",
|
||||
"prettier": "^3.0.3",
|
||||
"tsconfig-to-dual-package": "^1.2.0",
|
||||
"typescript": "latest"
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "latest",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vitest": "^1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design-vue/nuxt": "^1.4.1",
|
||||
"@pinia/nuxt": "^0.4.11",
|
||||
"@tinymce/tinymce-vue": "latest",
|
||||
"@sidebase/nuxt-auth": "0.7.0",
|
||||
"@tinymce/tinymce-vue": "latest",
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/turndown": "^5.0.4",
|
||||
@ -33,7 +44,7 @@
|
||||
"axios": "^1.5.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"date-fns": "latest",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash-move": "^1.1.1",
|
||||
@ -44,13 +55,13 @@
|
||||
"mongoose-sequence": "https://github.com/amansingh63/mongoose-sequence",
|
||||
"nitropack": "^2.9.4",
|
||||
"nuxi": "^3.10.0",
|
||||
"nuxt-booster": "^3.0.0",
|
||||
"nuxt-security": "^0.14.4",
|
||||
"nuxt-speedkit": "3.0.0-next.26",
|
||||
"pinia": "^2.1.6",
|
||||
"sanitize-html": "^2.11.0",
|
||||
"sharp": "^0.33.2",
|
||||
"sharp": "^0.33.3",
|
||||
"string-strip-html": "^13.4.3",
|
||||
"tinymce": "^6.7.0",
|
||||
"turndown": "^7.1.2",
|
||||
"uuid": "^9.0.1",
|
||||
"vee-validate": "^4.11.7",
|
||||
|
@ -12,22 +12,24 @@
|
||||
},
|
||||
middleware: ["auth"],
|
||||
});
|
||||
useHead({
|
||||
title: "Log In",
|
||||
});
|
||||
|
||||
const formState = reactive<FormState>({
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const darkRef = inject<Ref<boolean>>("dark");
|
||||
const onFinish = async (values: any) => {
|
||||
const { signIn, data } = useAuth();
|
||||
const { signIn } = useAuth();
|
||||
|
||||
let reso: any;
|
||||
try {
|
||||
reso = await signIn(values);
|
||||
|
||||
await navigateTo({
|
||||
path: "/",
|
||||
});
|
||||
await signIn(values, { redirect: true, callbackUrl: "/" });
|
||||
const { data } = useAuth();
|
||||
darkRef.value = data.value.user.profile.nightMode;
|
||||
if (darkRef.value) document.body.dataset.theme = "dark";
|
||||
await navigateTo("/");
|
||||
} catch (e: any) {
|
||||
if (e.data) {
|
||||
notification["error"]({
|
||||
|
@ -6,6 +6,9 @@
|
||||
signOut({
|
||||
callbackUrl: "/",
|
||||
});
|
||||
const d = inject<Ref<boolean>>("dark");
|
||||
d.value = false;
|
||||
document.body.dataset.theme = undefined;
|
||||
</script>
|
||||
<template>
|
||||
<a-typography-title :level="3"> Signed out. 👋 </a-typography-title>
|
||||
|
@ -6,12 +6,18 @@
|
||||
|
||||
const { data: bands } = (await useApiFetch<NonNullable<IBand[]>>("/band/all")) as unknown as { data: Ref<IBand[]> };
|
||||
|
||||
const { data: rd }: { data: any } = useAuth();
|
||||
const {
|
||||
data: { value: rd },
|
||||
getSession,
|
||||
} = useAuth();
|
||||
await getSession({ force: true });
|
||||
let inc = ref<number>(1);
|
||||
const data = ref(rd);
|
||||
const refresh = async () => {
|
||||
await useAuth().getSession({ force: true });
|
||||
rd.value = useAuth().data.value;
|
||||
data.value = useAuth().data.value;
|
||||
//inc.value += 1;
|
||||
};
|
||||
|
||||
const hider = subscriptions;
|
||||
if (bands.value == null) bands.value = [];
|
||||
useHead({
|
||||
@ -21,7 +27,7 @@
|
||||
<template>
|
||||
<a-list v-model:data-source="bands" :grid="bp">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item :key="item._id + inc">
|
||||
<a-row :gutter="[5, 5]">
|
||||
<a-col>
|
||||
<nuxt-link :to="`/band/${item._id}`">
|
||||
@ -29,12 +35,12 @@
|
||||
</nuxt-link>
|
||||
</a-col>
|
||||
<!-- subscribe... -->
|
||||
<a-col v-if="rd && rd.user?._id" style="margin-left: auto">
|
||||
<a-col v-if="data && data.user?._id" style="margin-left: auto">
|
||||
<a
|
||||
v-if="!rd?.user.subscriptions.bands.includes(item._id)"
|
||||
v-if="!data?.user.subscriptions.bands.includes(item._id)"
|
||||
@click="
|
||||
async (e) => {
|
||||
hider(bands, item._id, 'subscribe', 'bands');
|
||||
await hider(bands, item._id, 'subscribe', 'bands');
|
||||
await refresh();
|
||||
}
|
||||
"
|
||||
@ -45,7 +51,7 @@
|
||||
v-else
|
||||
@click="
|
||||
async (e) => {
|
||||
hider(bands, item._id, 'unsubscribe', 'bands');
|
||||
await hider(bands, item._id, 'unsubscribe', 'bands');
|
||||
await refresh();
|
||||
}
|
||||
"
|
||||
@ -53,11 +59,11 @@
|
||||
<icon :istyle="'regular'" name="x" :size="12" />
|
||||
</a>
|
||||
</a-col>
|
||||
<a-col v-if="rd?.user._id">
|
||||
<a-col v-if="data?.user._id">
|
||||
<a
|
||||
@click="
|
||||
async (e) => {
|
||||
hider(bands, item._id, 'hide', 'bands');
|
||||
await hider(bands, item._id, 'hide', 'bands');
|
||||
await refresh();
|
||||
}
|
||||
"
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-typography-title :level="1"> The Latest Fics </a-typography-title>
|
||||
<a-typography-title :level="1" style="margin-top: 0"> The Latest Fics </a-typography-title>
|
||||
<story-list :last="true" prefix="/latest" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -2,11 +2,12 @@ import mongoose from "mongoose";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { User } from "@models/user";
|
||||
import { log } from "@server/logger";
|
||||
import { doNotSelect } from "@server/constants";
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const wrongMsg = "wrong credentials";
|
||||
let reqbody = await readBody(event);
|
||||
let user = await User.findOne({ username: reqbody.username }).exec();
|
||||
let user = await User.findOne({ username: reqbody.username }).select(doNotSelect).exec();
|
||||
// log.debug(reqbody, { label: "login/body" });
|
||||
// log.debug("USER -> " + user, { label: "login" });
|
||||
// log.debug("conn ->" + mongoose.connection, { label: "login" });
|
||||
@ -30,8 +31,9 @@ export default eventHandler(async (event) => {
|
||||
}
|
||||
let tok = user.generateRefreshToken(useRuntimeConfig().jwt);
|
||||
// setCookie(event, "rockfic_cookie", tok);
|
||||
const fu = user.toObject();
|
||||
return {
|
||||
user,
|
||||
user: fu,
|
||||
token: {
|
||||
refresh: tok,
|
||||
access: user.generateAccessToken(useRuntimeConfig().jwt),
|
||||
|
@ -11,6 +11,12 @@ import { messages } from "@server/constants";
|
||||
export default eventHandler(async (ev) => {
|
||||
let os: (Document<unknown, {}, IStory> & IStory) | null = await storyQuerier(ev);
|
||||
isLoggedIn(ev);
|
||||
if (!os) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
message: messages[404],
|
||||
});
|
||||
}
|
||||
if (!canModify(ev, os)) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
@ -23,39 +29,31 @@ export default eventHandler(async (ev) => {
|
||||
completed: body.completed,
|
||||
coAuthor: !!body.coAuthor ? body.coAuthor : null,
|
||||
};
|
||||
for (const oc of os.chapters) {
|
||||
let filename = `/stories/${oc.id}.txt`;
|
||||
const bucket = getBucket();
|
||||
const curs = bucket.find({ filename }).limit(1);
|
||||
for await (const d of curs) {
|
||||
await bucket.delete(d._id);
|
||||
}
|
||||
}
|
||||
const cc = os.chapters;
|
||||
os.chapters = [];
|
||||
await os.save();
|
||||
|
||||
for (const c of body.chapters) {
|
||||
let idx = cc.findIndex((k) => k.id === c.id);
|
||||
let idx = os.chapters.findIndex((k) => k.id === c.id);
|
||||
const cont = await bodyHandler(c);
|
||||
if (idx === -1) {
|
||||
os.chapters!.push({
|
||||
...modelFormChapter(c),
|
||||
words: countWords(cont),
|
||||
posted: new Date(Date.now()),
|
||||
});
|
||||
} else {
|
||||
os.chapters!.push({
|
||||
os.chapters[idx] = {
|
||||
...modelFormChapter(c),
|
||||
// id: os.chapters[idx].id,
|
||||
id: os.chapters[idx].id,
|
||||
words: countWords(cont),
|
||||
posted: cc[idx].posted,
|
||||
});
|
||||
posted: os.chapters[idx].posted,
|
||||
};
|
||||
}
|
||||
}
|
||||
os.chapters.sort((a, b) => a.index - b.index);
|
||||
await os.save();
|
||||
for (let i = 0; i < os.chapters.length; i++) {
|
||||
const c = os.chapters[i];
|
||||
const cont = await bodyHandler(body.chapters[i]);
|
||||
await replaceOrUploadContent(c.id ?? c._id, cont);
|
||||
await replaceOrUploadContent(c.id ?? c._id, cont, getBucket());
|
||||
}
|
||||
os = await Story.findOneAndUpdate(
|
||||
{
|
||||
|
@ -4,6 +4,8 @@
|
||||
"allowJs": true,
|
||||
"outDir": "../out",
|
||||
"noImplicitAny": false,
|
||||
"verbatimModuleSyntax": false
|
||||
"verbatimModuleSyntax": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": false,
|
||||
"verbatimModuleSyntax": false
|
||||
// "paths": {
|
||||
// "@dbconfig": ["./lib/dbconfig.ts"],
|
||||
// "@functions": ["./lib/functions.ts"]
|
||||
// }
|
||||
}
|
||||
"verbatimModuleSyntax": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user