♻️ Interactive v2 #1
| @@ -25,15 +25,15 @@ func createArticle(c *fiber.Ctx) error { | |||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Alias       string              `json:"alias"` | 		Alias       string              `json:"alias" form:"alias"` | ||||||
| 		Title       string              `json:"title" validate:"required"` | 		Title       string              `json:"title" form:"title" validate:"required"` | ||||||
| 		Description string              `json:"description"` | 		Description string              `json:"description" form:"description"` | ||||||
| 		Content     string              `json:"content" validate:"required"` | 		Content     string              `json:"content" form:"content" validate:"required"` | ||||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||||
| 		Categories  []models.Category   `json:"categories"` | 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||||
| 		Attachments []models.Attachment `json:"attachments"` | 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||||
| 		PublishedAt *time.Time          `json:"published_at"` | 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||||
| 		RealmID     *uint               `json:"realm_id"` | 		RealmID     *uint               `json:"realm_id" form:"realm_id"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
| @@ -78,14 +78,14 @@ func editArticle(c *fiber.Ctx) error { | |||||||
| 	id, _ := c.ParamsInt("articleId", 0) | 	id, _ := c.ParamsInt("articleId", 0) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Alias       string              `json:"alias" validate:"required"` | 		Alias       string              `json:"alias" form:"alias" validate:"required"` | ||||||
| 		Title       string              `json:"title" validate:"required"` | 		Title       string              `json:"title" form:"title" validate:"required"` | ||||||
| 		Description string              `json:"description"` | 		Description string              `json:"description" form:"description"` | ||||||
| 		Content     string              `json:"content" validate:"required"` | 		Content     string              `json:"content" form:"content" validate:"required"` | ||||||
| 		PublishedAt *time.Time          `json:"published_at"` | 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||||
| 		Categories  []models.Category   `json:"categories"` | 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||||
| 		Attachments []models.Attachment `json:"attachments"` | 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|   | |||||||
| @@ -56,13 +56,13 @@ func createComment(c *fiber.Ctx) error { | |||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Alias       string              `json:"alias"` | 		Alias       string              `json:"alias" form:"alias"` | ||||||
| 		Content     string              `json:"content" validate:"required"` | 		Content     string              `json:"content" form:"content" validate:"required"` | ||||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||||
| 		Categories  []models.Category   `json:"categories"` | 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||||
| 		Attachments []models.Attachment `json:"attachments"` | 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||||
| 		PublishedAt *time.Time          `json:"published_at"` | 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||||
| 		ReplyTo     uint                `json:"reply_to"` | 		ReplyTo     uint                `json:"reply_to" form:"reply_to"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
| @@ -133,12 +133,12 @@ func editComment(c *fiber.Ctx) error { | |||||||
| 	id, _ := c.ParamsInt("commentId", 0) | 	id, _ := c.ParamsInt("commentId", 0) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Alias       string              `json:"alias" validate:"required"` | 		Alias       string              `json:"alias" form:"alias" validate:"required"` | ||||||
| 		Content     string              `json:"content" validate:"required"` | 		Content     string              `json:"content" form:"content" validate:"required"` | ||||||
| 		PublishedAt *time.Time          `json:"published_at"` | 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||||
| 		Categories  []models.Category   `json:"categories"` | 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||||
| 		Attachments []models.Attachment `json:"attachments"` | 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|   | |||||||
| @@ -25,14 +25,14 @@ func createMoment(c *fiber.Ctx) error { | |||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Alias       string              `json:"alias"` | 		Alias       string              `json:"alias" form:"alias"` | ||||||
| 		Content     string              `json:"content" validate:"required"` | 		Content     string              `json:"content" form:"content" validate:"required"` | ||||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||||
| 		Categories  []models.Category   `json:"categories"` | 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||||
| 		Attachments []models.Attachment `json:"attachments"` | 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||||
| 		PublishedAt *time.Time          `json:"published_at"` | 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||||
| 		RealmID     *uint               `json:"realm_id"` | 		RealmID     *uint               `json:"realm_id" form:"realm_id"` | ||||||
| 		RepostTo    uint                `json:"repost_to"` | 		RepostTo    uint                `json:"repost_to" form:"repost_to"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
| @@ -88,12 +88,12 @@ func editMoment(c *fiber.Ctx) error { | |||||||
| 	id, _ := c.ParamsInt("momentId", 0) | 	id, _ := c.ParamsInt("momentId", 0) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Alias       string              `json:"alias" validate:"required"` | 		Alias       string              `json:"alias" form:"alias" validate:"required"` | ||||||
| 		Content     string              `json:"content" validate:"required"` | 		Content     string              `json:"content" form:"content" validate:"required"` | ||||||
| 		PublishedAt *time.Time          `json:"published_at"` | 		PublishedAt *time.Time          `json:"published_at" form:"published_at"` | ||||||
| 		Hashtags    []models.Tag        `json:"hashtags"` | 		Hashtags    []models.Tag        `json:"hashtags" form:"hashtags"` | ||||||
| 		Categories  []models.Category   `json:"categories"` | 		Categories  []models.Category   `json:"categories" form:"categories"` | ||||||
| 		Attachments []models.Attachment `json:"attachments"` | 		Attachments []models.Attachment `json:"attachments" form:"attachments"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|   | |||||||
| @@ -97,8 +97,8 @@ func reactPost(c *fiber.Ctx) error { | |||||||
| 	user := c.Locals("principal").(models.Account) | 	user := c.Locals("principal").(models.Account) | ||||||
|  |  | ||||||
| 	var data struct { | 	var data struct { | ||||||
| 		Symbol   string                  `json:"symbol" validate:"required"` | 		Symbol   string                  `json:"symbol" form:"symbol" validate:"required"` | ||||||
| 		Attitude models.ReactionAttitude `json:"attitude" validate:"required"` | 		Attitude models.ReactionAttitude `json:"attitude" form:"attitude" validate:"required"` | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := BindAndValidate(c, &data); err != nil { | 	if err := BindAndValidate(c, &data); err != nil { | ||||||
|   | |||||||
| @@ -80,7 +80,7 @@ func NewServer() { | |||||||
| 			posts.Post("/:postId/comments", authMiddleware, createComment) | 			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.Post("/", authMiddleware, createMoment) | ||||||
| 			moments.Put("/:momentId", authMiddleware, editMoment) | 			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 | 	table := viper.GetString("database.prefix") + v.TableName | ||||||
| 	if err := v.Tx. | 	if err := v.Tx. | ||||||
| 		Table(table). | 		Table(table). | ||||||
|  | 		Select("*, ? as model_type", v.ColumnName). | ||||||
| 		Where("alias = ?", alias). | 		Where("alias = ?", alias). | ||||||
| 		First(&item).Error; err != nil { | 		First(&item).Error; err != nil { | ||||||
| 		return item, err | 		return item, err | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| html, body, #app, .v-application { | html, body, #app, .v-application { | ||||||
|     overflow: auto !important; |     overflow: auto !important; | ||||||
|  |     font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif; | ||||||
| } | } | ||||||
|  |  | ||||||
| .no-scrollbar { | .no-scrollbar { | ||||||
|   | |||||||
| @@ -5,20 +5,28 @@ | |||||||
|  |  | ||||||
|   <div v-else class="flex flex-col gap-2 mt-3"> |   <div v-else class="flex flex-col gap-2 mt-3"> | ||||||
|     <div v-for="(item, idx) in props.comments" class="text-sm"> |     <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> | ||||||
|   </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> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { request } from "@/scripts/request" | 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" | import PostItem from "@/components/posts/PostItem.vue" | ||||||
|  |  | ||||||
|  | const editor = useEditor() | ||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|   comments: any[] |   comments: any[] | ||||||
|   model: any |   model: any | ||||||
|   alias: any |   alias: any | ||||||
|  |   item: any | ||||||
| }>() | }>() | ||||||
| const emits = defineEmits(["update:comments"]) | const emits = defineEmits(["update:comments"]) | ||||||
|  |  | ||||||
| @@ -50,8 +58,20 @@ async function readComments() { | |||||||
| readComments() | readComments() | ||||||
|  |  | ||||||
| function updateItem(idx: number, data: any) { | function updateItem(idx: number, data: any) { | ||||||
|   const comments = JSON.parse(JSON.stringify(props.comments)); |   const comments = JSON.parse(JSON.stringify(props.comments)) | ||||||
|   comments[idx] = data; |   comments[idx] = data | ||||||
|   emits("update:comments", comments); |   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> | </script> | ||||||
|   | |||||||
| @@ -12,9 +12,7 @@ | |||||||
|  |  | ||||||
|     <v-menu v-if="!props.readonly" location="bottom center"> |     <v-menu v-if="!props.readonly" location="bottom center"> | ||||||
|       <template v-slot:activator="{ props: binding }"> |       <template v-slot:activator="{ props: binding }"> | ||||||
|         <v-chip v-bind="binding" :size="props.size" prepend-icon="mdi-emoticon-plus"> |         <v-chip v-bind="binding" :size="props.size" prepend-icon="mdi-emoticon-plus"> React </v-chip> | ||||||
|           React |  | ||||||
|         </v-chip> |  | ||||||
|       </template> |       </template> | ||||||
|  |  | ||||||
|       <v-list density="compact" lines="one"> |       <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.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> |     <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> |     <v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { request } from "@/scripts/request"; | import { request } from "@/scripts/request" | ||||||
| import { getAtk } from "@/stores/userinfo"; | import { getAtk } from "@/stores/userinfo" | ||||||
| import { reactive, ref } from "vue"; | import { reactive, ref } from "vue" | ||||||
|  |  | ||||||
| const emits = defineEmits(["update"]); | const emits = defineEmits(["update"]) | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
|   size?: string, |   size?: string | ||||||
|   readonly?: boolean, |   readonly?: boolean | ||||||
|   model: any, |   model: any | ||||||
|   item: any, |   item: any | ||||||
|   reactions: { [id: string]: number } |   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 }, |   thumb_up: { icon: "👍", attitude: 1 }, | ||||||
|   clap: { 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 }); | function pickColor(): string { | ||||||
| const error = ref<string | null>(null); |   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) { | async function reactPost(symbol: string, attitude: number) { | ||||||
|   const res = await request(`/api/p/${props.model}/${props.item?.id}/react`, { |   const res = await request(`/api/p/${props.model}/${props.item?.id}/react`, { | ||||||
|     method: "POST", |     method: "POST", | ||||||
|     headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" }, |     headers: { Authorization: `Bearer ${getAtk()}`, "Content-Type": "application/json" }, | ||||||
|     body: JSON.stringify({ symbol, attitude }) |     body: JSON.stringify({ symbol, attitude }) | ||||||
|   }); |   }) | ||||||
|   if (res.status === 201) { |   if (res.status === 201) { | ||||||
|     status.added = true; |     status.added = true | ||||||
|     emits("update", symbol, 1); |     emits("update", symbol, 1) | ||||||
|   } else if (res.status === 204) { |   } else if (res.status === 204) { | ||||||
|     status.removed = true; |     status.removed = true | ||||||
|     emits("update", symbol, -1); |     emits("update", symbol, -1) | ||||||
|   } else { |   } else { | ||||||
|     error.value = await res.text(); |     error.value = await res.text() | ||||||
|   } |   } | ||||||
| } | } | ||||||
| </script> | </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> | <template> | ||||||
|   <v-navigation-drawer v-model="drawerOpen" color="grey-lighten-5" floating> |   <v-navigation-drawer v-model="drawerOpen" color="grey-lighten-5" floating> | ||||||
|     <v-list density="compact" nav> |     <v-list density="compact" nav> </v-list> | ||||||
|     </v-list> |  | ||||||
|   </v-navigation-drawer> |   </v-navigation-drawer> | ||||||
|  |  | ||||||
|   <v-app-bar height="64" color="primary" scroll-behavior="elevate" flat> |   <v-app-bar height="64" color="primary" scroll-behavior="elevate" flat> | ||||||
| @@ -35,14 +34,7 @@ | |||||||
|     transition="scroll-y-reverse-transition" |     transition="scroll-y-reverse-transition" | ||||||
|   > |   > | ||||||
|     <template v-slot:activator="{ props }"> |     <template v-slot:activator="{ props }"> | ||||||
|       <v-fab |       <v-fab v-bind="props" class="editor-fab" icon="mdi-pencil" color="primary" size="64" appear /> | ||||||
|         v-bind="props" |  | ||||||
|         class="editor-fab" |  | ||||||
|         icon="mdi-pencil" |  | ||||||
|         color="primary" |  | ||||||
|         size="64" |  | ||||||
|         appear |  | ||||||
|       /> |  | ||||||
|     </template> |     </template> | ||||||
|  |  | ||||||
|     <div class="flex flex-col items-center gap-4 mb-4"> |     <div class="flex flex-col items-center gap-4 mb-4"> | ||||||
| @@ -51,23 +43,21 @@ | |||||||
|     </div> |     </div> | ||||||
|   </v-menu> |   </v-menu> | ||||||
|  |  | ||||||
|   <post-editor /> |   <post-action /> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { ref } from "vue"; | import { ref } from "vue" | ||||||
| import { useEditor } from "@/stores/editor"; | import { useEditor } from "@/stores/editor" | ||||||
| import PostEditor from "@/components/publish/PostEditor.vue"; | import PostAction from "@/components/publish/PostAction.vue" | ||||||
|  |  | ||||||
| const editor = useEditor(); | const editor = useEditor() | ||||||
| const navigationMenu = [ | const navigationMenu = [{ name: "Explore", icon: "mdi-compass", to: "explore" }] | ||||||
|   { name: "Explore", icon: "mdi-compass", to: "explore" } |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| const drawerOpen = ref(true); | const drawerOpen = ref(true) | ||||||
|  |  | ||||||
| function toggleDrawer() { | function toggleDrawer() { | ||||||
|   drawerOpen.value = !drawerOpen.value; |   drawerOpen.value = !drawerOpen.value | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,20 @@ | |||||||
| import { defineStore } from "pinia"; | import { defineStore } from "pinia" | ||||||
| import { reactive, ref } from "vue"; | import { reactive, ref } from "vue" | ||||||
|  |  | ||||||
| export const useEditor = defineStore("editor", () => { | export const useEditor = defineStore("editor", () => { | ||||||
|  |   const done = ref(false) | ||||||
|  |  | ||||||
|   const show = reactive({ |   const show = reactive({ | ||||||
|     moment: false, |     moment: false, | ||||||
|     article: 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]"> |     <div class="aside sticky top-0 w-full h-fit md:min-w-[280px]"> | ||||||
|       <v-card title="Comments"> |       <v-card title="Comments"> | ||||||
|         <div class="px-[1rem] pb-[0.825rem] mt-[-12px]"> |         <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> |         </div> | ||||||
|       </v-card> |       </v-card> | ||||||
|     </div> |     </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user