♻️ Interactive v2 #1
| @@ -25,15 +25,15 @@ func createArticle(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Alias       string              `json:"alias"` | ||||
| 		Title       string              `json:"title" validate:"required"` | ||||
| 		Description string              `json:"description"` | ||||
| 		Content     string              `json:"content" validate:"required"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		RealmID     *uint               `json:"realm_id"` | ||||
| 		Alias       string              `json:"alias" form:"alias"` | ||||
| 		Title       string              `json:"title" form:"title" validate:"required"` | ||||
| 		Description string              `json:"description" form:"description"` | ||||
| 		Content     string              `json:"content" form:"content" validate:"required"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||
| 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||
| 		RealmID     *uint               `json:"realm_id" form:"realm_id"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| @@ -78,14 +78,14 @@ func editArticle(c *fiber.Ctx) error { | ||||
| 	id, _ := c.ParamsInt("articleId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Alias       string              `json:"alias" validate:"required"` | ||||
| 		Title       string              `json:"title" validate:"required"` | ||||
| 		Description string              `json:"description"` | ||||
| 		Content     string              `json:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		Alias       string              `json:"alias" form:"alias" validate:"required"` | ||||
| 		Title       string              `json:"title" form:"title" validate:"required"` | ||||
| 		Description string              `json:"description" form:"description"` | ||||
| 		Content     string              `json:"content" form:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
|   | ||||
| @@ -56,13 +56,13 @@ func createComment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Alias       string              `json:"alias"` | ||||
| 		Content     string              `json:"content" validate:"required"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		ReplyTo     uint                `json:"reply_to"` | ||||
| 		Alias       string              `json:"alias" form:"alias"` | ||||
| 		Content     string              `json:"content" form:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||
| 		ReplyTo     uint                `json:"reply_to" form:"reply_to"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| @@ -133,12 +133,12 @@ func editComment(c *fiber.Ctx) error { | ||||
| 	id, _ := c.ParamsInt("commentId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Alias       string              `json:"alias" validate:"required"` | ||||
| 		Content     string              `json:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		Alias       string              `json:"alias" form:"alias" validate:"required"` | ||||
| 		Content     string              `json:"content" form:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
|   | ||||
| @@ -25,14 +25,14 @@ func createMoment(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Alias       string              `json:"alias"` | ||||
| 		Content     string              `json:"content" validate:"required"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		RealmID     *uint               `json:"realm_id"` | ||||
| 		RepostTo    uint                `json:"repost_to"` | ||||
| 		Alias       string              `json:"alias" form:"alias"` | ||||
| 		Content     string              `json:"content" form:"content" validate:"required"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||
| 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||
| 		RealmID     *uint               `json:"realm_id" form:"realm_id"` | ||||
| 		RepostTo    uint                `json:"repost_to" form:"repost_to"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
| @@ -88,12 +88,12 @@ func editMoment(c *fiber.Ctx) error { | ||||
| 	id, _ := c.ParamsInt("momentId", 0) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Alias       string              `json:"alias" validate:"required"` | ||||
| 		Content     string              `json:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments"` | ||||
| 		Alias       string              `json:"alias" form:"alias" validate:"required"` | ||||
| 		Content     string              `json:"content" form:"content" validate:"required"` | ||||
| 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||
| 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||
| 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||
| 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
|   | ||||
| @@ -97,8 +97,8 @@ func reactPost(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
|  | ||||
| 	var data struct { | ||||
| 		Symbol   string                  `json:"symbol" validate:"required"` | ||||
| 		Attitude models.ReactionAttitude `json:"attitude" validate:"required"` | ||||
| 		Symbol   string                  `json:"symbol" form:"symbol" validate:"required"` | ||||
| 		Attitude models.ReactionAttitude `json:"attitude" form:"attitude" validate:"required"` | ||||
| 	} | ||||
|  | ||||
| 	if err := BindAndValidate(c, &data); err != nil { | ||||
|   | ||||
| @@ -80,7 +80,7 @@ func NewServer() { | ||||
| 			posts.Post("/:postId/comments", authMiddleware, createComment) | ||||
| 		} | ||||
|  | ||||
| 		moments := api.Group("/moments").Name("Moments API") | ||||
| 		moments := api.Group("/p/moments").Name("Moments API") | ||||
| 		{ | ||||
| 			moments.Post("/", authMiddleware, createMoment) | ||||
| 			moments.Put("/:momentId", authMiddleware, editMoment) | ||||
|   | ||||
| @@ -77,6 +77,7 @@ func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) { | ||||
| 	table := viper.GetString("database.prefix") + v.TableName | ||||
| 	if err := v.Tx. | ||||
| 		Table(table). | ||||
| 		Select("*, ? as model_type", v.ColumnName). | ||||
| 		Where("alias = ?", alias). | ||||
| 		First(&item).Error; err != nil { | ||||
| 		return item, err | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| html, body, #app, .v-application { | ||||
|     overflow: auto !important; | ||||
|     font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif; | ||||
| } | ||||
|  | ||||
| .no-scrollbar { | ||||
|   | ||||
| @@ -5,20 +5,28 @@ | ||||
|  | ||||
|   <div v-else class="flex flex-col gap-2 mt-3"> | ||||
|     <div v-for="(item, idx) in props.comments" class="text-sm"> | ||||
|       <post-item :item="item" @update:item="val => updateItem(idx, val)" /> | ||||
|       <post-item :item="item" @update:item="(val) => updateItem(idx, val)" /> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <v-divider class="mt-2 mb-3 border-opacity-50 mx-[-1rem]" /> | ||||
|  | ||||
|   <v-btn block prepend-icon="mdi-pencil" variant="plain" @click="leaveComment">Leave your comment</v-btn> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { request } from "@/scripts/request" | ||||
| import { reactive, ref } from "vue" | ||||
| import { reactive, ref, watch } from "vue" | ||||
| import { useEditor } from "@/stores/editor" | ||||
| import PostItem from "@/components/posts/PostItem.vue" | ||||
|  | ||||
| const editor = useEditor() | ||||
|  | ||||
| const props = defineProps<{ | ||||
|   comments: any[] | ||||
|   model: any | ||||
|   alias: any | ||||
|   item: any | ||||
| }>() | ||||
| const emits = defineEmits(["update:comments"]) | ||||
|  | ||||
| @@ -50,8 +58,20 @@ async function readComments() { | ||||
| readComments() | ||||
|  | ||||
| function updateItem(idx: number, data: any) { | ||||
|   const comments = JSON.parse(JSON.stringify(props.comments)); | ||||
|   comments[idx] = data; | ||||
|   emits("update:comments", comments); | ||||
|   const comments = JSON.parse(JSON.stringify(props.comments)) | ||||
|   comments[idx] = data | ||||
|   emits("update:comments", comments) | ||||
| } | ||||
|  | ||||
| watch(editor, (val) => { | ||||
|   if (val.done) { | ||||
|     readComments().then(() => (val.done = false)) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| function leaveComment() { | ||||
|   editor.related.comment_to = props.item | ||||
|   editor.related.comment_to.model_type += "s" | ||||
|   editor.show.comment = true | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -12,9 +12,7 @@ | ||||
|  | ||||
|     <v-menu v-if="!props.readonly" location="bottom center"> | ||||
|       <template v-slot:activator="{ props: binding }"> | ||||
|         <v-chip v-bind="binding" :size="props.size" prepend-icon="mdi-emoticon-plus"> | ||||
|           React | ||||
|         </v-chip> | ||||
|         <v-chip v-bind="binding" :size="props.size" prepend-icon="mdi-emoticon-plus"> React </v-chip> | ||||
|       </template> | ||||
|  | ||||
|       <v-list density="compact" lines="one"> | ||||
| @@ -30,52 +28,53 @@ | ||||
|     <v-snackbar v-model="status.added" :timeout="3000">Your react has been added into post.</v-snackbar> | ||||
|     <v-snackbar v-model="status.removed" :timeout="3000">Your react has been removed from post.</v-snackbar> | ||||
|  | ||||
|     <!-- @vue-ignore --> | ||||
|     <v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { request } from "@/scripts/request"; | ||||
| import { getAtk } from "@/stores/userinfo"; | ||||
| import { reactive, ref } from "vue"; | ||||
| import { request } from "@/scripts/request" | ||||
| import { getAtk } from "@/stores/userinfo" | ||||
| import { reactive, ref } from "vue" | ||||
|  | ||||
| const emits = defineEmits(["update"]); | ||||
| const emits = defineEmits(["update"]) | ||||
| const props = defineProps<{ | ||||
|   size?: string, | ||||
|   readonly?: boolean, | ||||
|   model: any, | ||||
|   item: any, | ||||
|   size?: string | ||||
|   readonly?: boolean | ||||
|   model: any | ||||
|   item: any | ||||
|   reactions: { [id: string]: number } | ||||
| }>(); | ||||
| }>() | ||||
|  | ||||
| const emojis: { [id: string]: { icon: string, attitude: number } } = { | ||||
| const emojis: { [id: string]: { icon: string; attitude: number } } = { | ||||
|   thumb_up: { icon: "👍", attitude: 1 }, | ||||
|   clap: { icon: "👏", attitude: 1 } | ||||
| }; | ||||
|  | ||||
| function pickColor(): string { | ||||
|   const colors = ["blue", "green", "purple"]; | ||||
|   const randomIndex = Math.floor(Math.random() * colors.length); | ||||
|   return colors[randomIndex]; | ||||
| } | ||||
|  | ||||
| const status = reactive({ added: false, removed: false }); | ||||
| const error = ref<string | null>(null); | ||||
| function pickColor(): string { | ||||
|   const colors = ["blue", "green", "purple"] | ||||
|   const randomIndex = Math.floor(Math.random() * colors.length) | ||||
|   return colors[randomIndex] | ||||
| } | ||||
|  | ||||
| const status = reactive({ added: false, removed: false }) | ||||
| const error = ref<string | null>(null) | ||||
|  | ||||
| async function reactPost(symbol: string, attitude: number) { | ||||
|   const res = await request(`/api/p/${props.model}/${props.item?.id}/react`, { | ||||
|     method: "POST", | ||||
|     headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" }, | ||||
|     headers: { Authorization: `Bearer ${getAtk()}`, "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify({ symbol, attitude }) | ||||
|   }); | ||||
|   }) | ||||
|   if (res.status === 201) { | ||||
|     status.added = true; | ||||
|     emits("update", symbol, 1); | ||||
|     status.added = true | ||||
|     emits("update", symbol, 1) | ||||
|   } else if (res.status === 204) { | ||||
|     status.removed = true; | ||||
|     emits("update", symbol, -1); | ||||
|     status.removed = true | ||||
|     emits("update", symbol, -1) | ||||
|   } else { | ||||
|     error.value = await res.text(); | ||||
|     error.value = await res.text() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										65
									
								
								pkg/views/src/components/publish/CommentEditor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								pkg/views/src/components/publish/CommentEditor.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| <template> | ||||
|   <v-card title="Leave your comment" :loading="loading"> | ||||
|     <v-form @submit.prevent="postComment"> | ||||
|       <v-card-text> | ||||
|         <v-textarea required hide-details name="content" variant="outlined" label="What do you want to say?" /> | ||||
|  | ||||
|         <p class="px-2 mt-1 text-body-2 opacity-80">Your comment will leave below {{ postIdentifier }}</p> | ||||
|       </v-card-text> | ||||
|  | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|  | ||||
|         <v-btn type="reset" color="grey-darken-3" @click="editor.show.comment = false">Cancel</v-btn> | ||||
|         <v-btn type="submit" :disabled="loading">Publish</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-form> | ||||
|   </v-card> | ||||
|  | ||||
|   <v-snackbar v-model="success" :timeout="3000">Your comment has been published.</v-snackbar> | ||||
|  | ||||
|   <!-- @vue-ignore --> | ||||
|   <v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { request } from "@/scripts/request" | ||||
| import { useEditor } from "@/stores/editor" | ||||
| import { getAtk } from "@/stores/userinfo" | ||||
| import { computed, ref } from "vue" | ||||
|  | ||||
| const editor = useEditor() | ||||
|  | ||||
| const target = computed<any>(() => editor.related.comment_to) | ||||
| const postIdentifier = computed(() => { | ||||
|   if (editor.related.comment_to?.title) { | ||||
|     return `${editor.related.comment_to.title}` | ||||
|   } else { | ||||
|     return `#${editor.related.comment_to?.alias}` | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const error = ref<string | null>(null) | ||||
| const success = ref(false) | ||||
| const loading = ref(false) | ||||
|  | ||||
| async function postComment(evt: SubmitEvent) { | ||||
|   const data = new FormData(evt.target as HTMLFormElement) | ||||
|   if (!data.has("content")) return | ||||
|  | ||||
|   loading.value = true | ||||
|   const res = await request(`/api/p/${target.value?.model_type}/${target.value?.alias}/comments`, { | ||||
|     method: "POST", | ||||
|     headers: { Authorization: `Bearer ${getAtk()}` }, | ||||
|     body: data | ||||
|   }) | ||||
|   if (res.status === 200) { | ||||
|     success.value = true | ||||
|   } else { | ||||
|     error.value = await res.text() | ||||
|   } | ||||
|   loading.value = false | ||||
|   editor.show.comment = false | ||||
|   editor.done = true | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										123
									
								
								pkg/views/src/components/publish/MomentEditor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								pkg/views/src/components/publish/MomentEditor.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| <template> | ||||
|   <v-card title="Record a moment" :loading="loading"> | ||||
|     <v-form @submit.prevent="postMoment"> | ||||
|       <v-card-text> | ||||
|         <v-textarea required hide-details name="content" variant="outlined" label="What's happened?!" /> | ||||
|  | ||||
|         <div class="flex mt-1"> | ||||
|           <v-tooltip text="Planned publish" location="start"> | ||||
|             <template #activator="{ props }"> | ||||
|               <v-btn | ||||
|                 v-bind="props" | ||||
|                 type="button" | ||||
|                 variant="text" | ||||
|                 icon="mdi-calendar" | ||||
|                 size="small" | ||||
|                 @click="dialogs.plan = true" | ||||
|               /> | ||||
|             </template> | ||||
|           </v-tooltip> | ||||
|           <v-tooltip text="Categories" location="start"> | ||||
|             <template #activator="{ props }"> | ||||
|               <v-btn | ||||
|                 v-bind="props" | ||||
|                 type="button" | ||||
|                 variant="text" | ||||
|                 icon="mdi-shape" | ||||
|                 size="small" | ||||
|                 @click="dialogs.categories = true" | ||||
|               /> | ||||
|             </template> | ||||
|           </v-tooltip> | ||||
|           <v-tooltip text="Media" location="start"> | ||||
|             <template #activator="{ props }"> | ||||
|               <v-btn | ||||
|                 v-bind="props" | ||||
|                 type="button" | ||||
|                 variant="text" | ||||
|                 icon="mdi-camera" | ||||
|                 size="small" | ||||
|                 @click="dialogs.media = true" | ||||
|               /> | ||||
|             </template> | ||||
|           </v-tooltip> | ||||
|         </div> | ||||
|       </v-card-text> | ||||
|  | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|  | ||||
|         <v-btn type="reset" color="grey-darken-3" @click="editor.show.moment = false">Cancel</v-btn> | ||||
|         <v-btn type="submit" :disabled="loading">Publish</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-form> | ||||
|   </v-card> | ||||
|  | ||||
|   <v-dialog eager v-model="dialogs.plan" class="max-w-[540px]"> | ||||
|     <v-card title="Plan your publish"> | ||||
|       <template #text> | ||||
|         <v-text-field | ||||
|           v-model="extras.publishedAt" | ||||
|           class="mt-2" | ||||
|           label="Publish date" | ||||
|           hint="Your post will hidden for public before this time. Leave blank will publish immediately" | ||||
|           variant="outlined" | ||||
|           type="datetime-local" | ||||
|           clearable | ||||
|         /> | ||||
|       </template> | ||||
|       <template #actions> | ||||
|         <v-btn class="ms-auto" text="Ok" @click="dialogs.plan = false"></v-btn> | ||||
|       </template> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
|  | ||||
|   <v-snackbar v-model="success" :timeout="3000">Your post has been published.</v-snackbar> | ||||
|  | ||||
|   <!-- @vue-ignore --> | ||||
|   <v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { request } from "@/scripts/request" | ||||
| import { useEditor } from "@/stores/editor" | ||||
| import { getAtk } from "@/stores/userinfo" | ||||
| import { reactive, ref } from "vue" | ||||
|  | ||||
| const editor = useEditor() | ||||
|  | ||||
| const dialogs = reactive({ | ||||
|   plan: false, | ||||
|   categories: false, | ||||
|   media: false | ||||
| }) | ||||
|  | ||||
| const extras = reactive({ | ||||
|   publishedAt: null | ||||
| }) | ||||
|  | ||||
| const error = ref<string | null>(null) | ||||
| const success = ref(false) | ||||
| const loading = ref(false) | ||||
|  | ||||
| async function postMoment(evt: SubmitEvent) { | ||||
|   const data = new FormData(evt.target as HTMLFormElement) | ||||
|   if (!data.has("content")) return | ||||
|   if (!extras.publishedAt) data.set("published_at", new Date().toISOString()) | ||||
|   else data.set("published_at", extras.publishedAt) | ||||
|  | ||||
|   loading.value = true | ||||
|   const res = await request("/api/p/moments", { | ||||
|     method: "POST", | ||||
|     headers: { Authorization: `Bearer ${getAtk()}` }, | ||||
|     body: data | ||||
|   }) | ||||
|   if (res.status === 200) { | ||||
|     success.value = true | ||||
|   } else { | ||||
|     error.value = await res.text() | ||||
|   } | ||||
|   loading.value = false | ||||
|   editor.show.moment = false | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										16
									
								
								pkg/views/src/components/publish/PostAction.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								pkg/views/src/components/publish/PostAction.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="editor.show.moment" class="max-w-[540px]"> | ||||
|     <moment-editor /> | ||||
|   </v-dialog> | ||||
|   <v-dialog v-model="editor.show.comment" class="max-w-[540px]"> | ||||
|     <comment-editor /> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useEditor } from "@/stores/editor" | ||||
| import MomentEditor from "@/components/publish/MomentEditor.vue" | ||||
| import CommentEditor from "@/components/publish/CommentEditor.vue"; | ||||
|  | ||||
| const editor = useEditor() | ||||
| </script> | ||||
| @@ -1,47 +0,0 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="editor.show.moment" class="max-w-[540px]"> | ||||
|     <v-card title="Record a moment"> | ||||
|       <v-form> | ||||
|         <v-card-text> | ||||
|           <v-textarea | ||||
|             required | ||||
|             hide-details | ||||
|             variant="outlined" | ||||
|             label="What's happened?!" | ||||
|           /> | ||||
|  | ||||
|           <div class="flex mt-1"> | ||||
|             <v-tooltip text="Planned publish" location="start"> | ||||
|               <template #activator="{ props }"> | ||||
|                 <v-btn v-bind="props" type="button" variant="text" icon="mdi-calendar" size="small" /> | ||||
|               </template> | ||||
|             </v-tooltip> | ||||
|             <v-tooltip text="Categories" location="start"> | ||||
|               <template #activator="{ props }"> | ||||
|                 <v-btn v-bind="props" type="button" variant="text" icon="mdi-shape" size="small" /> | ||||
|               </template> | ||||
|             </v-tooltip> | ||||
|             <v-tooltip text="Media" location="start"> | ||||
|               <template #activator="{ props }"> | ||||
|                 <v-btn v-bind="props" type="button" variant="text" icon="mdi-camera" size="small" /> | ||||
|               </template> | ||||
|             </v-tooltip> | ||||
|           </div> | ||||
|         </v-card-text> | ||||
|  | ||||
|         <v-card-actions> | ||||
|           <v-spacer></v-spacer> | ||||
|  | ||||
|           <v-btn type="reset" color="grey-darken-3" @click="editor.show.moment = false">Cancel</v-btn> | ||||
|           <v-btn type="submit" @click.prevent>Publish</v-btn> | ||||
|         </v-card-actions> | ||||
|       </v-form> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useEditor } from "@/stores/editor"; | ||||
|  | ||||
| const editor = useEditor(); | ||||
| </script> | ||||
| @@ -1,7 +1,6 @@ | ||||
| <template> | ||||
|   <v-navigation-drawer v-model="drawerOpen" color="grey-lighten-5" floating> | ||||
|     <v-list density="compact" nav> | ||||
|     </v-list> | ||||
|     <v-list density="compact" nav> </v-list> | ||||
|   </v-navigation-drawer> | ||||
|  | ||||
|   <v-app-bar height="64" color="primary" scroll-behavior="elevate" flat> | ||||
| @@ -35,14 +34,7 @@ | ||||
|     transition="scroll-y-reverse-transition" | ||||
|   > | ||||
|     <template v-slot:activator="{ props }"> | ||||
|       <v-fab | ||||
|         v-bind="props" | ||||
|         class="editor-fab" | ||||
|         icon="mdi-pencil" | ||||
|         color="primary" | ||||
|         size="64" | ||||
|         appear | ||||
|       /> | ||||
|       <v-fab v-bind="props" class="editor-fab" icon="mdi-pencil" color="primary" size="64" appear /> | ||||
|     </template> | ||||
|  | ||||
|     <div class="flex flex-col items-center gap-4 mb-4"> | ||||
| @@ -51,23 +43,21 @@ | ||||
|     </div> | ||||
|   </v-menu> | ||||
|  | ||||
|   <post-editor /> | ||||
|   <post-action /> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { ref } from "vue"; | ||||
| import { useEditor } from "@/stores/editor"; | ||||
| import PostEditor from "@/components/publish/PostEditor.vue"; | ||||
| import { ref } from "vue" | ||||
| import { useEditor } from "@/stores/editor" | ||||
| import PostAction from "@/components/publish/PostAction.vue" | ||||
|  | ||||
| const editor = useEditor(); | ||||
| const navigationMenu = [ | ||||
|   { name: "Explore", icon: "mdi-compass", to: "explore" } | ||||
| ]; | ||||
| const editor = useEditor() | ||||
| const navigationMenu = [{ name: "Explore", icon: "mdi-compass", to: "explore" }] | ||||
|  | ||||
| const drawerOpen = ref(true); | ||||
| const drawerOpen = ref(true) | ||||
|  | ||||
| function toggleDrawer() { | ||||
|   drawerOpen.value = !drawerOpen.value; | ||||
|   drawerOpen.value = !drawerOpen.value | ||||
| } | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,20 @@ | ||||
| import { defineStore } from "pinia"; | ||||
| import { reactive, ref } from "vue"; | ||||
| import { defineStore } from "pinia" | ||||
| import { reactive, ref } from "vue" | ||||
|  | ||||
| export const useEditor = defineStore("editor", () => { | ||||
|   const done = ref(false) | ||||
|  | ||||
|   const show = reactive({ | ||||
|     moment: false, | ||||
|     article: false, | ||||
|   }); | ||||
|     comment: false | ||||
|   }) | ||||
|  | ||||
|   return { show }; | ||||
| }); | ||||
|   const related = reactive<{ comment_to: any; reply_to: any; repost_to: any }>({ | ||||
|     comment_to: null, | ||||
|     reply_to: null, | ||||
|     repost_to: null | ||||
|   }) | ||||
|  | ||||
|   return { show, related, done } | ||||
| }) | ||||
|   | ||||
| @@ -30,7 +30,12 @@ | ||||
|     <div class="aside sticky top-0 w-full h-fit md:min-w-[280px]"> | ||||
|       <v-card title="Comments"> | ||||
|         <div class="px-[1rem] pb-[0.825rem] mt-[-12px]"> | ||||
|           <comment-list v-model:comments="comments" :model="route.params.postType" :alias="route.params.alias" /> | ||||
|           <comment-list | ||||
|             v-model:comments="comments" | ||||
|             :item="post" | ||||
|             :model="route.params.postType" | ||||
|             :alias="route.params.alias" | ||||
|           /> | ||||
|         </div> | ||||
|       </v-card> | ||||
|     </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user