Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
This commit is contained in:
		@@ -3,7 +3,7 @@
 | 
			
		||||
	<p v-if="choices.length < 2" class="caution">
 | 
			
		||||
		<i class="fas fa-exclamation-triangle"></i>{{ $ts._poll.noOnlyOneChoice }}
 | 
			
		||||
	</p>
 | 
			
		||||
	<ul ref="choices">
 | 
			
		||||
	<ul>
 | 
			
		||||
		<li v-for="(choice, i) in choices" :key="i">
 | 
			
		||||
			<MkInput class="input" :model-value="choice" :placeholder="$t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)">
 | 
			
		||||
			</MkInput>
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
	</ul>
 | 
			
		||||
	<MkButton v-if="choices.length < 10" class="add" @click="add">{{ $ts.add }}</MkButton>
 | 
			
		||||
	<MkButton v-else class="add" disabled>{{ $ts._poll.noMore }}</MkButton>
 | 
			
		||||
	<MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch>
 | 
			
		||||
	<section>
 | 
			
		||||
		<MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch>
 | 
			
		||||
		<div>
 | 
			
		||||
			<MkSelect v-model="expiration">
 | 
			
		||||
				<template #label>{{ $ts._poll.expiration }}</template>
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
					<template #label>{{ $ts._poll.deadlineTime }}</template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
			</section>
 | 
			
		||||
			<section v-if="expiration === 'after'">
 | 
			
		||||
			<section v-else-if="expiration === 'after'">
 | 
			
		||||
				<MkInput v-model="after" type="number" class="input">
 | 
			
		||||
					<template #label>{{ $ts._poll.duration }}</template>
 | 
			
		||||
				</MkInput>
 | 
			
		||||
@@ -47,8 +47,8 @@
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, watch } from 'vue';
 | 
			
		||||
import { addTime } from '@/scripts/time';
 | 
			
		||||
import { formatDateTimeString } from '@/scripts/format-time-string';
 | 
			
		||||
import MkInput from './form/input.vue';
 | 
			
		||||
@@ -56,125 +56,85 @@ import MkSelect from './form/select.vue';
 | 
			
		||||
import MkSwitch from './form/switch.vue';
 | 
			
		||||
import MkButton from './ui/button.vue';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
	components: {
 | 
			
		||||
		MkInput,
 | 
			
		||||
		MkSelect,
 | 
			
		||||
		MkSwitch,
 | 
			
		||||
		MkButton,
 | 
			
		||||
	},
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
	modelValue: {
 | 
			
		||||
		expiresAt: string;
 | 
			
		||||
		expiredAfter: number;
 | 
			
		||||
		choices: string[];
 | 
			
		||||
		multiple: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}>();
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(ev: 'update:modelValue', v: {
 | 
			
		||||
		expiresAt: string;
 | 
			
		||||
		expiredAfter: number;
 | 
			
		||||
		choices: string[];
 | 
			
		||||
		multiple: boolean;
 | 
			
		||||
	}): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		poll: {
 | 
			
		||||
			type: Object,
 | 
			
		||||
			required: true
 | 
			
		||||
const choices = ref(props.modelValue.choices);
 | 
			
		||||
const multiple = ref(props.modelValue.multiple);
 | 
			
		||||
const expiration = ref('infinite');
 | 
			
		||||
const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'));
 | 
			
		||||
const atTime = ref('00:00');
 | 
			
		||||
const after = ref(0);
 | 
			
		||||
const unit = ref('second');
 | 
			
		||||
 | 
			
		||||
if (props.modelValue.expiresAt) {
 | 
			
		||||
	expiration.value = 'at';
 | 
			
		||||
	atDate.value = atTime.value = props.modelValue.expiresAt;
 | 
			
		||||
} else if (typeof props.modelValue.expiredAfter === 'number') {
 | 
			
		||||
	expiration.value = 'after';
 | 
			
		||||
	after.value = props.modelValue.expiredAfter / 1000;
 | 
			
		||||
} else {
 | 
			
		||||
	expiration.value = 'infinite';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onInput(i, value) {
 | 
			
		||||
	choices.value[i] = value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function add() {
 | 
			
		||||
	choices.value.push('');
 | 
			
		||||
	// TODO
 | 
			
		||||
	// nextTick(() => {
 | 
			
		||||
	//   (this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus();
 | 
			
		||||
	// });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function remove(i) {
 | 
			
		||||
	choices.value = choices.value.filter((_, _i) => _i != i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function get() {
 | 
			
		||||
	const calcAt = () => {
 | 
			
		||||
		return new Date(`${atDate.value} ${atTime.value}`).getTime();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const calcAfter = () => {
 | 
			
		||||
		let base = parseInt(after.value);
 | 
			
		||||
		switch (unit.value) {
 | 
			
		||||
			case 'day': base *= 24;
 | 
			
		||||
			case 'hour': base *= 60;
 | 
			
		||||
			case 'minute': base *= 60;
 | 
			
		||||
			case 'second': return base *= 1000;
 | 
			
		||||
			default: return null;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	emits: ['updated'],
 | 
			
		||||
	return {
 | 
			
		||||
		choices: choices.value,
 | 
			
		||||
		multiple: multiple.value,
 | 
			
		||||
		...(
 | 
			
		||||
			expiration.value === 'at' ? { expiresAt: calcAt() } :
 | 
			
		||||
			expiration.value === 'after' ? { expiredAfter: calcAfter() } : {}
 | 
			
		||||
		)
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			choices: this.poll.choices,
 | 
			
		||||
			multiple: this.poll.multiple,
 | 
			
		||||
			expiration: 'infinite',
 | 
			
		||||
			atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'),
 | 
			
		||||
			atTime: '00:00',
 | 
			
		||||
			after: 0,
 | 
			
		||||
			unit: 'second',
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		choices: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.$emit('updated', this.get());
 | 
			
		||||
			},
 | 
			
		||||
			deep: true
 | 
			
		||||
		},
 | 
			
		||||
		multiple: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.$emit('updated', this.get());
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		expiration: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.$emit('updated', this.get());
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		atDate: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.$emit('updated', this.get());
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		after: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.$emit('updated', this.get());
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		unit: {
 | 
			
		||||
			handler() {
 | 
			
		||||
				this.$emit('updated', this.get());
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		const poll = this.poll;
 | 
			
		||||
		if (poll.expiresAt) {
 | 
			
		||||
			this.expiration = 'at';
 | 
			
		||||
			this.atDate = this.atTime = poll.expiresAt;
 | 
			
		||||
		} else if (typeof poll.expiredAfter === 'number') {
 | 
			
		||||
			this.expiration = 'after';
 | 
			
		||||
			this.after = poll.expiredAfter / 1000;
 | 
			
		||||
		} else {
 | 
			
		||||
			this.expiration = 'infinite';
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onInput(i, e) {
 | 
			
		||||
			this.choices[i] = e;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		add() {
 | 
			
		||||
			this.choices.push('');
 | 
			
		||||
			this.$nextTick(() => {
 | 
			
		||||
				// TODO
 | 
			
		||||
				//(this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus();
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		remove(i) {
 | 
			
		||||
			this.choices = this.choices.filter((_, _i) => _i != i);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		get() {
 | 
			
		||||
			const at = () => {
 | 
			
		||||
				return new Date(`${this.atDate} ${this.atTime}`).getTime();
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			const after = () => {
 | 
			
		||||
				let base = parseInt(this.after);
 | 
			
		||||
				switch (this.unit) {
 | 
			
		||||
					case 'day': base *= 24;
 | 
			
		||||
					case 'hour': base *= 60;
 | 
			
		||||
					case 'minute': base *= 60;
 | 
			
		||||
					case 'second': return base *= 1000;
 | 
			
		||||
					default: return null;
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			return {
 | 
			
		||||
				choices: this.choices,
 | 
			
		||||
				multiple: this.multiple,
 | 
			
		||||
				...(
 | 
			
		||||
					this.expiration === 'at' ? { expiresAt: at() } :
 | 
			
		||||
					this.expiration === 'after' ? { expiredAfter: after() } : {}
 | 
			
		||||
				)
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
watch([choices, multiple, expiration, atDate, atTime, after, unit], () => emit('update:modelValue', get()), {
 | 
			
		||||
	deep: true,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +176,7 @@ export default defineComponent({
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .add {
 | 
			
		||||
		margin: 8px 0 0 0;
 | 
			
		||||
		margin: 8px 0;
 | 
			
		||||
		z-index: 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -225,21 +185,27 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
		> div {
 | 
			
		||||
			margin: 0 8px;
 | 
			
		||||
			display: flex;
 | 
			
		||||
			flex-direction: row;
 | 
			
		||||
			flex-wrap: wrap;
 | 
			
		||||
			gap: 12px;
 | 
			
		||||
 | 
			
		||||
			&:last-child {
 | 
			
		||||
				flex: 1 0 auto;
 | 
			
		||||
 | 
			
		||||
				> section {
 | 
			
		||||
					align-items: center;
 | 
			
		||||
					display: flex;
 | 
			
		||||
					margin: -32px 0 0;
 | 
			
		||||
				> div {
 | 
			
		||||
					flex-grow: 1;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
					> &:first-child {
 | 
			
		||||
						margin-right: 16px;
 | 
			
		||||
					}
 | 
			
		||||
				> section {
 | 
			
		||||
					// MAGIC: Prevent div above from growing unless wrapped to its own line
 | 
			
		||||
					flex-grow: 9999;
 | 
			
		||||
					align-items: end;
 | 
			
		||||
					display: flex;
 | 
			
		||||
					gap: 4px;
 | 
			
		||||
 | 
			
		||||
					> .input {
 | 
			
		||||
						flex: 1 0 auto;
 | 
			
		||||
						flex: 1 1 auto;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
		<textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
 | 
			
		||||
		<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.locale.hashtags" list="hashtags">
 | 
			
		||||
		<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
 | 
			
		||||
		<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/>
 | 
			
		||||
		<XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
 | 
			
		||||
		<XNotePreview v-if="showPreview" class="preview" :text="text"/>
 | 
			
		||||
		<footer>
 | 
			
		||||
			<button v-tooltip="i18n.locale.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button>
 | 
			
		||||
@@ -111,9 +111,9 @@ const props = withDefaults(defineProps<{
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
	(e: 'posted'): void;
 | 
			
		||||
	(e: 'cancel'): void;
 | 
			
		||||
	(e: 'esc'): void;
 | 
			
		||||
	(ev: 'posted'): void;
 | 
			
		||||
	(ev: 'cancel'): void;
 | 
			
		||||
	(ev: 'esc'): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const textareaEl = $ref<HTMLTextAreaElement | null>(null);
 | 
			
		||||
@@ -127,8 +127,8 @@ let files = $ref(props.initialFiles ?? []);
 | 
			
		||||
let poll = $ref<{
 | 
			
		||||
	choices: string[];
 | 
			
		||||
	multiple: boolean;
 | 
			
		||||
	expiresAt: string;
 | 
			
		||||
	expiredAfter: string;
 | 
			
		||||
	expiresAt: string | null;
 | 
			
		||||
	expiredAfter: string | null;
 | 
			
		||||
} | null>(null);
 | 
			
		||||
let useCw = $ref(false);
 | 
			
		||||
let showPreview = $ref(false);
 | 
			
		||||
@@ -371,11 +371,6 @@ function upload(file: File, name?: string) {
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onPollUpdate(poll) {
 | 
			
		||||
	poll = poll;
 | 
			
		||||
	saveDraft();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setVisibility() {
 | 
			
		||||
	if (props.channel) {
 | 
			
		||||
		// TODO: information dialog
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user