Compare commits

..

No commits in common. "05d11e0ea58fbbb0ccfdaf9f37027a152a8e012b" and "bb7a84259cd06a3d506a1ce72ca1822bcee1875a" have entirely different histories.

19 changed files with 201 additions and 265 deletions

@ -24,8 +24,7 @@
<template> <template>
<i <i
:style="{ :style="{
...$attrs.style, fontSize: `${size}${propUnit}`,
fontSize: pixi,
color: icolor || 'currentcolor', color: icolor || 'currentcolor',
}" }"
:class="`fa${styleMap[istyle]} fa-${name}`" :class="`fa${styleMap[istyle]} fa-${name}`"

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Grid, ItemType } from "ant-design-vue"; import { Grid } from "ant-design-vue";
const bp = Grid.useBreakpoint(); const bp = Grid.useBreakpoint();
const { data, status } = useAuth(); const { data, status } = useAuth();
const itemMap = ref({ const itemMap = ref({
@ -22,7 +22,6 @@
let selected: string[] = [cur.value]; let selected: string[] = [cur.value];
const clickFn = (minfo) => { const clickFn = (minfo) => {
console.log("clicky", minfo);
if (itemMap.value[minfo.key] === undefined) return; if (itemMap.value[minfo.key] === undefined) return;
cur.value = itemMap.value[minfo.key]; cur.value = itemMap.value[minfo.key];
selected = [cur.value]; selected = [cur.value];
@ -44,13 +43,12 @@
}" }"
@click="clickFn" @click="clickFn"
:active-key="cur" :active-key="cur"
:key="data?.user?._id"
> >
<a-menu-item key="home"> Home </a-menu-item> <a-menu-item key="home"> Home </a-menu-item>
<a-menu-item key="bands"> Bands </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="authors"> Authors </a-menu-item>
<a-menu-item key="forum"> Message Board</a-menu-item> <a-menu-item key="forum"> Message Board</a-menu-item>
<a-sub-menu :disabled="!data?.user" v-if="!!data?.user" title="My Stuff" key="group/my-stuff"> <a-sub-menu title="My Stuff" v-if="!!data?.user" key="group/my-stuff">
<a-menu-item key="account"> Account </a-menu-item> <a-menu-item key="account"> Account </a-menu-item>
<a-menu-item key="edit-profile"> Edit Profile </a-menu-item> <a-menu-item key="edit-profile"> Edit Profile </a-menu-item>
<a-menu-item key="profile"> View Profile </a-menu-item> <a-menu-item key="profile"> View Profile </a-menu-item>
@ -62,9 +60,9 @@
<a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> Admin </a-menu-item> <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-item key="logout" v-if="!!data?.user"> Logout </a-menu-item>
</a-menu> </a-menu>
<div v-if="data?.user"> <div>
<nuxt-link to="/new-story"> <nuxt-link to="/new-story">
<a-button type="primary" tooltip="Post a New Story"> <a-button v-if="data?.user" type="primary" tooltip="Post a New Story">
<!-- <template #icon> <!-- <template #icon>
</template> --> </template> -->
<icon istyle="regular" name="file-plus" /> <icon istyle="regular" name="file-plus" />
@ -72,7 +70,7 @@
</a-button> </a-button>
</nuxt-link> </nuxt-link>
</div> </div>
<div class="acbut" v-if="!data?.user"> <div class="acbut" v-if="!data">
<a-button size="large" @click="() => navigateTo('/auth/login')"> Login </a-button> <a-button size="large" @click="() => navigateTo('/auth/login')"> Login </a-button>
<a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register </a-button> <a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register </a-button>
</div> </div>

@ -3,7 +3,6 @@
import { ItemType, theme } from "ant-design-vue"; import { ItemType, theme } from "ant-design-vue";
import Icon from "../icon.vue"; import Icon from "../icon.vue";
import { ISidebarItem } from "@models/sidebarEntry"; import { ISidebarItem } from "@models/sidebarEntry";
import { NuxtLink } from "#components";
const { useToken } = theme; const { useToken } = theme;
const { token } = useToken(); const { token } = useToken();
@ -11,8 +10,8 @@
const selState = ref<string>(""); const selState = ref<string>("");
const { data: injecto } = await useApiFetch<ISidebarItem[]>("/sidebar"); const { data: injecto } = await useApiFetch<ISidebarItem[]>("/sidebar");
let custItems = computed(() => (injecto.value || ([] as ISidebarItem[])).sort((a, b) => a.index - b.index));
/*let items = ref<ItemType[]>([ let items = reactive<ItemType[]>([
{ {
key: "important", key: "important",
label: h("span", { class: "smallcaps" }, ["Pinned"]), label: h("span", { class: "smallcaps" }, ["Pinned"]),
@ -58,7 +57,9 @@
name: "sparkles", name: "sparkles",
size: 19, size: 19,
}), }),
children: custItems.value.map((b) => ({ children: (injecto.value || ([] as ISidebarItem[]))
.sort((a, b) => a.index - b.index)
.map((b) => ({
key: b.url, key: b.url,
label: h( label: h(
"span", "span",
@ -70,12 +71,13 @@
), ),
})), })),
} as SubMenuType, } as SubMenuType,
]);*/ ]);
// console.log("wtf", items)
</script> </script>
<template> <template>
<!-- <client-only>--> <!-- <client-only>-->
<a-menu <a-menu
id="sidebar-menu"
mode="inline" mode="inline"
@select=" @select="
({ item, key, selectedKeys }) => { ({ item, key, selectedKeys }) => {
@ -87,41 +89,37 @@
" "
:trigger-sub-menu-action="'click'" :trigger-sub-menu-action="'click'"
v-model:active-key="selState" v-model:active-key="selState"
:items="items"
:inline-indent="16" :inline-indent="16"
> >
<a-sub-menu key="important"> <!-- <a-sub-menu>
<template #icon>
<icon istyle="regular" name="thumbtack" :size="19" />
</template>
<template #title> <template #title>
<span class="smallcaps">Pinned</span> <sidebar-icon>
</template>
<a-menu-item key="/submission-rules">
<template #icon>
<icon istyle="regular" name="memo" :size="15" />
</template>
<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> <template #icon>
<Icon name="sparkles" istyle="regular" :size="19"/> <Icon name="sparkles" istyle="regular" :size="19"/>
</template> </template>
<template #title> <template #rest>
<div class="smallcaps">fun features</div> <div class="smallcaps">
fun features
</div>
</template> </template>
<a-menu-item v-for="item in custItems" :key="item.url"> </sidebar-icon>
<span :style="{ color: item.color }"> </template>
<nuxt-link :to="item.url">{{ item.linkTitle }}</nuxt-link> <a-menu-item>
</span> <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-menu-item>
</a-sub-menu> </a-sub-menu> -->
</a-menu> </a-menu>
<!-- </client-only>--> <!-- </client-only>-->
</template> </template>
@ -129,7 +127,4 @@
.smallcaps { .smallcaps {
font-variant: small-caps; font-variant: small-caps;
} }
#sidebar-menu ul {
height: 100%;
}
</style> </style>

@ -52,8 +52,8 @@
/> />
</div> </div>
</template> </template>
<style> <style scoped>
ul.ant-list-items > * + * { .ant-list-items > * + * {
margin-top: 1.2em; margin-top: 1.2em;
} }

@ -5,7 +5,7 @@
import { Field, FieldArray, useForm, useFieldArray } from "vee-validate"; import { Field, FieldArray, useForm, useFieldArray } from "vee-validate";
import { ASpin } from "#components"; import { ASpin } from "#components";
import { storySchema } from "@client/storyFormSchema"; import { storySchema } from "@client/storyFormSchema";
import { FormStory, defaultChapter, FormChapter } from "@client/types/form/story"; import { FormStory, defaultChapter } from "@client/types/form/story";
import { autoEdit, autoSave, debouncedAutoEdit, debouncedAutoSave } from "@client/utils"; import { autoEdit, autoSave, debouncedAutoEdit, debouncedAutoSave } from "@client/utils";
import findUser from "~/components/findUser.vue"; import findUser from "~/components/findUser.vue";
@ -20,16 +20,17 @@
endpoint: string; endpoint: string;
endpointMethod: "put" | "post"; endpointMethod: "put" | "post";
submitText?: string; submitText?: string;
draftData?: {
endpoint: string;
endpointMethod: "put" | "post";
};
}>(); }>();
let w;
onMounted(() => {
w = window;
});
const dc = defaultChapter; const dc = defaultChapter;
// data: FormStory;
const sdata = defineModel<FormStory>("data", { const sdata = defineModel<FormStory>("data", {
required: true, required: true,
}); });
let drag: boolean = false; let drag = false;
const expandos = ref<string[]>([]); const expandos = ref<string[]>([]);
function logSubmit(values, actions) { function logSubmit(values, actions) {
@ -45,32 +46,27 @@
otherBtnInvoked.value = false; otherBtnInvoked.value = false;
await autoSave(values); await autoSave(values);
} else { } else {
const { data: dat } = await useApiFetch<any>(`/story/new`, { const { data: dat } = await useApiFetch(`/story/new`, {
method: "post", method: "post",
body: values, body: values,
}); });
if (dat.value.success) { if (dat.success) {
await router.push(`/story/${dat.value.story._id}/1`); await router.push(`/story/${dat.story._id}/1`);
} }
} }
} else { } else {
await autoEdit(values, props.endpoint, props.endpointMethod); await autoEdit(values, props.endpoint, props.endpointMethod);
} }
} }
function inval({ values, errors, results }) {
logSubmit(values, undefined);
}
const { values, setFieldValue, handleSubmit } = useForm<FormStory>({ const { values, setFieldValue, handleSubmit } = useForm<FormStory>({
keepValuesOnUnmount: true, keepValuesOnUnmount: true,
validationSchema: storySchema, validationSchema: storySchema,
initialValues: sdata.value, initialValues: sdata.value,
}); });
function renumber(update: (idx: number, value: FormChapter) => void) { // const { push, remove, move, fields } = useFieldArray<FormChapter>("chapters");
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 subCb = handleSubmit(onSubmit);
const pushHOF = (push) => (e) => { const pushHOF = (push) => (e) => {
@ -100,18 +96,7 @@
}; };
</script> </script>
<template> <template>
<form <form data-testid="storyform" @submit="subCb" @change="() => (canDraft ? debouncedAutoSave(values) : debouncedAutoEdit(values, endpoint, endpointMethod))">
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"> --> <!-- <a-form v-bind:model="acData"> -->
<Field name="title" v-slot="{ value, field, errorMessage }"> <Field name="title" v-slot="{ value, field, errorMessage }">
@ -127,26 +112,30 @@
</Field> </Field>
<a-divider /> <a-divider />
<!-- <test1/> --> <!-- <test1/> -->
<field-array name="chapters" v-slot="{ fields, push, remove, move, update }"> <field-array name="chapters" v-slot="{ fields, push, remove, move }">
<client-only :fallback="h(ASpin)"> <client-only :fallback="h(ASpin)">
<div> <div>
<div v-for="(element, index) in values.chapters"> <div v-for="(element, index) in data.chapters">
<client-only> <client-only>
<Teleport :to="`#chapter-\\[${element.uuidKey}\\]`"> <Teleport :to="`#chapter-\\[${element.uuidKey}\\]`">
<a-collapse v-model:active-key="expandos" collapsible="icon"> <a-collapse v-model:active-key="expandos" collapsible="icon">
<template #expandIcon="{ isActive }"> <template #expandIcon="{ isActive }">
<span :data-testid="`storyform.chapters[${index}].collapse`"> <RightOutlined :rotate="isActive ? 90 : undefined" /></span> <span :data-testid="`storyform.chapters[${index}].collapse`"> <RightOutlined :rotate="isActive ? 90 : undefined" /></span>
</template> </template>
<a-collapse-panel :key="`${element.uuidKey}`" :data-testid="`storyform.chapters[${index}].outer`"> <a-collapse-panel :key="`${element.uuidKey}`">
<template #header> <template #header>
<div :data-testid="`storyform.chapters[${index}].header`" style="display: flex; align-items: center; justify-content: space-between"> <div :data-testid="`storyform.chapters[${index}].header`" style="display: flex; justify-content: space-between">
<span :data-testid="`storyform.chapters[${index}].titleEl`">{{ values.chapters[index]?.chapterTitle || "Untitled" }}</span> <span :data-testid="`storyform.chapters[${index}].titleEl`">{{ values.chapters[index]?.chapterTitle || "Untitled" }}</span>
<a-button <a-button
@click=" @click="
(e) => { (e) => {
// let localFields = toRaw(fields);
// log.debug(`${index} | ${element.index}`);
// log.debug('fields->', localFields);
data.chapters.splice(index, 1); data.chapters.splice(index, 1);
remove(index); remove(index);
renumber(update); // todo renumber
// renumber()
} }
" "
> >
@ -183,7 +172,6 @@
console.debug(e.moved); console.debug(e.moved);
move(e.moved.oldIndex, e.moved.newIndex); move(e.moved.oldIndex, e.moved.newIndex);
data.chapters = lmove(data.chapters, e.moved.oldIndex, e.moved.newIndex); data.chapters = lmove(data.chapters, e.moved.oldIndex, e.moved.newIndex);
renumber(update);
// w.tinymce.remove(); // w.tinymce.remove();
// log.debug(toRaw(acData.chapters.map((a) => toRaw(a)))); // log.debug(toRaw(acData.chapters.map((a) => toRaw(a))));
} }
@ -203,11 +191,3 @@
<a-button html-type="submit" v-if="canDraft" @click="() => (otherBtnInvoked = true)"> Save for Later </a-button> <a-button html-type="submit" v-if="canDraft" @click="() => (otherBtnInvoked = true)"> Save for Later </a-button>
</form> </form>
</template> </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); console.log(fileField.value);
} }
} catch (e) { } catch (e) {
console.warn('not yet'); console.error('not yet');
} }
} }
" "

@ -50,8 +50,7 @@
<i> Nothing here but crickets. </i> <i> Nothing here but crickets. </i>
</template> </template>
<a-layout class="ylayout"> <a-layout class="ylayout">
<a-layout> <a-layout-header class="alayhead">
<a-layout-header class="alayhead" :style="{ backgroundColor: darkBool ? '#141414' : '#f5f5f5' }">
<div style="display: flex; align-items: center; flex-wrap: wrap"> <div style="display: flex; align-items: center; flex-wrap: wrap">
<div class="siteTitle">Rockfic</div> <div class="siteTitle">Rockfic</div>
<div class="stat-block"> <div class="stat-block">
@ -62,21 +61,13 @@
</div> </div>
<navbar v-if="!!bp['md']" :inline="false" /> <navbar v-if="!!bp['md']" :inline="false" />
</div> </div>
<a-button class="mobileTrigger" type="primary" @click="() => (nav = !nav)" v-if="!bp['md']"> <a-button class="mobileTrigger" type="primary" @click="() => (collapsed = !collapsed)" v-if="!bp['md']">
<menu-unfold-outlined v-if="nav" /> <menu-unfold-outlined v-if="nav" />
<menu-fold-outlined v-else /> <menu-fold-outlined v-else />
</a-button> </a-button>
</a-layout-header> </a-layout-header>
<a-layout class="mlayout" has-sider style="padding-top: 1em"> <a-layout class="mlayout" has-sider style="padding-top: 1em">
<a-layout-sider <a-layout-sider :trigger="null" :collapsed="true" :collapsed-width="0" :collapsible="true" v-model:collapsed="collapsed" v-if="!bp['md']">
:trigger="null"
:theme="darkBool ? 'dark' : 'light'"
:collapsed="nav"
:collapsed-width="0"
:collapsible="true"
v-model:collapsed="collapsed"
v-if="!bp['md']"
>
<navbar inline /> <navbar inline />
</a-layout-sider> </a-layout-sider>
<a-layout-content style="flex-grow: 1"> <a-layout-content style="flex-grow: 1">
@ -101,8 +92,9 @@
:collapsed-width="0" :collapsed-width="0"
:style="{ :style="{
color: col, color: col,
right: '-3px', height: '100%',
alignSelf: 'stretch', position: 'fixed',
right: '0px',
borderLeft: `2px solid ${darko ? '#fff' : '#ccc'}`, borderLeft: `2px solid ${darko ? '#fff' : '#ccc'}`,
}" }"
> >
@ -120,7 +112,6 @@
<cfooter /> <cfooter />
</a-layout-footer> </a-layout-footer>
</a-layout> </a-layout>
</a-layout>
<!-- <div class="mlayout"> <!-- <div class="mlayout">
<a-skeleton/> <a-skeleton/>
<a-skeleton/> <a-skeleton/>
@ -146,7 +137,7 @@
.sideWrap { .sideWrap {
height: 100vh; height: 100vh;
right: -3rem; right: -1rem;
width: inherit; width: inherit;
/* display: flex; /* display: flex;
flex-direction: column; flex-direction: column;

@ -4,14 +4,14 @@ import { useRoute, useRouter } from "#app";
const base = `/user/me`; const base = `/user/me`;
export const favourites = async (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => { export const favourites = (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => {
values?.splice( values?.splice(
values!.findIndex((a) => a._id == id), values!.findIndex((a) => a._id == id),
1, 1,
); );
const key = type === "story" ? "stories" : "authors"; const key = type === "story" ? "stories" : "authors";
const todo = [id]; const todo = [id];
await useApiFetch(`${base}/favs`, { useApiFetch(`${base}/favs`, {
method: "put", method: "put",
body: { body: {
[key]: { [key]: {
@ -22,7 +22,7 @@ export const favourites = async (values: (any & { _id: number })[], id: number,
}); });
}; };
export const subscriptions = async ( export const subscriptions = (
values: ((any & { _id: number }) | number)[], values: ((any & { _id: number }) | number)[],
id: number, id: number,
action: "hide" | "subscribe" | "unsubscribe", action: "hide" | "subscribe" | "unsubscribe",
@ -36,7 +36,7 @@ export const subscriptions = async (
values!.findIndex((a) => a._id == id || a == id), values!.findIndex((a) => a._id == id || a == id),
1, 1,
); );
await useApiFetch(`${base}/${action}`, { useApiFetch(`${base}/${action}`, {
body: { body: {
push: { push: {
[type]: [id], [type]: [id],
@ -46,7 +46,7 @@ export const subscriptions = async (
method: "put", method: "put",
}); });
} else if (action == "subscribe") { } else if (action == "subscribe") {
await useApiFetch(`${base}/subscriptions`, { useApiFetch(`${base}/subscriptions`, {
body: { body: {
push: { push: {
[type]: [id], [type]: [id],
@ -56,7 +56,7 @@ export const subscriptions = async (
method: "put", method: "put",
}); });
} else if (action == "unsubscribe") { } else if (action == "unsubscribe") {
await useApiFetch(`${base}/subscriptions`, { useApiFetch(`${base}/subscriptions`, {
body: { body: {
pull: { pull: {
[type]: [id], [type]: [id],

@ -142,4 +142,3 @@ export const rc: RuntimeConfig & any = {
}, },
app: {}, app: {},
}; };
export const doNotSelect = ["password", "auth", "ipLog"].map((b) => `-${b}`).join(" ");

@ -4,7 +4,7 @@ export interface IChapter {
title: string; title: string;
summary: string; summary: string;
id?: number; id?: number;
index: number; // index: number;
words?: number; words?: number;
notes: string; notes: string;
genre: string[]; genre: string[];
@ -25,9 +25,6 @@ export const Chapter = new Schema<IChapter>({
id: { id: {
type: Number, type: Number,
}, },
index: {
type: Number,
},
summary: { summary: {
type: String, type: String,
}, },

@ -4,9 +4,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "nuxt build", "build": "nuxt build",
"dev": "nuxt dev --host 127.0.0.1", "dev": "nuxt dev",
"generate": "nuxt generate", "generate": "nuxt generate",
"postinstall": "nuxt prepare",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
@ -14,27 +13,17 @@
"@nuxt/devtools": "latest", "@nuxt/devtools": "latest",
"@nuxtjs/i18n": "^8.0.0-rc.9", "@nuxtjs/i18n": "^8.0.0-rc.9",
"@types/uuid": "^9.0.4", "@types/uuid": "^9.0.4",
"@vitejs/plugin-vue": "^5.0.2", "@vue/language-server": "^1.8.27",
"@vitest/browser": "^1.1.2", "nuxt": "^3.9.0",
"@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", "prettier": "^3.0.3",
"ts-node": "^10.9.2", "tsconfig-to-dual-package": "^1.2.0",
"typescript": "latest", "typescript": "latest"
"unplugin-vue-components": "^0.26.0",
"vitest": "^1.4.0"
}, },
"dependencies": { "dependencies": {
"@ant-design-vue/nuxt": "^1.4.1", "@ant-design-vue/nuxt": "^1.4.1",
"@pinia/nuxt": "^0.4.11", "@pinia/nuxt": "^0.4.11",
"@sidebase/nuxt-auth": "0.7.0",
"@tinymce/tinymce-vue": "latest", "@tinymce/tinymce-vue": "latest",
"@sidebase/nuxt-auth": "0.7.0",
"@types/jsonwebtoken": "^9.0.3", "@types/jsonwebtoken": "^9.0.3",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/turndown": "^5.0.4", "@types/turndown": "^5.0.4",
@ -44,7 +33,7 @@
"axios": "^1.5.1", "axios": "^1.5.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"date-fns": "latest", "date-fns": "^2.30.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lodash-move": "^1.1.1", "lodash-move": "^1.1.1",
@ -55,13 +44,13 @@
"mongoose-sequence": "https://github.com/amansingh63/mongoose-sequence", "mongoose-sequence": "https://github.com/amansingh63/mongoose-sequence",
"nitropack": "^2.9.4", "nitropack": "^2.9.4",
"nuxi": "^3.10.0", "nuxi": "^3.10.0",
"nuxt-booster": "^3.0.0",
"nuxt-security": "^0.14.4", "nuxt-security": "^0.14.4",
"nuxt-speedkit": "3.0.0-next.26", "nuxt-speedkit": "3.0.0-next.26",
"pinia": "^2.1.6", "pinia": "^2.1.6",
"sanitize-html": "^2.11.0", "sanitize-html": "^2.11.0",
"sharp": "^0.33.3", "sharp": "^0.33.2",
"string-strip-html": "^13.4.3", "string-strip-html": "^13.4.3",
"tinymce": "^6.7.0",
"turndown": "^7.1.2", "turndown": "^7.1.2",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vee-validate": "^4.11.7", "vee-validate": "^4.11.7",

@ -12,24 +12,22 @@
}, },
middleware: ["auth"], middleware: ["auth"],
}); });
useHead({
title: "Log In",
});
const formState = reactive<FormState>({ const formState = reactive<FormState>({
username: "", username: "",
password: "", password: "",
}); });
const darkRef = inject<Ref<boolean>>("dark");
const onFinish = async (values: any) => {
const { signIn } = useAuth();
const onFinish = async (values: any) => {
const { signIn, data } = useAuth();
let reso: any;
try { try {
await signIn(values, { redirect: true, callbackUrl: "/" }); reso = await signIn(values);
const { data } = useAuth();
darkRef.value = data.value.user.profile.nightMode; await navigateTo({
if (darkRef.value) document.body.dataset.theme = "dark"; path: "/",
await navigateTo("/"); });
} catch (e: any) { } catch (e: any) {
if (e.data) { if (e.data) {
notification["error"]({ notification["error"]({

@ -6,9 +6,6 @@
signOut({ signOut({
callbackUrl: "/", callbackUrl: "/",
}); });
const d = inject<Ref<boolean>>("dark");
d.value = false;
document.body.dataset.theme = undefined;
</script> </script>
<template> <template>
<a-typography-title :level="3"> Signed out. 👋 </a-typography-title> <a-typography-title :level="3"> Signed out. 👋 </a-typography-title>

@ -6,18 +6,12 @@
const { data: bands } = (await useApiFetch<NonNullable<IBand[]>>("/band/all")) as unknown as { data: Ref<IBand[]> }; const { data: bands } = (await useApiFetch<NonNullable<IBand[]>>("/band/all")) as unknown as { data: Ref<IBand[]> };
const { const { data: rd }: { data: any } = useAuth();
data: { value: rd },
getSession,
} = useAuth();
await getSession({ force: true });
let inc = ref<number>(1);
const data = ref(rd);
const refresh = async () => { const refresh = async () => {
await useAuth().getSession({ force: true }); await useAuth().getSession({ force: true });
data.value = useAuth().data.value; rd.value = useAuth().data.value;
//inc.value += 1;
}; };
const hider = subscriptions; const hider = subscriptions;
if (bands.value == null) bands.value = []; if (bands.value == null) bands.value = [];
useHead({ useHead({
@ -27,7 +21,7 @@
<template> <template>
<a-list v-model:data-source="bands" :grid="bp"> <a-list v-model:data-source="bands" :grid="bp">
<template #renderItem="{ item }"> <template #renderItem="{ item }">
<a-list-item :key="item._id + inc"> <a-list-item>
<a-row :gutter="[5, 5]"> <a-row :gutter="[5, 5]">
<a-col> <a-col>
<nuxt-link :to="`/band/${item._id}`"> <nuxt-link :to="`/band/${item._id}`">
@ -35,12 +29,12 @@
</nuxt-link> </nuxt-link>
</a-col> </a-col>
<!-- subscribe... --> <!-- subscribe... -->
<a-col v-if="data && data.user?._id" style="margin-left: auto"> <a-col v-if="rd && rd.user?._id" style="margin-left: auto">
<a <a
v-if="!data?.user.subscriptions.bands.includes(item._id)" v-if="!rd?.user.subscriptions.bands.includes(item._id)"
@click=" @click="
async (e) => { async (e) => {
await hider(bands, item._id, 'subscribe', 'bands'); hider(bands, item._id, 'subscribe', 'bands');
await refresh(); await refresh();
} }
" "
@ -51,7 +45,7 @@
v-else v-else
@click=" @click="
async (e) => { async (e) => {
await hider(bands, item._id, 'unsubscribe', 'bands'); hider(bands, item._id, 'unsubscribe', 'bands');
await refresh(); await refresh();
} }
" "
@ -59,11 +53,11 @@
<icon :istyle="'regular'" name="x" :size="12" /> <icon :istyle="'regular'" name="x" :size="12" />
</a> </a>
</a-col> </a-col>
<a-col v-if="data?.user._id"> <a-col v-if="rd?.user._id">
<a <a
@click=" @click="
async (e) => { async (e) => {
await hider(bands, item._id, 'hide', 'bands'); hider(bands, item._id, 'hide', 'bands');
await refresh(); await refresh();
} }
" "

@ -7,7 +7,7 @@
<template> <template>
<div> <div>
<a-typography-title :level="1" style="margin-top: 0"> The Latest Fics </a-typography-title> <a-typography-title :level="1"> The Latest Fics </a-typography-title>
<story-list :last="true" prefix="/latest" /> <story-list :last="true" prefix="/latest" />
</div> </div>
</template> </template>

@ -2,12 +2,11 @@ import mongoose from "mongoose";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { User } from "@models/user"; import { User } from "@models/user";
import { log } from "@server/logger"; import { log } from "@server/logger";
import { doNotSelect } from "@server/constants";
export default eventHandler(async (event) => { export default eventHandler(async (event) => {
const wrongMsg = "wrong credentials"; const wrongMsg = "wrong credentials";
let reqbody = await readBody(event); let reqbody = await readBody(event);
let user = await User.findOne({ username: reqbody.username }).select(doNotSelect).exec(); let user = await User.findOne({ username: reqbody.username }).exec();
// log.debug(reqbody, { label: "login/body" }); // log.debug(reqbody, { label: "login/body" });
// log.debug("USER -> " + user, { label: "login" }); // log.debug("USER -> " + user, { label: "login" });
// log.debug("conn ->" + mongoose.connection, { label: "login" }); // log.debug("conn ->" + mongoose.connection, { label: "login" });
@ -31,9 +30,8 @@ export default eventHandler(async (event) => {
} }
let tok = user.generateRefreshToken(useRuntimeConfig().jwt); let tok = user.generateRefreshToken(useRuntimeConfig().jwt);
// setCookie(event, "rockfic_cookie", tok); // setCookie(event, "rockfic_cookie", tok);
const fu = user.toObject();
return { return {
user: fu, user,
token: { token: {
refresh: tok, refresh: tok,
access: user.generateAccessToken(useRuntimeConfig().jwt), access: user.generateAccessToken(useRuntimeConfig().jwt),

@ -11,12 +11,6 @@ import { messages } from "@server/constants";
export default eventHandler(async (ev) => { export default eventHandler(async (ev) => {
let os: (Document<unknown, {}, IStory> & IStory) | null = await storyQuerier(ev); let os: (Document<unknown, {}, IStory> & IStory) | null = await storyQuerier(ev);
isLoggedIn(ev); isLoggedIn(ev);
if (!os) {
throw createError({
statusCode: 404,
message: messages[404],
});
}
if (!canModify(ev, os)) { if (!canModify(ev, os)) {
throw createError({ throw createError({
statusCode: 403, statusCode: 403,
@ -29,31 +23,39 @@ export default eventHandler(async (ev) => {
completed: body.completed, completed: body.completed,
coAuthor: !!body.coAuthor ? body.coAuthor : null, 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) { for (const c of body.chapters) {
let idx = os.chapters.findIndex((k) => k.id === c.id); let idx = cc.findIndex((k) => k.id === c.id);
const cont = await bodyHandler(c); const cont = await bodyHandler(c);
if (idx === -1) { if (idx === -1) {
os.chapters!.push({ os.chapters!.push({
...modelFormChapter(c), ...modelFormChapter(c),
words: countWords(cont),
posted: new Date(Date.now()), posted: new Date(Date.now()),
}); });
} else { } else {
os.chapters[idx] = { os.chapters!.push({
...modelFormChapter(c), ...modelFormChapter(c),
id: os.chapters[idx].id, // id: os.chapters[idx].id,
words: countWords(cont), words: countWords(cont),
posted: os.chapters[idx].posted, posted: cc[idx].posted,
}; });
} }
} }
os.chapters.sort((a, b) => a.index - b.index);
await os.save(); await os.save();
for (let i = 0; i < os.chapters.length; i++) { for (let i = 0; i < os.chapters.length; i++) {
const c = os.chapters[i]; const c = os.chapters[i];
const cont = await bodyHandler(body.chapters[i]); const cont = await bodyHandler(body.chapters[i]);
await replaceOrUploadContent(c.id ?? c._id, cont, getBucket()); await replaceOrUploadContent(c.id ?? c._id, cont);
} }
os = await Story.findOneAndUpdate( os = await Story.findOneAndUpdate(
{ {

@ -4,8 +4,6 @@
"allowJs": true, "allowJs": true,
"outDir": "../out", "outDir": "../out",
"noImplicitAny": false, "noImplicitAny": false,
"verbatimModuleSyntax": false, "verbatimModuleSyntax": false
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
} }
} }

@ -1,13 +1,14 @@
{ {
"extends": "./.nuxt/tsconfig.json",
// https://nuxt.com/docs/guide/concepts/typescript // https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"esModuleInterop": true,
"noImplicitAny": false, "noImplicitAny": false,
"noImplicitThis": false, "noImplicitThis": false,
"verbatimModuleSyntax": false, "verbatimModuleSyntax": false
"forceConsistentCasingInFileNames": false, // "paths": {
"allowSyntheticDefaultImports": true // "@dbconfig": ["./lib/dbconfig.ts"],
}, // "@functions": ["./lib/functions.ts"]
// }
}
} }