Merge remote-tracking branch 'origin/develop' into shigusegubu-vue3
* origin/develop: (80 commits) Make suggestor suggest according to cldr annotations Make chunks named Use import() for emoji.json Add regional indicators Support filtering by keywords from cldr Display localized unicode emoji names Load unicode emoji annotations Extract language list to its own file using the half-shit approach since proper approach is full-shit Make unicode emoji phrases match with _ Use console.info Fix non-square emojis being truncated Fix emoji picker lint Fix emoji picker lint Tweak efficiency when changing filter keywords in emoji picker Use trimmed keyword for filtering emojis Limit the width of unsupported multichar emojis Make emoji picker work with vue3 Make StillImage react to src changes Lint ...
This commit is contained in:
commit
f580929717
53 changed files with 1096 additions and 211 deletions
2
.babelrc
2
.babelrc
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
|
||||
"comments": false
|
||||
"comments": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,5 @@ Contributors of this project.
|
|||
- shpuld (shpuld@shitposter.club): CSS and styling
|
||||
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
|
||||
- hj (hj@shigusegubu.club): Code
|
||||
- Sean King (seanking@freespeechextremist.com): Code
|
||||
- Tusooa Zhu (tusooa@kazv.moe): Code
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
module.exports = {
|
||||
updateEmoji () {
|
||||
const emojis = require('unicode-emoji-json/data-by-group')
|
||||
const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
|
||||
const fs = require('fs')
|
||||
|
||||
Object.keys(emojis)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ module.exports = {
|
|||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
|
||||
filename: '[name].js'
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].js'
|
||||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -18,11 +18,12 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "6.1.2",
|
||||
"@fortawesome/free-regular-svg-icons": "6.1.2",
|
||||
"@fortawesome/free-solid-svg-icons": "6.1.2",
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.0",
|
||||
"@fortawesome/vue-fontawesome": "3.0.1",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||
"@kazvmoe-infra/unicode-emoji-json": "^0.4.0",
|
||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12",
|
||||
"@vuelidate/core": "2.0.0-alpha.44",
|
||||
"@vuelidate/validators": "2.0.0-alpha.31",
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
"escape-html": "1.0.3",
|
||||
"js-cookie": "3.0.1",
|
||||
"localforage": "1.10.0",
|
||||
"lozad": "^1.16.0",
|
||||
"parse-link-header": "2.0.0",
|
||||
"lozad": "^1.16.0",
|
||||
"phoenix": "1.6.2",
|
||||
|
|
@ -42,7 +44,7 @@
|
|||
"querystring-es3": "0.2.1",
|
||||
"url": "0.11.0",
|
||||
"utf8": "3.0.0",
|
||||
"vue": "3.2.37",
|
||||
"vue": "3.2.38",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "4.1.5",
|
||||
"vue-template-compiler": "2.7.10",
|
||||
|
|
@ -58,7 +60,7 @@
|
|||
"@ungap/event-target": "0.2.3",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||
"@vue/babel-plugin-jsx": "1.1.1",
|
||||
"@vue/compiler-sfc": "3.2.37",
|
||||
"@vue/compiler-sfc": "3.2.38",
|
||||
"@vue/test-utils": "2.0.2",
|
||||
"autoprefixer": "10.4.8",
|
||||
"babel-loader": "8.2.5",
|
||||
|
|
@ -105,7 +107,7 @@
|
|||
"ora": "0.4.1",
|
||||
"postcss": "8.4.16",
|
||||
"postcss-loader": "7.0.1",
|
||||
"sass": "1.54.5",
|
||||
"sass": "1.54.8",
|
||||
"sass-loader": "13.0.2",
|
||||
"selenium-server": "2.53.1",
|
||||
"semver": "7.3.7",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil
|
|||
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
||||
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
||||
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
||||
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
|
||||
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
||||
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
|
||||
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
||||
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
|
@ -35,6 +37,8 @@ export default {
|
|||
UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
|
||||
UserReportingModal,
|
||||
PostStatusModal,
|
||||
EditStatusModal,
|
||||
StatusHistoryModal,
|
||||
GlobalNoticeList
|
||||
},
|
||||
data: () => ({
|
||||
|
|
@ -101,6 +105,7 @@ export default {
|
|||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
|
||||
},
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||
shoutboxPosition () {
|
||||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
|
||||
},
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@
|
|||
<MobilePostStatusButton />
|
||||
<UserReportingModal />
|
||||
<PostStatusModal />
|
||||
<EditStatusModal v-if="editingAvailable" />
|
||||
<StatusHistoryModal v-if="editingAvailable" />
|
||||
<SettingsModal />
|
||||
<UpdateNotification />
|
||||
<div id="modal" />
|
||||
|
|
|
|||
BIN
src/assets/pleromatan_apology_fox_mask.png
Normal file
BIN
src/assets/pleromatan_apology_fox_mask.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/pleromatan_apology_mask.png
Normal file
BIN
src/assets/pleromatan_apology_mask.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -251,6 +251,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ const AccountActions = {
|
|||
unblockUser () {
|
||||
this.$store.dispatch('unblockUser', this.user.id)
|
||||
},
|
||||
removeUserFromFollowers () {
|
||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
||||
},
|
||||
reportUser () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@
|
|||
/>
|
||||
</template>
|
||||
<UserListMenu :user="user" />
|
||||
<button
|
||||
v-if="relationship.followed_by"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
@click="removeUserFromFollowers"
|
||||
>
|
||||
{{ $t('user_card.remove_follower') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="relationship.blocking"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ const Attachment = {
|
|||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
watch: {
|
||||
'attachment.description' (newVal) {
|
||||
this.localDescription = newVal
|
||||
},
|
||||
localDescription (newVal) {
|
||||
this.onEdit(newVal)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { reduce, filter, findIndex, clone, get } from 'lodash'
|
||||
import Status from '../status/status.vue'
|
||||
import ThreadTree from '../thread_tree/thread_tree.vue'
|
||||
import { WSConnectionStatus } from '../../services/api/api.service.js'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
|
||||
import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
|
||||
|
||||
|
|
@ -79,6 +81,9 @@ const conversation = {
|
|||
const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
|
||||
return maxDepth >= 1 ? maxDepth : 1
|
||||
},
|
||||
streamingEnabled () {
|
||||
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
|
||||
},
|
||||
displayStyle () {
|
||||
return this.$store.getters.mergedConfig.conversationDisplay
|
||||
},
|
||||
|
|
@ -341,7 +346,11 @@ const conversation = {
|
|||
},
|
||||
maybeHighlight () {
|
||||
return this.isExpanded ? this.highlight : null
|
||||
}
|
||||
},
|
||||
...mapGetters(['mergedConfig']),
|
||||
...mapState({
|
||||
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
|
||||
})
|
||||
},
|
||||
components: {
|
||||
Status,
|
||||
|
|
@ -399,6 +408,11 @@ const conversation = {
|
|||
setHighlight (id) {
|
||||
if (!id) return
|
||||
this.highlight = id
|
||||
|
||||
if (!this.streamingEnabled) {
|
||||
this.$store.dispatch('fetchStatus', id)
|
||||
}
|
||||
|
||||
this.$store.dispatch('fetchFavsAndRepeats', id)
|
||||
this.$store.dispatch('fetchEmojiReactionsBy', id)
|
||||
},
|
||||
|
|
|
|||
75
src/components/edit_status_modal/edit_status_modal.js
Normal file
75
src/components/edit_status_modal/edit_status_modal.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import Modal from '../modal/modal.vue'
|
||||
import statusPosterService from '../../services/status_poster/status_poster.service.js'
|
||||
import get from 'lodash/get'
|
||||
|
||||
const EditStatusModal = {
|
||||
components: {
|
||||
PostStatusForm,
|
||||
Modal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
resettingForm: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
},
|
||||
modalActivated () {
|
||||
return this.$store.state.editStatus.modalActivated
|
||||
},
|
||||
isFormVisible () {
|
||||
return this.isLoggedIn && !this.resettingForm && this.modalActivated
|
||||
},
|
||||
params () {
|
||||
return this.$store.state.editStatus.params || {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
params (newVal, oldVal) {
|
||||
if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
|
||||
this.resettingForm = true
|
||||
this.$nextTick(() => {
|
||||
this.resettingForm = false
|
||||
})
|
||||
}
|
||||
},
|
||||
isFormVisible (val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
|
||||
const params = {
|
||||
store: this.$store,
|
||||
statusId: this.$store.state.editStatus.params.statusId,
|
||||
status,
|
||||
spoilerText,
|
||||
sensitive,
|
||||
poll,
|
||||
media,
|
||||
contentType
|
||||
}
|
||||
|
||||
return statusPosterService.editStatus(params)
|
||||
.then((data) => {
|
||||
return data
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error editing status', err)
|
||||
return {
|
||||
error: err.message
|
||||
}
|
||||
})
|
||||
},
|
||||
closeModal () {
|
||||
this.$store.dispatch('closeEditStatusModal')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EditStatusModal
|
||||
48
src/components/edit_status_modal/edit_status_modal.vue
Normal file
48
src/components/edit_status_modal/edit_status_modal.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<Modal
|
||||
v-if="isFormVisible"
|
||||
class="edit-form-modal-view"
|
||||
@backdropClicked="closeModal"
|
||||
>
|
||||
<div class="edit-form-modal-panel panel">
|
||||
<div class="panel-heading">
|
||||
{{ $t('post_status.edit_status') }}
|
||||
</div>
|
||||
<PostStatusForm
|
||||
class="panel-body"
|
||||
v-bind="params"
|
||||
:post-handler="doEditStatus"
|
||||
:disable-polls="true"
|
||||
:disable-visibility-selector="true"
|
||||
@posted="closeModal"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script src="./edit_status_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-view.edit-form-modal-view {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.edit-form-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin-top: 25%;
|
||||
margin-bottom: 2em;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
|
||||
@media (orientation: landscape) {
|
||||
margin-top: 8%;
|
||||
}
|
||||
|
||||
.form-bottom-left {
|
||||
max-width: 6.5em;
|
||||
|
||||
.emoji-icon {
|
||||
justify-content: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,7 +3,7 @@ import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
|||
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||
import { take } from 'lodash'
|
||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||
|
||||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSmileBeam
|
||||
|
|
@ -143,6 +143,51 @@ const EmojiInput = {
|
|||
const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
|
||||
return word
|
||||
}
|
||||
},
|
||||
languages () {
|
||||
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
|
||||
},
|
||||
maybeLocalizedEmojiNamesAndKeywords () {
|
||||
return emoji => {
|
||||
const names = [emoji.displayText]
|
||||
const keywords = []
|
||||
|
||||
if (emoji.displayTextI18n) {
|
||||
names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args))
|
||||
}
|
||||
|
||||
if (emoji.annotations) {
|
||||
this.languages.forEach(lang => {
|
||||
names.push(emoji.annotations[lang]?.name)
|
||||
|
||||
keywords.push(...(emoji.annotations[lang]?.keywords || []))
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
names: names.filter(k => k),
|
||||
keywords: keywords.filter(k => k)
|
||||
}
|
||||
}
|
||||
},
|
||||
maybeLocalizedEmojiName () {
|
||||
return emoji => {
|
||||
if (!emoji.annotations) {
|
||||
return emoji.displayText
|
||||
}
|
||||
|
||||
if (emoji.displayTextI18n) {
|
||||
return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
|
||||
}
|
||||
|
||||
for (const lang of this.languages) {
|
||||
if (emoji.annotations[lang]?.name) {
|
||||
return emoji.annotations[lang].name
|
||||
}
|
||||
}
|
||||
|
||||
return emoji.displayText
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
@ -181,7 +226,7 @@ const EmojiInput = {
|
|||
const firstchar = newWord.charAt(0)
|
||||
this.suggestions = []
|
||||
if (newWord === firstchar) return
|
||||
const matchedSuggestions = await this.suggest(newWord)
|
||||
const matchedSuggestions = await this.suggest(newWord, this.maybeLocalizedEmojiNamesAndKeywords)
|
||||
// Async: cancel if textAtCaret has changed during wait
|
||||
if (this.textAtCaret !== newWord) return
|
||||
if (matchedSuggestions.length <= 0) return
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
v-if="!suggestion.user"
|
||||
class="displayText"
|
||||
>
|
||||
{{ suggestion.displayText }}
|
||||
{{ maybeLocalizedEmojiName(suggestion) }}
|
||||
</span>
|
||||
<span class="detailText">{{ suggestion.detailText }}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
export default data => {
|
||||
const emojiCurry = suggestEmoji(data.emoji)
|
||||
const usersCurry = data.store && suggestUsers(data.store)
|
||||
return input => {
|
||||
return (input, nameKeywordLocalizer) => {
|
||||
const firstChar = input[0]
|
||||
if (firstChar === ':' && data.emoji) {
|
||||
return emojiCurry(input)
|
||||
return emojiCurry(input, nameKeywordLocalizer)
|
||||
}
|
||||
if (firstChar === '@' && usersCurry) {
|
||||
return usersCurry(input)
|
||||
|
|
@ -25,34 +25,34 @@ export default data => {
|
|||
}
|
||||
}
|
||||
|
||||
export const suggestEmoji = emojis => input => {
|
||||
export const suggestEmoji = emojis => (input, nameKeywordLocalizer) => {
|
||||
const noPrefix = input.toLowerCase().substr(1)
|
||||
return emojis
|
||||
.filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
|
||||
.sort((a, b) => {
|
||||
let aScore = 0
|
||||
let bScore = 0
|
||||
.map(emoji => ({ ...emoji, ...nameKeywordLocalizer(emoji) }))
|
||||
.filter((emoji) => (emoji.names.concat(emoji.keywords)).filter(kw => kw.toLowerCase().match(noPrefix)).length)
|
||||
.map(k => {
|
||||
let score = 0
|
||||
|
||||
// An exact match always wins
|
||||
aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
|
||||
bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
|
||||
score += Math.max(...k.names.map(name => name.toLowerCase() === noPrefix ? 200 : 0), 0)
|
||||
|
||||
// Prioritize custom emoji a lot
|
||||
aScore += a.imageUrl ? 100 : 0
|
||||
bScore += b.imageUrl ? 100 : 0
|
||||
score += k.imageUrl ? 100 : 0
|
||||
|
||||
// Prioritize prefix matches somewhat
|
||||
aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
|
||||
bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
|
||||
score += Math.max(...k.names.map(kw => kw.toLowerCase().startsWith(noPrefix) ? 10 : 0), 0)
|
||||
|
||||
// Sort by length
|
||||
aScore -= a.displayText.length
|
||||
bScore -= b.displayText.length
|
||||
score -= k.displayText.length
|
||||
|
||||
k.score = score
|
||||
return k
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Break ties alphabetically
|
||||
const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
|
||||
|
||||
return bScore - aScore + alphabetically
|
||||
return b.score - a.score + alphabetically
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
import Checkbox from '../checkbox/checkbox.vue'
|
||||
import StillImage from '../still-image/still-image.vue'
|
||||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
||||
import lozad from 'lozad'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
|
@ -46,13 +47,30 @@ const UNICODE_EMOJI_GROUP_ICON = {
|
|||
flags: 'flag'
|
||||
}
|
||||
|
||||
const filterByKeyword = (list, keyword = '') => {
|
||||
const maybeLocalizedKeywords = (emoji, languages, nameLocalizer) => {
|
||||
const res = [emoji.displayText, nameLocalizer(emoji)]
|
||||
if (emoji.annotations) {
|
||||
languages.forEach(lang => {
|
||||
const keywords = emoji.annotations[lang]?.keywords || []
|
||||
const name = emoji.annotations[lang]?.name
|
||||
res.push(...(keywords.concat([name]).filter(k => k)))
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
|
||||
if (keyword === '') return list
|
||||
|
||||
const keywordLowercase = keyword.toLowerCase()
|
||||
const orderedEmojiList = []
|
||||
for (const emoji of list) {
|
||||
const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
|
||||
const indices = maybeLocalizedKeywords(emoji, languages, nameLocalizer)
|
||||
.map(k => k.toLowerCase().indexOf(keywordLowercase))
|
||||
.filter(k => k > -1)
|
||||
|
||||
const indexOfKeyword = indices.length ? Math.min(...indices) : -1
|
||||
|
||||
if (indexOfKeyword > -1) {
|
||||
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
|
||||
orderedEmojiList[indexOfKeyword] = []
|
||||
|
|
@ -171,7 +189,7 @@ const EmojiPicker = {
|
|||
this.showingStickers = value
|
||||
},
|
||||
filterByKeyword (list, keyword) {
|
||||
return filterByKeyword(list, keyword)
|
||||
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
|
||||
},
|
||||
initializeLazyLoad () {
|
||||
this.destroyLazyLoad()
|
||||
|
|
@ -220,7 +238,7 @@ const EmojiPicker = {
|
|||
return this.allEmojiGroups
|
||||
.map(group => ({
|
||||
...group,
|
||||
emojis: filterByKeyword(group.emojis, trim(this.keyword))
|
||||
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
|
||||
}))
|
||||
.filter(group => group.emojis.length > 0)
|
||||
}
|
||||
|
|
@ -285,6 +303,28 @@ const EmojiPicker = {
|
|||
this.waitForDomAndInitializeLazyLoad()
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
}, 500)
|
||||
},
|
||||
languages () {
|
||||
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
|
||||
},
|
||||
maybeLocalizedEmojiName () {
|
||||
return emoji => {
|
||||
if (!emoji.annotations) {
|
||||
return emoji.displayText
|
||||
}
|
||||
|
||||
if (emoji.displayTextI18n) {
|
||||
return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
|
||||
}
|
||||
|
||||
for (const lang of this.languages) {
|
||||
if (emoji.annotations[lang]?.name) {
|
||||
return emoji.annotations[lang].name
|
||||
}
|
||||
}
|
||||
|
||||
return emoji.displayText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@
|
|||
<span
|
||||
v-for="emoji in group.emojis"
|
||||
:key="group.id + emoji.displayText"
|
||||
:title="emoji.displayText"
|
||||
:title="maybeLocalizedEmojiName(emoji)"
|
||||
class="emoji-item"
|
||||
@click.stop.prevent="onEmoji(emoji)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
faThumbtack,
|
||||
faShareAlt,
|
||||
faExternalLinkAlt,
|
||||
faHistory,
|
||||
faPlus,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
|
@ -24,6 +25,7 @@ library.add(
|
|||
faShareAlt,
|
||||
faExternalLinkAlt,
|
||||
faFlag,
|
||||
faHistory,
|
||||
faPlus,
|
||||
faTimes
|
||||
)
|
||||
|
|
@ -86,6 +88,25 @@ const ExtraButtons = {
|
|||
},
|
||||
reportStatus () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
|
||||
},
|
||||
editStatus () {
|
||||
this.$store.dispatch('fetchStatusSource', { id: this.status.id })
|
||||
.then(data => this.$store.dispatch('openEditStatusModal', {
|
||||
statusId: this.status.id,
|
||||
subject: data.spoiler_text,
|
||||
statusText: data.text,
|
||||
statusIsSensitive: this.status.nsfw,
|
||||
statusPoll: this.status.poll,
|
||||
statusFiles: [...this.status.attachments],
|
||||
visibility: this.status.visibility,
|
||||
statusContentType: data.content_type
|
||||
}))
|
||||
},
|
||||
showStatusHistory () {
|
||||
const originalStatus = { ...this.status }
|
||||
const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html']
|
||||
stripFieldsList.forEach(p => delete originalStatus[p])
|
||||
this.$store.dispatch('openStatusHistoryModal', originalStatus)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -109,7 +130,11 @@ const ExtraButtons = {
|
|||
},
|
||||
statusLink () {
|
||||
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
|
||||
}
|
||||
},
|
||||
isEdited () {
|
||||
return this.status.edited_at !== null
|
||||
},
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,28 @@
|
|||
/><span>{{ $t("status.unbookmark") }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-if="ownStatus && editingAvailable"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="editStatus"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="pen"
|
||||
/><span>{{ $t("status.edit") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="isEdited && editingAvailable"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="showStatusHistory"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="history"
|
||||
/><span>{{ $t("status.status_history") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canDelete"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
import RemoteFollow from '../remote_follow/remote_follow.vue'
|
||||
import FollowButton from '../follow_button/follow_button.vue'
|
||||
import RemoveFollowerButton from '../remove_follower_button/remove_follower_button.vue'
|
||||
|
||||
const FollowCard = {
|
||||
props: [
|
||||
|
|
@ -10,7 +11,8 @@ const FollowCard = {
|
|||
components: {
|
||||
BasicUserCard,
|
||||
RemoteFollow,
|
||||
FollowButton
|
||||
FollowButton,
|
||||
RemoveFollowerButton
|
||||
},
|
||||
computed: {
|
||||
isMe () {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@
|
|||
class="follow-card-follow-button"
|
||||
:user="user"
|
||||
/>
|
||||
<RemoveFollowerButton
|
||||
v-if="noFollowsYou && relationship.followed_by"
|
||||
:relationship="relationship"
|
||||
class="follow-card-button"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</basic-user-card>
|
||||
|
|
@ -40,6 +45,12 @@
|
|||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
&-button {
|
||||
margin-top: 0.5em;
|
||||
padding: 0 1.5em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
&-follow-button {
|
||||
margin-top: 0.5em;
|
||||
margin-left: auto;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,14 @@ const pxStringToNumber = (str) => {
|
|||
|
||||
const PostStatusForm = {
|
||||
props: [
|
||||
'statusId',
|
||||
'statusText',
|
||||
'statusIsSensitive',
|
||||
'statusPoll',
|
||||
'statusFiles',
|
||||
'statusMediaDescriptions',
|
||||
'statusScope',
|
||||
'statusContentType',
|
||||
'replyTo',
|
||||
'repliedUser',
|
||||
'attentions',
|
||||
|
|
@ -62,6 +70,7 @@ const PostStatusForm = {
|
|||
'subject',
|
||||
'disableSubject',
|
||||
'disableScopeSelector',
|
||||
'disableVisibilitySelector',
|
||||
'disableNotice',
|
||||
'disableLockWarning',
|
||||
'disablePolls',
|
||||
|
|
@ -125,22 +134,38 @@ const PostStatusForm = {
|
|||
|
||||
const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
|
||||
|
||||
let statusParams = {
|
||||
spoilerText: this.subject || '',
|
||||
status: statusText,
|
||||
nsfw: !!sensitiveByDefault,
|
||||
files: [],
|
||||
poll: {},
|
||||
mediaDescriptions: {},
|
||||
visibility: scope,
|
||||
contentType
|
||||
}
|
||||
|
||||
if (this.statusId) {
|
||||
const statusContentType = this.statusContentType || contentType
|
||||
statusParams = {
|
||||
spoilerText: this.subject || '',
|
||||
status: this.statusText || '',
|
||||
nsfw: this.statusIsSensitive || !!sensitiveByDefault,
|
||||
files: this.statusFiles || [],
|
||||
poll: this.statusPoll || {},
|
||||
mediaDescriptions: this.statusMediaDescriptions || {},
|
||||
visibility: this.statusScope || scope,
|
||||
contentType: statusContentType
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dropFiles: [],
|
||||
uploadingFiles: false,
|
||||
error: null,
|
||||
posting: false,
|
||||
highlighted: 0,
|
||||
newStatus: {
|
||||
spoilerText: this.subject || '',
|
||||
status: statusText,
|
||||
nsfw: !!sensitiveByDefault,
|
||||
files: [],
|
||||
poll: {},
|
||||
mediaDescriptions: {},
|
||||
visibility: scope,
|
||||
contentType
|
||||
},
|
||||
newStatus: statusParams,
|
||||
caret: 0,
|
||||
pollFormVisible: false,
|
||||
showDropIcon: 'hide',
|
||||
|
|
@ -236,6 +261,9 @@ const PostStatusForm = {
|
|||
uploadFileLimitReached () {
|
||||
return this.newStatus.files.length >= this.fileLimit
|
||||
},
|
||||
isEdit () {
|
||||
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
|
||||
},
|
||||
...mapGetters(['mergedConfig']),
|
||||
...mapState({
|
||||
mobileLayout: state => state.interface.mobileLayout
|
||||
|
|
|
|||
|
|
@ -66,6 +66,13 @@
|
|||
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
|
||||
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
|
||||
</p>
|
||||
<div
|
||||
v-if="isEdit"
|
||||
class="visibility-notice edit-warning"
|
||||
>
|
||||
<p>{{ $t('post_status.edit_remote_warning') }}</p>
|
||||
<p>{{ $t('post_status.edit_unsupported_warning') }}</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="!disablePreview"
|
||||
class="preview-heading faint"
|
||||
|
|
@ -170,6 +177,7 @@
|
|||
class="visibility-tray"
|
||||
>
|
||||
<scope-selector
|
||||
v-if="!disableVisibilitySelector"
|
||||
:show-all="showAllScopes"
|
||||
:user-default="userDefaultScope"
|
||||
:original-scope="copyMessageScope"
|
||||
|
|
@ -410,6 +418,16 @@
|
|||
align-items: baseline;
|
||||
}
|
||||
|
||||
.visibility-notice.edit-warning {
|
||||
> :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-icon, .poll-icon, .emoji-icon {
|
||||
font-size: 1.85em;
|
||||
line-height: 1.1;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
export default {
|
||||
props: ['relationship'],
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
label () {
|
||||
if (this.inProgress) {
|
||||
return this.$t('user_card.follow_progress')
|
||||
} else {
|
||||
return this.$t('user_card.remove_follower')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick () {
|
||||
this.inProgress = true
|
||||
this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
|
||||
this.inProgress = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<button
|
||||
class="btn button-default follow-button"
|
||||
:class="{ toggled: inProgress }"
|
||||
:disabled="inProgress"
|
||||
:title="$t('user_card.remove_follower')"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ label }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script src="./remove_follower_button.js"></script>
|
||||
|
|
@ -395,6 +395,12 @@ const Status = {
|
|||
},
|
||||
visibilityLocalized () {
|
||||
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
|
||||
},
|
||||
isEdited () {
|
||||
return this.status.edited_at !== null
|
||||
},
|
||||
editingAvailable () {
|
||||
return this.$store.state.instance.editingAvailable
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -156,7 +156,8 @@
|
|||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
& .heading-reply-row {
|
||||
& .heading-reply-row,
|
||||
& .heading-edited-row {
|
||||
position: relative;
|
||||
align-content: baseline;
|
||||
font-size: 0.85em;
|
||||
|
|
|
|||
|
|
@ -327,6 +327,24 @@
|
|||
class="mentions-line"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="isEdited && editingAvailable && !isPreview"
|
||||
class="heading-edited-row"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="status.edited_at"
|
||||
tag="span"
|
||||
>
|
||||
<template #time>
|
||||
<Timeago
|
||||
template-key="time.in_past"
|
||||
:time="status.edited_at"
|
||||
:auto-update="60"
|
||||
:long-format="true"
|
||||
/>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StatusContent
|
||||
|
|
|
|||
60
src/components/status_history_modal/status_history_modal.js
Normal file
60
src/components/status_history_modal/status_history_modal.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { get } from 'lodash'
|
||||
import Modal from '../modal/modal.vue'
|
||||
import Status from '../status/status.vue'
|
||||
|
||||
const StatusHistoryModal = {
|
||||
components: {
|
||||
Modal,
|
||||
Status
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
statuses: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
modalActivated () {
|
||||
return this.$store.state.statusHistory.modalActivated
|
||||
},
|
||||
params () {
|
||||
return this.$store.state.statusHistory.params
|
||||
},
|
||||
statusId () {
|
||||
return this.params.id
|
||||
},
|
||||
historyCount () {
|
||||
return this.statuses.length
|
||||
},
|
||||
history () {
|
||||
return this.statuses
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
params (newVal, oldVal) {
|
||||
const newStatusId = get(newVal, 'id') !== get(oldVal, 'id')
|
||||
if (newStatusId) {
|
||||
this.resetHistory()
|
||||
}
|
||||
|
||||
if (newStatusId || get(newVal, 'edited_at') !== get(oldVal, 'edited_at')) {
|
||||
this.fetchStatusHistory()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetHistory () {
|
||||
this.statuses = []
|
||||
},
|
||||
fetchStatusHistory () {
|
||||
this.$store.dispatch('fetchStatusHistory', this.params)
|
||||
.then(data => {
|
||||
this.statuses = data
|
||||
})
|
||||
},
|
||||
closeModal () {
|
||||
this.$store.dispatch('closeStatusHistoryModal')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default StatusHistoryModal
|
||||
46
src/components/status_history_modal/status_history_modal.vue
Normal file
46
src/components/status_history_modal/status_history_modal.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<Modal
|
||||
v-if="modalActivated"
|
||||
class="status-history-modal-view"
|
||||
@backdropClicked="closeModal"
|
||||
>
|
||||
<div class="status-history-modal-panel panel">
|
||||
<div class="panel-heading">
|
||||
{{ $t('status.status_history') }} ({{ historyCount }})
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div
|
||||
v-if="historyCount > 0"
|
||||
class="history-body"
|
||||
>
|
||||
<status
|
||||
v-for="status in history"
|
||||
:key="status.id"
|
||||
:statusoid="status"
|
||||
:is-preview="true"
|
||||
class="conversation-status status-fadein panel-body"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script src="./status_history_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-view.status-history-modal-view {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.status-history-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin-top: 25%;
|
||||
margin-bottom: 2em;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
|
||||
@media (orientation: landscape) {
|
||||
margin-top: 8%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
:datetime="time"
|
||||
:title="localeDateString"
|
||||
>
|
||||
{{ $tc(relativeTime.key, relativeTime.num, [relativeTime.num]) }}
|
||||
{{ relativeTimeString }}
|
||||
</time>
|
||||
</template>
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import localeService from 'src/services/locale/locale.service.js'
|
|||
|
||||
export default {
|
||||
name: 'Timeago',
|
||||
props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold'],
|
||||
props: ['time', 'autoUpdate', 'longFormat', 'nowThreshold', 'templateKey'],
|
||||
data () {
|
||||
return {
|
||||
relativeTime: { key: 'time.now', num: 0 },
|
||||
|
|
@ -26,6 +26,23 @@ export default {
|
|||
return typeof this.time === 'string'
|
||||
? new Date(Date.parse(this.time)).toLocaleString(browserLocale)
|
||||
: this.time.toLocaleString(browserLocale)
|
||||
},
|
||||
relativeTimeString () {
|
||||
const timeString = this.$i18n.tc(this.relativeTime.key, this.relativeTime.num, [this.relativeTime.num])
|
||||
|
||||
if (typeof this.templateKey === 'string' && this.relativeTime.key !== 'time.now') {
|
||||
return this.$i18n.t(this.templateKey, [timeString])
|
||||
}
|
||||
|
||||
return timeString
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
time (newVal, oldVal) {
|
||||
if (oldVal !== newVal) {
|
||||
clearTimeout(this.interval)
|
||||
this.refreshRelativeTimeObject()
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import Modal from 'src/components/modal/modal.vue'
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import pleromaTan from 'src/assets/pleromatan_apology.png'
|
||||
import pleromaTanFox from 'src/assets/pleromatan_apology_fox.png'
|
||||
import pleromaTanMask from 'src/assets/pleromatan_apology_mask.png'
|
||||
import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png'
|
||||
|
||||
import {
|
||||
faTimes
|
||||
|
|
@ -15,9 +17,9 @@ export const CURRENT_UPDATE_COUNTER = 1
|
|||
const UpdateNotification = {
|
||||
data () {
|
||||
return {
|
||||
showingImage: false,
|
||||
pleromaTanVariant: Math.random() > 0.5 ? pleromaTan : pleromaTanFox,
|
||||
showingMore: false,
|
||||
contentHeight: 0
|
||||
showingMore: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
@ -25,13 +27,9 @@ const UpdateNotification = {
|
|||
},
|
||||
computed: {
|
||||
pleromaTanStyles () {
|
||||
const mask = this.pleromaTanVariant === pleromaTan ? pleromaTanMask : pleromaTanFoxMask
|
||||
return {
|
||||
'shape-outside': 'url(' + this.pleromaTanVariant + ')'
|
||||
}
|
||||
},
|
||||
dynamicStyles () {
|
||||
return {
|
||||
'--____extraInfoGroupHeight': this.contentHeight + 'px'
|
||||
'shape-outside': 'url(' + mask + ')'
|
||||
}
|
||||
},
|
||||
shouldShow () {
|
||||
|
|
@ -57,9 +55,14 @@ const UpdateNotification = {
|
|||
}
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.contentHeight = this.$refs.animatedText.scrollHeight
|
||||
}, 1000)
|
||||
this.contentHeightNoImage = this.$refs.animatedText.scrollHeight
|
||||
|
||||
// Workaround to get the text height only after mask loaded. A bit hacky.
|
||||
const newImg = new Image()
|
||||
newImg.onload = () => {
|
||||
setTimeout(() => { this.showingImage = true }, 100)
|
||||
}
|
||||
newImg.src = this.pleromaTanVariant === pleromaTan ? pleromaTanMask : pleromaTanFoxMask
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@
|
|||
margin-top: calc(-1 * var(--__top-fringe));
|
||||
margin-bottom: calc(-1 * var(--__bottom-fringe));
|
||||
margin-right: calc(-1 * var(--__right-fringe));
|
||||
|
||||
&.-noImage {
|
||||
.text {
|
||||
padding-right: var(--__right-fringe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
|
|
@ -75,9 +81,9 @@
|
|||
|
||||
.extra-info-group {
|
||||
transition: max-height, padding, height;
|
||||
transition-timing-function: ease-in-out;
|
||||
transition-duration: 500ms;
|
||||
max-height: calc(var(--____extraInfoGroupHeight) + 1em); // include bottom padding
|
||||
transition-timing-function: ease-in;
|
||||
transition-duration: 700ms;
|
||||
max-height: 70vh;
|
||||
mask:
|
||||
linear-gradient(to top, white, transparent) bottom/100% 2px no-repeat,
|
||||
linear-gradient(to top, white, white);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<div
|
||||
class="UpdateNotificationModal panel"
|
||||
:class="{ '-peek': !showingMore }"
|
||||
:style="dynamicStyles"
|
||||
>
|
||||
<div class="panel-heading">
|
||||
<span class="title">
|
||||
|
|
@ -15,8 +14,12 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="content">
|
||||
<div
|
||||
class="content"
|
||||
:class="{ '-noImage': !showingImage }"
|
||||
>
|
||||
<img
|
||||
v-if="showingImage"
|
||||
class="pleroma-tan"
|
||||
:src="pleromaTanVariant"
|
||||
:style="pleromaTanStyles"
|
||||
|
|
|
|||
|
|
@ -211,7 +211,8 @@
|
|||
"travel-and-places": "Travel & Places"
|
||||
},
|
||||
"load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
|
||||
"load_all": "Loading all {emojiAmount} emoji"
|
||||
"load_all": "Loading all {emojiAmount} emoji",
|
||||
"regional_indicator": "Regional indicator {letter}"
|
||||
},
|
||||
"errors": {
|
||||
"storage_unavailable": "Pleroma could not access browser storage. Your login or your local settings won't be saved and you might encounter unexpected issues. Try enabling cookies."
|
||||
|
|
@ -225,6 +226,7 @@
|
|||
"load_older": "Load older interactions"
|
||||
},
|
||||
"post_status": {
|
||||
"edit_status": "Edit status",
|
||||
"new_status": "Post new status",
|
||||
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
|
||||
"account_not_locked_warning_link": "locked",
|
||||
|
|
@ -240,6 +242,8 @@
|
|||
"default": "Just landed in L.A.",
|
||||
"direct_warning_to_all": "This post will be visible to all the mentioned users.",
|
||||
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
|
||||
"edit_remote_warning": "Other remote instances may not support editing and unable to receive the latest version of your post.",
|
||||
"edit_unsupported_warning": "Pleroma does not support editing mentions or polls.",
|
||||
"posting": "Posting",
|
||||
"post": "Post",
|
||||
"preview": "Preview",
|
||||
|
|
@ -808,6 +812,8 @@
|
|||
"favorites": "Favorites",
|
||||
"repeats": "Repeats",
|
||||
"delete": "Delete status",
|
||||
"edit": "Edit status",
|
||||
"edited_at": "(last edited {time})",
|
||||
"pin": "Pin on profile",
|
||||
"unpin": "Unpin from profile",
|
||||
"pinned": "Pinned",
|
||||
|
|
@ -855,7 +861,8 @@
|
|||
"ancestor_follow_with_icon": "{icon} {text}",
|
||||
"show_all_conversation_with_icon": "{icon} {text}",
|
||||
"show_all_conversation": "Show full conversation ({numStatus} other status) | Show full conversation ({numStatus} other statuses)",
|
||||
"show_only_conversation_under_this": "Only show replies to this status"
|
||||
"show_only_conversation_under_this": "Only show replies to this status",
|
||||
"status_history": "Status history"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "Approve",
|
||||
|
|
@ -883,6 +890,7 @@
|
|||
"muted": "Muted",
|
||||
"per_day": "per day",
|
||||
"remote_follow": "Remote follow",
|
||||
"remove_follower": "Remove follower",
|
||||
"report": "Report",
|
||||
"statuses": "Statuses",
|
||||
"subscribe": "Subscribe",
|
||||
|
|
|
|||
53
src/i18n/languages.js
Normal file
53
src/i18n/languages.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
const languages = [
|
||||
'ar',
|
||||
'ca',
|
||||
'cs',
|
||||
'de',
|
||||
'eo',
|
||||
'en',
|
||||
'es',
|
||||
'et',
|
||||
'eu',
|
||||
'fi',
|
||||
'fr',
|
||||
'ga',
|
||||
'he',
|
||||
'hu',
|
||||
'it',
|
||||
'ja',
|
||||
'ja_easy',
|
||||
'ko',
|
||||
'nb',
|
||||
'nl',
|
||||
'oc',
|
||||
'pl',
|
||||
'pt',
|
||||
'ro',
|
||||
'ru',
|
||||
'sk',
|
||||
'te',
|
||||
'uk',
|
||||
'zh',
|
||||
'zh_Hant'
|
||||
]
|
||||
|
||||
const specialJsonName = {
|
||||
ja: 'ja_pedantic'
|
||||
}
|
||||
|
||||
const langCodeToJsonName = (code) => specialJsonName[code] || code
|
||||
|
||||
const langCodeToCldrName = (code) => code
|
||||
|
||||
const ensureFinalFallback = codes => {
|
||||
const codeList = Array.isArray(codes) ? codes : [codes]
|
||||
return codeList.includes('en') ? codeList : codeList.concat(['en'])
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
languages,
|
||||
langCodeToJsonName,
|
||||
langCodeToCldrName,
|
||||
ensureFinalFallback
|
||||
}
|
||||
|
|
@ -7,46 +7,26 @@
|
|||
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
|
||||
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
|
||||
|
||||
const loaders = {
|
||||
ar: () => import('./ar.json'),
|
||||
ca: () => import('./ca.json'),
|
||||
cs: () => import('./cs.json'),
|
||||
de: () => import('./de.json'),
|
||||
eo: () => import('./eo.json'),
|
||||
es: () => import('./es.json'),
|
||||
et: () => import('./et.json'),
|
||||
eu: () => import('./eu.json'),
|
||||
fi: () => import('./fi.json'),
|
||||
fr: () => import('./fr.json'),
|
||||
ga: () => import('./ga.json'),
|
||||
he: () => import('./he.json'),
|
||||
hu: () => import('./hu.json'),
|
||||
it: () => import('./it.json'),
|
||||
ja: () => import('./ja_pedantic.json'),
|
||||
ja_easy: () => import('./ja_easy.json'),
|
||||
ko: () => import('./ko.json'),
|
||||
nb: () => import('./nb.json'),
|
||||
nl: () => import('./nl.json'),
|
||||
oc: () => import('./oc.json'),
|
||||
pl: () => import('./pl.json'),
|
||||
pt: () => import('./pt.json'),
|
||||
ro: () => import('./ro.json'),
|
||||
ru: () => import('./ru.json'),
|
||||
sk: () => import('./sk.json'),
|
||||
te: () => import('./te.json'),
|
||||
uk: () => import('./uk.json'),
|
||||
zh: () => import('./zh.json'),
|
||||
zh_Hant: () => import('./zh_Hant.json')
|
||||
import { languages, langCodeToJsonName } from './languages.js'
|
||||
|
||||
const hasLanguageFile = (code) => languages.includes(code)
|
||||
|
||||
const loadLanguageFile = (code) => {
|
||||
return import(
|
||||
/* webpackInclude: /\.json$/ */
|
||||
/* webpackChunkName: "i18n/[request]" */
|
||||
`./${langCodeToJsonName(code)}.json`
|
||||
)
|
||||
}
|
||||
|
||||
const messages = {
|
||||
languages: ['en', ...Object.keys(loaders)],
|
||||
languages,
|
||||
default: {
|
||||
en: require('./en.json').default
|
||||
},
|
||||
setLanguage: async (i18n, language) => {
|
||||
if (loaders[language]) {
|
||||
const messages = await loaders[language]()
|
||||
if (hasLanguageFile(language)) {
|
||||
const messages = await loadLanguageFile(language)
|
||||
i18n.setLocaleMessage(language, messages.default)
|
||||
}
|
||||
i18n.locale = language
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import oauthTokensModule from './modules/oauth_tokens.js'
|
|||
import reportsModule from './modules/reports.js'
|
||||
import pollsModule from './modules/polls.js'
|
||||
import postStatusModule from './modules/postStatus.js'
|
||||
import editStatusModule from './modules/editStatus.js'
|
||||
import statusHistoryModule from './modules/statusHistory.js'
|
||||
|
||||
import chatsModule from './modules/chats.js'
|
||||
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
|
@ -86,6 +89,8 @@ const persistedStateOptions = {
|
|||
reports: reportsModule,
|
||||
polls: pollsModule,
|
||||
postStatus: postStatusModule,
|
||||
editStatus: editStatusModule,
|
||||
statusHistory: statusHistoryModule,
|
||||
chats: chatsModule
|
||||
},
|
||||
plugins,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const api = {
|
|||
followRequests: []
|
||||
},
|
||||
getters: {
|
||||
followRequestCount: state => state.api.followRequests.length
|
||||
followRequestCount: state => state.followRequests.length
|
||||
},
|
||||
mutations: {
|
||||
setBackendInteractor (state, backendInteractor) {
|
||||
|
|
@ -103,6 +103,13 @@ const api = {
|
|||
showImmediately: timelineData.visibleStatuses.length === 0,
|
||||
timeline: 'friends'
|
||||
})
|
||||
} else if (message.event === 'status.update') {
|
||||
dispatch('addNewStatuses', {
|
||||
statuses: [message.status],
|
||||
userId: false,
|
||||
showImmediately: message.status.id in timelineData.visibleStatusesObject,
|
||||
timeline: 'friends'
|
||||
})
|
||||
} else if (message.event === 'delete') {
|
||||
dispatch('deleteStatusById', message.id)
|
||||
} else if (message.event === 'pleroma:chat_update') {
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ const config = {
|
|||
break
|
||||
case 'interfaceLanguage':
|
||||
messages.setLanguage(this.getters.i18n, value)
|
||||
dispatch('loadUnicodeEmojiData', value)
|
||||
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
|
||||
break
|
||||
case 'thirdColumnMode':
|
||||
|
|
|
|||
25
src/modules/editStatus.js
Normal file
25
src/modules/editStatus.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const editStatus = {
|
||||
state: {
|
||||
params: null,
|
||||
modalActivated: false
|
||||
},
|
||||
mutations: {
|
||||
openEditStatusModal (state, params) {
|
||||
state.params = params
|
||||
state.modalActivated = true
|
||||
},
|
||||
closeEditStatusModal (state) {
|
||||
state.modalActivated = false
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
openEditStatusModal ({ commit }, params) {
|
||||
commit('openEditStatusModal', params)
|
||||
},
|
||||
closeEditStatusModal ({ commit }) {
|
||||
commit('closeEditStatusModal')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default editStatus
|
||||
|
|
@ -2,6 +2,7 @@ import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
|||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||
import apiService from '../services/api/api.service.js'
|
||||
import { instanceDefaultProperties } from './config.js'
|
||||
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
|
||||
|
||||
const SORTED_EMOJI_GROUP_IDS = [
|
||||
'smileys-and-emotion',
|
||||
|
|
@ -15,6 +16,26 @@ const SORTED_EMOJI_GROUP_IDS = [
|
|||
'flags'
|
||||
]
|
||||
|
||||
const REGIONAL_INDICATORS = (() => {
|
||||
const start = 0x1F1E6
|
||||
const end = 0x1F1FF
|
||||
const A = 'A'.codePointAt(0)
|
||||
const res = new Array(end - start + 1)
|
||||
for (let i = start; i <= end; ++i) {
|
||||
const letter = String.fromCodePoint(A + i - start)
|
||||
res[i - start] = {
|
||||
replacement: String.fromCodePoint(i),
|
||||
imageUrl: false,
|
||||
displayText: 'regional_indicator_' + letter,
|
||||
displayTextI18n: {
|
||||
key: 'emoji.regional_indicator',
|
||||
args: { letter }
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
})()
|
||||
|
||||
const defaultState = {
|
||||
// Stuff from apiConfig
|
||||
name: 'Pleroma FE',
|
||||
|
|
@ -78,6 +99,7 @@ const defaultState = {
|
|||
customEmojiFetched: false,
|
||||
emoji: {},
|
||||
emojiFetched: false,
|
||||
unicodeEmojiAnnotations: {},
|
||||
pleromaBackend: true,
|
||||
postFormats: [],
|
||||
restrictedNicknames: [],
|
||||
|
|
@ -109,6 +131,31 @@ const defaultState = {
|
|||
}
|
||||
}
|
||||
|
||||
const loadAnnotations = (lang) => {
|
||||
return import(
|
||||
/* webpackChunkName: "emoji-annotations/[request]" */
|
||||
`@kazvmoe-infra/unicode-emoji-json/annotations/${langCodeToCldrName(lang)}.json`
|
||||
)
|
||||
.then(k => k.default)
|
||||
}
|
||||
|
||||
const injectAnnotations = (emoji, annotations) => {
|
||||
const availableLangs = Object.keys(annotations)
|
||||
|
||||
return {
|
||||
...emoji,
|
||||
annotations: availableLangs.reduce((acc, cur) => {
|
||||
acc[cur] = annotations[cur][emoji.replacement]
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
|
||||
const injectRegionalIndicators = groups => {
|
||||
groups.symbols.push(...REGIONAL_INDICATORS)
|
||||
return groups
|
||||
}
|
||||
|
||||
const instance = {
|
||||
state: defaultState,
|
||||
mutations: {
|
||||
|
|
@ -119,6 +166,9 @@ const instance = {
|
|||
},
|
||||
setKnownDomains (state, domains) {
|
||||
state.knownDomains = domains
|
||||
},
|
||||
setUnicodeEmojiAnnotations (state, { lang, annotations }) {
|
||||
state.unicodeEmojiAnnotations[lang] = annotations
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
|
@ -153,13 +203,13 @@ const instance = {
|
|||
},
|
||||
standardEmojiList (state) {
|
||||
return SORTED_EMOJI_GROUP_IDS
|
||||
.map(groupId => state.emoji[groupId] || [])
|
||||
.map(groupId => (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations)))
|
||||
.reduce((a, b) => a.concat(b), [])
|
||||
},
|
||||
standardEmojiGroupList (state) {
|
||||
return SORTED_EMOJI_GROUP_IDS.map(groupId => ({
|
||||
id: groupId,
|
||||
emojis: state.emoji[groupId] || []
|
||||
emojis: (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations))
|
||||
}))
|
||||
},
|
||||
instanceDomain (state) {
|
||||
|
|
@ -185,27 +235,36 @@ const instance = {
|
|||
},
|
||||
async getStaticEmoji ({ commit }) {
|
||||
try {
|
||||
const res = await window.fetch('/static/emoji.json')
|
||||
if (res.ok) {
|
||||
const values = await res.json()
|
||||
const emoji = Object.keys(values).reduce((res, groupId) => {
|
||||
res[groupId] = values[groupId].map(e => ({
|
||||
displayText: e.slug,
|
||||
imageUrl: false,
|
||||
replacement: e.emoji
|
||||
}))
|
||||
return res
|
||||
}, {})
|
||||
commit('setInstanceOption', { name: 'emoji', value: emoji })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
const values = (await import(/* webpackChunkName: 'emoji' */ '../../static/emoji.json')).default
|
||||
|
||||
const emoji = Object.keys(values).reduce((res, groupId) => {
|
||||
res[groupId] = values[groupId].map(e => ({
|
||||
displayText: e.slug,
|
||||
imageUrl: false,
|
||||
replacement: e.emoji
|
||||
}))
|
||||
return res
|
||||
}, {})
|
||||
commit('setInstanceOption', { name: 'emoji', value: injectRegionalIndicators(emoji) })
|
||||
} catch (e) {
|
||||
console.warn("Can't load static emoji")
|
||||
console.warn(e)
|
||||
}
|
||||
},
|
||||
|
||||
loadUnicodeEmojiData ({ commit, state }, language) {
|
||||
const langList = ensureFinalFallback(language)
|
||||
|
||||
return Promise.all(
|
||||
langList
|
||||
.map(async lang => {
|
||||
if (!state.unicodeEmojiAnnotations[lang]) {
|
||||
const annotations = await loadAnnotations(lang)
|
||||
commit('setUnicodeEmojiAnnotations', { lang, annotations })
|
||||
}
|
||||
}))
|
||||
},
|
||||
|
||||
async getCustomEmoji ({ commit, state }) {
|
||||
try {
|
||||
const res = await window.fetch('/api/pleroma/emoji.json')
|
||||
|
|
|
|||
25
src/modules/statusHistory.js
Normal file
25
src/modules/statusHistory.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const statusHistory = {
|
||||
state: {
|
||||
params: {},
|
||||
modalActivated: false
|
||||
},
|
||||
mutations: {
|
||||
openStatusHistoryModal (state, params) {
|
||||
state.params = params
|
||||
state.modalActivated = true
|
||||
},
|
||||
closeStatusHistoryModal (state) {
|
||||
state.modalActivated = false
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
openStatusHistoryModal ({ commit }, params) {
|
||||
commit('openStatusHistoryModal', params)
|
||||
},
|
||||
closeStatusHistoryModal ({ commit }) {
|
||||
commit('closeStatusHistoryModal')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default statusHistory
|
||||
|
|
@ -249,6 +249,9 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
status: (status) => {
|
||||
addStatus(status, showImmediately)
|
||||
},
|
||||
edit: (status) => {
|
||||
addStatus(status, showImmediately)
|
||||
},
|
||||
retweet: (status) => {
|
||||
// RetweetedStatuses are never shown immediately
|
||||
const retweetedStatus = addStatus(status.retweeted_status, false, false)
|
||||
|
|
@ -606,6 +609,12 @@ const statuses = {
|
|||
return rootState.api.backendInteractor.fetchStatus({ id })
|
||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
},
|
||||
fetchStatusSource ({ rootState, dispatch }, status) {
|
||||
return apiService.fetchStatusSource({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||
},
|
||||
fetchStatusHistory ({ rootState, dispatch }, status) {
|
||||
return apiService.fetchStatusHistory({ status })
|
||||
},
|
||||
deleteStatus ({ rootState, commit }, status) {
|
||||
commit('setDeleted', { status })
|
||||
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ const unblockUser = (store, id) => {
|
|||
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||
}
|
||||
|
||||
const removeUserFromFollowers = (store, id) => {
|
||||
return store.rootState.api.backendInteractor.removeUserFromFollowers({ id })
|
||||
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||
}
|
||||
|
||||
const muteUser = (store, id) => {
|
||||
const predictedRelationship = store.state.relationships[id] || { id }
|
||||
predictedRelationship.muting = true
|
||||
|
|
@ -321,6 +326,9 @@ const users = {
|
|||
unblockUser (store, id) {
|
||||
return unblockUser(store, id)
|
||||
},
|
||||
removeUserFromFollowers (store, id) {
|
||||
return removeUserFromFollowers(store, id)
|
||||
},
|
||||
blockUsers (store, ids = []) {
|
||||
return Promise.all(ids.map(id => blockUser(store, id)))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { each, map, concat, last, get } from 'lodash'
|
||||
import { parseStatus, parseUser, parseNotification, parseAttachment, parseChat, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
|
||||
import { parseStatus, parseSource, parseUser, parseNotification, parseAttachment, parseChat, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
|
||||
import { RegistrationError, StatusCodeError } from '../errors/errors'
|
||||
|
||||
/* eslint-env browser */
|
||||
|
|
@ -49,6 +49,8 @@ const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
|
|||
const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
|
||||
const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}`
|
||||
const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
|
||||
const MASTODON_STATUS_SOURCE_URL = id => `/api/v1/statuses/${id}/source`
|
||||
const MASTODON_STATUS_HISTORY_URL = id => `/api/v1/statuses/${id}/history`
|
||||
const MASTODON_USER_URL = '/api/v1/accounts'
|
||||
const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup'
|
||||
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
|
||||
|
|
@ -65,6 +67,7 @@ const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
|
|||
const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
|
||||
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
|
||||
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
|
||||
const MASTODON_REMOVE_USER_FROM_FOLLOWERS = id => `/api/v1/accounts/${id}/remove_from_followers`
|
||||
const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe`
|
||||
const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe`
|
||||
const MASTODON_BOOKMARK_STATUS_URL = id => `/api/v1/statuses/${id}/bookmark`
|
||||
|
|
@ -305,6 +308,13 @@ const unblockUser = ({ id, credentials }) => {
|
|||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const removeUserFromFollowers = ({ id, credentials }) => {
|
||||
return fetch(MASTODON_REMOVE_USER_FROM_FOLLOWERS(id), {
|
||||
headers: authHeaders(credentials),
|
||||
method: 'POST'
|
||||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const approveUser = ({ id, credentials }) => {
|
||||
const url = MASTODON_APPROVE_USER_URL(id)
|
||||
return fetch(url, {
|
||||
|
|
@ -522,6 +532,31 @@ const fetchStatus = ({ id, credentials }) => {
|
|||
.then((data) => parseStatus(data))
|
||||
}
|
||||
|
||||
const fetchStatusSource = ({ id, credentials }) => {
|
||||
const url = MASTODON_STATUS_SOURCE_URL(id)
|
||||
return fetch(url, { headers: authHeaders(credentials) })
|
||||
.then((data) => {
|
||||
if (data.ok) {
|
||||
return data
|
||||
}
|
||||
throw new Error('Error fetching source', data)
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.then((data) => parseSource(data))
|
||||
}
|
||||
|
||||
const fetchStatusHistory = ({ status, credentials }) => {
|
||||
const url = MASTODON_STATUS_HISTORY_URL(status.id)
|
||||
return promisedRequest({ url, credentials })
|
||||
.then((data) => {
|
||||
data.reverse()
|
||||
return data.map((item) => {
|
||||
item.originalStatus = status
|
||||
return parseStatus(item)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const tagUser = ({ tag, credentials, user }) => {
|
||||
const screenName = user.screen_name
|
||||
const form = {
|
||||
|
|
@ -825,6 +860,54 @@ const postStatus = ({
|
|||
.then((data) => data.error ? data : parseStatus(data))
|
||||
}
|
||||
|
||||
const editStatus = ({
|
||||
id,
|
||||
credentials,
|
||||
status,
|
||||
spoilerText,
|
||||
sensitive,
|
||||
poll,
|
||||
mediaIds = [],
|
||||
contentType
|
||||
}) => {
|
||||
const form = new FormData()
|
||||
const pollOptions = poll.options || []
|
||||
|
||||
form.append('status', status)
|
||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||
if (sensitive) form.append('sensitive', sensitive)
|
||||
if (contentType) form.append('content_type', contentType)
|
||||
mediaIds.forEach(val => {
|
||||
form.append('media_ids[]', val)
|
||||
})
|
||||
|
||||
if (pollOptions.some(option => option !== '')) {
|
||||
const normalizedPoll = {
|
||||
expires_in: poll.expiresIn,
|
||||
multiple: poll.multiple
|
||||
}
|
||||
Object.keys(normalizedPoll).forEach(key => {
|
||||
form.append(`poll[${key}]`, normalizedPoll[key])
|
||||
})
|
||||
|
||||
pollOptions.forEach(option => {
|
||||
form.append('poll[options][]', option)
|
||||
})
|
||||
}
|
||||
|
||||
const putHeaders = authHeaders(credentials)
|
||||
|
||||
return fetch(MASTODON_STATUS_URL(id), {
|
||||
body: form,
|
||||
method: 'PUT',
|
||||
headers: putHeaders
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((data) => data.error ? data : parseStatus(data))
|
||||
}
|
||||
|
||||
const deleteStatus = ({ id, credentials }) => {
|
||||
return fetch(MASTODON_DELETE_URL(id), {
|
||||
headers: authHeaders(credentials),
|
||||
|
|
@ -1291,7 +1374,8 @@ const MASTODON_STREAMING_EVENTS = new Set([
|
|||
'update',
|
||||
'notification',
|
||||
'delete',
|
||||
'filters_changed'
|
||||
'filters_changed',
|
||||
'status.update'
|
||||
])
|
||||
|
||||
const PLEROMA_STREAMING_EVENTS = new Set([
|
||||
|
|
@ -1363,6 +1447,8 @@ export const handleMastoWS = (wsEvent) => {
|
|||
const data = payload ? JSON.parse(payload) : null
|
||||
if (event === 'update') {
|
||||
return { event, status: parseStatus(data) }
|
||||
} else if (event === 'status.update') {
|
||||
return { event, status: parseStatus(data) }
|
||||
} else if (event === 'notification') {
|
||||
return { event, notification: parseNotification(data) }
|
||||
} else if (event === 'pleroma:chat_update') {
|
||||
|
|
@ -1497,6 +1583,8 @@ const apiService = {
|
|||
fetchPinnedStatuses,
|
||||
fetchConversation,
|
||||
fetchStatus,
|
||||
fetchStatusSource,
|
||||
fetchStatusHistory,
|
||||
fetchFriends,
|
||||
exportFriends,
|
||||
fetchFollowers,
|
||||
|
|
@ -1508,6 +1596,7 @@ const apiService = {
|
|||
unmuteConversation,
|
||||
blockUser,
|
||||
unblockUser,
|
||||
removeUserFromFollowers,
|
||||
fetchUser,
|
||||
fetchUserByName,
|
||||
fetchUserRelationship,
|
||||
|
|
@ -1518,6 +1607,7 @@ const apiService = {
|
|||
bookmarkStatus,
|
||||
unbookmarkStatus,
|
||||
postStatus,
|
||||
editStatus,
|
||||
deleteStatus,
|
||||
uploadMedia,
|
||||
setMediaDescription,
|
||||
|
|
|
|||
|
|
@ -251,6 +251,16 @@ export const parseAttachment = (data) => {
|
|||
return output
|
||||
}
|
||||
|
||||
export const parseSource = (data) => {
|
||||
const output = {}
|
||||
|
||||
output.text = data.text
|
||||
output.spoiler_text = data.spoiler_text
|
||||
output.content_type = data.content_type
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
export const parseStatus = (data) => {
|
||||
const output = {}
|
||||
const masto = Object.prototype.hasOwnProperty.call(data, 'account')
|
||||
|
|
@ -272,6 +282,8 @@ export const parseStatus = (data) => {
|
|||
|
||||
output.tags = data.tags
|
||||
|
||||
output.edited_at = data.edited_at
|
||||
|
||||
if (data.pleroma) {
|
||||
const { pleroma } = data
|
||||
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
|
||||
|
|
@ -373,6 +385,10 @@ export const parseStatus = (data) => {
|
|||
output.favoritedBy = []
|
||||
output.rebloggedBy = []
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'originalStatus')) {
|
||||
Object.assign(output, data.originalStatus)
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,47 @@ const postStatus = ({
|
|||
})
|
||||
}
|
||||
|
||||
const editStatus = ({
|
||||
store,
|
||||
statusId,
|
||||
status,
|
||||
spoilerText,
|
||||
sensitive,
|
||||
poll,
|
||||
media = [],
|
||||
contentType = 'text/plain'
|
||||
}) => {
|
||||
const mediaIds = map(media, 'id')
|
||||
|
||||
return apiService.editStatus({
|
||||
id: statusId,
|
||||
credentials: store.state.users.currentUser.credentials,
|
||||
status,
|
||||
spoilerText,
|
||||
sensitive,
|
||||
poll,
|
||||
mediaIds,
|
||||
contentType
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data.error) {
|
||||
store.dispatch('addNewStatuses', {
|
||||
statuses: [data],
|
||||
timeline: 'friends',
|
||||
showImmediately: true,
|
||||
noIdUpdate: true // To prevent missing notices on next pull.
|
||||
})
|
||||
}
|
||||
return data
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error editing status', err)
|
||||
return {
|
||||
error: err.message
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const uploadMedia = ({ store, formData }) => {
|
||||
const credentials = store.state.users.currentUser.credentials
|
||||
return apiService.uploadMedia({ credentials, formData })
|
||||
|
|
@ -59,6 +100,7 @@ const setMediaDescription = ({ store, id, description }) => {
|
|||
|
||||
const statusPosterService = {
|
||||
postStatus,
|
||||
editStatus,
|
||||
uploadMedia,
|
||||
setMediaDescription
|
||||
}
|
||||
|
|
|
|||
187
yarn.lock
187
yarn.lock
|
|
@ -1433,31 +1433,31 @@
|
|||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@fortawesome/fontawesome-common-types@6.1.2":
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.2.tgz#c1095b1bbabf19f37f9ff0719db38d92a410bcfe"
|
||||
integrity sha512-wBaAPGz1Awxg05e0PBRkDRuTsy4B3dpBm+zreTTyd9TH4uUM27cAL4xWyWR0rLJCrRwzVsQ4hF3FvM6rqydKPA==
|
||||
"@fortawesome/fontawesome-common-types@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f"
|
||||
integrity sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@6.1.2":
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.2.tgz#11e2e8583a7dea75d734e4d0e53d91c63fae7511"
|
||||
integrity sha512-853G/Htp0BOdXnPoeCPTjFrVwyrJHpe8MhjB/DYE9XjwhnNDfuBCd3aKc2YUYbEfHEcBws4UAA0kA9dymZKGjA==
|
||||
"@fortawesome/fontawesome-svg-core@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz#11856eaf4dd1d865c442ddea1eed8ee855186ba2"
|
||||
integrity sha512-Cf2mAAeMWFMzpLC7Y9H1I4o3wEU+XovVJhTiNG8ZNgSQj53yl7OCJaS80K4YjrABWZzbAHVaoHE1dVJ27AAYXw==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.2"
|
||||
"@fortawesome/fontawesome-common-types" "6.2.0"
|
||||
|
||||
"@fortawesome/free-regular-svg-icons@6.1.2":
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.1.2.tgz#9f04009098addcc11d0d185126f058ed042c3099"
|
||||
integrity sha512-xR4hA+tAwsaTHGfb+25H1gVU/aJ0Rzu+xIUfnyrhaL13yNQ7TWiI2RvzniAaB+VGHDU2a+Pk96Ve+pkN3/+TTQ==
|
||||
"@fortawesome/free-regular-svg-icons@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.2.0.tgz#947e1f03be17da3a60bfeb2666b5348b19448ce2"
|
||||
integrity sha512-M1dG+PAmkYMTL9BSUHFXY5oaHwBYfHCPhbJ8qj8JELsc9XCrUJ6eEHWip4q0tE+h9C0DVyFkwIM9t7QYyCpprQ==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.2"
|
||||
"@fortawesome/fontawesome-common-types" "6.2.0"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@6.1.2":
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.1.2.tgz#491d668b8a6603698d0ce1ac620f66fd22b74c84"
|
||||
integrity sha512-lTgZz+cMpzjkHmCwOG3E1ilUZrnINYdqMmrkv30EC3XbRsGlbIOL8H9LaNp5SV4g0pNJDfQ4EdTWWaMvdwyLiQ==
|
||||
"@fortawesome/free-solid-svg-icons@6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.0.tgz#8dcde48109354fd7a5ece8ea48d678bb91d4b5f0"
|
||||
integrity sha512-UjCILHIQ4I8cN46EiQn0CZL/h8AwCGgR//1c4R96Q5viSRwuKVo0NdQEc4bm+69ZwC0dUvjbDqAHF1RR5FA3XA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.1.2"
|
||||
"@fortawesome/fontawesome-common-types" "6.2.0"
|
||||
|
||||
"@fortawesome/vue-fontawesome@3.0.1":
|
||||
version "3.0.1"
|
||||
|
|
@ -1629,6 +1629,11 @@
|
|||
dependencies:
|
||||
pointer-tracker "^2.0.3"
|
||||
|
||||
"@kazvmoe-infra/unicode-emoji-json@^0.4.0":
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@kazvmoe-infra/unicode-emoji-json/-/unicode-emoji-json-0.4.0.tgz#555bab2f8d11db74820ef0a2fbe2805b17c22587"
|
||||
integrity sha512-22OffREdHzD0U6A/W4RaFPV8NR73za6euibtAxNxO/fu5A6TwxRO2lAdbDWKJH9COv/vYs8zqfEiSalXH2nXJA==
|
||||
|
||||
"@nightwatch/chai@5.0.2":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@nightwatch/chai/-/chai-5.0.2.tgz#86b20908fc090dffd5c9567c0392bc6a494cc2e6"
|
||||
|
|
@ -1882,47 +1887,47 @@
|
|||
html-tags "^3.1.0"
|
||||
svg-tags "^1.0.0"
|
||||
|
||||
"@vue/compiler-core@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a"
|
||||
integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==
|
||||
"@vue/compiler-core@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.38.tgz#0a2a7bffd2280ac19a96baf5301838a2cf1964d7"
|
||||
integrity sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/shared" "3.2.38"
|
||||
estree-walker "^2.0.2"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-dom@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5"
|
||||
integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==
|
||||
"@vue/compiler-dom@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz#53d04ed0c0c62d1ef259bf82f9b28100a880b6fd"
|
||||
integrity sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==
|
||||
dependencies:
|
||||
"@vue/compiler-core" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/compiler-core" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
"@vue/compiler-sfc@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4"
|
||||
integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==
|
||||
"@vue/compiler-sfc@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz#9e763019471a535eb1fceeaac9d4d18a83f0940f"
|
||||
integrity sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.37"
|
||||
"@vue/compiler-dom" "3.2.37"
|
||||
"@vue/compiler-ssr" "3.2.37"
|
||||
"@vue/reactivity-transform" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/compiler-core" "3.2.38"
|
||||
"@vue/compiler-dom" "3.2.38"
|
||||
"@vue/compiler-ssr" "3.2.38"
|
||||
"@vue/reactivity-transform" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
postcss "^8.1.10"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-ssr@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff"
|
||||
integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==
|
||||
"@vue/compiler-ssr@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz#933b23bf99e667e5078eefc6ba94cb95fd765dfe"
|
||||
integrity sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/compiler-dom" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
"@vue/devtools-api@^6.0.0-beta.11":
|
||||
version "6.1.3"
|
||||
|
|
@ -1934,53 +1939,53 @@
|
|||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.2.1.tgz#6f2948ff002ec46df01420dfeff91de16c5b4092"
|
||||
integrity sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==
|
||||
|
||||
"@vue/reactivity-transform@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca"
|
||||
integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==
|
||||
"@vue/reactivity-transform@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz#a856c217b2ead99eefb6fddb1d61119b2cb67984"
|
||||
integrity sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/compiler-core" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848"
|
||||
integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==
|
||||
"@vue/reactivity@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.38.tgz#d576fdcea98eefb96a1f1ad456e289263e87292e"
|
||||
integrity sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
"@vue/runtime-core@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3"
|
||||
integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==
|
||||
"@vue/runtime-core@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz#d19cf591c210713f80e6a94ffbfef307c27aea06"
|
||||
integrity sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/reactivity" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
"@vue/runtime-dom@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd"
|
||||
integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==
|
||||
"@vue/runtime-dom@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz#fec711f65c2485991289fd4798780aa506469b48"
|
||||
integrity sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==
|
||||
dependencies:
|
||||
"@vue/runtime-core" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/runtime-core" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
csstype "^2.6.8"
|
||||
|
||||
"@vue/server-renderer@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc"
|
||||
integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==
|
||||
"@vue/server-renderer@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.38.tgz#01a4c0f218e90b8ad1815074208a1974ded109aa"
|
||||
integrity sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==
|
||||
dependencies:
|
||||
"@vue/compiler-ssr" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/compiler-ssr" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
"@vue/shared@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702"
|
||||
integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==
|
||||
"@vue/shared@3.2.38":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.38.tgz#e823f0cb2e85b6bf43430c0d6811b1441c300f3c"
|
||||
integrity sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==
|
||||
|
||||
"@vue/test-utils@2.0.2":
|
||||
version "2.0.2"
|
||||
|
|
@ -7387,10 +7392,10 @@ sass-loader@13.0.2:
|
|||
klona "^2.0.4"
|
||||
neo-async "^2.6.2"
|
||||
|
||||
sass@1.54.5:
|
||||
version "1.54.5"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.5.tgz#93708f5560784f6ff2eab8542ade021a4a947b3a"
|
||||
integrity sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==
|
||||
sass@1.54.8:
|
||||
version "1.54.8"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.8.tgz#4adef0dd86ea2b1e4074f551eeda4fc5f812a996"
|
||||
integrity sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
|
|
@ -8350,16 +8355,16 @@ vue-template-compiler@2.7.10:
|
|||
de-indent "^1.0.2"
|
||||
he "^1.2.0"
|
||||
|
||||
vue@3.2.37:
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e"
|
||||
integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==
|
||||
vue@3.2.38:
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.38.tgz#cda3a414631745b194971219318a792dbbccdec0"
|
||||
integrity sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.37"
|
||||
"@vue/compiler-sfc" "3.2.37"
|
||||
"@vue/runtime-dom" "3.2.37"
|
||||
"@vue/server-renderer" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
"@vue/compiler-dom" "3.2.38"
|
||||
"@vue/compiler-sfc" "3.2.38"
|
||||
"@vue/runtime-dom" "3.2.38"
|
||||
"@vue/server-renderer" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
vuex@4.0.2:
|
||||
version "4.0.2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue