Merge branch 'proper-attachments' into shigusegubu

* proper-attachments:
  ability to move attachments around when making a new post
  inline description display
  add media description into media modal
  add key attribute to make image refresh in media modal to give feedback when images are still loaded
  fix "+X more" sticking
  fix video attachments in notifications not having pointer cursor
  replace poll with an icon in notifications
  fix long posts double-fading in notifications
  fix links sticking to mentionsline
  fix console errors
This commit is contained in:
Henry Jameson 2021-08-15 21:06:07 +03:00
commit 42dd1d8611
15 changed files with 185 additions and 81 deletions

View file

@ -15,7 +15,8 @@ import {
faStop,
faSearchPlus,
faTrashAlt,
faPencilAlt
faPencilAlt,
faAlignRight
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -28,7 +29,8 @@ library.add(
faStop,
faSearchPlus,
faTrashAlt,
faPencilAlt
faPencilAlt,
faAlignRight
)
const Attachment = {
@ -40,6 +42,8 @@ const Attachment = {
'size',
'setMedia',
'remove',
'shiftUp',
'shiftDn',
'edit'
],
data () {
@ -52,7 +56,8 @@ const Attachment = {
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
modalOpen: false,
showHidden: false,
flashLoaded: false
flashLoaded: false,
showDescription: false
}
},
components: {
@ -118,6 +123,9 @@ const Attachment = {
}
return modalTypes.includes(this.type)
},
videoTag () {
return this.useModal ? 'button' : 'span'
},
...mapGetters(['mergedConfig'])
},
watch: {
@ -148,12 +156,21 @@ const Attachment = {
onRemove () {
this.remove && this.remove(this.attachment)
},
onShiftUp () {
this.shiftUp && this.shiftUp(this.attachment)
},
onShiftDn () {
this.shiftDn && this.shiftDn(this.attachment)
},
stopFlash () {
this.$refs.flash.closePlayer()
},
setFlashLoaded (event) {
this.flashLoaded = event
},
toggleDescription () {
this.showDescription = !this.showDescription
},
toggleHidden (event) {
if (
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&

View file

@ -42,19 +42,10 @@
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
padding-top: 0;
background: var(--popover);
box-shadow: var(--popupShadow);
opacity: 0;
transition: 0.35s all;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
}
&:hover {
.description-container.-static {
opacity: 1;
transform: translateY(-3em);
}
}

View file

@ -30,7 +30,7 @@
</button>
</div>
<div
v-if="size !== 'hide' && !hideDescription && (edit || localDescription)"
v-if="size !== 'hide' && !hideDescription && (edit || localDescription || showDescription)"
class="description-container"
:class="{ '-static': !edit }"
>
@ -83,13 +83,23 @@
v-if="type === 'flash' && flashLoaded"
class="button-unstyled attachment-button"
@click.prevent="stopFlash"
:title="$t('status.attachment_stop_flash')"
>
<FAIcon icon="stop" />
</button>
<button
v-if="attachment.description && size !== 'small' && !edit"
class="button-unstyled attachment-button"
@click.prevent="toggleDescription"
:title="$t('status.show_attachment_description')"
>
<FAIcon icon="align-right" />
</button>
<button
v-if="!useModal"
class="button-unstyled attachment-button"
@click.prevent="openModalForce"
:title="$t('status.show_attachment_in_modal')"
>
<FAIcon icon="search-plus" />
</button>
@ -97,13 +107,31 @@
v-if="nsfw && hideNsfwLocal"
class="button-unstyled attachment-button"
@click.prevent="toggleHidden"
:title="$t('status.hide_attachment')"
>
<FAIcon icon="times" />
</button>
<button
v-if="shiftUp"
class="button-unstyled attachment-button"
@click.prevent="onShiftUp"
:title="$t('status.move_up')"
>
<FAIcon icon="chevron-left" />
</button>
<button
v-if="shiftDn"
class="button-unstyled attachment-button"
@click.prevent="onShiftDn"
:title="$t('status.move_down')"
>
<FAIcon icon="chevron-right" />
</button>
<button
v-if="remove"
class="button-unstyled attachment-button"
@click.prevent="onRemove"
:title="$t('status.remove_attachment')"
>
<FAIcon icon="trash-alt" />
</button>
@ -127,9 +155,11 @@
/>
</a>
<span
<component
:is="videoTag"
v-if="type === 'video' && !hidden"
class="video-container"
:class="{ 'button-unstyled': 'isModal' }"
:href="attachment.url"
@click.stop.prevent="openModal"
>
@ -145,7 +175,7 @@
class="play-icon"
icon="play-circle"
/>
</span>
</component>
<span
v-if="type === 'audio' && !hidden"
@ -199,7 +229,7 @@
</span>
</div>
<div
v-if="size !== 'hide' && !hideDescription && (edit || localDescription)"
v-if="size !== 'hide' && !hideDescription && (edit || (localDescription && showDescription))"
class="description-container"
:class="{ '-static': !edit }"
>

View file

@ -12,6 +12,8 @@ const Gallery = {
'size',
'editable',
'removeAttachment',
'shiftUpAttachment',
'shiftDnAttachment',
'editAttachment',
'grid'
],

View file

@ -6,8 +6,8 @@
>
<div class="gallery-rows">
<div
v-for="(row, index) in rows"
:key="index"
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="gallery-row"
:style="rowStyle(row)"
:class="{ '-audio': row.audio, '-minimal': row.minimal, '-grid': grid }"
@ -16,8 +16,8 @@
class="gallery-row-inner"
:class="{ '-grid': grid }"
>
<attachment
v-for="attachment in row.items"
<Attachment
v-for="(attachment, attachmentIndex) in row.items"
:key="attachment.id"
class="gallery-item"
:nsfw="nsfw"
@ -26,6 +26,8 @@
:size="size"
:editable="editable"
:remove="removeAttachment"
:shiftUp="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment"
:shiftDn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment"
:edit="editAttachment"
:description="descriptions && descriptions[attachment.id]"
:hide-description="size === 'small' || tooManyAttachments && hidingLong"

View file

@ -29,6 +29,9 @@ const MediaModal = {
media () {
return this.$store.state.mediaViewer.media
},
description () {
return this.currentMedia.description
},
currentIndex () {
return this.$store.state.mediaViewer.currentIndex
},

View file

@ -8,6 +8,7 @@
v-if="type === 'image'"
class="modal-image"
:src="currentMedia.url"
:key="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
@touchstart.stop="mediaTouchStart"
@ -19,11 +20,13 @@
class="modal-image"
:attachment="currentMedia"
:controls="true"
:key="currentMedia.url"
/>
<audio
v-if="type === 'audio'"
class="modal-image"
:src="currentMedia.url"
:key="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
controls
@ -31,6 +34,7 @@
<Flash
v-if="type === 'flash'"
class="modal-image"
:key="currentMedia.url"
:src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
@ -57,6 +61,12 @@
icon="chevron-right"
/>
</button>
<span
v-if="description"
class="description"
>
{{ description }}
</span>
</Modal>
</template>
@ -65,6 +75,7 @@
<style lang="scss">
.modal-view.media-modal-view {
z-index: 1001;
flex-direction: column;
.modal-view-button-arrow {
opacity: 0.75;
@ -80,63 +91,76 @@
}
}
@keyframes media-fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-image {
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
image-orientation: from-image; // NOTE: only FF supports this
animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
}
.modal-view-button-arrow {
position: absolute;
display: block;
top: 50%;
margin-top: -50px;
width: 70px;
height: 100px;
border: 0;
padding: 0;
opacity: 0;
box-shadow: none;
background: none;
appearance: none;
overflow: visible;
cursor: pointer;
transition: opacity 333ms cubic-bezier(.4,0,.22,1);
.arrow-icon {
position: absolute;
top: 35px;
height: 30px;
width: 32px;
font-size: 14px;
line-height: 30px;
color: #FFF;
text-align: center;
background-color: rgba(0,0,0,.3);
}
&--prev {
left: 0;
.arrow-icon {
left: 6px;
.media-modal-view {
@keyframes media-fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
&--next {
right: 0;
/* Hardcoded since background is also hardcoded */
.description {
color: white;
margin-top: 1em;
text-shadow: 0 0 10px black, 0 0 10px black;
max-width: 500px;
max-height: 9.5em;
overflow-y: auto;
padding: 0 2em;
}
.modal-image {
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
image-orientation: from-image; // NOTE: only FF supports this
animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
}
.modal-view-button-arrow {
position: absolute;
display: block;
top: 50%;
margin-top: -50px;
width: 70px;
height: 100px;
border: 0;
padding: 0;
opacity: 0;
box-shadow: none;
background: none;
appearance: none;
overflow: visible;
cursor: pointer;
transition: opacity 333ms cubic-bezier(.4,0,.22,1);
.arrow-icon {
right: 6px;
position: absolute;
top: 35px;
height: 30px;
width: 32px;
font-size: 14px;
line-height: 30px;
color: #FFF;
text-align: center;
background-color: rgba(0,0,0,.3);
}
&--prev {
left: 0;
.arrow-icon {
left: 6px;
}
}
&--next {
right: 0;
.arrow-icon {
right: 6px;
}
}
}
}

View file

@ -2,9 +2,10 @@
.showMoreLess {
white-space: normal;
color: var(--link);
margin-right: 0.25em;
}
.mention-link:not(:last-child) {
.mention-link {
margin-right: 0.25em;
}
}

View file

@ -391,9 +391,20 @@ const PostStatusForm = {
this.$emit('resize')
},
editAttachment (fileInfo, newText) {
console.log(fileInfo, newText)
this.newStatus.mediaDescriptions[fileInfo.id] = newText
},
shiftUpMediaFile (fileInfo) {
const { files } = this.newStatus
const index = this.newStatus.files.indexOf(fileInfo)
files.splice(index, 1)
files.splice(index - 1, 0, fileInfo)
},
shiftDnMediaFile (fileInfo) {
const { files } = this.newStatus
const index = this.newStatus.files.indexOf(fileInfo)
files.splice(index, 1)
files.splice(index + 1, 0, fileInfo)
},
uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)

View file

@ -298,6 +298,8 @@
:editable="true"
:edit-attachment="editAttachment"
:remove-attachment="removeMediaFile"
:shift-up-attachment="newStatus.files.length > 1 && shiftUpMediaFile"
:shift-dn-attachment="newStatus.files.length > 1 && shiftDnMediaFile"
@play="$emit('mediaplay', attachment.id)"
@pause="$emit('mediapause', attachment.id)"
/>

View file

@ -121,6 +121,13 @@ export default Vue.component('RichContent', {
// in MentionsLine
return currentMentions !== null ? item.trim() : item
}
// We add space with mentionsLine, otherwise non-text elements will
// stick to them.
if (currentMentions !== null) {
// single whitespace trim
item = item[0].match(/\s/) ? item.slice(1) : item
}
currentMentions = null
if (item.includes(':')) {
item = ['', processTextForEmoji(

View file

@ -50,6 +50,7 @@ const StatusContent = {
// Using max-height + overflow: auto for status components resulted in false positives
// very often with japanese characters, and it was very annoying.
tallStatus () {
if (this.singleLine || this.compact) return false
const lengthScore = this.status.raw_html.split(/<p|<br/).length + this.postLength / 80
return lengthScore > 20
},

View file

@ -10,13 +10,20 @@
:single-line="singleLine"
@parseReady="$emit('parseReady', $event)"
>
<div v-if="status.poll && status.poll.options">
<div v-if="status.poll && status.poll.options && !compact">
<Poll
:base-poll="status.poll"
:emoji="status.emojis"
/>
</div>
<div v-else-if="status.poll && status.poll.options && compact">
<FAIcon
icon="poll-h"
size="2x"
/>
</div>
<gallery
v-if="status.attachments.length !== 0"
class="attachments media-body"
@ -24,7 +31,6 @@
:attachments="status.attachments"
:limit="compact ? 1 : 0"
:size="attachmentSize"
@setMedia="onMedia"
@play="$emit('mediaplay', attachment.id)"
@pause="$emit('mediapause', attachment.id)"
/>

View file

@ -727,6 +727,13 @@
"many_attachments": "Post has {number} attachment(s)",
"collapse_attachments": "Collapse attachments",
"show_all_attachments": "Show all attachments",
"show_attachment_in_modal": "Show in media modal",
"show_attachment_description": "Preview description (open attachment for full description)",
"hide_attachment": "Hide attachment",
"remove_attachment": "Remove attachment",
"attachment_stop_flash": "Stop Flash player",
"move_up": "Shift attachment left",
"move_down": "Shift attachment right",
"open_gallery": "Open gallery"
},
"user_card": {

View file

@ -59,7 +59,7 @@ describe('RichContent', () => {
it('replaces mention with mentionsline', () => {
const html = p(
makeMention('John'),
' how are you doing today?'
' how are you doing today?' // also testing single-trimming function
)
const wrapper = shallowMount(RichContent, {
localVue,
@ -74,7 +74,7 @@ describe('RichContent', () => {
expect(wrapper.html()).to.eql(compwrap(p(
mentionsLine(1),
' how are you doing today?'
' how are you doing today?' // space removed to compensate for <ML> padding
)))
})