Compare commits

...

1 Commits

Author SHA1 Message Date
tusooa b2cd38f603
Support displaying multilang statuses 2023-01-28 14:04:25 -05:00
13 changed files with 115 additions and 70 deletions

View File

@ -320,7 +320,8 @@ const conversation = {
expandingSubject: false, expandingSubject: false,
showingLongSubject: false, showingLongSubject: false,
isReplying: false, isReplying: false,
mediaPlaying: [] mediaPlaying: [],
currentLanguage: undefined
} }
if (this.statusContentPropertiesObject[id]) { if (this.statusContentPropertiesObject[id]) {

View File

@ -94,11 +94,13 @@
:controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject" :controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject"
:controlled-replying="statusContentProperties[status.id].replying" :controlled-replying="statusContentProperties[status.id].replying"
:controlled-media-playing="statusContentProperties[status.id].mediaPlaying" :controlled-media-playing="statusContentProperties[status.id].mediaPlaying"
:controlled-current-language="statusContentProperties[status.id].currentLanguage"
:controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')" :controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')"
:controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')" :controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')"
:controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')" :controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')"
:controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')" :controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')"
:controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)" :controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
:controlled-set-current-language="(newVal) => setStatusContentProperty(status.id, 'currentLanguage', newVal)"
@goto="setHighlight" @goto="setHighlight"
@toggleExpanded="toggleExpanded" @toggleExpanded="toggleExpanded"

View File

@ -19,6 +19,7 @@ import MentionLink from 'src/components/mention_link/mention_link.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { muteWordHits } from '../../services/status_parser/status_parser.js' import { muteWordHits } from '../../services/status_parser/status_parser.js'
import { controlledOrUncontrolledGetters, controlledOrUncontrolledSet, controlledOrUncontrolledToggle } from 'src/services/control/control.service.js'
import { unescape, uniqBy } from 'lodash' import { unescape, uniqBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -62,41 +63,6 @@ library.add(
faAngleDoubleRight faAngleDoubleRight
) )
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
const controlledOrUncontrolledGetters = list => list.reduce((res, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const controlledName = `controlled${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
res[name] = function () {
return ((this.$data[toggle] !== undefined || this.$props[toggle] !== undefined) && this[toggle]) ? this[controlledName] : this[uncontrolledName]
}
return res
}, {})
const controlledOrUncontrolledToggle = (obj, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
if (obj[toggle]) {
obj[toggle]()
} else {
obj[uncontrolledName] = !obj[uncontrolledName]
}
}
const controlledOrUncontrolledSet = (obj, name, val) => {
const camelized = camelCase(name)
const set = `controlledSet${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
if (obj[set]) {
obj[set](val)
} else {
obj[uncontrolledName] = val
}
}
const Status = { const Status = {
name: 'Status', name: 'Status',
components: { components: {
@ -149,6 +115,8 @@ const Status = {
'controlledToggleReplying', 'controlledToggleReplying',
'controlledMediaPlaying', 'controlledMediaPlaying',
'controlledSetMediaPlaying', 'controlledSetMediaPlaying',
'controlledCurrentLanguage',
'controlledSetCurrentLanguage',
'dive' 'dive'
], ],
data () { data () {
@ -157,13 +125,14 @@ const Status = {
unmuted: false, unmuted: false,
userExpanded: false, userExpanded: false,
uncontrolledMediaPlaying: [], uncontrolledMediaPlaying: [],
uncontrolledCurrentLanguage: undefined,
suspendable: true, suspendable: true,
error: null, error: null,
headTailLinks: null headTailLinks: null
} }
}, },
computed: { computed: {
...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']), ...controlledOrUncontrolledGetters(['replying', 'mediaPlaying', 'currentLanguage']),
muteWords () { muteWords () {
return this.mergedConfig.muteWords return this.mergedConfig.muteWords
}, },
@ -448,6 +417,9 @@ const Status = {
removeMediaPlaying (id) { removeMediaPlaying (id) {
controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.filter(mediaId => mediaId !== id)) controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.filter(mediaId => mediaId !== id))
}, },
setCurrentLanguage (language) {
controlledOrUncontrolledSet(this, 'currentLanguage', language)
},
setHeadTailLinks (headTailLinks) { setHeadTailLinks (headTailLinks) {
this.headTailLinks = headTailLinks this.headTailLinks = headTailLinks
}, },

View File

@ -356,9 +356,11 @@
:controlled-showing-tall="controlledShowingTall" :controlled-showing-tall="controlledShowingTall"
:controlled-expanding-subject="controlledExpandingSubject" :controlled-expanding-subject="controlledExpandingSubject"
:controlled-showing-long-subject="controlledShowingLongSubject" :controlled-showing-long-subject="controlledShowingLongSubject"
:controlled-current-language="controlledCurrentLanguage"
:controlled-toggle-showing-tall="controlledToggleShowingTall" :controlled-toggle-showing-tall="controlledToggleShowingTall"
:controlled-toggle-expanding-subject="controlledToggleExpandingSubject" :controlled-toggle-expanding-subject="controlledToggleExpandingSubject"
:controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject" :controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject"
:controlled-set-current-language="controlledSetCurrentLanguage"
@mediaplay="addMediaPlaying($event)" @mediaplay="addMediaPlaying($event)"
@mediapause="removeMediaPlaying($event)" @mediapause="removeMediaPlaying($event)"
@parseReady="setHeadTailLinks" @parseReady="setHeadTailLinks"

View File

@ -32,7 +32,9 @@ const StatusContent = {
'showingLongSubject', 'showingLongSubject',
'toggleShowingTall', 'toggleShowingTall',
'toggleExpandingSubject', 'toggleExpandingSubject',
'toggleShowingLongSubject' 'toggleShowingLongSubject',
'currentLanguage',
'setCurrentLanguage'
], ],
data () { data () {
return { return {
@ -78,6 +80,18 @@ const StatusContent = {
attachmentTypes () { attachmentTypes () {
return this.status.attachments.map(file => fileType.fileType(file.mimetype)) return this.status.attachments.map(file => fileType.fileType(file.mimetype))
}, },
languages () {
return this.status.languages
},
currentLanguageOrDefault () {
return this.currentLanguage || this.languages[0]
},
summary () {
return this.currentLanguageOrDefault ? this.status.summary_raw_html_map[this.currentLanguageOrDefault] : this.status.summary_raw_html
},
content () {
return this.currentLanguageOrDefault ? this.status.raw_html_map[this.currentLanguageOrDefault] : this.status.raw_html
},
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
components: { components: {

View File

@ -4,6 +4,12 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.language-selector {
display: flex;
flex-direction: row;
overflow: hidden;
}
.emoji { .emoji {
--_still_image-label-scale: 0.5; --_still_image-label-scale: 0.5;
} }

View File

@ -5,13 +5,27 @@
> >
<div class="body"> <div class="body">
<div <div
v-if="status.summary_raw_html" v-if="languages"
class="language-selector"
>
<button
v-for="language in languages"
:key="language"
class="language-selector-item tab button-default"
:class="{ active: language === currentLanguageOrDefault }"
@click="setCurrentLanguage(language)"
>
{{ language }}
</button>
</div>
<div
v-if="summary"
class="summary-wrapper" class="summary-wrapper"
:class="{ '-tall': (longSubject && !showingLongSubject) }" :class="{ '-tall': (longSubject && !showingLongSubject) }"
> >
<RichContent <RichContent
class="media-body summary" class="media-body summary"
:html="status.summary_raw_html" :html="summary"
:emoji="status.emojis" :emoji="status.emojis"
/> />
<button <button
@ -42,10 +56,10 @@
{{ $t("general.show_more") }} {{ $t("general.show_more") }}
</button> </button>
<RichContent <RichContent
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)" v-if="!hideSubjectStatus && !(singleLine && summary)"
:class="{ '-single-line': singleLine }" :class="{ '-single-line': singleLine }"
class="text media-body" class="text media-body"
:html="status.raw_html" :html="content"
:emoji="status.emojis" :emoji="status.emojis"
:handle-links="true" :handle-links="true"
:greentext="mergedConfig.greentext" :greentext="mergedConfig.greentext"

View File

@ -4,6 +4,7 @@ import Gallery from '../gallery/gallery.vue'
import StatusBody from 'src/components/status_body/status_body.vue' import StatusBody from 'src/components/status_body/status_body.vue'
import LinkPreview from '../link-preview/link-preview.vue' import LinkPreview from '../link-preview/link-preview.vue'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { controlledOrUncontrolledGetters, controlledOrUncontrolledSet, controlledOrUncontrolledToggle } from 'src/services/control/control.service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faCircleNotch, faCircleNotch,
@ -23,30 +24,6 @@ library.add(
faPollH faPollH
) )
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
const controlledOrUncontrolledGetters = list => list.reduce((res, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const controlledName = `controlled${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
res[name] = function () {
return ((this.$data[toggle] !== undefined || this.$props[toggle] !== undefined) && this[toggle]) ? this[controlledName] : this[uncontrolledName]
}
return res
}, {})
const controlledOrUncontrolledToggle = (obj, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
if (obj[toggle]) {
obj[toggle]()
} else {
obj[uncontrolledName] = !obj[uncontrolledName]
}
}
const StatusContent = { const StatusContent = {
name: 'StatusContent', name: 'StatusContent',
props: [ props: [
@ -61,18 +38,21 @@ const StatusContent = {
'controlledToggleShowingTall', 'controlledToggleShowingTall',
'controlledToggleExpandingSubject', 'controlledToggleExpandingSubject',
'controlledShowingLongSubject', 'controlledShowingLongSubject',
'controlledToggleShowingLongSubject' 'controlledToggleShowingLongSubject',
'controlledCurrentLanguage',
'controlledSetCurrentLanguage'
], ],
data () { data () {
return { return {
uncontrolledShowingTall: this.fullContent || (this.inConversation && this.focused), uncontrolledShowingTall: this.fullContent || (this.inConversation && this.focused),
uncontrolledShowingLongSubject: false, uncontrolledShowingLongSubject: false,
// not as computed because it sets the initial state which will be changed later // not as computed because it sets the initial state which will be changed later
uncontrolledExpandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject uncontrolledExpandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
uncontrolledCurrentLanguage: undefined
} }
}, },
computed: { computed: {
...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject']), ...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject', 'currentLanguage']),
hideAttachments () { hideAttachments () {
return (this.mergedConfig.hideAttachments && !this.inConversation) || return (this.mergedConfig.hideAttachments && !this.inConversation) ||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation) (this.mergedConfig.hideAttachmentsInConv && this.inConversation)
@ -121,6 +101,9 @@ const StatusContent = {
toggleShowingLongSubject () { toggleShowingLongSubject () {
controlledOrUncontrolledToggle(this, 'showingLongSubject') controlledOrUncontrolledToggle(this, 'showingLongSubject')
}, },
setCurrentLanguage (language) {
controlledOrUncontrolledSet(this, 'currentLanguage', language)
},
setMedia () { setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments) return () => this.$store.dispatch('setMedia', attachments)

View File

@ -11,9 +11,11 @@
:showing-tall="showingTall" :showing-tall="showingTall"
:expanding-subject="expandingSubject" :expanding-subject="expandingSubject"
:showing-long-subject="showingLongSubject" :showing-long-subject="showingLongSubject"
:current-language="currentLanguage"
:toggle-showing-tall="toggleShowingTall" :toggle-showing-tall="toggleShowingTall"
:toggle-expanding-subject="toggleExpandingSubject" :toggle-expanding-subject="toggleExpandingSubject"
:toggle-showing-long-subject="toggleShowingLongSubject" :toggle-showing-long-subject="toggleShowingLongSubject"
:set-current-language="setCurrentLanguage"
@parseReady="$emit('parseReady', $event)" @parseReady="$emit('parseReady', $event)"
> >
<div v-if="status.poll && status.poll.options && !compact"> <div v-if="status.poll && status.poll.options && !compact">

View File

@ -82,7 +82,7 @@ const ThreadTree = {
this.toggleStatusContentProperty(this.status.id, name) this.toggleStatusContentProperty(this.status.id, name)
}, },
setCurrentProp (name, newVal) { setCurrentProp (name, newVal) {
this.setStatusContentProperty(this.status.id, name) this.setStatusContentProperty(this.status.id, name, newVal)
} }
} }
} }

View File

@ -24,11 +24,13 @@
:controlled-showing-long-subject="currentProp.showingLongSubject" :controlled-showing-long-subject="currentProp.showingLongSubject"
:controlled-replying="currentProp.replying" :controlled-replying="currentProp.replying"
:controlled-media-playing="currentProp.mediaPlaying" :controlled-media-playing="currentProp.mediaPlaying"
:controlled-current-language="currentProp.currentLanguage"
:controlled-toggle-showing-tall="() => toggleCurrentProp('showingTall')" :controlled-toggle-showing-tall="() => toggleCurrentProp('showingTall')"
:controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')"
:controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')"
:controlled-toggle-replying="() => toggleCurrentProp('replying')" :controlled-toggle-replying="() => toggleCurrentProp('replying')"
:controlled-set-media-playing="(newVal) => setCurrentProp('mediaPlaying', newVal)" :controlled-set-media-playing="(newVal) => setCurrentProp('mediaPlaying', newVal)"
:controlled-set-current-language="(newVal) => setCurrentProp('currentLanguage', newVal)"
:dive="dive ? () => dive(status.id) : undefined" :dive="dive ? () => dive(status.id) : undefined"
@goto="setHighlight" @goto="setHighlight"

View File

@ -0,0 +1,39 @@
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
export const controlledOrUncontrolledGetters = list => list.reduce((res, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const set = `controlledSet${camelized}`
const controlledName = `controlled${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
res[name] = function () {
return (((this.$data[toggle] !== undefined || this.$props[toggle] !== undefined) && this[toggle]) ||
((this.$data[set] !== undefined || this.$props[set] !== undefined) && this[set])
)
? this[controlledName]
: this[uncontrolledName]
}
return res
}, {})
export const controlledOrUncontrolledToggle = (obj, name) => {
const camelized = camelCase(name)
const toggle = `controlledToggle${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
if (obj[toggle]) {
obj[toggle]()
} else {
obj[uncontrolledName] = !obj[uncontrolledName]
}
}
export const controlledOrUncontrolledSet = (obj, name, val) => {
const camelized = camelCase(name)
const set = `controlledSet${camelized}`
const uncontrolledName = `uncontrolled${camelized}`
if (obj[set]) {
obj[set](val)
} else {
obj[uncontrolledName] = val
}
}

View File

@ -2,6 +2,7 @@ import escape from 'escape-html'
import parseLinkHeader from 'parse-link-header' import parseLinkHeader from 'parse-link-header'
import { isStatusNotification } from '../notification_utils/notification_utils.js' import { isStatusNotification } from '../notification_utils/notification_utils.js'
import punycode from 'punycode.js' import punycode from 'punycode.js'
import { uniq } from 'lodash'
/** NOTICE! ** /** NOTICE! **
* Do not initialize UI-generated data here. * Do not initialize UI-generated data here.
@ -309,6 +310,7 @@ export const parseStatus = (data) => {
output.nsfw = data.sensitive output.nsfw = data.sensitive
output.raw_html = data.content output.raw_html = data.content
output.raw_html_map = data.content_map || {}
output.emojis = data.emojis output.emojis = data.emojis
output.tags = data.tags output.tags = data.tags
@ -339,6 +341,11 @@ export const parseStatus = (data) => {
} }
output.summary_raw_html = escape(data.spoiler_text) output.summary_raw_html = escape(data.spoiler_text)
output.summary_raw_html_map = Object.entries(data.spoiler_text_map || {})
.reduce((acc, [lang, txt]) => {
acc[lang] = escape(txt)
return acc
}, {})
output.external_url = data.url output.external_url = data.url
output.poll = data.poll output.poll = data.poll
if (output.poll) { if (output.poll) {
@ -349,6 +356,7 @@ export const parseStatus = (data) => {
} }
output.pinned = data.pinned output.pinned = data.pinned
output.muted = data.muted output.muted = data.muted
output.languages = uniq(Object.keys(output.raw_html_map).concat(Object.keys(output.summary_raw_html_map)))
} else { } else {
output.favorited = data.favorited output.favorited = data.favorited
output.fave_num = data.fave_num output.fave_num = data.fave_num