From 0ba4d86c9a52899a90be491ee62442c929df8a3f Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Fri, 1 Mar 2024 02:11:13 +0900 Subject: [PATCH 01/18] feat: add schedule post button to publish widget --- components/publish/PublishWidget.vue | 20 +++++++++++++++++++- locales/en.json | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue index cc7190dd..49e3ed7c 100644 --- a/components/publish/PublishWidget.vue +++ b/components/publish/PublishWidget.vue @@ -132,6 +132,8 @@ const expiresInOptions = computed(() => [ const expiresInDefaultOptionIndex = 2 +const scheduleDateTime = ref('') + const characterCount = computed(() => { const text = htmlToText(editor.value?.getHTML() || '') @@ -462,7 +464,23 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { -
+ + + + + + + +
diff --git a/locales/en.json b/locales/en.json index 4097fe3b..0c67c48b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -742,6 +742,7 @@ "pick_an_icon": "Pick an icon", "publish_failed": "Close failed messages at the top of editor to republish posts", "remove_thread_item": "Remove item from thread", + "schedule_post": "Schedule post", "start_thread": "Start thread", "toggle_bold": "Toggle bold", "toggle_code_block": "Toggle code block", From 281145853e2d26a62260ed539e3ad44f3265c568 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Fri, 1 Mar 2024 03:05:04 +0900 Subject: [PATCH 02/18] feat: colorize schedule icon when schedule time is set --- components/publish/PublishWidget.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue index 49e3ed7c..af0cb93e 100644 --- a/components/publish/PublishWidget.vue +++ b/components/publish/PublishWidget.vue @@ -467,7 +467,7 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { @@ -589,7 +606,7 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { v-if="!threadIsActive || isFinalItemOfThread" btn-solid rounded-3 text-sm w-full flex="~ gap1" items-center md:w-fit class="publish-button" :aria-disabled="isPublishDisabled || isExceedingCharacterLimit" aria-describedby="publish-tooltip" - :disabled="isPublishDisabled || isExceedingCharacterLimit" + :disabled="isPublishDisabled || isExceedingCharacterLimit || !isValidScheduledTime" @click="publish" > @@ -603,8 +620,8 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { diff --git a/locales/en.json b/locales/en.json index f840d7ac..e38036b6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -618,6 +618,7 @@ "publishing": "Publishing", "save_failed": "Save failed", "schedule_failed": "Schedule failed", + "schedule_time_invalid": "The scheduled time must be at least 5 minutes later in the future. Set to {0} or later.", "scheduling": "Scheduling", "upload_failed": "Upload failed", "uploading": "Uploading..." From 54632dbb4c828f698dfe561ade752e785ab9a945 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Mon, 4 Mar 2024 02:43:23 +0900 Subject: [PATCH 14/18] style: change text color to red in datetime input with invalid scheduled time --- components/publish/PublishWidget.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue index 2a0f9271..9ac0cf1d 100644 --- a/components/publish/PublishWidget.vue +++ b/components/publish/PublishWidget.vue @@ -645,7 +645,7 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { color: var(--c-text-btn-disabled); } -.option-input:focus+.delete-button { +.option-input:focus + .delete-button { display: none; } @@ -659,4 +659,8 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { align-items: center; border-radius: 50%; } + +input[name="schedule-datetime"]:invalid { + color: var(--c-danger); +} From 40a57d0f3442aa9fcd94c39d38164e84a6a64986 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 1 Feb 2025 16:50:12 +0900 Subject: [PATCH 15/18] chore: format and lint --- components/publish/PublishWidget.vue | 95 ++++++++++++++-------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/components/publish/PublishWidget.vue b/components/publish/PublishWidget.vue index 9ac0cf1d..fb7f1b20 100644 --- a/components/publish/PublishWidget.vue +++ b/components/publish/PublishWidget.vue @@ -90,6 +90,7 @@ function trimPollOptions() { && trimmedOptions.length >= currentInstance.value?.configuration?.polls.maxOptions) { draft.value.params.poll!.options = trimmedOptions } + else { draft.value.params.poll!.options = [...trimmedOptions, ''] } @@ -334,37 +335,37 @@ function stopQuestionMarkPropagation(e: KeyboardEvent) { - -
-
- - - - -
-
    -
  1. - {{ i + 1 }}. - {{ error }} -
  2. -
-
+ +
+
+ + + + +
+
    +
  1. + {{ i + 1 }}. + {{ error }} +
  2. +
+
- -
-
- -
-
+ +
+
+ +
+
- - - - - - + + + + + + -
+
From c00f6c727be448737536f53d3467f1d11811ced6 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 1 Feb 2025 16:55:05 +0900 Subject: [PATCH 16/18] fix: wrong variable name caused by previous merge --- composables/masto/publish.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composables/masto/publish.ts b/composables/masto/publish.ts index 3d611a79..f6dd9df8 100644 --- a/composables/masto/publish.ts +++ b/composables/masto/publish.ts @@ -87,8 +87,8 @@ export function usePublish(options: { } let scheduledAt - if (draft.value.params.scheduledAt) - scheduledAt = new Date(draft.value.params.scheduledAt).toISOString() + if (draftItem.value.params.scheduledAt) + scheduledAt = new Date(draftItem.value.params.scheduledAt).toISOString() const payload = { ...draftItem.value.params, From aef24b65c7962e689567a177a32c373f7a49c019 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Wed, 20 Aug 2025 22:51:14 +0900 Subject: [PATCH 17/18] feat: set initial scheduled post time and handle local-to-UTC conversion --- app/components/publish/PublishWidget.vue | 77 ++++++++++++++++++++---- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/app/components/publish/PublishWidget.vue b/app/components/publish/PublishWidget.vue index 0244ba4f..86aeee2b 100644 --- a/app/components/publish/PublishWidget.vue +++ b/app/components/publish/PublishWidget.vue @@ -54,7 +54,16 @@ const { dropZoneRef, } = useUploadMediaAttachment(draft) -const { shouldExpanded, isExpanded, isSending, isPublishDisabled, publishDraft, failedMessages, preferredLanguage, publishSpoilerText } = usePublish( +const { + shouldExpanded, + isExpanded, + isSending, + isPublishDisabled, + publishDraft, + failedMessages, + preferredLanguage, + publishSpoilerText, +} = usePublish( { draftItem: draft, ...{ expanded: toRef(() => expanded), isUploading, initialDraft: initial, isPartOfThread: false }, @@ -145,6 +154,32 @@ const isValidScheduledTime = computed(() => { return minimumScheduledTime.value.getTime() <= scheduledTimeDate.getTime() }) +const initialDateTime = computed(() => { + const t = new Date(minimumScheduledTime.value.getTime()) + t.setHours(t.getHours() + 1) + t.setMinutes(0) + t.setSeconds(0) + t.setMilliseconds(0) + return t +}) + +watchEffect(() => { + // Convert the local datetime string from the input to a UTC ISO string for the API + if (scheduledTime.value) { + const localDate = new Date(scheduledTime.value) + draft.value.params.scheduledAt = localDate.toISOString() + } + else { + draft.value.params.scheduledAt = '' + } +}) + +function setInitialScheduledTime() { + if (scheduledTime.value === '') { + scheduledTime.value = getDatetimeInputFormat(initialDateTime.value) + } +} + watchEffect(() => { draft.value.params.scheduledAt = scheduledTime.value }) @@ -163,7 +198,15 @@ function getMinimumScheduledTime(now: Date): Date { } function getDatetimeInputFormat(time: Date) { - return time.toISOString().slice(0, 16) + // Returns string in 'YYYY-MM-DDTHH:MM' format using local time components + // This is the format expected by the element. + const year = time.getFullYear() + const month = (time.getMonth() + 1).toString().padStart(2, '0') + const day = time.getDate().toString().padStart(2, '0') + const hours = time.getHours().toString().padStart(2, '0') + const minutes = time.getMinutes().toString().padStart(2, '0') + + return `${year}-${month}-${day}T${hours}:${minutes}` } const characterCount = computed(() => { @@ -220,6 +263,7 @@ async function handlePaste(evt: ClipboardEvent) { function insertEmoji(name: string) { editor.value?.chain().focus().insertEmoji(name).run() } + function insertCustomEmoji(image: any) { editor.value?.chain().focus().insertCustomEmoji(image).run() } @@ -393,9 +437,13 @@ const detectLanguage = useDebounceFn(async () => { - +
@@ -554,7 +603,8 @@ const detectLanguage = useDebounceFn(async () => { /> @@ -571,7 +621,8 @@ const detectLanguage = useDebounceFn(async () => { @@ -580,8 +631,7 @@ const detectLanguage = useDebounceFn(async () => { - - +