diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index eed17edb14598fa27b09a800a1b7c9ac1f25a6b4..e3d0afcefbcf508496d2875bfdd64280581c457a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -768,6 +768,7 @@ squareAvatars: "アイコンを四角形ã§è¡¨ç¤º" sent: "é€ä¿¡" received: "å—ä¿¡" searchResult: "検索çµæžœ" +hashtags: "ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°" _docs: continueReading: "続ãã‚’èªã‚€" diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 13bbb3f9e57eb3322a5f5843a498b2bce622ab9b..ed2a934c263f9fb7f11fb355703018adc7295fcc 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -37,6 +37,7 @@ <MkInfo warn v-if="hasNotSpecifiedMentions" class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> <input v-show="useCw" ref="cw" class="cw" v-model="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> <textarea v-model="text" class="text" :class="{ withCw: useCw }" ref="text" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd" /> + <input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.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"/> <footer> @@ -44,9 +45,13 @@ <button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button> <button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$ts.useCw"><i class="fas fa-eye-slash"></i></button> <button class="_button" @click="insertMention" v-tooltip="$ts.mention"><i class="fas fa-at"></i></button> + <button class="_button" @click="withHashtags = !withHashtags" v-tooltip="$ts.hashtags"><i class="fas fa-hashtag"></i></button> <button class="_button" @click="insertEmoji" v-tooltip="$ts.emoji"><i class="fas fa-laugh-squint"></i></button> <button class="_button" @click="showActions" v-tooltip="$ts.plugin" v-if="postFormActions.length > 0"><i class="fas fa-plug"></i></button> </footer> + <datalist id="hashtags"> + <option v-for="hashtag in recentHashtags" :value="hashtag" :key="hashtag"/> + </datalist> </div> </div> </template> @@ -67,10 +72,11 @@ import { Autocomplete } from '@client/scripts/autocomplete'; import { noteVisibilities } from '../../types'; import * as os from '@client/os'; import { selectFile } from '@client/scripts/select-file'; -import { notePostInterruptors, postFormActions } from '@client/store'; +import { defaultStore, notePostInterruptors, postFormActions } from '@client/store'; import { isMobile } from '@client/scripts/is-mobile'; import { throttle } from 'throttle-debounce'; import MkInfo from '@client/components/ui/info.vue'; +import { defaultStore } from '@client/store'; export default defineComponent({ components: { @@ -212,7 +218,10 @@ export default defineComponent({ max(): number { return this.$instance ? this.$instance.maxNoteTextLength : 1000; - } + }, + + withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'), + hashtags: defaultStore.makeGetterSetter('postFormHashtags'), }, watch: { @@ -303,6 +312,7 @@ export default defineComponent({ // TODO: detach when unmount new Autocomplete(this.$refs.text, this, { model: 'text' }); new Autocomplete(this.$refs.cw, this, { model: 'cw' }); + new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' }); this.$nextTick(() => { // 書ãã‹ã‘ã®æŠ•ç¨¿ã‚’復元 @@ -605,6 +615,11 @@ export default defineComponent({ viaMobile: isMobile }; + if (this.withHashtags) { + const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); + data.text = data.text ? `${data.text} ${hashtags}` : hashtags; + } + // plugin if (notePostInterruptors.length > 0) { for (const interruptor of notePostInterruptors) { @@ -618,8 +633,8 @@ export default defineComponent({ this.$nextTick(() => { this.deleteDraft(); this.$emit('posted'); - if (this.text && this.text != '') { - const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); + if (data.text && data.text != '') { + const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); } @@ -785,6 +800,7 @@ export default defineComponent({ } > .cw, + > .hashtags, > .text { display: block; box-sizing: border-box; @@ -813,6 +829,13 @@ export default defineComponent({ border-bottom: solid 0.5px var(--divider); } + > .hashtags { + z-index: 1; + padding-top: 8px; + padding-bottom: 8px; + border-top: solid 0.5px var(--divider); + } + > .text { max-width: 100%; min-width: 100%; @@ -872,6 +895,7 @@ export default defineComponent({ } > .cw, + > .hashtags, > .text { padding: 0 16px; } diff --git a/src/client/store.ts b/src/client/store.ts index 6ca431e05943992dd24e6d471de6e7864b98a15f..364d8afd93626b48448d5737fff43a98bbec78ed 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -198,6 +198,14 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: false }, + postFormWithHashtags: { + where: 'device', + default: false + }, + postFormHashtags: { + where: 'device', + default: '' + }, })); // TODO: ä»–ã®ã‚¿ãƒ–ã¨æ°¸ç¶šåŒ–ã•ã‚ŒãŸstateã‚’åŒæœŸ