Merge remote-tracking branch 'origin/develop' into scrolltotop
* origin/develop: (89 commits) Update dependency @vuelidate/validators to v2.0.0 Remove lolex package Remove diff package Pin dependencies Update dependency sass to v1.55.0 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 ...
This commit is contained in:
commit
5fa533fbb7
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
|
||||
}
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ test/e2e/reports
|
||||
selenium-debug.log
|
||||
.idea/
|
||||
config/local.json
|
||||
static/emoji.json
|
||||
|
@ -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
|
||||
|
@ -18,6 +18,9 @@ console.log(
|
||||
var spinner = ora('building for production...')
|
||||
spinner.start()
|
||||
|
||||
var updateEmoji = require('./update-emoji').updateEmoji
|
||||
updateEmoji()
|
||||
|
||||
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
|
||||
rm('-rf', assetsPath)
|
||||
mkdir('-p', assetsPath)
|
||||
|
@ -10,6 +10,9 @@ var webpackConfig = process.env.NODE_ENV === 'testing'
|
||||
? require('./webpack.prod.conf')
|
||||
: require('./webpack.dev.conf')
|
||||
|
||||
var updateEmoji = require('./update-emoji').updateEmoji
|
||||
updateEmoji()
|
||||
|
||||
// default port where dev server listens for incoming traffic
|
||||
var port = process.env.PORT || config.dev.port
|
||||
// Define HTTP proxies to your custom API backend
|
||||
|
27
build/update-emoji.js
Normal file
27
build/update-emoji.js
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
module.exports = {
|
||||
updateEmoji () {
|
||||
const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
|
||||
const fs = require('fs')
|
||||
|
||||
Object.keys(emojis)
|
||||
.map(k => {
|
||||
emojis[k].map(e => {
|
||||
delete e.unicode_version
|
||||
delete e.emoji_version
|
||||
delete e.skin_tone_support_unicode_version
|
||||
})
|
||||
})
|
||||
|
||||
const res = {}
|
||||
Object.keys(emojis)
|
||||
.map(k => {
|
||||
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
|
||||
res[groupId] = emojis[k]
|
||||
})
|
||||
|
||||
console.info('Updating emojis...')
|
||||
fs.writeFileSync('static/emoji.json', JSON.stringify(res))
|
||||
console.info('Done.')
|
||||
}
|
||||
}
|
@ -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: {
|
||||
|
20
package.json
20
package.json
@ -18,22 +18,23 @@
|
||||
"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",
|
||||
"@vuelidate/validators": "2.0.0",
|
||||
"body-scroll-lock": "3.1.5",
|
||||
"chromatism": "3.0.0",
|
||||
"click-outside-vue3": "4.0.1",
|
||||
"cropperjs": "1.5.12",
|
||||
"diff": "3.5.0",
|
||||
"escape-html": "1.0.3",
|
||||
"js-cookie": "3.0.1",
|
||||
"localforage": "1.10.0",
|
||||
"lozad": "1.16.0",
|
||||
"parse-link-header": "2.0.0",
|
||||
"phoenix": "1.6.2",
|
||||
"punycode.js": "2.1.0",
|
||||
@ -41,7 +42,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",
|
||||
@ -57,7 +58,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",
|
||||
@ -96,7 +97,6 @@
|
||||
"karma-spec-reporter": "0.0.34",
|
||||
"karma-webpack": "5.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"lolex": "1.6.0",
|
||||
"mini-css-extract-plugin": "2.6.1",
|
||||
"mocha": "10.0.0",
|
||||
"nightwatch": "2.3.3",
|
||||
@ -104,13 +104,13 @@
|
||||
"ora": "0.4.1",
|
||||
"postcss": "8.4.16",
|
||||
"postcss-loader": "7.0.1",
|
||||
"sass": "1.54.5",
|
||||
"sass": "1.55.0",
|
||||
"sass-loader": "13.0.2",
|
||||
"selenium-server": "2.53.1",
|
||||
"semver": "7.3.7",
|
||||
"serviceworker-webpack5-plugin": "2.0.0",
|
||||
"shelljs": "0.8.5",
|
||||
"sinon": "2.4.1",
|
||||
"sinon": "14.0.0",
|
||||
"sinon-chai": "3.7.0",
|
||||
"stylelint": "13.13.1",
|
||||
"stylelint-config-standard": "20.0.0",
|
||||
|
@ -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
|
||||
@ -207,7 +252,6 @@ const EmojiInput = {
|
||||
},
|
||||
triggerShowPicker () {
|
||||
this.showPicker = true
|
||||
this.$refs.picker.startEmojiLoad()
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView()
|
||||
this.focusPickerInput()
|
||||
|
@ -19,6 +19,7 @@
|
||||
v-if="enableEmojiPicker"
|
||||
ref="picker"
|
||||
:class="{ hide: !showPicker }"
|
||||
:showing="showPicker"
|
||||
:enable-sticker-picker="enableStickerPicker"
|
||||
class="emoji-picker-panel"
|
||||
@emoji="insert"
|
||||
@ -63,7 +64,7 @@
|
||||
v-if="!suggestion.user"
|
||||
class="displayText"
|
||||
>
|
||||
{{ suggestion.displayText }}
|
||||
{{ maybeLocalizedEmojiName(suggestion) }}
|
||||
</span>
|
||||
<span class="detailText">{{ suggestion.detailText }}</span>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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)
|
||||
* (getters.standardEmojiList + state.instance.customEmoji)
|
||||
* data.users - optional, an array of all known users
|
||||
* updateUsersList - optional, a function to search and append to users
|
||||
*
|
||||
@ -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,33 +1,76 @@
|
||||
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 {
|
||||
faBoxOpen,
|
||||
faStickyNote,
|
||||
faSmileBeam
|
||||
faSmileBeam,
|
||||
faSmile,
|
||||
faUser,
|
||||
faPaw,
|
||||
faIceCream,
|
||||
faBus,
|
||||
faBasketballBall,
|
||||
faLightbulb,
|
||||
faCode,
|
||||
faFlag
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { trim } from 'lodash'
|
||||
import { debounce, trim } from 'lodash'
|
||||
|
||||
library.add(
|
||||
faBoxOpen,
|
||||
faStickyNote,
|
||||
faSmileBeam
|
||||
faSmileBeam,
|
||||
faSmile,
|
||||
faUser,
|
||||
faPaw,
|
||||
faIceCream,
|
||||
faBus,
|
||||
faBasketballBall,
|
||||
faLightbulb,
|
||||
faCode,
|
||||
faFlag
|
||||
)
|
||||
|
||||
// At widest, approximately 20 emoji are visible in a row,
|
||||
// loading 3 rows, could be overkill for narrow picker
|
||||
const LOAD_EMOJI_BY = 60
|
||||
const UNICODE_EMOJI_GROUP_ICON = {
|
||||
'smileys-and-emotion': 'smile',
|
||||
'people-and-body': 'user',
|
||||
'animals-and-nature': 'paw',
|
||||
'food-and-drink': 'ice-cream',
|
||||
'travel-and-places': 'bus',
|
||||
activities: 'basketball-ball',
|
||||
objects: 'lightbulb',
|
||||
symbols: 'code',
|
||||
flags: 'flag'
|
||||
}
|
||||
|
||||
// When to start loading new batch emoji, in pixels
|
||||
const LOAD_EMOJI_MARGIN = 64
|
||||
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 = '') => {
|
||||
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] = []
|
||||
@ -44,6 +87,10 @@ const EmojiPicker = {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showing: {
|
||||
required: true,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@ -53,16 +100,26 @@ const EmojiPicker = {
|
||||
showingStickers: false,
|
||||
groupsScrolledClass: 'scrolled-top',
|
||||
keepOpen: false,
|
||||
customEmojiBufferSlice: LOAD_EMOJI_BY,
|
||||
customEmojiTimeout: null,
|
||||
customEmojiLoadAllConfirmed: false
|
||||
// Lazy-load only after the first time `showing` becomes true.
|
||||
contentLoaded: false,
|
||||
groupRefs: {},
|
||||
emojiRefs: {},
|
||||
filteredEmojiGroups: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
|
||||
Checkbox
|
||||
Checkbox,
|
||||
StillImage
|
||||
},
|
||||
methods: {
|
||||
setGroupRef (name) {
|
||||
return el => { this.groupRefs[name] = el }
|
||||
},
|
||||
setEmojiRef (name) {
|
||||
return el => { this.emojiRefs[name] = el }
|
||||
},
|
||||
onStickerUploaded (e) {
|
||||
this.$emit('sticker-uploaded', e)
|
||||
},
|
||||
@ -77,10 +134,38 @@ const EmojiPicker = {
|
||||
const target = (e && e.target) || this.$refs['emoji-groups']
|
||||
this.updateScrolledClass(target)
|
||||
this.scrolledGroup(target)
|
||||
this.triggerLoadMore(target)
|
||||
},
|
||||
scrolledGroup (target) {
|
||||
const top = target.scrollTop + 5
|
||||
this.$nextTick(() => {
|
||||
this.allEmojiGroups.forEach(group => {
|
||||
const ref = this.groupRefs['group-' + group.id]
|
||||
if (ref && ref.offsetTop <= top) {
|
||||
this.activeGroup = group.id
|
||||
}
|
||||
})
|
||||
this.scrollHeader()
|
||||
})
|
||||
},
|
||||
scrollHeader () {
|
||||
// Scroll the active tab's header into view
|
||||
const headerRef = this.groupRefs['group-header-' + this.activeGroup]
|
||||
const left = headerRef.offsetLeft
|
||||
const right = left + headerRef.offsetWidth
|
||||
const headerCont = this.$refs.header
|
||||
const currentScroll = headerCont.scrollLeft
|
||||
const currentScrollRight = currentScroll + headerCont.clientWidth
|
||||
const setScroll = s => { headerCont.scrollLeft = s }
|
||||
|
||||
const margin = 7 // .emoji-tabs-item: padding
|
||||
if (left - margin < currentScroll) {
|
||||
setScroll(left - margin)
|
||||
} else if (right + margin > currentScrollRight) {
|
||||
setScroll(right + margin - headerCont.clientWidth)
|
||||
}
|
||||
},
|
||||
highlight (key) {
|
||||
const ref = this.$refs['group-' + key]
|
||||
const ref = this.groupRefs['group-' + key]
|
||||
const top = ref.offsetTop
|
||||
this.setShowStickers(false)
|
||||
this.activeGroup = key
|
||||
@ -97,73 +182,90 @@ const EmojiPicker = {
|
||||
this.groupsScrolledClass = 'scrolled-middle'
|
||||
}
|
||||
},
|
||||
triggerLoadMore (target) {
|
||||
const ref = this.$refs['group-end-custom']
|
||||
if (!ref) return
|
||||
const bottom = ref.offsetTop + ref.offsetHeight
|
||||
|
||||
const scrollerBottom = target.scrollTop + target.clientHeight
|
||||
const scrollerTop = target.scrollTop
|
||||
const scrollerMax = target.scrollHeight
|
||||
|
||||
// Loads more emoji when they come into view
|
||||
const approachingBottom = bottom - scrollerBottom < LOAD_EMOJI_MARGIN
|
||||
// Always load when at the very top in case there's no scroll space yet
|
||||
const atTop = scrollerTop < 5
|
||||
// Don't load when looking at unicode category or at the very bottom
|
||||
const bottomAboveViewport = bottom < scrollerTop || scrollerBottom === scrollerMax
|
||||
if (!bottomAboveViewport && (approachingBottom || atTop)) {
|
||||
this.loadEmoji()
|
||||
}
|
||||
},
|
||||
scrolledGroup (target) {
|
||||
const top = target.scrollTop + 5
|
||||
this.$nextTick(() => {
|
||||
this.emojisView.forEach(group => {
|
||||
const ref = this.$refs['group-' + group.id]
|
||||
if (ref.offsetTop <= top) {
|
||||
this.activeGroup = group.id
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
loadEmoji () {
|
||||
const allLoaded = this.customEmojiBuffer.length === this.filteredEmoji.length
|
||||
|
||||
if (allLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
this.customEmojiBufferSlice += LOAD_EMOJI_BY
|
||||
},
|
||||
startEmojiLoad (forceUpdate = false) {
|
||||
if (!forceUpdate) {
|
||||
this.keyword = ''
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs['emoji-groups'].scrollTop = 0
|
||||
})
|
||||
const bufferSize = this.customEmojiBuffer.length
|
||||
const bufferPrefilledAll = bufferSize === this.filteredEmoji.length
|
||||
if (bufferPrefilledAll && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
this.customEmojiBufferSlice = LOAD_EMOJI_BY
|
||||
},
|
||||
toggleStickers () {
|
||||
this.showingStickers = !this.showingStickers
|
||||
},
|
||||
setShowStickers (value) {
|
||||
this.showingStickers = value
|
||||
},
|
||||
filterByKeyword (list, keyword) {
|
||||
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
|
||||
},
|
||||
initializeLazyLoad () {
|
||||
this.destroyLazyLoad()
|
||||
this.$nextTick(() => {
|
||||
this.$lozad = lozad('.still-image.emoji-picker-emoji', {
|
||||
load: el => {
|
||||
const name = el.getAttribute('data-emoji-name')
|
||||
const vn = this.emojiRefs[name]
|
||||
if (!vn) {
|
||||
return
|
||||
}
|
||||
|
||||
vn.loadLazy()
|
||||
}
|
||||
})
|
||||
this.$lozad.observe()
|
||||
})
|
||||
},
|
||||
waitForDomAndInitializeLazyLoad () {
|
||||
this.$nextTick(() => this.initializeLazyLoad())
|
||||
},
|
||||
destroyLazyLoad () {
|
||||
if (this.$lozad) {
|
||||
if (this.$lozad.observer) {
|
||||
this.$lozad.observer.disconnect()
|
||||
}
|
||||
if (this.$lozad.mutationObserver) {
|
||||
this.$lozad.mutationObserver.disconnect()
|
||||
}
|
||||
}
|
||||
},
|
||||
onShowing () {
|
||||
const oldContentLoaded = this.contentLoaded
|
||||
this.contentLoaded = true
|
||||
this.waitForDomAndInitializeLazyLoad()
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
if (!oldContentLoaded) {
|
||||
this.$nextTick(() => {
|
||||
if (this.defaultGroup) {
|
||||
this.highlight(this.defaultGroup)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
getFilteredEmojiGroups () {
|
||||
return this.allEmojiGroups
|
||||
.map(group => ({
|
||||
...group,
|
||||
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
|
||||
}))
|
||||
.filter(group => group.emojis.length > 0)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyword () {
|
||||
this.customEmojiLoadAllConfirmed = false
|
||||
this.onScroll()
|
||||
this.startEmojiLoad(true)
|
||||
this.debouncedHandleKeywordChange()
|
||||
},
|
||||
allCustomGroups () {
|
||||
this.waitForDomAndInitializeLazyLoad()
|
||||
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
|
||||
},
|
||||
showing (val) {
|
||||
if (val) {
|
||||
this.onShowing()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.showing) {
|
||||
this.onShowing()
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
this.destroyLazyLoad()
|
||||
},
|
||||
computed: {
|
||||
activeGroupView () {
|
||||
return this.showingStickers ? '' : this.activeGroup
|
||||
@ -174,39 +276,55 @@ const EmojiPicker = {
|
||||
}
|
||||
return 0
|
||||
},
|
||||
filteredEmoji () {
|
||||
return filterByKeyword(
|
||||
this.$store.state.instance.customEmoji || [],
|
||||
trim(this.keyword)
|
||||
)
|
||||
allCustomGroups () {
|
||||
return this.$store.getters.groupedCustomEmojis
|
||||
},
|
||||
customEmojiBuffer () {
|
||||
return this.filteredEmoji.slice(0, this.customEmojiBufferSlice)
|
||||
defaultGroup () {
|
||||
return Object.keys(this.allCustomGroups)[0]
|
||||
},
|
||||
emojis () {
|
||||
const standardEmojis = this.$store.state.instance.emoji || []
|
||||
const customEmojis = this.customEmojiBuffer
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'custom',
|
||||
text: this.$t('emoji.custom'),
|
||||
icon: 'smile-beam',
|
||||
emojis: customEmojis
|
||||
},
|
||||
{
|
||||
id: 'standard',
|
||||
text: this.$t('emoji.unicode'),
|
||||
icon: 'box-open',
|
||||
emojis: filterByKeyword(standardEmojis, trim(this.keyword))
|
||||
}
|
||||
]
|
||||
unicodeEmojiGroups () {
|
||||
return this.$store.getters.standardEmojiGroupList.map(group => ({
|
||||
id: `standard-${group.id}`,
|
||||
text: this.$t(`emoji.unicode_groups.${group.id}`),
|
||||
icon: UNICODE_EMOJI_GROUP_ICON[group.id],
|
||||
emojis: group.emojis
|
||||
}))
|
||||
},
|
||||
emojisView () {
|
||||
return this.emojis.filter(value => value.emojis.length > 0)
|
||||
allEmojiGroups () {
|
||||
return Object.entries(this.allCustomGroups)
|
||||
.map(([_, v]) => v)
|
||||
.concat(this.unicodeEmojiGroups)
|
||||
},
|
||||
stickerPickerEnabled () {
|
||||
return (this.$store.state.instance.stickers || []).length !== 0
|
||||
},
|
||||
debouncedHandleKeywordChange () {
|
||||
return debounce(() => {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
@import '../../_variables.scss';
|
||||
|
||||
$emoji-picker-header-height: 36px;
|
||||
$emoji-picker-header-picture-width: 32px;
|
||||
$emoji-picker-header-picture-height: 32px;
|
||||
$emoji-picker-emoji-size: 32px;
|
||||
|
||||
.emoji-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -19,6 +24,23 @@
|
||||
--lightText: var(--popoverLightText, $fallback--lightText);
|
||||
--icon: var(--popoverIcon, $fallback--icon);
|
||||
|
||||
&-header-image {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: $emoji-picker-header-picture-width;
|
||||
max-width: $emoji-picker-header-picture-width;
|
||||
height: $emoji-picker-header-picture-height;
|
||||
max-height: $emoji-picker-header-picture-height;
|
||||
.still-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.keep-open,
|
||||
.too-many-emoji {
|
||||
padding: 7px;
|
||||
@ -37,7 +59,6 @@
|
||||
|
||||
.heading {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
padding: 10px 7px 5px;
|
||||
}
|
||||
|
||||
@ -50,6 +71,10 @@
|
||||
|
||||
.emoji-tabs {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.emoji-groups {
|
||||
@ -57,6 +82,8 @@
|
||||
}
|
||||
|
||||
.additional-tabs {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
border-left: 1px solid;
|
||||
border-left-color: $fallback--icon;
|
||||
border-left-color: var(--icon, $fallback--icon);
|
||||
@ -66,15 +93,20 @@
|
||||
|
||||
.additional-tabs,
|
||||
.emoji-tabs {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
flex-basis: auto;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
&-item {
|
||||
padding: 0 7px;
|
||||
cursor: pointer;
|
||||
font-size: 1.85em;
|
||||
width: $emoji-picker-header-picture-width;
|
||||
max-width: $emoji-picker-header-picture-width;
|
||||
height: $emoji-picker-header-picture-height;
|
||||
max-height: $emoji-picker-header-picture-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
@ -164,22 +196,26 @@
|
||||
}
|
||||
|
||||
&-item {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: $emoji-picker-emoji-size;
|
||||
height: $emoji-picker-emoji-size;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
font-size: 32px;
|
||||
line-height: $emoji-picker-emoji-size;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
.emoji-picker-emoji.-custom {
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
.emoji-picker-emoji.-unicode {
|
||||
font-size: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,34 @@
|
||||
<template>
|
||||
<div class="emoji-picker panel panel-default panel-body">
|
||||
<div
|
||||
class="emoji-picker panel panel-default panel-body"
|
||||
>
|
||||
<div class="heading">
|
||||
<span class="emoji-tabs">
|
||||
<span
|
||||
ref="header"
|
||||
class="emoji-tabs"
|
||||
>
|
||||
<span
|
||||
v-for="group in emojis"
|
||||
v-for="group in filteredEmojiGroups"
|
||||
:ref="setGroupRef('group-header-' + group.id)"
|
||||
:key="group.id"
|
||||
class="emoji-tabs-item"
|
||||
:class="{
|
||||
active: activeGroupView === group.id,
|
||||
disabled: group.emojis.length === 0
|
||||
active: activeGroupView === group.id
|
||||
}"
|
||||
:title="group.text"
|
||||
@click.prevent="highlight(group.id)"
|
||||
>
|
||||
<span
|
||||
v-if="group.image"
|
||||
class="emoji-picker-header-image"
|
||||
>
|
||||
<still-image
|
||||
:alt="group.text"
|
||||
:src="group.image"
|
||||
/>
|
||||
</span>
|
||||
<FAIcon
|
||||
v-else
|
||||
:icon="group.icon"
|
||||
fixed-width
|
||||
/>
|
||||
@ -36,7 +51,10 @@
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div
|
||||
v-if="contentLoaded"
|
||||
class="content"
|
||||
>
|
||||
<div
|
||||
class="emoji-content"
|
||||
:class="{hidden: showingStickers}"
|
||||
@ -57,12 +75,12 @@
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
v-for="group in emojisView"
|
||||
v-for="group in filteredEmojiGroups"
|
||||
:key="group.id"
|
||||
class="emoji-group"
|
||||
>
|
||||
<h6
|
||||
:ref="'group-' + group.id"
|
||||
:ref="setGroupRef('group-' + group.id)"
|
||||
class="emoji-group-title"
|
||||
>
|
||||
{{ group.text }}
|
||||
@ -70,17 +88,23 @@
|
||||
<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)"
|
||||
>
|
||||
<span v-if="!emoji.imageUrl">{{ emoji.replacement }}</span>
|
||||
<img
|
||||
<span
|
||||
v-if="!emoji.imageUrl"
|
||||
class="emoji-picker-emoji -unicode"
|
||||
>{{ emoji.replacement }}</span>
|
||||
<still-image
|
||||
v-else
|
||||
:src="emoji.imageUrl"
|
||||
>
|
||||
:ref="setEmojiRef(group.id + emoji.displayText)"
|
||||
class="emoji-picker-emoji -custom"
|
||||
:data-src="emoji.imageUrl"
|
||||
:data-emoji-name="group.id + emoji.displayText"
|
||||
/>
|
||||
</span>
|
||||
<span :ref="'group-end-' + group.id" />
|
||||
<span :ref="setGroupRef('group-end-' + group.id)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="keep-open">
|
||||
|
@ -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;
|
||||
|
@ -121,7 +121,6 @@
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> li {
|
||||
@ -150,12 +149,6 @@
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
.timelines-chevron {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.timelines-background {
|
||||
padding: 0 0 0 0.6em;
|
||||
background-color: $fallback--lightBg;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { mapState } from 'vuex'
|
||||
import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
|
||||
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@ -7,6 +8,9 @@ library.add(faThumbtack)
|
||||
|
||||
const NavigationEntry = {
|
||||
props: ['item', 'showPin'],
|
||||
components: {
|
||||
OptionalRouterLink
|
||||
},
|
||||
methods: {
|
||||
isPinned (value) {
|
||||
return this.pinnedItems.has(value)
|
||||
|
@ -1,26 +1,37 @@
|
||||
<template>
|
||||
<li class="NavigationEntry">
|
||||
<component
|
||||
:is="routeTo ? 'router-link' : 'button'"
|
||||
class="menu-item button-unstyled"
|
||||
:to="routeTo"
|
||||
<OptionalRouterLink
|
||||
v-slot="{ isActive, href, navigate } = {}"
|
||||
ass="ass"
|
||||
:to="routeTo"
|
||||
>
|
||||
<li
|
||||
class="NavigationEntry menu-item"
|
||||
:class="{ '-active': isActive }"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<span>
|
||||
<FAIcon
|
||||
v-if="item.icon"
|
||||
fixed-width
|
||||
class="fa-scale-110 menu-icon"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="item.iconLetter"
|
||||
class="icon iconLetter fa-scale-110 menu-icon"
|
||||
>{{ item.iconLetter }}
|
||||
</span>
|
||||
<span class="label">
|
||||
{{ item.labelRaw || $t(item.label) }}
|
||||
</span>
|
||||
<component
|
||||
:is="routeTo ? 'a' : 'button'"
|
||||
class="main-link button-unstyled"
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
>
|
||||
<span>
|
||||
<FAIcon
|
||||
v-if="item.icon"
|
||||
fixed-width
|
||||
class="fa-scale-110 menu-icon"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="item.iconLetter"
|
||||
class="icon iconLetter fa-scale-110 menu-icon"
|
||||
>{{ item.iconLetter }}
|
||||
</span>
|
||||
<span class="label">
|
||||
{{ item.labelRaw || $t(item.label) }}
|
||||
</span>
|
||||
</component>
|
||||
<slot />
|
||||
<div
|
||||
v-if="item.badgeGetter && getters[item.badgeGetter]"
|
||||
@ -45,8 +56,8 @@
|
||||
icon="thumbtack"
|
||||
/>
|
||||
</button>
|
||||
</component>
|
||||
</li>
|
||||
</li>
|
||||
</OptionalRouterLink>
|
||||
</template>
|
||||
|
||||
<script src="./navigation_entry.js"></script>
|
||||
@ -55,7 +66,21 @@
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.NavigationEntry {
|
||||
.label {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: baseline;
|
||||
height: 3.5em;
|
||||
line-height: 3.5em;
|
||||
padding: 0 1em;
|
||||
width: 100%;
|
||||
color: $fallback--link;
|
||||
color: var(--link, $fallback--link);
|
||||
|
||||
.timelines-chevron {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.main-link {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@ -72,48 +97,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
align-items: baseline;
|
||||
height: 3.5em;
|
||||
line-height: 3.5em;
|
||||
padding: 0 1em;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--link;
|
||||
color: var(--link, $fallback--link);
|
||||
color: var(--selectedMenuText, $fallback--link);
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--link;
|
||||
color: var(--selectedMenuText, $fallback--link);
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
.menu-icon {
|
||||
--icon: var(--text, $fallback--icon);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
--icon: var(--text, $fallback--icon);
|
||||
}
|
||||
&.-active {
|
||||
font-weight: bolder;
|
||||
background-color: $fallback--lightBg;
|
||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--text;
|
||||
color: var(--selectedMenuText, $fallback--text);
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
|
||||
.menu-icon {
|
||||
--icon: var(--text, $fallback--icon);
|
||||
}
|
||||
|
||||
&.router-link-active {
|
||||
font-weight: bolder;
|
||||
background-color: $fallback--lightBg;
|
||||
background-color: var(--selectedMenu, $fallback--lightBg);
|
||||
color: $fallback--text;
|
||||
color: var(--selectedMenuText, $fallback--text);
|
||||
--faint: var(--selectedMenuFaintText, $fallback--faint);
|
||||
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuLightText, $fallback--lightText);
|
||||
|
||||
.menu-icon {
|
||||
--icon: var(--text, $fallback--icon);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
src/components/optional_router_link/optional_router_link.vue
Normal file
23
src/components/optional_router_link/optional_router_link.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<!-- eslint-disable vue/no-multiple-template-root -->
|
||||
<router-link
|
||||
v-if="to"
|
||||
v-slot="props"
|
||||
:to="to"
|
||||
custom
|
||||
>
|
||||
<slot
|
||||
v-bind="props"
|
||||
/>
|
||||
</router-link>
|
||||
<slot
|
||||
v-else
|
||||
v-bind="{}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['to']
|
||||
}
|
||||
</script>
|
@ -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',
|
||||
@ -164,7 +189,7 @@ const PostStatusForm = {
|
||||
emojiUserSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
...this.$store.state.instance.emoji,
|
||||
...this.$store.getters.standardEmojiList,
|
||||
...this.$store.state.instance.customEmoji
|
||||
],
|
||||
store: this.$store
|
||||
@ -173,13 +198,13 @@ const PostStatusForm = {
|
||||
emojiSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
...this.$store.state.instance.emoji,
|
||||
...this.$store.getters.standardEmojiList,
|
||||
...this.$store.state.instance.customEmoji
|
||||
]
|
||||
})
|
||||
},
|
||||
emoji () {
|
||||
return this.$store.state.instance.emoji || []
|
||||
return this.$store.getters.standardEmojiList || []
|
||||
},
|
||||
customEmoji () {
|
||||
return this.$store.state.instance.customEmoji || []
|
||||
@ -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;
|
||||
|
@ -59,7 +59,7 @@ const ReactButton = {
|
||||
if (this.filterWord !== '') {
|
||||
const filterWordLowercase = trim(this.filterWord.toLowerCase())
|
||||
const orderedEmojiList = []
|
||||
for (const emoji of this.$store.state.instance.emoji) {
|
||||
for (const emoji of this.$store.getters.standardEmojiList) {
|
||||
if (emoji.replacement === this.filterWord) return [emoji]
|
||||
|
||||
const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
|
||||
@ -72,7 +72,7 @@ const ReactButton = {
|
||||
}
|
||||
return orderedEmojiList.flat()
|
||||
}
|
||||
return this.$store.state.instance.emoji || []
|
||||
return this.$store.getters.standardEmojiList || []
|
||||
},
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
|
@ -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>
|
@ -64,7 +64,7 @@ const ProfileTab = {
|
||||
emojiUserSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
...this.$store.state.instance.emoji,
|
||||
...this.$store.getters.standardEmojiList,
|
||||
...this.$store.state.instance.customEmoji
|
||||
],
|
||||
store: this.$store
|
||||
@ -73,7 +73,7 @@ const ProfileTab = {
|
||||
emojiSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
...this.$store.state.instance.emoji,
|
||||
...this.$store.getters.standardEmojiList,
|
||||
...this.$store.state.instance.customEmoji
|
||||
]
|
||||
})
|
||||
|
@ -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>
|
@ -7,16 +7,23 @@ const StillImage = {
|
||||
'imageLoadHandler',
|
||||
'alt',
|
||||
'height',
|
||||
'width'
|
||||
'width',
|
||||
'dataSrc'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
// for lazy loading, see loadLazy()
|
||||
realSrc: this.src,
|
||||
stopGifs: this.$store.getters.mergedConfig.stopGifs
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
animated () {
|
||||
return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))
|
||||
if (!this.realSrc) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.stopGifs && (this.mimetype === 'image/gif' || this.realSrc.endsWith('.gif'))
|
||||
},
|
||||
style () {
|
||||
const appendPx = (str) => /\d$/.test(str) ? str + 'px' : str
|
||||
@ -27,7 +34,15 @@ const StillImage = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadLazy () {
|
||||
if (this.dataSrc) {
|
||||
this.realSrc = this.dataSrc
|
||||
}
|
||||
},
|
||||
onLoad () {
|
||||
if (!this.realSrc) {
|
||||
return
|
||||
}
|
||||
const image = this.$refs.src
|
||||
if (!image) return
|
||||
this.imageLoadHandler && this.imageLoadHandler(image)
|
||||
@ -42,6 +57,14 @@ const StillImage = {
|
||||
onError () {
|
||||
this.imageLoadError && this.imageLoadError()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
src () {
|
||||
this.realSrc = this.src
|
||||
},
|
||||
dataSrc () {
|
||||
this.$el.removeAttribute('data-loaded')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,10 +11,11 @@
|
||||
<!-- NOTE: key is required to force to re-render img tag when src is changed -->
|
||||
<img
|
||||
ref="src"
|
||||
:key="src"
|
||||
:key="realSrc"
|
||||
:alt="alt"
|
||||
:title="alt"
|
||||
:src="src"
|
||||
:data-src="dataSrc"
|
||||
:src="realSrc"
|
||||
:referrerpolicy="referrerpolicy"
|
||||
@load="onLoad"
|
||||
@error="onError"
|
||||
|
@ -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"
|
||||
|
@ -199,8 +199,20 @@
|
||||
"add_emoji": "Insert emoji",
|
||||
"custom": "Custom emoji",
|
||||
"unicode": "Unicode emoji",
|
||||
"unicode_groups": {
|
||||
"activities": "Activities",
|
||||
"animals-and-nature": "Animals & Nature",
|
||||
"flags": "Flags",
|
||||
"food-and-drink": "Food & Drink",
|
||||
"objects": "Objects",
|
||||
"people-and-body": "People & Body",
|
||||
"smileys-and-emotion": "Smileys & Emotion",
|
||||
"symbols": "Symbols",
|
||||
"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."
|
||||
@ -214,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",
|
||||
@ -229,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",
|
||||
@ -797,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",
|
||||
@ -844,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",
|
||||
@ -872,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,39 @@ 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',
|
||||
'people-and-body',
|
||||
'animals-and-nature',
|
||||
'food-and-drink',
|
||||
'travel-and-places',
|
||||
'activities',
|
||||
'objects',
|
||||
'symbols',
|
||||
'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
|
||||
@ -64,8 +97,9 @@ const defaultState = {
|
||||
// Nasty stuff
|
||||
customEmoji: [],
|
||||
customEmojiFetched: false,
|
||||
emoji: [],
|
||||
emoji: {},
|
||||
emojiFetched: false,
|
||||
unicodeEmojiAnnotations: {},
|
||||
pleromaBackend: true,
|
||||
postFormats: [],
|
||||
restrictedNicknames: [],
|
||||
@ -97,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: {
|
||||
@ -107,6 +166,9 @@ const instance = {
|
||||
},
|
||||
setKnownDomains (state, domains) {
|
||||
state.knownDomains = domains
|
||||
},
|
||||
setUnicodeEmojiAnnotations (state, { lang, annotations }) {
|
||||
state.unicodeEmojiAnnotations[lang] = annotations
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@ -115,6 +177,41 @@ const instance = {
|
||||
.map(key => [key, state[key]])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
},
|
||||
groupedCustomEmojis (state) {
|
||||
const packsOf = emoji => {
|
||||
return emoji.tags
|
||||
.filter(k => k.startsWith('pack:'))
|
||||
.map(k => k.slice(5)) // remove 'pack:' prefix
|
||||
}
|
||||
|
||||
return state.customEmoji
|
||||
.reduce((res, emoji) => {
|
||||
packsOf(emoji).forEach(packName => {
|
||||
const packId = `custom-${packName}`
|
||||
if (!res[packId]) {
|
||||
res[packId] = ({
|
||||
id: packId,
|
||||
text: packName,
|
||||
image: emoji.imageUrl,
|
||||
emojis: []
|
||||
})
|
||||
}
|
||||
res[packId].emojis.push(emoji)
|
||||
})
|
||||
return res
|
||||
}, {})
|
||||
},
|
||||
standardEmojiList (state) {
|
||||
return SORTED_EMOJI_GROUP_IDS
|
||||
.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] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations))
|
||||
}))
|
||||
},
|
||||
instanceDomain (state) {
|
||||
return new URL(state.server).hostname
|
||||
}
|
||||
@ -138,32 +235,52 @@ 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).map((key) => {
|
||||
return {
|
||||
displayText: key,
|
||||
imageUrl: false,
|
||||
replacement: values[key]
|
||||
}
|
||||
}).sort((a, b) => a.name > b.name ? 1 : -1)
|
||||
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')
|
||||
if (res.ok) {
|
||||
const result = await res.json()
|
||||
const values = Array.isArray(result) ? Object.assign({}, ...result) : result
|
||||
const caseInsensitiveStrCmp = (a, b) => {
|
||||
const la = a.toLowerCase()
|
||||
const lb = b.toLowerCase()
|
||||
return la > lb ? 1 : (la < lb ? -1 : 0)
|
||||
}
|
||||
const byPackThenByName = (a, b) => {
|
||||
const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
|
||||
return caseInsensitiveStrCmp(packOf(a), packOf(b)) || caseInsensitiveStrCmp(a.displayText, b.displayText)
|
||||
}
|
||||
|
||||
const emoji = Object.entries(values).map(([key, value]) => {
|
||||
const imageUrl = value.image_url
|
||||
return {
|
||||
@ -174,7 +291,7 @@ const instance = {
|
||||
}
|
||||
// Technically could use tags but those are kinda useless right now,
|
||||
// should have been "pack" field, that would be more useful
|
||||
}).sort((a, b) => a.displayText.toLowerCase() > b.displayText.toLowerCase() ? 1 : -1)
|
||||
}).sort(byPackThenByName)
|
||||
commit('setInstanceOption', { name: 'customEmoji', value: emoji })
|
||||
} else {
|
||||
throw (res)
|
||||
|
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
|
||||
}
|
||||
|
1431
static/emoji.json
1431
static/emoji.json
File diff suppressed because it is too large
Load Diff
320
yarn.lock
320
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"
|
||||
@ -1667,6 +1672,34 @@
|
||||
resolved "https://registry.yarnpkg.com/@ruffle-rs/ruffle/-/ruffle-0.1.0-nightly.2022.7.12.tgz#c2d77fce7a0e98d51a6535371550e0bff019d0ea"
|
||||
integrity sha512-DFsiT4kdUuSHsYXzHV97e9Ui3FkcsHEg1GyHJipt/lCpCoZ2uRtP41uEz9eNc9ug8jWd7UyXxJmdkkRvs9UHgQ==
|
||||
|
||||
"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3":
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||
integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/fake-timers@>=5", "@sinonjs/fake-timers@^9.1.2":
|
||||
version "9.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c"
|
||||
integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@sinonjs/samsam@^6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-6.1.1.tgz#627f7f4cbdb56e6419fa2c1a3e4751ce4f6a00b1"
|
||||
integrity sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.6.0"
|
||||
lodash.get "^4.4.2"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918"
|
||||
integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==
|
||||
|
||||
"@socket.io/base64-arraybuffer@~1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61"
|
||||
@ -1854,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"
|
||||
@ -1906,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"
|
||||
@ -1966,12 +1999,12 @@
|
||||
dependencies:
|
||||
vue-demi "^0.13.4"
|
||||
|
||||
"@vuelidate/validators@2.0.0-alpha.31":
|
||||
version "2.0.0-alpha.31"
|
||||
resolved "https://registry.yarnpkg.com/@vuelidate/validators/-/validators-2.0.0-alpha.31.tgz#04d63307bc0a12db9f7ad94243350b83aacee998"
|
||||
integrity sha512-+MFA9nZ7Y9zCpq383/voPDk/hiAmu6KqiJJhLOYB/FmrUPVoyKnuKnI9Bwiq8ok9GZlVkI8BnIrKPKGj9QpwiQ==
|
||||
"@vuelidate/validators@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@vuelidate/validators/-/validators-2.0.0.tgz#1ddd86c6c81b2cfbb5720961e951cc53ec0a80be"
|
||||
integrity sha512-fQQcmDWfz7pyH5/JPi0Ng2GEgNK1pUHn/Z/j5rG/Q+HwhgIXvJblTPcZwKOj1ABL7V4UVuGKECvZCDHNGOwdrg==
|
||||
dependencies:
|
||||
vue-demi "^0.13.4"
|
||||
vue-demi "^0.13.11"
|
||||
|
||||
"@webassemblyjs/ast@1.11.1":
|
||||
version "1.11.1"
|
||||
@ -3373,16 +3406,16 @@ didyoumean@1.2.2:
|
||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
||||
|
||||
diff@3.5.0, diff@^3.1.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
|
||||
|
||||
diff@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
|
||||
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
|
||||
|
||||
diff@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
|
||||
integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
|
||||
|
||||
dijkstrajs@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257"
|
||||
@ -4213,12 +4246,6 @@ form-data@^4.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formatio@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
|
||||
dependencies:
|
||||
samsam "1.x"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
@ -5210,6 +5237,11 @@ jszip@^3.10.0:
|
||||
readable-stream "~2.3.6"
|
||||
setimmediate "^1.0.5"
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
|
||||
integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==
|
||||
|
||||
karma-coverage@2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.0.tgz#64f838b66b71327802e7f6f6c39d569b7024e40c"
|
||||
@ -5530,6 +5562,11 @@ lodash.find@^3.2.1:
|
||||
lodash.isarray "^3.0.0"
|
||||
lodash.keys "^3.0.0"
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
|
||||
|
||||
lodash.isarguments@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||
@ -5672,11 +5709,6 @@ log4js@^6.4.1:
|
||||
rfdc "^1.3.0"
|
||||
streamroller "^3.0.6"
|
||||
|
||||
lolex@1.6.0, lolex@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
|
||||
integrity sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=
|
||||
|
||||
longest-streak@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
|
||||
@ -5696,6 +5728,11 @@ lower-case@^2.0.2:
|
||||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
lozad@1.16.0:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/lozad/-/lozad-1.16.0.tgz#86ce732c64c69926ccdebb81c8c90bb3735948b4"
|
||||
integrity sha512-JBr9WjvEFeKoyim3svo/gsQPTkgG/mOHJmDctZ/+U9H3ymUuvEkqpn8bdQMFsvTMcyRJrdJkLv0bXqGm0sP72w==
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
@ -6064,10 +6101,6 @@ nanoid@^3.3.4:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||
|
||||
native-promise-only@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
@ -6118,6 +6151,17 @@ nightwatch@2.3.3:
|
||||
stacktrace-parser "^0.1.10"
|
||||
strip-ansi "6.0.1"
|
||||
|
||||
nise@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.1.tgz#ac4237e0d785ecfcb83e20f389185975da5c31f3"
|
||||
integrity sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.8.3"
|
||||
"@sinonjs/fake-timers" ">=5"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
no-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
|
||||
@ -7330,10 +7374,6 @@ safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
samsam@1.x, samsam@^1.1.3:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
|
||||
|
||||
sass-loader@13.0.2:
|
||||
version "13.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.0.2.tgz#e81a909048e06520e9f2ff25113a801065adb3fe"
|
||||
@ -7342,10 +7382,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.55.0:
|
||||
version "1.55.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.55.0.tgz#0c4d3c293cfe8f8a2e8d3b666e1cf1bff8065d1c"
|
||||
integrity sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
@ -7522,19 +7562,17 @@ sinon-chai@3.7.0:
|
||||
resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783"
|
||||
integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==
|
||||
|
||||
sinon@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36"
|
||||
integrity sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==
|
||||
sinon@14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-14.0.0.tgz#203731c116d3a2d58dc4e3cbe1f443ba9382a031"
|
||||
integrity sha512-ugA6BFmE+WrJdh0owRZHToLd32Uw3Lxq6E6LtNRU+xTVBefx632h03Q7apXWRsRdZAJ41LB8aUfn2+O4jsDNMw==
|
||||
dependencies:
|
||||
diff "^3.1.0"
|
||||
formatio "1.2.0"
|
||||
lolex "^1.6.0"
|
||||
native-promise-only "^0.8.1"
|
||||
path-to-regexp "^1.7.0"
|
||||
samsam "^1.1.3"
|
||||
text-encoding "0.6.4"
|
||||
type-detect "^4.0.0"
|
||||
"@sinonjs/commons" "^1.8.3"
|
||||
"@sinonjs/fake-timers" "^9.1.2"
|
||||
"@sinonjs/samsam" "^6.1.1"
|
||||
diff "^5.0.0"
|
||||
nise "^5.1.1"
|
||||
supports-color "^7.2.0"
|
||||
|
||||
slash@^3.0.0:
|
||||
version "3.0.0"
|
||||
@ -7893,6 +7931,13 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-preserve-symlinks-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
@ -7960,10 +8005,6 @@ terser@^5.10.0, terser@^5.14.1:
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-encoding@0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
|
||||
|
||||
text-table@0.2.0, text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
@ -8023,7 +8064,7 @@ type-check@^0.4.0, type-check@~0.4.0:
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
|
||||
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
|
||||
@ -8239,6 +8280,11 @@ void-elements@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
|
||||
|
||||
vue-demi@^0.13.11:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
|
||||
integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
|
||||
|
||||
vue-demi@^0.13.4:
|
||||
version "0.13.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.5.tgz#d5eddbc9eaefb89ce5995269d1fa6b0486312092"
|
||||
@ -8299,16 +8345,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…
Reference in New Issue
Block a user