forked from AkkomaGang/akkoma-fe
Merge branch 'develop' into 'feat/conversation-muting'
# Conflicts: # src/components/extra_buttons/extra_buttons.js # src/components/extra_buttons/extra_buttons.vue
This commit is contained in:
commit
d3f6b581d1
28 changed files with 638 additions and 177 deletions
|
@ -24,9 +24,6 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
|||
stats: {
|
||||
colors: true,
|
||||
chunks: false
|
||||
},
|
||||
headers: {
|
||||
'content-security-policy': "base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'; script-src 'self' 'unsafe-eval';"
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -25,14 +25,13 @@
|
|||
"localforage": "^1.5.0",
|
||||
"object-path": "^0.11.3",
|
||||
"phoenix": "^1.3.0",
|
||||
"popper.js": "^1.14.7",
|
||||
"portal-vue": "^2.1.4",
|
||||
"sanitize-html": "^1.13.0",
|
||||
"v-click-outside": "^2.1.1",
|
||||
"v-tooltip": "^2.0.2",
|
||||
"vue": "^2.5.13",
|
||||
"vue-chat-scroll": "^1.2.1",
|
||||
"vue-i18n": "^7.3.2",
|
||||
"vue-popperjs": "^2.0.3",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.3.4",
|
||||
"vuelidate": "^0.7.4",
|
||||
|
@ -81,8 +80,8 @@
|
|||
"json-loader": "^0.5.4",
|
||||
"karma": "^3.0.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-mocha": "^1.2.0",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-mocha": "^1.2.0",
|
||||
"karma-sinon-chai": "^2.0.2",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
|
|
|
@ -148,6 +148,37 @@ const getInstancePanel = async ({ store }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getStickers = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/static/stickers.json')
|
||||
if (res.ok) {
|
||||
const values = await res.json()
|
||||
const stickers = (await Promise.all(
|
||||
Object.entries(values).map(async ([name, path]) => {
|
||||
const resPack = await window.fetch(path + 'pack.json')
|
||||
var meta = {}
|
||||
if (resPack.ok) {
|
||||
meta = await resPack.json()
|
||||
}
|
||||
return {
|
||||
pack: name,
|
||||
path,
|
||||
meta
|
||||
}
|
||||
})
|
||||
)).sort((a, b) => {
|
||||
return a.meta.title.localeCompare(b.meta.title)
|
||||
})
|
||||
store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load stickers")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
const getStaticEmoji = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/static/emoji.json')
|
||||
|
@ -286,6 +317,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
setConfig({ store }),
|
||||
getTOS({ store }),
|
||||
getInstancePanel({ store }),
|
||||
getStickers({ store }),
|
||||
getStaticEmoji({ store }),
|
||||
getCustomEmoji({ store }),
|
||||
getNodeInfo({ store })
|
||||
|
|
|
@ -19,6 +19,14 @@ import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
|||
import About from 'components/about/about.vue'
|
||||
|
||||
export default (store) => {
|
||||
const validateAuthenticatedRoute = (to, from, next) => {
|
||||
if (store.state.users.currentUser) {
|
||||
next()
|
||||
} else {
|
||||
next(store.state.instance.redirectRootNoLogin || '/main/all')
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{ name: 'root',
|
||||
path: '/',
|
||||
|
@ -30,23 +38,23 @@ export default (store) => {
|
|||
},
|
||||
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
|
||||
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
|
||||
{ name: 'friends', path: '/main/friends', component: FriendsTimeline },
|
||||
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
||||
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
|
||||
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions },
|
||||
{ name: 'dms', path: '/users/:username/dms', component: DMs },
|
||||
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'settings', path: '/settings', component: Settings },
|
||||
{ name: 'registration', path: '/registration', component: Registration },
|
||||
{ name: 'registration-token', path: '/registration/:token', component: Registration },
|
||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
|
||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings },
|
||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications },
|
||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'login', path: '/login', component: AuthForm },
|
||||
{ name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },
|
||||
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
||||
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
|
||||
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow },
|
||||
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'about', path: '/about', component: About },
|
||||
{ name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
|
||||
]
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
import { debounce } from 'lodash'
|
||||
/**
|
||||
* suggest - generates a suggestor function to be used by emoji-input
|
||||
* data: object providing source information for specific types of suggestions:
|
||||
* data.emoji - optional, an array of all emoji available i.e.
|
||||
* (state.instance.emoji + state.instance.customEmoji)
|
||||
* data.users - optional, an array of all known users
|
||||
* updateUsersList - optional, a function to search and append to users
|
||||
*
|
||||
* Depending on data present one or both (or none) can be present, so if field
|
||||
* doesn't support user linking you can just provide only emoji.
|
||||
*/
|
||||
|
||||
const debounceUserSearch = debounce((data, input) => {
|
||||
data.updateUsersList(input)
|
||||
}, 500, { leading: true, trailing: false })
|
||||
|
||||
export default data => input => {
|
||||
const firstChar = input[0]
|
||||
if (firstChar === ':' && data.emoji) {
|
||||
return suggestEmoji(data.emoji)(input)
|
||||
}
|
||||
if (firstChar === '@' && data.users) {
|
||||
return suggestUsers(data.users)(input)
|
||||
return suggestUsers(data)(input)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
@ -38,9 +45,11 @@ export const suggestEmoji = emojis => input => {
|
|||
})
|
||||
}
|
||||
|
||||
export const suggestUsers = users => input => {
|
||||
export const suggestUsers = data => input => {
|
||||
const noPrefix = input.toLowerCase().substr(1)
|
||||
return users.filter(
|
||||
const users = data.users
|
||||
|
||||
const newUsers = users.filter(
|
||||
user =>
|
||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||
user.name.toLowerCase().startsWith(noPrefix)
|
||||
|
@ -75,5 +84,11 @@ export const suggestUsers = users => input => {
|
|||
imageUrl: profile_image_url_original,
|
||||
replacement: '@' + screen_name + ' '
|
||||
}))
|
||||
|
||||
// BE search users if there are no matches
|
||||
if (newUsers.length === 0 && data.updateUsersList) {
|
||||
debounceUserSearch(data, noPrefix)
|
||||
}
|
||||
return newUsers
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
|
|
|
@ -1,35 +1,18 @@
|
|||
import Popper from 'vue-popperjs/src/component/popper.js.vue'
|
||||
|
||||
const ExtraButtons = {
|
||||
props: [ 'status' ],
|
||||
components: {
|
||||
Popper
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showDropDown: false,
|
||||
showPopper: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteStatus () {
|
||||
this.refreshPopper()
|
||||
const confirmed = window.confirm(this.$t('status.delete_confirm'))
|
||||
if (confirmed) {
|
||||
this.$store.dispatch('deleteStatus', { id: this.status.id })
|
||||
}
|
||||
},
|
||||
toggleMenu () {
|
||||
this.showDropDown = !this.showDropDown
|
||||
},
|
||||
pinStatus () {
|
||||
this.refreshPopper()
|
||||
this.$store.dispatch('pinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
unpinStatus () {
|
||||
this.refreshPopper()
|
||||
this.$store.dispatch('unpinStatus', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
|
@ -45,13 +28,6 @@ const ExtraButtons = {
|
|||
this.$store.dispatch('unmuteConversation', this.status.id)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
refreshPopper () {
|
||||
this.showPopper = false
|
||||
this.showDropDown = false
|
||||
setTimeout(() => {
|
||||
this.showPopper = true
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
<template>
|
||||
<Popper
|
||||
v-if="showPopper"
|
||||
<v-popover
|
||||
v-if="enabled"
|
||||
trigger="click"
|
||||
append-to-body
|
||||
:options="{
|
||||
placement: 'top',
|
||||
modifiers: {
|
||||
arrow: { enabled: true },
|
||||
offset: { offset: '0, 5px' },
|
||||
}
|
||||
}"
|
||||
@hide="showDropDown = false"
|
||||
placement="top"
|
||||
class="extra-button-popover"
|
||||
:offset="5"
|
||||
:container="false"
|
||||
>
|
||||
<div class="popper-wrapper">
|
||||
<div slot="popover">
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
v-if="!status.muted"
|
||||
|
@ -30,6 +25,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="!status.pinned && canPin"
|
||||
v-close-popover
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
@click.prevent="pinStatus"
|
||||
>
|
||||
|
@ -37,6 +33,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="status.pinned && canPin"
|
||||
v-close-popover
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
@click.prevent="unpinStatus"
|
||||
>
|
||||
|
@ -44,6 +41,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="canDelete"
|
||||
v-close-popover
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
@click.prevent="deleteStatus"
|
||||
>
|
||||
|
@ -51,17 +49,10 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
slot="reference"
|
||||
class="button-icon"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<i
|
||||
class="icon-ellipsis"
|
||||
:class="{'icon-clicked': showDropDown}"
|
||||
/>
|
||||
<div class="button-icon">
|
||||
<i class="icon-ellipsis" />
|
||||
</div>
|
||||
</Popper>
|
||||
</v-popover>
|
||||
</template>
|
||||
|
||||
<script src="./extra_buttons.js" ></script>
|
||||
|
@ -73,7 +64,8 @@
|
|||
.icon-ellipsis {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &.icon-clicked {
|
||||
&:hover,
|
||||
.extra-button-popover.open & {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import DialogModal from '../dialog_modal/dialog_modal.vue'
|
||||
import Popper from 'vue-popperjs/src/component/popper.js.vue'
|
||||
|
||||
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
|
||||
const STRIP_MEDIA = 'mrf_tag:media-strip'
|
||||
|
@ -29,8 +28,7 @@ const ModerationTools = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
DialogModal,
|
||||
Popper
|
||||
DialogModal
|
||||
},
|
||||
computed: {
|
||||
tagsSet () {
|
||||
|
@ -41,9 +39,6 @@ const ModerationTools = {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
toggleMenu () {
|
||||
this.showDropDown = !this.showDropDown
|
||||
},
|
||||
hasTag (tagName) {
|
||||
return this.tagsSet.has(tagName)
|
||||
},
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<Popper
|
||||
<v-popover
|
||||
trigger="click"
|
||||
append-to-body
|
||||
:options="{
|
||||
placement: 'bottom-end',
|
||||
modifiers: {
|
||||
arrow: { enabled: true },
|
||||
offset: { offset: '0, 5px' },
|
||||
}
|
||||
}"
|
||||
class="moderation-tools-popover"
|
||||
:container="false"
|
||||
placement="bottom-end"
|
||||
:offset="5"
|
||||
@show="showDropDown = true"
|
||||
@hide="showDropDown = false"
|
||||
>
|
||||
<div class="popper-wrapper">
|
||||
<div slot="popover">
|
||||
<div class="dropdown-menu">
|
||||
<span v-if="user.is_local">
|
||||
<button
|
||||
|
@ -127,14 +124,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<button
|
||||
slot="reference"
|
||||
class="btn btn-default btn-block"
|
||||
:class="{ pressed: showDropDown }"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.moderation') }}
|
||||
</button>
|
||||
</Popper>
|
||||
</v-popover>
|
||||
<portal to="modal">
|
||||
<DialogModal
|
||||
v-if="showDeleteUserDialog"
|
||||
|
@ -188,4 +183,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.moderation-tools-popover {
|
||||
height: 100%;
|
||||
.trigger {
|
||||
display: flex !important;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,71 +1,99 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.popper-wrapper {
|
||||
.tooltip.popover {
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.popper-wrapper .popper__arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
}
|
||||
.popover-inner {
|
||||
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
|
||||
box-shadow: var(--panelShadow);
|
||||
border-radius: $fallback--btnRadius;
|
||||
border-radius: var(--btnRadius, $fallback--btnRadius);
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
}
|
||||
|
||||
.popper-wrapper[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.popover-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
border-color: $fallback--bg;
|
||||
border-color: var(--bg, $fallback--bg);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.popper-wrapper[x-placement^="top"] .popper__arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-color: $fallback--bg transparent transparent transparent;
|
||||
border-color: var(--bg, $fallback--bg) transparent transparent transparent;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.popper-wrapper[x-placement^="bottom"] {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.popover-arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popper-wrapper[x-placement^="bottom"] .popper__arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-color: transparent transparent $fallback--bg transparent;
|
||||
border-color: transparent transparent var(--bg, $fallback--bg) transparent;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&[x-placement^="bottom"] {
|
||||
margin-top: 5px;
|
||||
|
||||
.popper-wrapper[x-placement^="right"] {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.popover-arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popper-wrapper[x-placement^="right"] .popper__arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
border-color: transparent $fallback--bg transparent transparent;
|
||||
border-color: transparent var(--bg, $fallback--bg) transparent transparent;
|
||||
left: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
&[x-placement^="right"] {
|
||||
margin-left: 5px;
|
||||
|
||||
.popper-wrapper[x-placement^="left"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.popover-arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
border-left-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
left: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popper-wrapper[x-placement^="left"] .popper__arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
border-color: transparent transparent transparent $fallback--bg;
|
||||
border-color: transparent transparent transparent var(--bg, $fallback--bg);
|
||||
right: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
&[x-placement^="left"] {
|
||||
margin-right: 5px;
|
||||
|
||||
.popover-arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
right: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity .15s, visibility .15s;
|
||||
}
|
||||
|
||||
&[aria-hidden='false'] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
|
@ -76,13 +104,6 @@
|
|||
list-style: none;
|
||||
max-width: 100vw;
|
||||
z-index: 10;
|
||||
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
|
||||
box-shadow: var(--panelShadow);
|
||||
border: none;
|
||||
border-radius: $fallback--btnRadius;
|
||||
border-radius: var(--btnRadius, $fallback--btnRadius);
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
|
||||
.dropdown-divider {
|
||||
height: 0;
|
||||
|
|
|
@ -3,6 +3,7 @@ import MediaUpload from '../media_upload/media_upload.vue'
|
|||
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
||||
import EmojiInput from '../emoji-input/emoji-input.vue'
|
||||
import PollForm from '../poll/poll_form.vue'
|
||||
import StickerPicker from '../sticker_picker/sticker_picker.vue'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import { reject, map, uniqBy } from 'lodash'
|
||||
import suggestor from '../emoji-input/suggestor.js'
|
||||
|
@ -34,6 +35,7 @@ const PostStatusForm = {
|
|||
MediaUpload,
|
||||
EmojiInput,
|
||||
PollForm,
|
||||
StickerPicker,
|
||||
ScopeSelector
|
||||
},
|
||||
mounted () {
|
||||
|
@ -82,7 +84,8 @@ const PostStatusForm = {
|
|||
contentType
|
||||
},
|
||||
caret: 0,
|
||||
pollFormVisible: false
|
||||
pollFormVisible: false,
|
||||
stickerPickerVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -104,7 +107,8 @@ const PostStatusForm = {
|
|||
...this.$store.state.instance.emoji,
|
||||
...this.$store.state.instance.customEmoji
|
||||
],
|
||||
users: this.$store.state.users.users
|
||||
users: this.$store.state.users.users,
|
||||
updateUsersList: (input) => this.$store.dispatch('searchUsers', input)
|
||||
})
|
||||
},
|
||||
emojiSuggestor () {
|
||||
|
@ -157,6 +161,12 @@ const PostStatusForm = {
|
|||
safeDMEnabled () {
|
||||
return this.$store.state.instance.safeDM
|
||||
},
|
||||
stickersAvailable () {
|
||||
if (this.$store.state.instance.stickers) {
|
||||
return this.$store.state.instance.stickers.length > 0
|
||||
}
|
||||
return 0
|
||||
},
|
||||
pollsAvailable () {
|
||||
return this.$store.state.instance.pollsAvailable &&
|
||||
this.$store.state.instance.pollLimits.max_options >= 2
|
||||
|
@ -212,6 +222,7 @@ const PostStatusForm = {
|
|||
poll: {}
|
||||
}
|
||||
this.pollFormVisible = false
|
||||
this.stickerPickerVisible = false
|
||||
this.$refs.mediaUpload.clearFile()
|
||||
this.clearPollForm()
|
||||
this.$emit('posted')
|
||||
|
@ -228,6 +239,7 @@ const PostStatusForm = {
|
|||
addMediaFile (fileInfo) {
|
||||
this.newStatus.files.push(fileInfo)
|
||||
this.enableSubmit()
|
||||
this.stickerPickerVisible = false
|
||||
},
|
||||
removeMediaFile (fileInfo) {
|
||||
let index = this.newStatus.files.indexOf(fileInfo)
|
||||
|
@ -287,6 +299,14 @@ const PostStatusForm = {
|
|||
changeVis (visibility) {
|
||||
this.newStatus.visibility = visibility
|
||||
},
|
||||
toggleStickerPicker () {
|
||||
this.stickerPickerVisible = !this.stickerPickerVisible
|
||||
},
|
||||
clearStickerPicker () {
|
||||
if (this.$refs.stickerPicker) {
|
||||
this.$refs.stickerPicker.clear()
|
||||
}
|
||||
},
|
||||
togglePollForm () {
|
||||
this.pollFormVisible = !this.pollFormVisible
|
||||
},
|
||||
|
|
|
@ -157,6 +157,17 @@
|
|||
@uploaded="addMediaFile"
|
||||
@upload-failed="uploadFailed"
|
||||
/>
|
||||
<div
|
||||
v-if="stickersAvailable"
|
||||
class="sticker-icon"
|
||||
>
|
||||
<i
|
||||
:title="$t('stickers.add_sticker')"
|
||||
class="icon-picture btn btn-default"
|
||||
:class="{ selected: stickerPickerVisible }"
|
||||
@click="toggleStickerPicker"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="pollsAvailable"
|
||||
class="poll-icon"
|
||||
|
@ -169,7 +180,6 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="posting"
|
||||
disabled
|
||||
|
@ -248,6 +258,11 @@
|
|||
<label for="filesSensitive">{{ $t('post_status.attachments_sensitive') }}</label>
|
||||
</div>
|
||||
</form>
|
||||
<sticker-picker
|
||||
v-if="stickerPickerVisible"
|
||||
ref="stickerPicker"
|
||||
@uploaded="addMediaFile"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -310,7 +325,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.poll-icon {
|
||||
.poll-icon, .sticker-icon {
|
||||
font-size: 26px;
|
||||
flex: 1;
|
||||
|
||||
|
@ -320,6 +335,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.sticker-icon {
|
||||
flex: 0;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.icon-chart-bar {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -110,8 +110,9 @@ const Status = {
|
|||
},
|
||||
muteWordHits () {
|
||||
const statusText = this.status.text.toLowerCase()
|
||||
const statusSummary = this.status.summary.toLowerCase()
|
||||
const hits = filter(this.muteWords, (muteWord) => {
|
||||
return statusText.includes(muteWord.toLowerCase())
|
||||
return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
|
||||
})
|
||||
|
||||
return hits
|
||||
|
@ -280,6 +281,11 @@ const Status = {
|
|||
},
|
||||
tags () {
|
||||
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
|
||||
},
|
||||
hidePostStats () {
|
||||
return typeof this.$store.state.config.hidePostStats === 'undefined'
|
||||
? this.$store.state.instance.hidePostStats
|
||||
: this.$store.state.config.hidePostStats
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -316,11 +322,8 @@ const Status = {
|
|||
this.error = undefined
|
||||
},
|
||||
linkClicked (event) {
|
||||
let { target } = event
|
||||
if (target.tagName === 'SPAN') {
|
||||
target = target.parentNode
|
||||
}
|
||||
if (target.tagName === 'A') {
|
||||
const target = event.target.closest('.status-content a')
|
||||
if (target) {
|
||||
if (target.className.match(/mention/)) {
|
||||
const href = target.href
|
||||
const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href))
|
||||
|
|
|
@ -344,7 +344,7 @@
|
|||
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="isFocused && combinedFavsAndRepeatsUsers.length > 0"
|
||||
v-if="!hidePostStats && isFocused && combinedFavsAndRepeatsUsers.length > 0"
|
||||
class="favs-repeated-users"
|
||||
>
|
||||
<div class="stats">
|
||||
|
@ -820,11 +820,12 @@ $status-margin: 0.75em;
|
|||
}
|
||||
|
||||
.status-actions {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: $status-margin;
|
||||
|
||||
div, favorite-button {
|
||||
> * {
|
||||
max-width: 4em;
|
||||
flex: 1;
|
||||
}
|
||||
|
|
52
src/components/sticker_picker/sticker_picker.js
Normal file
52
src/components/sticker_picker/sticker_picker.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* eslint-env browser */
|
||||
import statusPosterService from '../../services/status_poster/status_poster.service.js'
|
||||
import TabSwitcher from '../tab_switcher/tab_switcher.js'
|
||||
|
||||
const StickerPicker = {
|
||||
components: [
|
||||
TabSwitcher
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
meta: {
|
||||
stickers: []
|
||||
},
|
||||
path: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pack () {
|
||||
return this.$store.state.instance.stickers || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clear () {
|
||||
this.meta = {
|
||||
stickers: []
|
||||
}
|
||||
},
|
||||
pick (sticker, name) {
|
||||
const store = this.$store
|
||||
// TODO remove this workaround by finding a way to bypass reuploads
|
||||
fetch(sticker)
|
||||
.then((res) => {
|
||||
res.blob().then((blob) => {
|
||||
var file = new File([blob], name, { mimetype: 'image/png' })
|
||||
var formData = new FormData()
|
||||
formData.append('file', file)
|
||||
statusPosterService.uploadMedia({ store, formData })
|
||||
.then((fileData) => {
|
||||
this.$emit('uploaded', fileData)
|
||||
this.clear()
|
||||
}, (error) => {
|
||||
console.warn("Can't attach sticker")
|
||||
console.warn(error)
|
||||
this.$emit('upload-failed', 'default')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default StickerPicker
|
62
src/components/sticker_picker/sticker_picker.vue
Normal file
62
src/components/sticker_picker/sticker_picker.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div
|
||||
class="sticker-picker"
|
||||
>
|
||||
<div
|
||||
class="sticker-picker-panel"
|
||||
>
|
||||
<tab-switcher
|
||||
:render-only-focused="true"
|
||||
>
|
||||
<div
|
||||
v-for="stickerpack in pack"
|
||||
:key="stickerpack.path"
|
||||
:image-tooltip="stickerpack.meta.title"
|
||||
:image="stickerpack.path + stickerpack.meta.tabIcon"
|
||||
class="sticker-picker-content"
|
||||
>
|
||||
<div
|
||||
v-for="sticker in stickerpack.meta.stickers"
|
||||
:key="sticker"
|
||||
class="sticker"
|
||||
@click="pick(stickerpack.path + sticker, stickerpack.meta.title)"
|
||||
>
|
||||
<img
|
||||
:src="stickerpack.path + sticker"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</tab-switcher>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./sticker_picker.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.sticker-picker {
|
||||
.sticker-picker-panel {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
.sticker-picker-content {
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto;
|
||||
.sticker {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
img {
|
||||
width: 100%;
|
||||
&:hover {
|
||||
filter: drop-shadow(0 0 5px var(--link, $fallback--link));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -45,7 +45,19 @@ export default Vue.component('tab-switcher', {
|
|||
classesTab.push('active')
|
||||
classesWrapper.push('active')
|
||||
}
|
||||
|
||||
if (slot.data.attrs.image) {
|
||||
return (
|
||||
<div class={ classesWrapper.join(' ')}>
|
||||
<button
|
||||
disabled={slot.data.attrs.disabled}
|
||||
onClick={this.activateTab(index)}
|
||||
class={classesTab.join(' ')}>
|
||||
<img src={slot.data.attrs.image} title={slot.data.attrs['image-tooltip']}/>
|
||||
{slot.data.attrs.label ? '' : slot.data.attrs.label}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div class={ classesWrapper.join(' ')}>
|
||||
<button
|
||||
|
|
|
@ -53,6 +53,12 @@
|
|||
background: transparent;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 26px;
|
||||
vertical-align: top;
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
|
|
|
@ -283,7 +283,6 @@
|
|||
|
||||
.user-card {
|
||||
background-size: cover;
|
||||
overflow: hidden;
|
||||
|
||||
.panel-heading {
|
||||
padding: .5em 0;
|
||||
|
@ -298,6 +297,8 @@
|
|||
word-wrap: break-word;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%);
|
||||
border-bottom-right-radius: inherit;
|
||||
border-bottom-left-radius: inherit;
|
||||
}
|
||||
|
||||
p {
|
||||
|
@ -503,6 +504,7 @@
|
|||
}
|
||||
}
|
||||
.user-interactions {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -91,7 +91,8 @@ const UserSettings = {
|
|||
...this.$store.state.instance.emoji,
|
||||
...this.$store.state.instance.customEmoji
|
||||
],
|
||||
users: this.$store.state.users.users
|
||||
users: this.$store.state.users.users,
|
||||
updateUsersList: (input) => this.$store.dispatch('searchUsers', input)
|
||||
})
|
||||
},
|
||||
emojiSuggestor () {
|
||||
|
|
|
@ -106,6 +106,9 @@
|
|||
"expired": "Poll ended {0} ago",
|
||||
"not_enough_options": "Too few unique options in poll"
|
||||
},
|
||||
"stickers": {
|
||||
"add_sticker": "Add Sticker"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "Repeats and Favorites",
|
||||
"follows": "New follows",
|
||||
|
|
123
src/i18n/es.json
123
src/i18n/es.json
|
@ -27,7 +27,11 @@
|
|||
"optional": "opcional",
|
||||
"show_more": "Mostrar más",
|
||||
"show_less": "Mostrar menos",
|
||||
"cancel": "Cancelar"
|
||||
"cancel": "Cancelar",
|
||||
"disable": "Inhabilitar",
|
||||
"enable": "Habilitar",
|
||||
"confirm": "Confirmar",
|
||||
"verify": "Verificar"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "Recortar la foto",
|
||||
|
@ -48,7 +52,15 @@
|
|||
"placeholder": "p.ej. lain",
|
||||
"register": "Registrar",
|
||||
"username": "Usuario",
|
||||
"hint": "Inicia sesión para unirte a la discusión"
|
||||
"hint": "Inicia sesión para unirte a la discusión",
|
||||
"authentication_code": "Código de autentificación",
|
||||
"enter_recovery_code": "Inserta el código de recuperación",
|
||||
"enter_two_factor_code": "Inserta el código de doble factor",
|
||||
"recovery_code": "Código de recuperación",
|
||||
"heading" : {
|
||||
"totp" : "Autentificación de doble factor",
|
||||
"recovery" : "Recuperación de doble factor"
|
||||
}
|
||||
},
|
||||
"media_modal": {
|
||||
"previous": "Anterior",
|
||||
|
@ -60,11 +72,13 @@
|
|||
"chat": "Chat Local",
|
||||
"friend_requests": "Solicitudes de amistad",
|
||||
"mentions": "Menciones",
|
||||
"interactions": "Interacciones",
|
||||
"dms": "Mensajes Directo",
|
||||
"public_tl": "Línea Temporal Pública",
|
||||
"timeline": "Línea Temporal",
|
||||
"twkn": "Toda La Red Conocida",
|
||||
"user_search": "Búsqueda de Usuarios",
|
||||
"search": "Buscar",
|
||||
"who_to_follow": "A quién seguir",
|
||||
"preferences": "Preferencias"
|
||||
},
|
||||
|
@ -78,6 +92,25 @@
|
|||
"repeated_you": "repite tu estado",
|
||||
"no_more_notifications": "No hay más notificaciones"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "Añadir encuesta",
|
||||
"add_option": "Añadir opción",
|
||||
"option": "Opción",
|
||||
"votes": "votos",
|
||||
"vote": "Votar",
|
||||
"type": "Tipo de encuesta",
|
||||
"single_choice": "Elección única",
|
||||
"multiple_choices": "Múltiples elecciones",
|
||||
"expiry": "Tiempo de vida de la encuesta",
|
||||
"expires_in": "La encuensta termina en {0}",
|
||||
"expired": "La encuesta terminó hace {0}",
|
||||
"not_enough_options": "Muy pocas opciones únicas en la encuesta"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "Favoritos y Repetidos",
|
||||
"follows": "Nuevos seguidores",
|
||||
"load_older": "Cargar interacciones antiguas"
|
||||
},
|
||||
"post_status": {
|
||||
"new_status": "Publicar un nuevo estado",
|
||||
"account_not_locked_warning": "Tu cuenta no está {0}. Cualquiera puede seguirte y leer las entradas para Solo-Seguidores.",
|
||||
|
@ -91,9 +124,14 @@
|
|||
},
|
||||
"content_warning": "Tema (opcional)",
|
||||
"default": "Acabo de aterrizar en L.A.",
|
||||
"direct_warning": "Esta publicación solo será visible para los usuarios mencionados.",
|
||||
"direct_warning_to_all": "Esta publicación será visible para todos los usarios mencionados.",
|
||||
"direct_warning_to_first_only": "Esta publicación solo será visible para los usuarios mencionados al comienzo del mensaje.",
|
||||
"posting": "Publicando",
|
||||
"scope_notice": {
|
||||
"public": "Esta publicación será visible para todo el mundo",
|
||||
"private": "Esta publicación solo será visible para tus seguidores.",
|
||||
"unlisted": "Esta publicación no será visible en la Línea Temporal Pública ni en Toda La Red Conocida"
|
||||
},
|
||||
"scope": {
|
||||
"direct": "Directo - Solo para los usuarios mencionados.",
|
||||
"private": "Solo-Seguidores - Solo tus seguidores leeran la publicación",
|
||||
|
@ -127,6 +165,29 @@
|
|||
},
|
||||
"settings": {
|
||||
"app_name": "Nombre de la aplicación",
|
||||
"security": "Seguridad",
|
||||
"enter_current_password_to_confirm": "Introduce la contraseña actual para confirmar tu identidad",
|
||||
"mfa": {
|
||||
"otp" : "OTP",
|
||||
"setup_otp" : "Configurar OTP",
|
||||
"wait_pre_setup_otp" : "preconfiguración OTP",
|
||||
"confirm_and_enable" : "Confirmar y habilitar OTP",
|
||||
"title": "Autentificación de Doble Factor",
|
||||
"generate_new_recovery_codes" : "Generar nuevos códigos de recuperación",
|
||||
"warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
|
||||
"recovery_codes" : "Códigos de recuperación.",
|
||||
"waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
|
||||
"recovery_codes_warning" : "Anote los códigos o guárdelos en un lugar seguro, de lo contrario no los volverá a ver. Si pierde el acceso a su aplicación 2FA y los códigos de recuperación, su cuenta quedará bloqueada.",
|
||||
"authentication_methods" : "Métodos de autentificación",
|
||||
"scan": {
|
||||
"title": "Escanear",
|
||||
"desc": "Usando su aplicación de doble factor, escanee este código QR o ingrese la clave de texto:",
|
||||
"secret_code": "Clave"
|
||||
},
|
||||
"verify": {
|
||||
"desc": "Para habilitar la autenticación de doble factor, ingrese el código de su aplicación 2FA:"
|
||||
}
|
||||
},
|
||||
"attachmentRadius": "Adjuntos",
|
||||
"attachments": "Adjuntos",
|
||||
"autoload": "Activar carga automática al llegar al final de la página",
|
||||
|
@ -233,6 +294,7 @@
|
|||
"reply_visibility_all": "Mostrar todas las réplicas",
|
||||
"reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
|
||||
"reply_visibility_self": "Solo mostrar réplicas para mí",
|
||||
"autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (móvil)",
|
||||
"saving_err": "Error al guardar los ajustes",
|
||||
"saving_ok": "Ajustes guardados",
|
||||
"search_user_to_block": "Buscar usuarios a bloquear",
|
||||
|
@ -265,6 +327,13 @@
|
|||
"true": "sí"
|
||||
},
|
||||
"notifications": "Notificaciones",
|
||||
"notification_setting": "Recibir notificaciones de:",
|
||||
"notification_setting_follows": "Usuarios que sigues",
|
||||
"notification_setting_non_follows": "Usuarios que no sigues",
|
||||
"notification_setting_followers": "Usuarios que te siguen",
|
||||
"notification_setting_non_followers": "Usuarios que no te siguen",
|
||||
"notification_mutes": "Para dejar de recibir notificaciones de un usuario específico, siléncialo.",
|
||||
"notification_blocks": "El bloqueo de un usuario detiene todas las notificaciones y también las cancela.",
|
||||
"enable_web_push_notifications": "Habilitar las notificiaciones en el navegador",
|
||||
"style": {
|
||||
"switcher": {
|
||||
|
@ -381,6 +450,40 @@
|
|||
"frontend_version": "Versión del Frontend"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"day": "{0} día",
|
||||
"days": "{0} días",
|
||||
"day_short": "{0}d",
|
||||
"days_short": "{0}d",
|
||||
"hour": "{0} hora",
|
||||
"hours": "{0} horas",
|
||||
"hour_short": "{0}h",
|
||||
"hours_short": "{0}h",
|
||||
"in_future": "en {0}",
|
||||
"in_past": "hace {0}",
|
||||
"minute": "{0} minuto",
|
||||
"minutes": "{0} minutos",
|
||||
"minute_short": "{0}min",
|
||||
"minutes_short": "{0}min",
|
||||
"month": "{0} mes",
|
||||
"months": "{0} meses",
|
||||
"month_short": "{0}m",
|
||||
"months_short": "{0}m",
|
||||
"now": "justo ahora",
|
||||
"now_short": "ahora",
|
||||
"second": "{0} segundo",
|
||||
"seconds": "{0} segundos",
|
||||
"second_short": "{0}s",
|
||||
"seconds_short": "{0}s",
|
||||
"week": "{0} semana",
|
||||
"weeks": "{0} semana",
|
||||
"week_short": "{0}sem",
|
||||
"weeks_short": "{0}sem",
|
||||
"year": "{0} año",
|
||||
"years": "{0} años",
|
||||
"year_short": "{0}a",
|
||||
"years_short": "{0}a"
|
||||
},
|
||||
"timeline": {
|
||||
"collapse": "Colapsar",
|
||||
"conversation": "Conversación",
|
||||
|
@ -396,6 +499,11 @@
|
|||
"status": {
|
||||
"favorites": "Favoritos",
|
||||
"repeats": "Repetidos",
|
||||
"delete": "Eliminar publicación",
|
||||
"pin": "Fijar en tu perfil",
|
||||
"unpin": "Desclavar de tu perfil",
|
||||
"pinned": "Fijado",
|
||||
"delete_confirm": "¿Realmente quieres borrar la publicación?",
|
||||
"reply_to": "Responder a",
|
||||
"replies_list": "Respuestas:"
|
||||
},
|
||||
|
@ -422,6 +530,8 @@
|
|||
"remote_follow": "Seguir",
|
||||
"report": "Reportar",
|
||||
"statuses": "Estados",
|
||||
"subscribe": "Suscribirse",
|
||||
"unsubscribe": "Desuscribirse",
|
||||
"unblock": "Desbloquear",
|
||||
"unblock_progress": "Desbloqueando...",
|
||||
"block_progress": "Bloqueando...",
|
||||
|
@ -486,5 +596,12 @@
|
|||
"GiB": "GiB",
|
||||
"TiB": "TiB"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"people": "Personas",
|
||||
"hashtags": "Hashtags",
|
||||
"person_talking": "{count} personas hablando",
|
||||
"people_talking": "{count} gente hablando",
|
||||
"no_results": "Sin resultados"
|
||||
}
|
||||
}
|
|
@ -27,7 +27,11 @@
|
|||
"optional": "かかなくてもよい",
|
||||
"show_more": "つづきをみる",
|
||||
"show_less": "たたむ",
|
||||
"cancel": "キャンセル"
|
||||
"cancel": "キャンセル",
|
||||
"disable": "なし",
|
||||
"enable": "あり",
|
||||
"confirm": "たしかめる",
|
||||
"verify": "たしかめる"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "がぞうをきりぬく",
|
||||
|
@ -48,7 +52,15 @@
|
|||
"placeholder": "れい: lain",
|
||||
"register": "はじめる",
|
||||
"username": "ユーザーめい",
|
||||
"hint": "はなしあいにくわわるには、ログインしてください"
|
||||
"hint": "はなしあいにくわわるには、ログインしてください",
|
||||
"authentication_code": "にんしょうコード",
|
||||
"enter_recovery_code": "リカバリーコードをいれてください",
|
||||
"enter_two_factor_code": "2-ファクターコードをいれてください",
|
||||
"recovery_code": "リカバリーコード",
|
||||
"heading" : {
|
||||
"totp" : "2-ファクターにんしょう",
|
||||
"recovery" : "2-ファクターリカバリー"
|
||||
}
|
||||
},
|
||||
"media_modal": {
|
||||
"previous": "まえ",
|
||||
|
@ -79,6 +91,20 @@
|
|||
"repeated_you": "あなたのステータスがリピートされました",
|
||||
"no_more_notifications": "つうちはありません"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "いれふだをはじめる",
|
||||
"add_option": "オプションをふやす",
|
||||
"option": "オプション",
|
||||
"votes": "いれふだ",
|
||||
"vote": "ふだをいれる",
|
||||
"type": "いれふだのかた",
|
||||
"single_choice": "ひとつえらぶ",
|
||||
"multiple_choices": "いくつでもえらべる",
|
||||
"expiry": "いれふだのながさ",
|
||||
"expires_in": "いれふだは {0} で、おわります",
|
||||
"expired": "いれふだは {0} まえに、おわりました",
|
||||
"not_enough_options": "ユニークなオプションが、たりません"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "リピートとおきにいり",
|
||||
"follows": "あたらしいフォロー",
|
||||
|
@ -139,6 +165,29 @@
|
|||
},
|
||||
"settings": {
|
||||
"app_name": "アプリのなまえ",
|
||||
"security": "セキュリティ",
|
||||
"enter_current_password_to_confirm": "あなたのアイデンティティをたしかめるため、あなたのいまのパスワードをかいてください",
|
||||
"mfa": {
|
||||
"otp" : "OTP",
|
||||
"setup_otp" : "OTPをつくる",
|
||||
"wait_pre_setup_otp" : "OTPをよういしています",
|
||||
"confirm_and_enable" : "OTPをたしかめて、ゆうこうにする",
|
||||
"title": "2-ファクターにんしょう",
|
||||
"generate_new_recovery_codes" : "あたらしいリカバリーコードをつくる",
|
||||
"warning_of_generate_new_codes" : "あたらしいリカバリーコードをつくったら、ふるいコードはつかえなくなります。",
|
||||
"recovery_codes" : "リカバリーコード。",
|
||||
"waiting_a_recovery_codes": "バックアップコードをうけとっています...",
|
||||
"recovery_codes_warning" : "コードをかきうつすか、ひとにみられないところにセーブしてください。そうでなければ、あなたはこのコードをふたたびみることはできません。もしあなたが、2FAアプリのアクセスをうしなって、なおかつ、リカバリーコードもおもいだせないならば、あなたはあなたのアカウントから、しめだされます。",
|
||||
"authentication_methods" : "にんしょうメソッド",
|
||||
"scan": {
|
||||
"title": "スキャン",
|
||||
"desc": "あなたの2-ファクターアプリをつかって、このQRコードをスキャンするか、テキストキーをうちこんでください:",
|
||||
"secret_code": "キー"
|
||||
},
|
||||
"verify": {
|
||||
"desc": "2-ファクターにんしょうをつかうには、あなたの2-ファクターアプリのコードをいれてください:"
|
||||
}
|
||||
},
|
||||
"attachmentRadius": "ファイル",
|
||||
"attachments": "ファイル",
|
||||
"autoload": "したにスクロールしたとき、じどうてきによみこむ。",
|
||||
|
|
|
@ -27,7 +27,11 @@
|
|||
"optional": "省略可",
|
||||
"show_more": "もっと見る",
|
||||
"show_less": "たたむ",
|
||||
"cancel": "キャンセル"
|
||||
"cancel": "キャンセル",
|
||||
"disable": "無効",
|
||||
"enable": "有効",
|
||||
"confirm": "確認",
|
||||
"verify": "検査"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "画像を切り抜く",
|
||||
|
@ -48,7 +52,15 @@
|
|||
"placeholder": "例: lain",
|
||||
"register": "登録",
|
||||
"username": "ユーザー名",
|
||||
"hint": "会話に加わるには、ログインしてください"
|
||||
"hint": "会話に加わるには、ログインしてください",
|
||||
"authentication_code": "認証コード",
|
||||
"enter_recovery_code": "リカバリーコードを入力してください",
|
||||
"enter_two_factor_code": "2段階認証コードを入力してください",
|
||||
"recovery_code": "リカバリーコード",
|
||||
"heading" : {
|
||||
"totp" : "2段階認証",
|
||||
"recovery" : "2段階リカバリー"
|
||||
}
|
||||
},
|
||||
"media_modal": {
|
||||
"previous": "前",
|
||||
|
@ -79,6 +91,20 @@
|
|||
"repeated_you": "あなたのステータスがリピートされました",
|
||||
"no_more_notifications": "通知はありません"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "投票を追加",
|
||||
"add_option": "選択肢を追加",
|
||||
"option": "選択肢",
|
||||
"votes": "票",
|
||||
"vote": "投票",
|
||||
"type": "投票の形式",
|
||||
"single_choice": "択一式",
|
||||
"multiple_choices": "複数選択式",
|
||||
"expiry": "投票期間",
|
||||
"expires_in": "投票は {0} で終了します",
|
||||
"expired": "投票は {0} 前に終了しました",
|
||||
"not_enough_options": "相異なる選択肢が不足しています"
|
||||
},
|
||||
"interactions": {
|
||||
"favs_repeats": "リピートとお気に入り",
|
||||
"follows": "新しいフォロワー",
|
||||
|
@ -139,6 +165,29 @@
|
|||
},
|
||||
"settings": {
|
||||
"app_name": "アプリの名称",
|
||||
"security": "セキュリティ",
|
||||
"enter_current_password_to_confirm": "あなたのアイデンティティを証明するため、現在のパスワードを入力してください",
|
||||
"mfa": {
|
||||
"otp" : "OTP",
|
||||
"setup_otp" : "OTPのセットアップ",
|
||||
"wait_pre_setup_otp" : "OTPのプリセット",
|
||||
"confirm_and_enable" : "OTPの確認と有効化",
|
||||
"title": "2段階認証",
|
||||
"generate_new_recovery_codes" : "新しいリカバリーコードを生成",
|
||||
"warning_of_generate_new_codes" : "新しいリカバリーコードを生成すると、古いコードは使用できなくなります。",
|
||||
"recovery_codes" : "リカバリーコード。",
|
||||
"waiting_a_recovery_codes": "バックアップコードを受信しています...",
|
||||
"recovery_codes_warning" : "コードを紙に書くか、安全な場所に保存してください。そうでなければ、あなたはコードを再び見ることはできません。もし2段階認証アプリのアクセスを喪失し、なおかつ、リカバリーコードもないならば、あなたは自分のアカウントから閉め出されます。",
|
||||
"authentication_methods" : "認証方法",
|
||||
"scan": {
|
||||
"title": "スキャン",
|
||||
"desc": "あなたの2段階認証アプリを使って、このQRコードをスキャンするか、テキストキーを入力してください:",
|
||||
"secret_code": "キー"
|
||||
},
|
||||
"verify": {
|
||||
"desc": "2段階認証を有効にするには、あなたの2段階認証アプリのコードを入力してください:"
|
||||
}
|
||||
},
|
||||
"attachmentRadius": "ファイル",
|
||||
"attachments": "ファイル",
|
||||
"autoload": "下にスクロールしたとき、自動的に読み込む。",
|
||||
|
|
|
@ -26,6 +26,7 @@ import messages from './i18n/messages.js'
|
|||
import VueChatScroll from 'vue-chat-scroll'
|
||||
import VueClickOutside from 'v-click-outside'
|
||||
import PortalVue from 'portal-vue'
|
||||
import VTooltip from 'v-tooltip'
|
||||
|
||||
import afterStoreSetup from './boot/after_store.js'
|
||||
|
||||
|
@ -37,6 +38,7 @@ Vue.use(VueI18n)
|
|||
Vue.use(VueChatScroll)
|
||||
Vue.use(VueClickOutside)
|
||||
Vue.use(PortalVue)
|
||||
Vue.use(VTooltip)
|
||||
|
||||
const i18n = new VueI18n({
|
||||
// By default, use the browser locale, we will update it if neccessary
|
||||
|
|
|
@ -70,6 +70,7 @@ const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
|
|||
const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
|
||||
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
|
||||
const MASTODON_SEARCH_2 = `/api/v2/search`
|
||||
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
|
||||
|
||||
const oldfetch = window.fetch
|
||||
|
||||
|
@ -865,6 +866,18 @@ const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
|
|||
})
|
||||
}
|
||||
|
||||
const searchUsers = ({ credentials, query }) => {
|
||||
return promisedRequest({
|
||||
url: MASTODON_USER_SEARCH_URL,
|
||||
params: {
|
||||
q: query,
|
||||
resolve: true
|
||||
},
|
||||
credentials
|
||||
})
|
||||
.then((data) => data.map(parseUser))
|
||||
}
|
||||
|
||||
const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
|
||||
let url = MASTODON_SEARCH_2
|
||||
let params = []
|
||||
|
@ -974,7 +987,8 @@ const apiService = {
|
|||
fetchRebloggedByUsers,
|
||||
reportUser,
|
||||
updateNotificationSettings,
|
||||
search2
|
||||
search2,
|
||||
searchUsers
|
||||
}
|
||||
|
||||
export default apiService
|
||||
|
|
|
@ -152,6 +152,7 @@ const backendInteractorService = credentials => {
|
|||
const unretweet = (id) => apiService.unretweet({ id, credentials })
|
||||
const search2 = ({ q, resolve, limit, offset, following }) =>
|
||||
apiService.search2({ credentials, q, resolve, limit, offset, following })
|
||||
const searchUsers = (query) => apiService.searchUsers({ query, credentials })
|
||||
|
||||
const backendInteractorServiceInstance = {
|
||||
fetchStatus,
|
||||
|
@ -216,7 +217,8 @@ const backendInteractorService = credentials => {
|
|||
retweet,
|
||||
unretweet,
|
||||
updateNotificationSettings,
|
||||
search2
|
||||
search2,
|
||||
searchUsers
|
||||
}
|
||||
|
||||
return backendInteractorServiceInstance
|
||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -5459,9 +5459,10 @@ pngjs@^3.3.0:
|
|||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
|
||||
|
||||
popper.js@^1.14.7:
|
||||
version "1.14.7"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e"
|
||||
popper.js@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
|
||||
integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
|
||||
|
||||
portal-vue@^2.1.4:
|
||||
version "2.1.4"
|
||||
|
@ -7198,6 +7199,15 @@ v-click-outside@^2.1.1:
|
|||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.3.tgz#b7297abe833a439dc0895e6418a494381e64b5e7"
|
||||
|
||||
v-tooltip@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.2.tgz#8610d9eece2cc44fd66c12ef2f12eec6435cab9b"
|
||||
integrity sha512-xQ+qzOFfywkLdjHknRPgMMupQNS8yJtf9Utd5Dxiu/0n4HtrxqsgDtN2MLZ0LKbburtSAQgyypuE/snM8bBZhw==
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
popper.js "^1.15.0"
|
||||
vue-resize "^0.4.5"
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
|
@ -7272,11 +7282,10 @@ vue-loader@^14.0.0:
|
|||
vue-style-loader "^4.0.1"
|
||||
vue-template-es2015-compiler "^1.6.0"
|
||||
|
||||
vue-popperjs@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-popperjs/-/vue-popperjs-2.0.3.tgz#7c446d0ba7c63170ccb33a02669d0df4efc3d8cd"
|
||||
dependencies:
|
||||
popper.js "^1.14.7"
|
||||
vue-resize@^0.4.5:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-0.4.5.tgz#4777a23042e3c05620d9cbda01c0b3cc5e32dcea"
|
||||
integrity sha512-bhP7MlgJQ8TIkZJXAfDf78uJO+mEI3CaLABLjv0WNzr4CcGRGPIAItyWYnP6LsPA4Oq0WE+suidNs6dgpO4RHg==
|
||||
|
||||
vue-router@^3.0.1:
|
||||
version "3.0.2"
|
||||
|
|
Loading…
Reference in a new issue