diff options
Diffstat (limited to 'src')
28 files changed, 321 insertions, 146 deletions
@@ -66,12 +66,16 @@ export default { }) }, logo () { return this.$store.state.instance.logo }, - style () { + bgStyle () { return { - '--body-background-image': `url(${this.background})`, 'background-image': `url(${this.background})` } }, + bgAppStyle () { + return { + '--body-background-image': `url(${this.background})` + } + }, sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, diff --git a/src/App.scss b/src/App.scss index 1eaed6ea..e7784329 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,15 +1,21 @@ @import './_variables.scss'; #app { - background-size: cover; - background-attachment: fixed; - background-repeat: no-repeat; - background-position: 0 50px; min-height: 100vh; max-width: 100%; overflow: hidden; } +.app-bg-wrapper { + position: fixed; + z-index: -1; + height: 100%; + width: 100%; + background-size: cover; + background-repeat: no-repeat; + background-position: 0 50%; +} + i { user-select: none; } @@ -733,3 +739,7 @@ nav { width: 100%; } } + +.btn.btn-default { + min-height: 28px; +} diff --git a/src/App.vue b/src/App.vue index 7541928f..acbbeb75 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,6 @@ <template> - <div id="app" v-bind:style="style"> + <div id="app" v-bind:style="bgAppStyle"> + <div class="app-bg-wrapper" v-bind:style="bgStyle"></div> <nav class='nav-bar container' @click="scrollToTop()" id="nav"> <div class='logo' :style='logoBgStyle'> <div class='mask' :style='logoMaskStyle'></div> diff --git a/src/boot/after_store.js b/src/boot/after_store.js index fa44dace..53ecc083 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -55,7 +55,6 @@ const afterStoreSetup = ({ store, i18n }) => { } copyInstanceOption('nsfwCensorImage') - copyInstanceOption('theme') copyInstanceOption('background') copyInstanceOption('hidePostStats') copyInstanceOption('hideUserStats') @@ -96,6 +95,9 @@ const afterStoreSetup = ({ store, i18n }) => { store.dispatch('initializeSocket') } + return store.dispatch('setTheme', config['theme']) + }) + .then(() => { const router = new VueRouter({ mode: 'history', routes: routes(store), diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index c18781de..48b8aaaa 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -9,9 +9,9 @@ const sortById = (a, b) => { if (isSeqA && isSeqB) { return seqA < seqB ? -1 : 1 } else if (isSeqA && !isSeqB) { - return 1 - } else if (!isSeqA && isSeqB) { return -1 + } else if (!isSeqA && isSeqB) { + return 1 } else { return a.id < b.id ? -1 : 1 } @@ -36,6 +36,13 @@ const conversation = { status () { return this.statusoid }, + statusId () { + if (this.statusoid.retweeted_status) { + return this.statusoid.retweeted_status.id + } else { + return this.statusoid.id + } + }, conversation () { if (!this.status) { return [] @@ -79,7 +86,7 @@ const conversation = { const conversationId = this.status.statusnet_conversation_id this.$store.state.api.backendInteractor.fetchConversation({id: conversationId}) .then((statuses) => this.$store.dispatch('addNewStatuses', { statuses })) - .then(() => this.setHighlight(this.statusoid.id)) + .then(() => this.setHighlight(this.statusId)) } else { const id = this.$route.params.id this.$store.state.api.backendInteractor.fetchStatus({id}) @@ -91,11 +98,7 @@ const conversation = { return this.replies[id] || [] }, focused (id) { - if (this.statusoid.retweeted_status) { - return (id === this.statusoid.retweeted_status.id) - } else { - return (id === this.statusoid.id) - } + return id === this.statusId }, setHighlight (id) { this.highlight = id diff --git a/src/components/follow_list/follow_list.js b/src/components/follow_list/follow_list.js index 6d00eb94..51327e2f 100644 --- a/src/components/follow_list/follow_list.js +++ b/src/components/follow_list/follow_list.js @@ -25,6 +25,9 @@ const FollowList = { }, entries () { return this.showFollowers ? this.user.followers : this.user.friends + }, + showFollowsYou () { + return !this.showFollowers || (this.showFollowers && this.userId !== this.$store.state.users.currentUser.id) } }, methods: { diff --git a/src/components/follow_list/follow_list.vue b/src/components/follow_list/follow_list.vue index 24ab97d8..27102edf 100644 --- a/src/components/follow_list/follow_list.vue +++ b/src/components/follow_list/follow_list.vue @@ -3,7 +3,7 @@ <user-card v-for="entry in entries" :key="entry.id" :user="entry" - :showFollows="true" + :noFollowsYou="!showFollowsYou" /> <div class="text-center panel-footer"> <a v-if="error" @click="fetchEntries" class="alert error"> diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index 534a9839..23c1acdb 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -91,7 +91,8 @@ const settings = { }, currentSaveStateNotice () { return this.$store.state.interface.settings.currentSaveStateNotice - } + }, + instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel } }, watch: { hideAttachmentsLocal (value) { diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index dfb2e49d..e5f8fefb 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -27,7 +27,7 @@ <li> <interface-language-switcher /> </li> - <li> + <li v-if="instanceSpecificPanelPresent"> <input type="checkbox" id="hideISP" v-model="hideISPLocal"> <label for="hideISP">{{$t('settings.hide_isp')}}</label> </li> diff --git a/src/components/status/status.js b/src/components/status/status.js index 8181b0db..0273a5be 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -10,8 +10,8 @@ import LinkPreview from '../link-preview/link-preview.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import fileType from 'src/services/file_type/file_type.service' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' -import { mentionMatchesUrl } from 'src/services/mention_matcher/mention_matcher.js' -import { filter, find } from 'lodash' +import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js' +import { filter, find, unescape } from 'lodash' const Status = { name: 'Status', @@ -209,14 +209,15 @@ const Status = { }, replySubject () { if (!this.status.summary) return '' + const decodedSummary = unescape(this.status.summary) const behavior = typeof this.$store.state.config.subjectLineBehavior === 'undefined' ? this.$store.state.instance.subjectLineBehavior : this.$store.state.config.subjectLineBehavior - const startsWithRe = this.status.summary.match(/^re[: ]/i) + const startsWithRe = decodedSummary.match(/^re[: ]/i) if (behavior !== 'noop' && startsWithRe || behavior === 'masto') { - return this.status.summary + return decodedSummary } else if (behavior === 'email') { - return 're: '.concat(this.status.summary) + return 're: '.concat(decodedSummary) } else if (behavior === 'noop') { return '' } @@ -281,7 +282,7 @@ const Status = { } if (target.tagName === 'A') { if (target.className.match(/mention/)) { - const href = target.getAttribute('href') + const href = target.href const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href)) if (attn) { event.stopPropagation() @@ -291,6 +292,15 @@ const Status = { return } } + if (target.className.match(/hashtag/)) { + // Extract tag name from link url + const tag = extractTagFromUrl(target.href) + if (tag) { + const link = this.generateTagLink(tag) + this.$router.push(link) + return + } + } window.open(target.href, '_blank') } }, @@ -347,6 +357,9 @@ const Status = { generateUserProfileLink (id, name) { return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) }, + generateTagLink (tag) { + return `/tag/${tag}` + }, setMedia () { const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments return () => this.$store.dispatch('setMedia', attachments) diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 5bc7c664..3fc5b486 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -56,7 +56,7 @@ </div> <h4 class="replies" v-if="inConversation && !noReplyLinks"> <small v-if="replies.length">Replies:</small> - <small class="reply-link" v-for="reply in replies"> + <small class="reply-link" v-bind:key="reply.id" v-for="reply in replies"> <a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}} </a> </small> </h4> @@ -438,6 +438,8 @@ .user-name { font-weight: bold; + overflow: hidden; + text-overflow: ellipsis; img { width: 14px; @@ -552,7 +554,7 @@ a.unmute { .timeline > { .status-el:last-child { - border-bottom-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;; + border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); border-bottom: none; } diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index ecc36a4d..28e22f09 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -1,16 +1,20 @@ import UserCardContent from '../user_card_content/user_card_content.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' const UserCard = { props: [ 'user', - 'showFollows', + 'noFollowsYou', 'showApproval' ], data () { return { - userExpanded: false + userExpanded: false, + followRequestInProgress: false, + followRequestSent: false, + updated: false } }, components: { @@ -18,7 +22,11 @@ const UserCard = { UserAvatar }, computed: { - currentUser () { return this.$store.state.users.currentUser } + currentUser () { return this.$store.state.users.currentUser }, + following () { return this.updated ? this.updated.following : this.user.following }, + showFollow () { + return !this.showApproval && (!this.following || this.updated && !this.updated.following) + } }, methods: { toggleUserExpanded () { @@ -34,6 +42,21 @@ const UserCard = { }, userProfileLink (user) { return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) + }, + followUser () { + this.followRequestInProgress = true + requestFollow(this.user, this.$store).then(({ sent, updated }) => { + this.followRequestInProgress = false + this.followRequestSent = sent + this.updated = updated + }) + }, + unfollowUser () { + this.followRequestInProgress = true + requestUnfollow(this.user, this.$store).then(({ updated }) => { + this.followRequestInProgress = false + this.updated = updated + }) } } } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 7129430b..ce4edb3c 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -1,32 +1,57 @@ <template> <div class="card"> <router-link :to="userProfileLink(user)"> - <UserAvatar class="avatar" :compact="true" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/> + <UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/> </router-link> - <div class="usercard" v-if="userExpanded"> - <user-card-content :user="user" :switcher="false"></user-card-content> - </div> - <div class="name-and-screen-name" v-else> - <div :title="user.name" v-if="user.name_html" class="user-name"> - <span v-html="user.name_html"></span> - <span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you"> - {{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }} - </span> + <div class="user-card-main-content"> + <div class="usercard" v-if="userExpanded"> + <user-card-content :user="user" :switcher="false"></user-card-content> </div> - <div :title="user.name" v-else class="user-name"> - {{ user.name }} - <span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you"> + <div class="name-and-screen-name" v-if="!userExpanded"> + <div :title="user.name" class="user-name"> + <span v-if="user.name_html" v-html="user.name_html"></span> + <span v-else>{{ user.name }}</span> + </div> + <div class="user-link-action"> + <router-link class='user-screen-name' :to="userProfileLink(user)"> + @{{user.screen_name}} + </router-link> + </div> + </div> + <div class="follow-box" v-if="!userExpanded"> + <span class="faint" v-if="!noFollowsYou && user.follows_you"> {{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }} </span> + <button + v-if="showFollow" + class="btn btn-default" + @click="followUser" + :disabled="followRequestInProgress" + :title="followRequestSent ? $t('user_card.follow_again') : ''" + > + <template v-if="followRequestInProgress"> + {{ $t('user_card.follow_progress') }} + </template> + <template v-else-if="followRequestSent"> + {{ $t('user_card.follow_sent') }} + </template> + <template v-else> + {{ $t('user_card.follow') }} + </template> + </button> + <button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="followRequestInProgress"> + <template v-if="followRequestInProgress"> + {{ $t('user_card.follow_progress') }} + </template> + <template v-else> + {{ $t('user_card.follow_unfollow') }} + </template> + </button> + </div> + <div class="approval" v-if="showApproval"> + <button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button> + <button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button> </div> - - <router-link class='user-screen-name' :to="userProfileLink(user)"> - @{{user.screen_name}} - </router-link> - </div> - <div class="approval" v-if="showApproval"> - <button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button> - <button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button> </div> </div> </template> @@ -36,11 +61,18 @@ <style lang="scss"> @import '../../_variables.scss'; -.name-and-screen-name { +.user-card-main-content { + display: flex; + flex-direction: column; + flex: 1 1 100%; margin-left: 0.7em; - margin-top:0.0em; + min-width: 0; +} + +.name-and-screen-name { text-align: left; width: 100%; + .user-name { img { object-fit: contain; @@ -49,13 +81,15 @@ vertical-align: middle; } } -} -.follows-you { - margin-left: 2em; - float: right; + .user-link-action { + display: flex; + align-items: flex-start; + justify-content: space-between; + } } + .card { display: flex; flex: 1 0; @@ -66,16 +100,31 @@ border-bottom: 1px solid; margin: 0; border-bottom-color: $fallback--border; - border-bottom-color: var(--border, $fallback--border); + border-bottom-color: var(--border, $fallback--border); .avatar { padding: 0; } + + .follow-box { + text-align: center; + flex-shrink: 0; + display: flex; + flex-direction: row; + justify-content: space-between; + flex-wrap: wrap; + line-height: 1.5em; + + .btn { + margin-top: 0.5em; + margin-left: auto; + width: 10em; + } + } } .usercard { width: fill-available; - margin: 0.2em 0 0 0.7em; border-radius: $fallback--panelRadius; border-radius: var(--panelRadius, $fallback--panelRadius); border-style: solid; @@ -96,9 +145,15 @@ } .approval { + display: flex; + flex-direction: row; + flex-wrap: wrap; button { - width: 100%; - margin-bottom: 0.5em; + margin-top: 0.5em; + margin-right: 0.5em; + flex: 1 1; + max-width: 12em; + min-width: 8em; } } </style> diff --git a/src/components/user_card_content/user_card_content.js b/src/components/user_card_content/user_card_content.js index 1888f8c6..7a7b89d4 100644 --- a/src/components/user_card_content/user_card_content.js +++ b/src/components/user_card_content/user_card_content.js @@ -1,5 +1,6 @@ import UserAvatar from '../user_avatar/user_avatar.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' +import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' export default { @@ -92,69 +93,17 @@ export default { }, methods: { followUser () { - const store = this.$store this.followRequestInProgress = true - store.state.api.backendInteractor.followUser(this.user.id) - .then((followedUser) => store.commit('addNewUsers', [followedUser])) - .then(() => { - // For locked users we just mark it that we sent the follow request - if (this.user.locked) { - this.followRequestInProgress = false - this.followRequestSent = true - return - } - - if (this.user.following) { - // If we get result immediately, just stop. - this.followRequestInProgress = false - return - } - - // But usually we don't get result immediately, so we ask server - // for updated user profile to confirm if we are following them - // Sometimes it takes several tries. Sometimes we end up not following - // user anyway, probably because they locked themselves and we - // don't know that yet. - // Recursive Promise, it will call itself up to 3 times. - const fetchUser = (attempt) => new Promise((resolve, reject) => { - setTimeout(() => { - store.state.api.backendInteractor.fetchUser({ id: this.user.id }) - .then((user) => store.commit('addNewUsers', [user])) - .then(() => resolve([this.user.following, attempt])) - .catch((e) => reject(e)) - }, 500) - }).then(([following, attempt]) => { - if (!following && attempt <= 3) { - // If we BE reports that we still not following that user - retry, - // increment attempts by one - return fetchUser(++attempt) - } else { - // If we run out of attempts, just return whatever status is. - return following - } - }) - - return fetchUser(1) - .then((following) => { - if (following) { - // We confirmed and everything its good. - this.followRequestInProgress = false - } else { - // If after all the tries, just treat it as if user is locked - this.followRequestInProgress = false - this.followRequestSent = true - } - }) - }) + requestFollow(this.user, this.$store).then(({sent}) => { + this.followRequestInProgress = false + this.followRequestSent = sent + }) }, unfollowUser () { - const store = this.$store this.followRequestInProgress = true - store.state.api.backendInteractor.unfollowUser(this.user.id) - .then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser])) - .then(() => { - this.followRequestInProgress = false - }) + requestUnfollow(this.user, this.$store).then(() => { + this.followRequestInProgress = false + }) }, blockUser () { const store = this.$store diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue index 7f9909c4..4d1950c5 100644 --- a/src/components/user_card_content/user_card_content.vue +++ b/src/components/user_card_content/user_card_content.vue @@ -386,6 +386,4 @@ } } -.floater { -} </style> diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index a22b8722..37179ce1 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -16,7 +16,7 @@ const UserProfile = { } }, destroyed () { - this.cleanUp(this.userId) + this.cleanUp() }, computed: { timeline () { diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index ea5b3de5..d2381da2 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -178,7 +178,7 @@ } .banner { - max-width: 400px; + max-width: 100%; } .uploading { @@ -189,5 +189,9 @@ .name-changer { width: 100%; } + + .bg { + max-width: 100%; + } } </style> diff --git a/src/i18n/en.json b/src/i18n/en.json index c664fbfa..eba90b50 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -344,7 +344,7 @@ "follow_sent": "Request sent!", "follow_progress": "Requesting…", "follow_again": "Send request again?", - "follow_unfollow": "Stop following", + "follow_unfollow": "Unfollow", "followees": "Following", "followers": "Followers", "following": "Following!", diff --git a/src/i18n/es.json b/src/i18n/es.json index 29c8aec4..d14e7a31 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -140,6 +140,7 @@ "use_one_click_nsfw": "Abrir los adjuntos NSFW con un solo click.", "hide_post_stats": "Ocultar las estadísticas de las entradas (p.ej. el número de favoritos)", "hide_user_stats": "Ocultar las estadísticas del usuario (p.ej. el número de seguidores)", + "hide_filtered_statuses": "Ocultar estados filtrados", "import_followers_from_a_csv_file": "Importar personas que tú sigues a partir de un archivo csv", "import_theme": "Importar tema", "inputRadius": "Campos de entrada", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index afce03a4..7849aa20 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -17,7 +17,9 @@ }, "general": { "apply": "てきよう", - "submit": "そうしん" + "submit": "そうしん", + "more": "つづき", + "generic_error": "エラーになりました" }, "login": { "login": "ログイン", @@ -26,7 +28,8 @@ "password": "パスワード", "placeholder": "れい: lain", "register": "はじめる", - "username": "ユーザーめい" + "username": "ユーザーめい", + "hint": "はなしあいにくわわるには、ログインしてください" }, "nav": { "about": "これはなに?", @@ -49,7 +52,8 @@ "load_older": "ふるいつうちをみる", "notifications": "つうち", "read": "よんだ!", - "repeated_you": "あなたのステータスがリピートされました" + "repeated_you": "あなたのステータスがリピートされました", + "no_more_notifications": "つうちはありません" }, "post_status": { "new_status": "とうこうする", @@ -117,6 +121,7 @@ "delete_account_description": "あなたのアカウントとメッセージが、きえます。", "delete_account_error": "アカウントをけすことが、できなかったかもしれません。インスタンスのかんりしゃに、れんらくしてください。", "delete_account_instructions": "ほんとうにアカウントをけしてもいいなら、パスワードをかいてください。", + "avatar_size_instruction": "アバターのおおきさは、150×150ピクセルか、それよりもおおきくするといいです。", "export_theme": "セーブ", "filtering": "フィルタリング", "filtering_explanation": "これらのことばをふくむすべてのものがミュートされます。1ぎょうに1つのことばをかいてください。", @@ -132,8 +137,10 @@ "hide_attachments_in_tl": "タイムラインのファイルをかくす", "hide_isp": "インスタンススペシフィックパネルをかくす", "preload_images": "がぞうをさきよみする", + "use_one_click_nsfw": "NSFWなファイルを1クリックでひらく", "hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)", "hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)", + "hide_filtered_statuses": "フィルターされたとうこうをかくす", "import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする", "import_theme": "ロード", "inputRadius": "インプットフィールド", @@ -148,6 +155,8 @@ "lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできる", "loop_video": "ビデオをくりかえす", "loop_video_silent_only": "おとのないビデオだけくりかえす", + "play_videos_in_modal": "ビデオをメディアビューアーでみる", + "use_contain_fit": "がぞうのサムネイルを、きりぬかない", "name": "なまえ", "name_bio": "なまえとプロフィール", "new_password": "あたらしいパスワード", @@ -157,8 +166,10 @@ "notification_visibility_mentions": "メンション", "notification_visibility_repeats": "リピート", "no_rich_text_description": "リッチテキストをつかわない", - "hide_follows_description": "フォローしている人を表示しない", - "hide_followers_description": "フォローしている人を表示しない", + "hide_follows_description": "フォローしているひとをみせない", + "hide_followers_description": "フォロワーをみせない", + "show_admin_badge": "アドミンのしるしをみる", + "show_moderator_badge": "モデレーターのしるしをみる", "nsfw_clickthrough": "NSFWなファイルをかくす", "panelRadius": "パネル", "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる", @@ -185,6 +196,8 @@ "subject_line_email": "メールふう: \"re: サブジェクト\"", "subject_line_mastodon": "マストドンふう: そのままコピー", "subject_line_noop": "コピーしない", + "post_status_content_type": "とうこうのコンテントタイプ", + "status_content_type_plain": "プレーンテキスト", "stop_gifs": "カーソルをかさねたとき、GIFをうごかす", "streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする", "text": "もじ", @@ -318,13 +331,15 @@ "no_retweet_hint": "とうこうを「フォロワーのみ」または「ダイレクト」にすると、リピートできなくなります", "repeated": "リピート", "show_new": "よみこみ", - "up_to_date": "さいしん" + "up_to_date": "さいしん", + "no_more_statuses": "これでおわりです" }, "user_card": { "approve": "うけいれ", "block": "ブロック", "blocked": "ブロックしています!", "deny": "おことわり", + "favorites": "おきにいり", "follow": "フォロー", "follow_sent": "リクエストを、おくりました!", "follow_progress": "リクエストしています…", @@ -335,6 +350,7 @@ "following": "フォローしています!", "follows_you": "フォローされました!", "its_you": "これはあなたです!", + "media": "メディア", "mute": "ミュート", "muted": "ミュートしています!", "per_day": "/日", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index ccd92633..6f7202ce 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -48,7 +48,7 @@ export default function createPersistedState ({ return getState(key, storage).then((savedState) => { return store => { try { - if (typeof savedState === 'object') { + if (savedState !== null && typeof savedState === 'object') { // build user cache const usersState = savedState.users || {} usersState.usersObject = {} diff --git a/src/modules/instance.js b/src/modules/instance.js index 9bef5235..59c6b91c 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -66,9 +66,11 @@ const instance = { case 'name': dispatch('setPageTitle') break - case 'theme': - setPreset(value, commit) } + }, + setTheme ({ commit }, themeName) { + commit('setInstanceOption', { name: 'theme', value: themeName }) + return setPreset(themeName, commit) } } } diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 56619455..46117fd7 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -296,7 +296,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot notifObj.image = action.attachments[0].url } - if (notification.fresh && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.ntype)) { + if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) { let notification = new window.Notification(title, notifObj) // Chrome is known for not closing notifications automatically // according to MDN, anyway. diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 92daa04e..13d31d91 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -258,7 +258,7 @@ const fetchFriends = ({id, page, credentials}) => { } const exportFriends = ({id, credentials}) => { - let url = `${FRIENDS_URL}?user_id=${id}&export=true` + let url = `${FRIENDS_URL}?user_id=${id}&all=true` return fetch(url, { headers: authHeaders(credentials) }) .then((data) => data.json()) .then((data) => data.map(parseUser)) diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js new file mode 100644 index 00000000..1e9bd679 --- /dev/null +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -0,0 +1,74 @@ +const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => { + setTimeout(() => { + store.state.api.backendInteractor.fetchUser({ id: user.id }) + .then((user) => store.commit('addNewUsers', [user])) + .then(() => resolve([user.following, attempt])) + .catch((e) => reject(e)) + }, 500) +}).then(([following, attempt]) => { + if (!following && attempt <= 3) { + // If we BE reports that we still not following that user - retry, + // increment attempts by one + return fetchUser(++attempt, user, store) + } else { + // If we run out of attempts, just return whatever status is. + return following + } +}) + +export const requestFollow = (user, store) => new Promise((resolve, reject) => { + store.state.api.backendInteractor.followUser(user.id) + .then((updated) => { + store.commit('addNewUsers', [updated]) + + // For locked users we just mark it that we sent the follow request + if (updated.locked) { + resolve({ + sent: true, + updated + }) + } + + if (updated.following) { + // If we get result immediately, just stop. + resolve({ + sent: false, + updated + }) + } + + // But usually we don't get result immediately, so we ask server + // for updated user profile to confirm if we are following them + // Sometimes it takes several tries. Sometimes we end up not following + // user anyway, probably because they locked themselves and we + // don't know that yet. + // Recursive Promise, it will call itself up to 3 times. + + return fetchUser(1, user, store) + .then((following) => { + if (following) { + // We confirmed and everything's good. + resolve({ + sent: false, + updated + }) + } else { + // If after all the tries, just treat it as if user is locked + resolve({ + sent: false, + updated + }) + } + }) + }) +}) + +export const requestUnfollow = (user, store) => new Promise((resolve, reject) => { + store.state.api.backendInteractor.unfollowUser(user.id) + .then((updated) => { + store.commit('addNewUsers', [updated]) + resolve({ + updated + }) + }) +}) diff --git a/src/services/matcher/matcher.service.js b/src/services/matcher/matcher.service.js new file mode 100644 index 00000000..b6c4e909 --- /dev/null +++ b/src/services/matcher/matcher.service.js @@ -0,0 +1,23 @@ +export const mentionMatchesUrl = (attention, url) => { + if (url === attention.statusnet_profile_url) { + return true + } + const [namepart, instancepart] = attention.screen_name.split('@') + const matchstring = new RegExp('://' + instancepart + '/.*' + namepart + '$', 'g') + + return !!url.match(matchstring) +} + +/** + * Extract tag name from pleroma or mastodon url. + * i.e https://bikeshed.party/tag/photo or https://quey.org/tags/sky + * @param {string} url + */ +export const extractTagFromUrl = (url) => { + const regex = /tag[s]*\/(\w+)$/g + const result = regex.exec(url) + if (!result) { + return false + } + return result[1] +} diff --git a/src/services/mention_matcher/mention_matcher.js b/src/services/mention_matcher/mention_matcher.js deleted file mode 100644 index 2c1ed970..00000000 --- a/src/services/mention_matcher/mention_matcher.js +++ /dev/null @@ -1,9 +0,0 @@ - -export const mentionMatchesUrl = (attention, url) => { - if (url === attention.statusnet_profile_url) { - return true - } - const [namepart, instancepart] = attention.screen_name.split('@') - const matchstring = new RegExp('://' + instancepart + '/.*' + namepart + '$', 'g') - return !!url.match(matchstring) -} diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 10e7ed9b..d0b6ccbf 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -480,7 +480,7 @@ const getThemes = () => { } const setPreset = (val, commit) => { - getThemes().then((themes) => { + return getThemes().then((themes) => { const theme = themes[val] ? themes[val] : themes['pleroma-dark'] const isV1 = Array.isArray(theme) const data = isV1 ? {} : theme.theme |
