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,
showingLongSubject: false,
isReplying: false,
mediaPlaying: []
mediaPlaying: [],
currentLanguage: undefined
}
if (this.statusContentPropertiesObject[id]) {

View File

@ -94,11 +94,13 @@
:controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject"
:controlled-replying="statusContentProperties[status.id].replying"
:controlled-media-playing="statusContentProperties[status.id].mediaPlaying"
:controlled-current-language="statusContentProperties[status.id].currentLanguage"
:controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')"
:controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')"
:controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')"
:controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')"
:controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
:controlled-set-current-language="(newVal) => setStatusContentProperty(status.id, 'currentLanguage', newVal)"
@goto="setHighlight"
@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 { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.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 { library } from '@fortawesome/fontawesome-svg-core'
@ -62,41 +63,6 @@ library.add(
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 = {
name: 'Status',
components: {
@ -149,6 +115,8 @@ const Status = {
'controlledToggleReplying',
'controlledMediaPlaying',
'controlledSetMediaPlaying',
'controlledCurrentLanguage',
'controlledSetCurrentLanguage',
'dive'
],
data () {
@ -157,13 +125,14 @@ const Status = {
unmuted: false,
userExpanded: false,
uncontrolledMediaPlaying: [],
uncontrolledCurrentLanguage: undefined,
suspendable: true,
error: null,
headTailLinks: null
}
},
computed: {
...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']),
...controlledOrUncontrolledGetters(['replying', 'mediaPlaying', 'currentLanguage']),
muteWords () {
return this.mergedConfig.muteWords
},
@ -448,6 +417,9 @@ const Status = {
removeMediaPlaying (id) {
controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.filter(mediaId => mediaId !== id))
},
setCurrentLanguage (language) {
controlledOrUncontrolledSet(this, 'currentLanguage', language)
},
setHeadTailLinks (headTailLinks) {
this.headTailLinks = headTailLinks
},

View File

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

View File

@ -32,7 +32,9 @@ const StatusContent = {
'showingLongSubject',
'toggleShowingTall',
'toggleExpandingSubject',
'toggleShowingLongSubject'
'toggleShowingLongSubject',
'currentLanguage',
'setCurrentLanguage'
],
data () {
return {
@ -78,6 +80,18 @@ const StatusContent = {
attachmentTypes () {
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'])
},
components: {

View File

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

View File

@ -5,13 +5,27 @@
>
<div class="body">
<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="{ '-tall': (longSubject && !showingLongSubject) }"
>
<RichContent
class="media-body summary"
:html="status.summary_raw_html"
:html="summary"
:emoji="status.emojis"
/>
<button
@ -42,10 +56,10 @@
{{ $t("general.show_more") }}
</button>
<RichContent
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
v-if="!hideSubjectStatus && !(singleLine && summary)"
:class="{ '-single-line': singleLine }"
class="text media-body"
:html="status.raw_html"
:html="content"
:emoji="status.emojis"
:handle-links="true"
: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 LinkPreview from '../link-preview/link-preview.vue'
import { mapGetters, mapState } from 'vuex'
import { controlledOrUncontrolledGetters, controlledOrUncontrolledSet, controlledOrUncontrolledToggle } from 'src/services/control/control.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch,
@ -23,30 +24,6 @@ library.add(
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 = {
name: 'StatusContent',
props: [
@ -61,18 +38,21 @@ const StatusContent = {
'controlledToggleShowingTall',
'controlledToggleExpandingSubject',
'controlledShowingLongSubject',
'controlledToggleShowingLongSubject'
'controlledToggleShowingLongSubject',
'controlledCurrentLanguage',
'controlledSetCurrentLanguage'
],
data () {
return {
uncontrolledShowingTall: this.fullContent || (this.inConversation && this.focused),
uncontrolledShowingLongSubject: false,
// 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: {
...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject']),
...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject', 'currentLanguage']),
hideAttachments () {
return (this.mergedConfig.hideAttachments && !this.inConversation) ||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation)
@ -121,6 +101,9 @@ const StatusContent = {
toggleShowingLongSubject () {
controlledOrUncontrolledToggle(this, 'showingLongSubject')
},
setCurrentLanguage (language) {
controlledOrUncontrolledSet(this, 'currentLanguage', language)
},
setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments)

View File

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

View File

@ -82,7 +82,7 @@ const ThreadTree = {
this.toggleStatusContentProperty(this.status.id, name)
},
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-replying="currentProp.replying"
:controlled-media-playing="currentProp.mediaPlaying"
:controlled-current-language="currentProp.currentLanguage"
:controlled-toggle-showing-tall="() => toggleCurrentProp('showingTall')"
:controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')"
:controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')"
:controlled-toggle-replying="() => toggleCurrentProp('replying')"
:controlled-set-media-playing="(newVal) => setCurrentProp('mediaPlaying', newVal)"
:controlled-set-current-language="(newVal) => setCurrentProp('currentLanguage', newVal)"
:dive="dive ? () => dive(status.id) : undefined"
@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 { isStatusNotification } from '../notification_utils/notification_utils.js'
import punycode from 'punycode.js'
import { uniq } from 'lodash'
/** NOTICE! **
* Do not initialize UI-generated data here.
@ -309,6 +310,7 @@ export const parseStatus = (data) => {
output.nsfw = data.sensitive
output.raw_html = data.content
output.raw_html_map = data.content_map || {}
output.emojis = data.emojis
output.tags = data.tags
@ -339,6 +341,11 @@ export const parseStatus = (data) => {
}
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.poll = data.poll
if (output.poll) {
@ -349,6 +356,7 @@ export const parseStatus = (data) => {
}
output.pinned = data.pinned
output.muted = data.muted
output.languages = uniq(Object.keys(output.raw_html_map).concat(Object.keys(output.summary_raw_html_map)))
} else {
output.favorited = data.favorited
output.fave_num = data.fave_num