resolve merge conflicts
This commit is contained in:
commit
643e087404
108 changed files with 4053 additions and 1928 deletions
53
CHANGELOG.md
53
CHANGELOG.md
|
@ -3,6 +3,56 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## Unreleased
|
||||
### Fixed
|
||||
- AdminFE button no longer scrolls page to top when clicked
|
||||
- Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough)
|
||||
- Fixed many many bugs related to new mentions, including spacing and alignment issues
|
||||
- Links in profile bios now properly open in new tabs
|
||||
- Inline images now respect their intended width/height attributes
|
||||
- Links with `&` in them work properly now
|
||||
- Interaction list popovers now properly emojify names
|
||||
- Completely hidden posts still had 1px border
|
||||
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
|
||||
- Attachment description is prefilled with backend-provided default when uploading
|
||||
- Proper visual feedback that next image is loading when browsing
|
||||
|
||||
### Changed
|
||||
- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out)
|
||||
- User highlight background now also covers the `@`
|
||||
- Reverted back to textual `@`, svg version is opt-in.
|
||||
- Settings window has been throughly rearranged to make make more sense and make navication settings easier.
|
||||
- Uploaded attachments are uniform with displayed attachments
|
||||
- Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues)
|
||||
- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post.
|
||||
|
||||
### Added
|
||||
- Options to show domains in mentions
|
||||
- Option to show user avatars in mention links (opt-in)
|
||||
- Option to disable the tooltip for mentions
|
||||
- Option to completely hide muted threads
|
||||
- Ability to open videos in modal even if you disabled that feature, via an icon button
|
||||
- New button on attachment that indicates that attachment has a description and shows a bar filled with description
|
||||
- Attachments are truncated just like post contents
|
||||
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
|
||||
- Ability to rearrange order of attachments when uploading
|
||||
- Enabled users to zoom and pan images in media viewer with mouse and touch
|
||||
|
||||
|
||||
## [2.4.2] - 2022-01-09
|
||||
### Added
|
||||
- Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel
|
||||
- Implemented user option to always show floating New Post button (normally mobile-only)
|
||||
- Display reasons for instance specific policies
|
||||
- Added functionality to cancel follow request
|
||||
|
||||
### Fixed
|
||||
- Fixed link to external profile not working on user profiles
|
||||
- Fixed mobile shoutbox display
|
||||
- Fixed favicon badge not working in Chrome
|
||||
- Escape html more properly in subject/display name
|
||||
|
||||
|
||||
## [2.4.0] - 2021-08-08
|
||||
### Added
|
||||
- Added a quick settings to timeline header for easier access
|
||||
|
@ -11,12 +61,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Implemented user option to change sidebar position to the right side
|
||||
- Implemented user option to hide floating shout panel
|
||||
- Implemented "edit profile" button if viewing own profile which opens profile settings
|
||||
- Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel
|
||||
- Implemented user option to always show floating New Post button (normally mobile-only)
|
||||
|
||||
### Fixed
|
||||
- Fixed follow request count showing in the wrong location in mobile view
|
||||
|
||||
|
||||
## [2.3.0] - 2021-03-01
|
||||
### Fixed
|
||||
- Button to remove uploaded media in post status form is now properly placed and sized.
|
||||
|
|
196
package.json
196
package.json
|
@ -16,106 +16,108 @@
|
|||
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"@chenfengyuan/vue-qrcode": "^1.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.0",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "https://lily.kazv.moe/infra/pinch-zoom-element.git",
|
||||
"body-scroll-lock": "^2.6.4",
|
||||
"chromatism": "^3.0.0",
|
||||
"cropperjs": "^1.4.3",
|
||||
"diff": "^3.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"localforage": "^1.5.0",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"phoenix": "^1.3.0",
|
||||
"portal-vue": "^2.1.4",
|
||||
"punycode.js": "^2.1.0",
|
||||
"v-click-outside": "^2.1.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^7.3.2",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuelidate": "^0.7.4",
|
||||
"vuex": "^3.0.1"
|
||||
"@babel/runtime": "7.7.6",
|
||||
"@chenfengyuan/vue-qrcode": "1.0.2",
|
||||
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "2.0.6",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||
"body-scroll-lock": "2.6.4",
|
||||
"chromatism": "3.0.0",
|
||||
"cropperjs": "1.4.3",
|
||||
"diff": "3.5.0",
|
||||
"escape-html": "1.0.3",
|
||||
"localforage": "1.7.3",
|
||||
"parse-link-header": "1.0.1",
|
||||
"phoenix": "1.4.0",
|
||||
"portal-vue": "2.1.7",
|
||||
"punycode.js": "2.1.0",
|
||||
"ruffle-mirror": "2021.4.11",
|
||||
"v-click-outside": "2.1.5",
|
||||
"vue": "2.6.11",
|
||||
"vue-i18n": "7.8.1",
|
||||
"vue-router": "3.0.2",
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vuelidate": "0.7.7",
|
||||
"vuex": "3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"@babel/register": "^7.7.4",
|
||||
"@ungap/event-target": "^0.1.0",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
|
||||
"@vue/babel-preset-jsx": "^1.2.4",
|
||||
"@vue/test-utils": "^1.0.0-beta.26",
|
||||
"autoprefixer": "^6.4.0",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"chai": "^3.5.0",
|
||||
"chalk": "^1.1.3",
|
||||
"chromedriver": "^87.0.1",
|
||||
"connect-history-api-fallback": "^1.1.0",
|
||||
"cross-spawn": "^4.0.2",
|
||||
"css-loader": "^0.28.0",
|
||||
"custom-event-polyfill": "^1.0.7",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-friendly-formatter": "^2.0.5",
|
||||
"eslint-loader": "^2.1.0",
|
||||
"eslint-plugin-import": "^2.13.0",
|
||||
"eslint-plugin-node": "^7.0.0",
|
||||
"eslint-plugin-promise": "^4.0.0",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^5.2.2",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"express": "^4.13.3",
|
||||
"file-loader": "^3.0.1",
|
||||
"function-bind": "^1.0.2",
|
||||
"html-webpack-plugin": "^3.0.0",
|
||||
"http-proxy-middleware": "^0.17.2",
|
||||
"inject-loader": "^2.0.1",
|
||||
"iso-639-1": "^2.0.3",
|
||||
"isparta-loader": "^2.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^3.0.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-firefox-launcher": "^1.1.0",
|
||||
"karma-mocha": "^1.2.0",
|
||||
"karma-mocha-reporter": "^2.2.1",
|
||||
"karma-sinon-chai": "^2.0.2",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
"karma-webpack": "^4.0.0-rc.3",
|
||||
"lodash": "^4.16.4",
|
||||
"lolex": "^1.4.0",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"mocha": "^3.1.0",
|
||||
"nightwatch": "^0.9.8",
|
||||
"opn": "^4.0.2",
|
||||
"ora": "^0.3.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"sass": "^1.17.3",
|
||||
"sass-loader": "git://github.com/webpack-contrib/sass-loader",
|
||||
"@babel/core": "7.7.5",
|
||||
"@babel/plugin-transform-runtime": "7.7.6",
|
||||
"@babel/preset-env": "7.7.6",
|
||||
"@babel/register": "7.7.4",
|
||||
"@ungap/event-target": "0.2.3",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
|
||||
"@vue/babel-preset-jsx": "1.2.4",
|
||||
"@vue/test-utils": "1.0.0-beta.28",
|
||||
"autoprefixer": "6.7.7",
|
||||
"babel-eslint": "7.2.3",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-lodash": "3.3.4",
|
||||
"chai": "3.5.0",
|
||||
"chalk": "1.1.3",
|
||||
"chromedriver": "87.0.7",
|
||||
"connect-history-api-fallback": "1.6.0",
|
||||
"copy-webpack-plugin": "6.4.1",
|
||||
"cross-spawn": "4.0.2",
|
||||
"css-loader": "0.28.11",
|
||||
"custom-event-polyfill": "1.0.7",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-standard": "12.0.0",
|
||||
"eslint-friendly-formatter": "2.0.7",
|
||||
"eslint-loader": "2.1.2",
|
||||
"eslint-plugin-import": "2.17.2",
|
||||
"eslint-plugin-node": "7.0.1",
|
||||
"eslint-plugin-promise": "4.1.1",
|
||||
"eslint-plugin-standard": "4.0.0",
|
||||
"eslint-plugin-vue": "5.2.3",
|
||||
"eventsource-polyfill": "0.9.6",
|
||||
"express": "4.16.4",
|
||||
"file-loader": "3.0.1",
|
||||
"function-bind": "1.1.1",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"http-proxy-middleware": "0.17.4",
|
||||
"inject-loader": "2.0.1",
|
||||
"iso-639-1": "2.0.3",
|
||||
"isparta-loader": "2.0.0",
|
||||
"json-loader": "0.5.7",
|
||||
"karma": "3.1.4",
|
||||
"karma-coverage": "1.1.2",
|
||||
"karma-firefox-launcher": "1.1.0",
|
||||
"karma-mocha": "1.3.0",
|
||||
"karma-mocha-reporter": "2.2.5",
|
||||
"karma-sinon-chai": "2.0.2",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-spec-reporter": "0.0.33",
|
||||
"karma-webpack": "4.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"lolex": "1.6.0",
|
||||
"mini-css-extract-plugin": "0.5.0",
|
||||
"mocha": "3.5.3",
|
||||
"nightwatch": "0.9.21",
|
||||
"opn": "4.0.2",
|
||||
"ora": "0.3.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"sass": "1.20.1",
|
||||
"sass-loader": "7.2.0",
|
||||
"selenium-server": "2.53.1",
|
||||
"semver": "^5.3.0",
|
||||
"serviceworker-webpack-plugin": "^1.0.0",
|
||||
"shelljs": "^0.8.4",
|
||||
"sinon": "^2.1.0",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"stylelint": "^13.6.1",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-rscss": "^0.4.0",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-loader": "^14.0.0",
|
||||
"vue-style-loader": "^4.0.0",
|
||||
"webpack": "^4.0.0",
|
||||
"webpack-dev-middleware": "^3.6.0",
|
||||
"webpack-hot-middleware": "^2.12.2",
|
||||
"webpack-merge": "^0.14.1"
|
||||
"semver": "5.6.0",
|
||||
"serviceworker-webpack-plugin": "1.0.1",
|
||||
"shelljs": "0.8.5",
|
||||
"sinon": "2.4.1",
|
||||
"sinon-chai": "2.14.0",
|
||||
"stylelint": "13.6.1",
|
||||
"stylelint-config-standard": "20.0.0",
|
||||
"stylelint-rscss": "0.4.0",
|
||||
"url-loader": "1.1.2",
|
||||
"vue-loader": "14.2.4",
|
||||
"vue-style-loader": "4.1.2",
|
||||
"webpack": "4.46.0",
|
||||
"webpack-dev-middleware": "3.7.3",
|
||||
"webpack-hot-middleware": "2.24.3",
|
||||
"webpack-merge": "0.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
|
|
6
renovate.json
Normal file
6
renovate.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
|
@ -115,6 +115,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
|||
copyInstanceOption('nsfwCensorImage')
|
||||
copyInstanceOption('background')
|
||||
copyInstanceOption('hidePostStats')
|
||||
copyInstanceOption('hideBotIndication')
|
||||
copyInstanceOption('hideUserStats')
|
||||
copyInstanceOption('hideFilteredStatuses')
|
||||
copyInstanceOption('logo')
|
||||
|
|
|
@ -10,7 +10,12 @@ import {
|
|||
faImage,
|
||||
faVideo,
|
||||
faPlayCircle,
|
||||
faTimes
|
||||
faTimes,
|
||||
faStop,
|
||||
faSearchPlus,
|
||||
faTrashAlt,
|
||||
faPencilAlt,
|
||||
faAlignRight
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
|
@ -19,27 +24,39 @@ library.add(
|
|||
faImage,
|
||||
faVideo,
|
||||
faPlayCircle,
|
||||
faTimes
|
||||
faTimes,
|
||||
faStop,
|
||||
faSearchPlus,
|
||||
faTrashAlt,
|
||||
faPencilAlt,
|
||||
faAlignRight
|
||||
)
|
||||
|
||||
const Attachment = {
|
||||
props: [
|
||||
'attachment',
|
||||
'description',
|
||||
'hideDescription',
|
||||
'nsfw',
|
||||
'size',
|
||||
'allowPlay',
|
||||
'setMedia',
|
||||
'naturalSizeLoad'
|
||||
'remove',
|
||||
'shiftUp',
|
||||
'shiftDn',
|
||||
'edit'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
localDescription: this.description || this.attachment.description,
|
||||
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
|
||||
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
|
||||
preloadImage: this.$store.getters.mergedConfig.preloadImage,
|
||||
loading: false,
|
||||
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
|
||||
modalOpen: false,
|
||||
showHidden: false
|
||||
showHidden: false,
|
||||
flashLoaded: false,
|
||||
showDescription: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -47,8 +64,23 @@ const Attachment = {
|
|||
VideoAttachment
|
||||
},
|
||||
computed: {
|
||||
classNames () {
|
||||
return [
|
||||
{
|
||||
'-loading': this.loading,
|
||||
'-nsfw-placeholder': this.hidden,
|
||||
'-editable': this.edit !== undefined
|
||||
},
|
||||
'-type-' + this.type,
|
||||
this.size && '-size-' + this.size,
|
||||
`-${this.useContainFit ? 'contain' : 'cover'}-fit`
|
||||
]
|
||||
},
|
||||
usePlaceholder () {
|
||||
return this.size === 'hide' || this.type === 'unknown'
|
||||
return this.size === 'hide'
|
||||
},
|
||||
useContainFit () {
|
||||
return this.$store.getters.mergedConfig.useContainFit
|
||||
},
|
||||
placeholderName () {
|
||||
if (this.attachment.description === '' || !this.attachment.description) {
|
||||
|
@ -72,24 +104,33 @@ const Attachment = {
|
|||
return this.nsfw && this.hideNsfwLocal && !this.showHidden
|
||||
},
|
||||
isEmpty () {
|
||||
return (this.type === 'html' && !this.attachment.oembed) || this.type === 'unknown'
|
||||
},
|
||||
isSmall () {
|
||||
return this.size === 'small'
|
||||
},
|
||||
fullwidth () {
|
||||
if (this.size === 'hide') return false
|
||||
return this.type === 'html' || this.type === 'audio' || this.type === 'unknown'
|
||||
return (this.type === 'html' && !this.attachment.oembed)
|
||||
},
|
||||
useModal () {
|
||||
const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio']
|
||||
: this.mergedConfig.playVideosInModal
|
||||
? ['image', 'video']
|
||||
: ['image']
|
||||
let modalTypes = []
|
||||
switch (this.size) {
|
||||
case 'hide':
|
||||
case 'small':
|
||||
modalTypes = ['image', 'video', 'audio', 'flash']
|
||||
break
|
||||
default:
|
||||
modalTypes = this.mergedConfig.playVideosInModal
|
||||
? ['image', 'video', 'flash']
|
||||
: ['image']
|
||||
break
|
||||
}
|
||||
return modalTypes.includes(this.type)
|
||||
},
|
||||
videoTag () {
|
||||
return this.useModal ? 'button' : 'span'
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
watch: {
|
||||
localDescription (newVal) {
|
||||
this.onEdit(newVal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
linkClicked ({ target }) {
|
||||
if (target.tagName === 'A') {
|
||||
|
@ -98,12 +139,37 @@ const Attachment = {
|
|||
},
|
||||
openModal (event) {
|
||||
if (this.useModal) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
this.setMedia()
|
||||
this.$store.dispatch('setCurrent', this.attachment)
|
||||
this.$emit('setMedia')
|
||||
this.$store.dispatch('setCurrentMedia', this.attachment)
|
||||
} else if (this.type === 'unknown') {
|
||||
window.open(this.attachment.url)
|
||||
}
|
||||
},
|
||||
openModalForce (event) {
|
||||
this.$emit('setMedia')
|
||||
this.$store.dispatch('setCurrentMedia', this.attachment)
|
||||
},
|
||||
onEdit (event) {
|
||||
this.edit && this.edit(this.attachment, event)
|
||||
},
|
||||
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) &&
|
||||
|
@ -130,7 +196,7 @@ const Attachment = {
|
|||
onImageLoad (image) {
|
||||
const width = image.naturalWidth
|
||||
const height = image.naturalHeight
|
||||
this.naturalSizeLoad && this.naturalSizeLoad({ width, height })
|
||||
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
268
src/components/attachment/attachment.scss
Normal file
268
src/components/attachment/attachment.scss
Normal file
|
@ -0,0 +1,268 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.Attachment {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
align-self: flex-start;
|
||||
line-height: 0;
|
||||
height: 100%;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: $fallback--attachmentRadius;
|
||||
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
|
||||
.attachment-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.description-container {
|
||||
flex: 0 1 0;
|
||||
display: flex;
|
||||
padding-top: 0.5em;
|
||||
z-index: 1;
|
||||
|
||||
p {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.-static {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding-top: 0;
|
||||
background: var(--popover);
|
||||
box-shadow: var(--popupShadow);
|
||||
}
|
||||
}
|
||||
|
||||
.description-field {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
& .placeholder-container,
|
||||
& .image-container,
|
||||
& .audio-container,
|
||||
& .video-container,
|
||||
& .flash-container,
|
||||
& .oembed-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
& .flash-container,
|
||||
& .video-container {
|
||||
& .flash,
|
||||
& video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-container {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
audio {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
font-size: 64px;
|
||||
top: calc(50% - 32px);
|
||||
left: calc(50% - 32px);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
|
||||
|
||||
&::before {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-buttons {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin-top: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
z-index: 1;
|
||||
|
||||
.attachment-button {
|
||||
padding: 0;
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
margin-left: 0.5em;
|
||||
font-size: 1.25em;
|
||||
// TODO: theming? hard to theme with unknown background image color
|
||||
background: rgba(230, 230, 230, 0.7);
|
||||
|
||||
.svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
&:hover .svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.oembed-container {
|
||||
line-height: 1.2em;
|
||||
flex: 1 0 100%;
|
||||
width: 100%;
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image {
|
||||
flex: 1;
|
||||
img {
|
||||
border: 0px;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 2;
|
||||
margin: 8px;
|
||||
word-break: break-all;
|
||||
h1 {
|
||||
font-size: 14px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.-size-small {
|
||||
.play-icon {
|
||||
zoom: 0.5;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.attachment-buttons {
|
||||
zoom: 0.7;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.-editable {
|
||||
padding: 0.5em;
|
||||
|
||||
& .description-container,
|
||||
& .attachment-buttons {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.-placeholder {
|
||||
display: inline-block;
|
||||
color: $fallback--link;
|
||||
color: var(--postLink, $fallback--link);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
|
||||
&:not(.-editable) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.-editable {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
& .description-container,
|
||||
& .attachment-buttons {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.description-container {
|
||||
flex: 1;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.attachment-buttons {
|
||||
order: 99;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&.-loading {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
&.-contain-fit {
|
||||
img,
|
||||
canvas {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&.-cover-fit {
|
||||
img,
|
||||
canvas {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div
|
||||
<button
|
||||
v-if="usePlaceholder"
|
||||
:class="{ 'fullwidth': fullwidth }"
|
||||
class="Attachment -placeholder button-unstyled"
|
||||
:class="classNames"
|
||||
@click="openModal"
|
||||
>
|
||||
<a
|
||||
|
@ -11,312 +12,257 @@
|
|||
:href="attachment.url"
|
||||
:alt="attachment.description"
|
||||
:title="attachment.description"
|
||||
@click.prevent
|
||||
>
|
||||
<FAIcon :icon="placeholderIconClass" />
|
||||
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
|
||||
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ edit ? '' : placeholderName }}
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-if="edit || remove"
|
||||
class="attachment-buttons"
|
||||
>
|
||||
<button
|
||||
v-if="remove"
|
||||
class="button-unstyled attachment-button"
|
||||
@click.prevent="onRemove"
|
||||
>
|
||||
<FAIcon icon="trash-alt" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="size !== 'hide' && !hideDescription && (edit || localDescription || showDescription)"
|
||||
class="description-container"
|
||||
:class="{ '-static': !edit }"
|
||||
>
|
||||
<input
|
||||
v-if="edit"
|
||||
v-model="localDescription"
|
||||
type="text"
|
||||
class="description-field"
|
||||
:placeholder="$t('post_status.media_description')"
|
||||
@keydown.enter.prevent=""
|
||||
>
|
||||
<p v-else>
|
||||
{{ localDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-else
|
||||
v-show="!isEmpty"
|
||||
class="attachment"
|
||||
:class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
|
||||
class="Attachment"
|
||||
:class="classNames"
|
||||
>
|
||||
<a
|
||||
v-if="hidden"
|
||||
class="image-attachment"
|
||||
:href="attachment.url"
|
||||
:alt="attachment.description"
|
||||
:title="attachment.description"
|
||||
@click.prevent.stop="toggleHidden"
|
||||
>
|
||||
<img
|
||||
:key="nsfwImage"
|
||||
class="nsfw"
|
||||
:src="nsfwImage"
|
||||
:class="{'small': isSmall}"
|
||||
>
|
||||
<FAIcon
|
||||
v-if="type === 'video'"
|
||||
class="play-icon"
|
||||
icon="play-circle"
|
||||
/>
|
||||
</a>
|
||||
<button
|
||||
v-if="nsfw && hideNsfwLocal && !hidden"
|
||||
class="button-unstyled hider"
|
||||
@click.prevent="toggleHidden"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-if="type === 'image' && (!hidden || preloadImage)"
|
||||
class="image-attachment"
|
||||
:class="{'hidden': hidden && preloadImage }"
|
||||
:href="attachment.url"
|
||||
target="_blank"
|
||||
@click="openModal"
|
||||
>
|
||||
<StillImage
|
||||
class="image"
|
||||
:referrerpolicy="referrerpolicy"
|
||||
:mimetype="attachment.mimetype"
|
||||
:src="attachment.large_thumb_url || attachment.url"
|
||||
:image-load-handler="onImageLoad"
|
||||
:alt="attachment.description"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
v-if="type === 'video' && !hidden"
|
||||
class="video-container"
|
||||
:class="{'small': isSmall}"
|
||||
:href="allowPlay ? undefined : attachment.url"
|
||||
@click="openModal"
|
||||
>
|
||||
<VideoAttachment
|
||||
class="video"
|
||||
:attachment="attachment"
|
||||
:controls="allowPlay"
|
||||
@play="$emit('play')"
|
||||
@pause="$emit('pause')"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="!allowPlay"
|
||||
class="play-icon"
|
||||
icon="play-circle"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<audio
|
||||
v-if="type === 'audio'"
|
||||
:src="attachment.url"
|
||||
:alt="attachment.description"
|
||||
:title="attachment.description"
|
||||
controls
|
||||
@play="$emit('play')"
|
||||
@pause="$emit('pause')"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="type === 'html' && attachment.oembed"
|
||||
class="oembed"
|
||||
@click.prevent="linkClicked"
|
||||
v-show="!isEmpty"
|
||||
class="attachment-wrapper"
|
||||
>
|
||||
<div
|
||||
v-if="attachment.thumb_url"
|
||||
class="image"
|
||||
<a
|
||||
v-if="hidden"
|
||||
class="image-container"
|
||||
:href="attachment.url"
|
||||
:alt="attachment.description"
|
||||
:title="attachment.description"
|
||||
@click.prevent.stop="toggleHidden"
|
||||
>
|
||||
<img :src="attachment.thumb_url">
|
||||
<img
|
||||
:key="nsfwImage"
|
||||
class="nsfw"
|
||||
:src="nsfwImage"
|
||||
>
|
||||
<FAIcon
|
||||
v-if="type === 'video'"
|
||||
class="play-icon"
|
||||
icon="play-circle"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
v-if="!hidden"
|
||||
class="attachment-buttons"
|
||||
>
|
||||
<button
|
||||
v-if="type === 'flash' && flashLoaded"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.attachment_stop_flash')"
|
||||
@click.prevent="stopFlash"
|
||||
>
|
||||
<FAIcon icon="stop" />
|
||||
</button>
|
||||
<button
|
||||
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.show_attachment_description')"
|
||||
@click.prevent="toggleDescription"
|
||||
>
|
||||
<FAIcon icon="align-right" />
|
||||
</button>
|
||||
<button
|
||||
v-if="!useModal && type !== 'unknown'"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.show_attachment_in_modal')"
|
||||
@click.prevent="openModalForce"
|
||||
>
|
||||
<FAIcon icon="search-plus" />
|
||||
</button>
|
||||
<button
|
||||
v-if="nsfw && hideNsfwLocal"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.hide_attachment')"
|
||||
@click.prevent="toggleHidden"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
</button>
|
||||
<button
|
||||
v-if="shiftUp"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.move_up')"
|
||||
@click.prevent="onShiftUp"
|
||||
>
|
||||
<FAIcon icon="chevron-left" />
|
||||
</button>
|
||||
<button
|
||||
v-if="shiftDn"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.move_down')"
|
||||
@click.prevent="onShiftDn"
|
||||
>
|
||||
<FAIcon icon="chevron-right" />
|
||||
</button>
|
||||
<button
|
||||
v-if="remove"
|
||||
class="button-unstyled attachment-button"
|
||||
:title="$t('status.remove_attachment')"
|
||||
@click.prevent="onRemove"
|
||||
>
|
||||
<FAIcon icon="trash-alt" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="text">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<h1><a :href="attachment.url">{{ attachment.oembed.title }}</a></h1>
|
||||
<div v-html="attachment.oembed.oembedHTML" />
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
|
||||
<a
|
||||
v-if="type === 'image' && (!hidden || preloadImage)"
|
||||
class="image-container"
|
||||
:class="{'-hidden': hidden && preloadImage }"
|
||||
:href="attachment.url"
|
||||
target="_blank"
|
||||
@click.stop.prevent="openModal"
|
||||
>
|
||||
<StillImage
|
||||
class="image"
|
||||
:referrerpolicy="referrerpolicy"
|
||||
:mimetype="attachment.mimetype"
|
||||
:src="attachment.large_thumb_url || attachment.url"
|
||||
:image-load-handler="onImageLoad"
|
||||
:alt="attachment.description"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a
|
||||
v-if="type === 'unknown' && !hidden"
|
||||
class="placeholder-container"
|
||||
:href="attachment.url"
|
||||
target="_blank"
|
||||
>
|
||||
<FAIcon
|
||||
size="5x"
|
||||
:icon="placeholderIconClass"
|
||||
/>
|
||||
<p>
|
||||
{{ localDescription }}
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<component
|
||||
:is="videoTag"
|
||||
v-if="type === 'video' && !hidden"
|
||||
class="video-container"
|
||||
:class="{ 'button-unstyled': 'isModal' }"
|
||||
:href="attachment.url"
|
||||
@click.stop.prevent="openModal"
|
||||
>
|
||||
<VideoAttachment
|
||||
class="video"
|
||||
:attachment="attachment"
|
||||
:controls="!useModal"
|
||||
@play="$emit('play')"
|
||||
@pause="$emit('pause')"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="useModal"
|
||||
class="play-icon"
|
||||
icon="play-circle"
|
||||
/>
|
||||
</component>
|
||||
|
||||
<span
|
||||
v-if="type === 'audio' && !hidden"
|
||||
class="audio-container"
|
||||
:href="attachment.url"
|
||||
@click.stop.prevent="openModal"
|
||||
>
|
||||
<audio
|
||||
v-if="type === 'audio'"
|
||||
:src="attachment.url"
|
||||
:alt="attachment.description"
|
||||
:title="attachment.description"
|
||||
controls
|
||||
@play="$emit('play')"
|
||||
@pause="$emit('pause')"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<div
|
||||
v-if="type === 'html' && attachment.oembed"
|
||||
class="oembed-container"
|
||||
@click.prevent="linkClicked"
|
||||
>
|
||||
<div
|
||||
v-if="attachment.thumb_url"
|
||||
class="image"
|
||||
>
|
||||
<img :src="attachment.thumb_url">
|
||||
</div>
|
||||
<div class="text">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<h1><a :href="attachment.url">{{ attachment.oembed.title }}</a></h1>
|
||||
<div v-html="attachment.oembed.oembedHTML" />
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="type === 'flash' && !hidden"
|
||||
class="flash-container"
|
||||
:href="attachment.url"
|
||||
@click.stop.prevent="openModal"
|
||||
>
|
||||
<Flash
|
||||
ref="flash"
|
||||
class="flash"
|
||||
:src="attachment.large_thumb_url || attachment.url"
|
||||
@playerOpened="setFlashLoaded(true)"
|
||||
@playerClosed="setFlashLoaded(false)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="size !== 'hide' && !hideDescription && (edit || (localDescription && showDescription))"
|
||||
class="description-container"
|
||||
:class="{ '-static': !edit }"
|
||||
>
|
||||
<input
|
||||
v-if="edit"
|
||||
v-model="localDescription"
|
||||
type="text"
|
||||
class="description-field"
|
||||
:placeholder="$t('post_status.media_description')"
|
||||
@keydown.enter.prevent=""
|
||||
>
|
||||
<p v-else>
|
||||
{{ localDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./attachment.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.non-gallery {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: inline-block;
|
||||
padding: 0.3em 1em 0.3em 0;
|
||||
color: $fallback--link;
|
||||
color: var(--postLink, $fallback--link);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
|
||||
svg {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.nsfw-placeholder {
|
||||
cursor: pointer;
|
||||
|
||||
&.loading {
|
||||
cursor: progress;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment {
|
||||
position: relative;
|
||||
margin-top: 0.5em;
|
||||
align-self: flex-start;
|
||||
line-height: 0;
|
||||
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: $fallback--attachmentRadius;
|
||||
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.non-gallery.attachment {
|
||||
&.video {
|
||||
flex: 1 0 40%;
|
||||
}
|
||||
.nsfw {
|
||||
height: 260px;
|
||||
}
|
||||
.small {
|
||||
height: 120px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
.video {
|
||||
height: 260px;
|
||||
display: flex;
|
||||
}
|
||||
video {
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
// fixes small gap below video
|
||||
&.video {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
display: flex;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
font-size: 64px;
|
||||
top: calc(50% - 32px);
|
||||
left: calc(50% - 32px);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.play-icon::before {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.html {
|
||||
flex-basis: 90%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hider {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 10px;
|
||||
padding: 0;
|
||||
z-index: 4;
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
font-size: 1.25em;
|
||||
// TODO: theming? hard to theme with unknown background image color
|
||||
background: rgba(230, 230, 230, 0.7);
|
||||
.svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
&:hover .svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
video {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
audio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img.media-upload {
|
||||
line-height: 0;
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.oembed {
|
||||
line-height: 1.2em;
|
||||
flex: 1 0 100%;
|
||||
width: 100%;
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image {
|
||||
flex: 1;
|
||||
img {
|
||||
border: 0px;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 2;
|
||||
margin: 8px;
|
||||
word-break: break-all;
|
||||
h1 {
|
||||
font-size: 14px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-attachment {
|
||||
&,
|
||||
& .image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nsfw {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
image-orientation: from-image; // NOTE: only FF supports this
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style src="./attachment.scss" lang="scss"></style>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.chat-message-wrapper {
|
||||
|
||||
&.hovered-message-chain {
|
||||
.animated.Avatar {
|
||||
canvas {
|
||||
|
@ -40,6 +41,12 @@
|
|||
.chat-message {
|
||||
display: flex;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.status-body:hover {
|
||||
--_still-image-img-visibility: visible;
|
||||
--_still-image-canvas-visibility: hidden;
|
||||
--_still-image-label-visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
|
@ -62,10 +69,6 @@
|
|||
&.with-media {
|
||||
width: 100%;
|
||||
|
||||
.gallery-row {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="chat-title"
|
||||
:title="title"
|
||||
|
@ -14,12 +13,13 @@
|
|||
height="23px"
|
||||
/>
|
||||
</router-link>
|
||||
<span
|
||||
<RichContent
|
||||
class="username"
|
||||
v-html="htmlTitle"
|
||||
:title="'@'+user.screen_name_ui"
|
||||
:html="htmlTitle"
|
||||
:emoji="user.emoji"
|
||||
/>
|
||||
</div>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</template>
|
||||
|
||||
<script src="./chat_title.js"></script>
|
||||
|
@ -34,6 +34,8 @@
|
|||
white-space: nowrap;
|
||||
align-items: center;
|
||||
|
||||
--emoji-size: 14px;
|
||||
|
||||
.username {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -41,14 +43,6 @@
|
|||
display: inline;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.emoji {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
object-fit: contain
|
||||
}
|
||||
}
|
||||
|
||||
.Avatar {
|
||||
|
|
|
@ -81,14 +81,17 @@ const conversation = {
|
|||
return this.$store.getters.mergedConfig.conversationDisplay
|
||||
},
|
||||
isTreeView () {
|
||||
return this.displayStyle === 'tree' || this.displayStyle === 'simple_tree'
|
||||
return !this.isLinearView
|
||||
},
|
||||
treeViewIsSimple () {
|
||||
return this.displayStyle === 'simple_tree'
|
||||
return !this.$store.getters.mergedConfig.conversationTreeAdvanced
|
||||
},
|
||||
isLinearView () {
|
||||
return this.displayStyle === 'linear'
|
||||
},
|
||||
shouldFadeAncestors () {
|
||||
return this.$store.getters.mergedConfig.conversationTreeFadeAncestors
|
||||
},
|
||||
otherRepliesButtonPosition () {
|
||||
return this.$store.getters.mergedConfig.conversationOtherRepliesButton
|
||||
},
|
||||
|
@ -142,8 +145,6 @@ const conversation = {
|
|||
|
||||
return sortAndFilterConversation(conversation, this.status)
|
||||
},
|
||||
conversationDive () {
|
||||
},
|
||||
statusMap () {
|
||||
return this.conversation.reduce((res, s) => {
|
||||
res[s.id] = s
|
||||
|
|
|
@ -19,29 +19,29 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="conversation-body panel-body">
|
||||
<div
|
||||
v-if="shouldShowAllConversationButton"
|
||||
class="conversation-dive-to-top-level-box"
|
||||
>
|
||||
<i18n
|
||||
path="status.show_all_conversation_with_icon"
|
||||
tag="button"
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="diveToTopLevel"
|
||||
>
|
||||
<FAIcon
|
||||
place="icon"
|
||||
icon="angle-double-left"
|
||||
/>
|
||||
<span place="text">
|
||||
{{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
|
||||
</span>
|
||||
</i18n>
|
||||
</div>
|
||||
<div
|
||||
v-if="isTreeView"
|
||||
class="thread-body"
|
||||
>
|
||||
<div
|
||||
v-if="shouldShowAllConversationButton"
|
||||
class="conversation-dive-to-top-level-box"
|
||||
>
|
||||
<i18n
|
||||
path="status.show_all_conversation_with_icon"
|
||||
tag="button"
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="diveToTopLevel"
|
||||
>
|
||||
<FAIcon
|
||||
place="icon"
|
||||
icon="angle-double-left"
|
||||
/>
|
||||
<span place="text">
|
||||
{{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
|
||||
</span>
|
||||
</i18n>
|
||||
</div>
|
||||
<div
|
||||
v-if="shouldShowAncestors"
|
||||
class="thread-ancestors"
|
||||
|
@ -50,7 +50,7 @@
|
|||
v-for="status in ancestorsOf(diveRoot)"
|
||||
:key="status.id"
|
||||
class="thread-ancestor"
|
||||
:class="{'thread-ancestor-has-other-replies': getReplies(status.id).length > 1}"
|
||||
:class="{'thread-ancestor-has-other-replies': getReplies(status.id).length > 1, '-faded': shouldFadeAncestors}"
|
||||
>
|
||||
<status
|
||||
ref="statusComponent"
|
||||
|
@ -194,7 +194,7 @@
|
|||
|
||||
.Conversation {
|
||||
.conversation-dive-to-top-level-box {
|
||||
padding: $status-margin;
|
||||
padding: var(--status-margin, $status-margin);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
|
@ -206,17 +206,17 @@
|
|||
}
|
||||
|
||||
.thread-ancestors {
|
||||
margin-left: $status-margin;
|
||||
margin-left: var(--status-margin, $status-margin);
|
||||
border-left: 2px solid var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
.thread-ancestor .StatusContent {
|
||||
.thread-ancestor.-faded .StatusContent {
|
||||
--link: var(--faintLink);
|
||||
--text: var(--faint);
|
||||
color: var(--text);
|
||||
}
|
||||
.thread-ancestor-dive-box {
|
||||
padding-left: $status-margin;
|
||||
padding-left: var(--status-margin, $status-margin);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--border, $fallback--border);
|
||||
|
@ -229,8 +229,7 @@
|
|||
}
|
||||
}
|
||||
.thread-ancestor-dive-box-inner {
|
||||
padding: $status-margin;
|
||||
//border-left: 2px solid var(--border, $fallback--border);
|
||||
padding: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
.conversation-status {
|
||||
|
@ -263,10 +262,5 @@
|
|||
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
||||
border-bottom: 1px solid var(--border, $fallback--border);
|
||||
}
|
||||
/* &.-expanded { */
|
||||
/* .conversation-status:last-child { */
|
||||
/* border-bottom: none; */
|
||||
/* } */
|
||||
/* } */
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
href="/pleroma/admin/#/login-pleroma"
|
||||
class="nav-icon"
|
||||
target="_blank"
|
||||
@click.stop
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
|
||||
export default {
|
||||
props: ['relationship', 'labelFollowing', 'buttonClass'],
|
||||
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
|
||||
data () {
|
||||
return {
|
||||
inProgress: false
|
||||
|
@ -14,7 +14,7 @@ export default {
|
|||
if (this.inProgress || this.relationship.following) {
|
||||
return this.$t('user_card.follow_unfollow')
|
||||
} else if (this.relationship.requested) {
|
||||
return this.$t('user_card.follow_again')
|
||||
return this.$t('user_card.follow_cancel')
|
||||
} else {
|
||||
return this.$t('user_card.follow')
|
||||
}
|
||||
|
@ -29,11 +29,14 @@ export default {
|
|||
} else {
|
||||
return this.$t('user_card.follow')
|
||||
}
|
||||
},
|
||||
disabled () {
|
||||
return this.inProgress || this.user.deactivated
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick () {
|
||||
this.relationship.following ? this.unfollow() : this.follow()
|
||||
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
|
||||
},
|
||||
follow () {
|
||||
this.inProgress = true
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<button
|
||||
class="btn button-default follow-button"
|
||||
:class="{ toggled: isPressed }"
|
||||
:disabled="inProgress"
|
||||
:disabled="disabled"
|
||||
:title="title"
|
||||
@click="onClick"
|
||||
>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
:relationship="relationship"
|
||||
:label-following="$t('user_card.follow_unfollow')"
|
||||
class="follow-card-follow-button"
|
||||
:user="user"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
import Attachment from '../attachment/attachment.vue'
|
||||
import { chunk, last, dropRight, sumBy } from 'lodash'
|
||||
import { sumBy } from 'lodash'
|
||||
|
||||
const Gallery = {
|
||||
props: [
|
||||
'attachments',
|
||||
'limitRows',
|
||||
'descriptions',
|
||||
'limit',
|
||||
'nsfw',
|
||||
'setMedia'
|
||||
'setMedia',
|
||||
'size',
|
||||
'editable',
|
||||
'removeAttachment',
|
||||
'shiftUpAttachment',
|
||||
'shiftDnAttachment',
|
||||
'editAttachment',
|
||||
'grid'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
sizes: {}
|
||||
sizes: {},
|
||||
hidingLong: true
|
||||
}
|
||||
},
|
||||
components: { Attachment },
|
||||
|
@ -18,26 +29,70 @@ const Gallery = {
|
|||
if (!this.attachments) {
|
||||
return []
|
||||
}
|
||||
const rows = chunk(this.attachments, 3)
|
||||
if (last(rows).length === 1 && rows.length > 1) {
|
||||
// if 1 attachment on last row -> add it to the previous row instead
|
||||
const lastAttachment = last(rows)[0]
|
||||
const allButLastRow = dropRight(rows)
|
||||
last(allButLastRow).push(lastAttachment)
|
||||
return allButLastRow
|
||||
const attachments = this.limit > 0
|
||||
? this.attachments.slice(0, this.limit)
|
||||
: this.attachments
|
||||
if (this.size === 'hide') {
|
||||
return attachments.map(item => ({ minimal: true, items: [item] }))
|
||||
}
|
||||
const rows = this.grid
|
||||
? [{ grid: true, items: attachments }]
|
||||
: attachments.reduce((acc, attachment, i) => {
|
||||
if (attachment.mimetype.includes('audio')) {
|
||||
return [...acc, { audio: true, items: [attachment] }, { items: [] }]
|
||||
}
|
||||
if (!(
|
||||
attachment.mimetype.includes('image') ||
|
||||
attachment.mimetype.includes('video') ||
|
||||
attachment.mimetype.includes('flash')
|
||||
)) {
|
||||
return [...acc, { minimal: true, items: [attachment] }, { items: [] }]
|
||||
}
|
||||
const maxPerRow = 3
|
||||
const attachmentsRemaining = this.attachments.length - i + 1
|
||||
const currentRow = acc[acc.length - 1].items
|
||||
currentRow.push(attachment)
|
||||
if (currentRow.length >= maxPerRow && attachmentsRemaining > maxPerRow) {
|
||||
return [...acc, { items: [] }]
|
||||
} else {
|
||||
return acc
|
||||
}
|
||||
}, [{ items: [] }]).filter(_ => _.items.length > 0)
|
||||
return rows
|
||||
},
|
||||
useContainFit () {
|
||||
return this.$store.getters.mergedConfig.useContainFit
|
||||
attachmentsDimensionalScore () {
|
||||
return this.rows.reduce((acc, row) => {
|
||||
let size = 0
|
||||
if (row.minimal) {
|
||||
size += 1 / 8
|
||||
} else if (row.audio) {
|
||||
size += 1 / 4
|
||||
} else {
|
||||
size += 1 / (row.items.length + 0.6)
|
||||
}
|
||||
return acc + size
|
||||
}, 0)
|
||||
},
|
||||
tooManyAttachments () {
|
||||
if (this.editable || this.size === 'small') {
|
||||
return false
|
||||
} else if (this.size === 'hide') {
|
||||
return this.attachments.length > 8
|
||||
} else {
|
||||
return this.attachmentsDimensionalScore > 1
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onNaturalSizeLoad (id, size) {
|
||||
this.$set(this.sizes, id, size)
|
||||
onNaturalSizeLoad ({ id, width, height }) {
|
||||
this.$set(this.sizes, id, { width, height })
|
||||
},
|
||||
rowStyle (itemsPerRow) {
|
||||
return { 'padding-bottom': `${(100 / (itemsPerRow + 0.6))}%` }
|
||||
rowStyle (row) {
|
||||
if (row.audio) {
|
||||
return { 'padding-bottom': '25%' } // fixed reduced height for audio
|
||||
} else if (!row.minimal && !row.grid) {
|
||||
return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
|
||||
}
|
||||
},
|
||||
itemStyle (id, row) {
|
||||
const total = sumBy(row, item => this.getAspectRatio(item.id))
|
||||
|
@ -46,6 +101,16 @@ const Gallery = {
|
|||
getAspectRatio (id) {
|
||||
const size = this.sizes[id]
|
||||
return size ? size.width / size.height : 1
|
||||
},
|
||||
toggleHidingLong (event) {
|
||||
this.hidingLong = event
|
||||
},
|
||||
openGallery () {
|
||||
this.$store.dispatch('setMedia', this.attachments)
|
||||
this.$store.dispatch('setCurrentMedia', this.attachments[0])
|
||||
},
|
||||
onMedia () {
|
||||
this.$store.dispatch('setMedia', this.attachments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,84 @@
|
|||
<template>
|
||||
<div
|
||||
ref="galleryContainer"
|
||||
style="width: 100%;"
|
||||
class="Gallery"
|
||||
:class="{ '-long': tooManyAttachments && hidingLong }"
|
||||
>
|
||||
<div class="gallery-rows">
|
||||
<div
|
||||
v-for="(row, rowIndex) in rows"
|
||||
:key="rowIndex"
|
||||
class="gallery-row"
|
||||
:style="rowStyle(row)"
|
||||
:class="{ '-audio': row.audio, '-minimal': row.minimal, '-grid': grid }"
|
||||
>
|
||||
<div
|
||||
class="gallery-row-inner"
|
||||
:class="{ '-grid': grid }"
|
||||
>
|
||||
<Attachment
|
||||
v-for="(attachment, attachmentIndex) in row.items"
|
||||
:key="attachment.id"
|
||||
class="gallery-item"
|
||||
:nsfw="nsfw"
|
||||
:attachment="attachment"
|
||||
:allow-play="false"
|
||||
:size="size"
|
||||
:editable="editable"
|
||||
:remove="removeAttachment"
|
||||
:shift-up="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment"
|
||||
:shift-dn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment"
|
||||
:edit="editAttachment"
|
||||
:description="descriptions && descriptions[attachment.id]"
|
||||
:hide-description="size === 'small' || tooManyAttachments && hidingLong"
|
||||
:style="itemStyle(attachment.id, row.items)"
|
||||
@setMedia="onMedia"
|
||||
@naturalSizeLoad="onNaturalSizeLoad"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(row, index) in rows"
|
||||
:key="index"
|
||||
class="gallery-row"
|
||||
:style="rowStyle(row.length)"
|
||||
:class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"
|
||||
v-if="tooManyAttachments"
|
||||
class="many-attachments"
|
||||
>
|
||||
<div class="gallery-row-inner">
|
||||
<attachment
|
||||
v-for="attachment in row"
|
||||
:key="attachment.id"
|
||||
:set-media="setMedia"
|
||||
:nsfw="nsfw"
|
||||
:attachment="attachment"
|
||||
:allow-play="false"
|
||||
:natural-size-load="onNaturalSizeLoad.bind(null, attachment.id)"
|
||||
:style="itemStyle(attachment.id, row)"
|
||||
/>
|
||||
<div class="many-attachments-text">
|
||||
{{ $t("status.many_attachments", { number: attachments.length }) }}
|
||||
</div>
|
||||
<div class="many-attachments-buttons">
|
||||
<span
|
||||
v-if="!hidingLong"
|
||||
class="many-attachments-button"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click="toggleHidingLong(true)"
|
||||
>
|
||||
{{ $t("status.collapse_attachments") }}
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
v-if="hidingLong"
|
||||
class="many-attachments-button"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click="toggleHidingLong(false)"
|
||||
>
|
||||
{{ $t("status.show_all_attachments") }}
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
v-if="hidingLong"
|
||||
class="many-attachments-button"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click="openGallery"
|
||||
>
|
||||
{{ $t("status.open_gallery") }}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,12 +89,66 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.gallery-row {
|
||||
position: relative;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
margin-top: 0.5em;
|
||||
.Gallery {
|
||||
.gallery-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gallery-row {
|
||||
position: relative;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.-long {
|
||||
.gallery-rows {
|
||||
max-height: 25em;
|
||||
overflow: hidden;
|
||||
mask:
|
||||
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
|
||||
linear-gradient(to top, white, white);
|
||||
|
||||
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
}
|
||||
|
||||
.many-attachments-text {
|
||||
text-align: center;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.many-attachments-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.many-attachments-button {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
line-height: 2;
|
||||
|
||||
button {
|
||||
padding: 0 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.gallery-row {
|
||||
&.-grid,
|
||||
&.-minimal {
|
||||
height: auto;
|
||||
.gallery-row-inner {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery-row-inner {
|
||||
position: absolute;
|
||||
|
@ -48,9 +160,24 @@
|
|||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: stretch;
|
||||
|
||||
&.-grid {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-column-gap: 0.5em;
|
||||
grid-row-gap: 0.5em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
|
||||
|
||||
.gallery-item {
|
||||
margin: 0;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery-row-inner .attachment {
|
||||
.gallery-item {
|
||||
margin: 0 0.5em 0 0;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
|
@ -61,32 +188,5 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.image-attachment {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.contain-fit {
|
||||
img,
|
||||
video,
|
||||
canvas {
|
||||
object-fit: contain;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.cover-fit {
|
||||
img,
|
||||
video,
|
||||
canvas {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -8,12 +8,16 @@ import fileTypeService from '../../services/file_type/file_type.service.js'
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight
|
||||
faChevronRight,
|
||||
faCircleNotch,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faChevronLeft,
|
||||
faChevronRight
|
||||
faChevronRight,
|
||||
faCircleNotch,
|
||||
faTimes
|
||||
)
|
||||
|
||||
const MediaModal = {
|
||||
|
@ -22,7 +26,19 @@ const MediaModal = {
|
|||
VideoAttachment,
|
||||
PinchZoom,
|
||||
SwipeClick,
|
||||
Modal
|
||||
Modal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
swipeDirection: GestureService.DIRECTION_LEFT,
|
||||
swipeThreshold: () => {
|
||||
const considerableMoveRatio = 1 / 4
|
||||
return window.innerWidth * considerableMoveRatio
|
||||
},
|
||||
pinchZoomMinScale: 1,
|
||||
pinchZoomScaleResetLimit: 1.2
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showing () {
|
||||
|
@ -31,6 +47,9 @@ const MediaModal = {
|
|||
media () {
|
||||
return this.$store.state.mediaViewer.media
|
||||
},
|
||||
description () {
|
||||
return this.currentMedia.description
|
||||
},
|
||||
currentIndex () {
|
||||
return this.$store.state.mediaViewer.currentIndex
|
||||
},
|
||||
|
@ -41,21 +60,13 @@ const MediaModal = {
|
|||
return this.media.length > 1
|
||||
},
|
||||
type () {
|
||||
return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
swipeDirection: GestureService.DIRECTION_LEFT,
|
||||
swipeThreshold: () => {
|
||||
const considerableMoveRatio = 1 / 4
|
||||
return window.innerWidth * considerableMoveRatio
|
||||
},
|
||||
pinchZoomMinScale: 1,
|
||||
pinchZoomScaleResetLimit: 1.2
|
||||
return this.currentMedia ? this.getType(this.currentMedia) : null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getType (media) {
|
||||
return fileTypeService.fileType(media.mimetype)
|
||||
},
|
||||
hide () {
|
||||
// HACK: Closing immediately via a touch will cause the click
|
||||
// to be processed on the content below the overlay
|
||||
|
@ -64,23 +75,42 @@ const MediaModal = {
|
|||
this.$store.dispatch('closeMediaViewer')
|
||||
}, transitionTime)
|
||||
},
|
||||
hideIfNotSwiped (event) {
|
||||
// If we have swiped over SwipeClick, do not trigger hide
|
||||
const comp = this.$refs.swipeClick
|
||||
if (!comp) {
|
||||
this.hide()
|
||||
} else {
|
||||
comp.$gesture.click(event)
|
||||
}
|
||||
},
|
||||
goPrev () {
|
||||
if (this.canNavigate) {
|
||||
const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
|
||||
this.$store.dispatch('setCurrent', this.media[prevIndex])
|
||||
const newMedia = this.media[prevIndex]
|
||||
if (this.getType(newMedia) === 'image') {
|
||||
this.loading = true
|
||||
}
|
||||
this.$store.dispatch('setCurrentMedia', newMedia)
|
||||
}
|
||||
},
|
||||
goNext () {
|
||||
if (this.canNavigate) {
|
||||
const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1)
|
||||
this.$store.dispatch('setCurrent', this.media[nextIndex])
|
||||
const newMedia = this.media[nextIndex]
|
||||
if (this.getType(newMedia) === 'image') {
|
||||
this.loading = true
|
||||
}
|
||||
this.$store.dispatch('setCurrentMedia', newMedia)
|
||||
}
|
||||
},
|
||||
onImageLoaded () {
|
||||
this.loading = false
|
||||
},
|
||||
handleSwipePreview (offsets) {
|
||||
this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 })
|
||||
},
|
||||
handleSwipeEnd (sign) {
|
||||
console.log('handleSwipeEnd:', sign)
|
||||
this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
|
||||
if (sign > 0) {
|
||||
this.goNext()
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
<Modal
|
||||
v-if="showing"
|
||||
class="media-modal-view"
|
||||
@backdropClicked="hide"
|
||||
@backdropClicked="hideIfNotSwiped"
|
||||
>
|
||||
<SwipeClick
|
||||
v-if="type === 'image'"
|
||||
ref="swipeClick"
|
||||
class="modal-image-container"
|
||||
:direction="swipeDirection"
|
||||
:threshold="swipeThreshold"
|
||||
|
@ -23,11 +25,12 @@
|
|||
:reset-to-min-scale-limit="pinchZoomScaleResetLimit"
|
||||
>
|
||||
<img
|
||||
v-if="type === 'image'"
|
||||
:class="{ loading }"
|
||||
class="modal-image"
|
||||
:src="currentMedia.url"
|
||||
:alt="currentMedia.description"
|
||||
:title="currentMedia.description"
|
||||
@load="onImageLoaded"
|
||||
>
|
||||
</PinchZoom>
|
||||
</SwipeClick>
|
||||
|
@ -48,35 +51,74 @@
|
|||
<button
|
||||
v-if="canNavigate"
|
||||
:title="$t('media_modal.previous')"
|
||||
class="modal-view-button-arrow modal-view-button-arrow--prev"
|
||||
class="modal-view-button modal-view-button-arrow modal-view-button-arrow--prev"
|
||||
@click.stop.prevent="goPrev"
|
||||
>
|
||||
<FAIcon
|
||||
class="arrow-icon"
|
||||
class="button-icon arrow-icon"
|
||||
icon="chevron-left"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="canNavigate"
|
||||
:title="$t('media_modal.next')"
|
||||
class="modal-view-button-arrow modal-view-button-arrow--next"
|
||||
class="modal-view-button modal-view-button-arrow modal-view-button-arrow--next"
|
||||
@click.stop.prevent="goNext"
|
||||
>
|
||||
<FAIcon
|
||||
class="arrow-icon"
|
||||
class="button-icon arrow-icon"
|
||||
icon="chevron-right"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="modal-view-button modal-view-button-hide"
|
||||
:title="$t('media_modal.hide')"
|
||||
@click.stop.prevent="hide"
|
||||
>
|
||||
<FAIcon
|
||||
class="button-icon"
|
||||
icon="times"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<span
|
||||
v-if="description"
|
||||
class="description"
|
||||
>
|
||||
{{ description }}
|
||||
</span>
|
||||
<span
|
||||
class="counter"
|
||||
>
|
||||
{{ $tc('media_modal.counter', currentIndex + 1, { current: currentIndex + 1, total: media.length }) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="loading"
|
||||
class="loading-spinner"
|
||||
>
|
||||
<FAIcon
|
||||
spin
|
||||
icon="circle-notch"
|
||||
size="5x"
|
||||
/>
|
||||
</span>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script src="./media_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
$modal-view-button-icon-height: 3em;
|
||||
$modal-view-button-icon-half-height: calc(#{$modal-view-button-icon-height} / 2);
|
||||
$modal-view-button-icon-width: 3em;
|
||||
$modal-view-button-icon-margin: 0.5em;
|
||||
|
||||
.modal-view.media-modal-view {
|
||||
z-index: 1001;
|
||||
flex-direction: column;
|
||||
|
||||
.modal-view-button-arrow {
|
||||
.modal-view-button-arrow,
|
||||
.modal-view-button-hide {
|
||||
opacity: 0.75;
|
||||
|
||||
&:focus,
|
||||
|
@ -84,6 +126,7 @@
|
|||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -91,88 +134,146 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes media-fadein {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-image-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
max-width: 90%;
|
||||
max-height: 95%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
|
||||
&-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
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;
|
||||
.modal-image-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
|
||||
&-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.description,
|
||||
.counter {
|
||||
/* Hardcoded since background is also hardcoded */
|
||||
color: white;
|
||||
margin-top: 1em;
|
||||
text-shadow: 0 0 10px black, 0 0 10px black;
|
||||
padding: 0.2em 2em;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 0 0 auto;
|
||||
overflow-y: auto;
|
||||
min-height: 1em;
|
||||
max-width: 500px;
|
||||
max-height: 9.5em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
image-orientation: from-image; // NOTE: only FF supports this
|
||||
animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-view-button {
|
||||
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);
|
||||
height: $modal-view-button-icon-height;
|
||||
width: $modal-view-button-icon-width;
|
||||
|
||||
.button-icon {
|
||||
position: absolute;
|
||||
height: $modal-view-button-icon-height;
|
||||
width: $modal-view-button-icon-width;
|
||||
font-size: 14px;
|
||||
line-height: $modal-view-button-icon-height;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-view-button-arrow {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
margin-top: $modal-view-button-icon-half-height;
|
||||
width: $modal-view-button-icon-width;
|
||||
height: $modal-view-button-icon-height;
|
||||
|
||||
.arrow-icon {
|
||||
right: 6px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
line-height: $modal-view-button-icon-height;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
&--prev {
|
||||
left: 0;
|
||||
.arrow-icon {
|
||||
left: $modal-view-button-icon-margin;
|
||||
}
|
||||
}
|
||||
|
||||
&--next {
|
||||
right: 0;
|
||||
.arrow-icon {
|
||||
right: $modal-view-button-icon-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-view-button-hide {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
.button-icon {
|
||||
top: $modal-view-button-icon-margin;
|
||||
right: $modal-view-button-icon-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faAt
|
||||
|
@ -12,6 +13,9 @@ library.add(
|
|||
|
||||
const MentionLink = {
|
||||
name: 'MentionLink',
|
||||
components: {
|
||||
UserAvatar
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
required: true,
|
||||
|
@ -50,6 +54,10 @@ const MentionLink = {
|
|||
userName () {
|
||||
return this.user && this.userNameFullUi.split('@')[0]
|
||||
},
|
||||
serverName () {
|
||||
// XXX assumed that domain does not contain @
|
||||
return this.user && (this.userNameFullUi.split('@')[1] || this.$store.getters.instanceDomain)
|
||||
},
|
||||
userNameFull () {
|
||||
return this.user && this.user.screen_name
|
||||
},
|
||||
|
@ -79,12 +87,43 @@ const MentionLink = {
|
|||
classnames () {
|
||||
return [
|
||||
{
|
||||
'-you': this.isYou,
|
||||
'-you': this.isYou && this.shouldBoldenYou,
|
||||
'-highlighted': this.highlight
|
||||
},
|
||||
this.highlightType
|
||||
]
|
||||
},
|
||||
useAtIcon () {
|
||||
return this.mergedConfig.useAtIcon
|
||||
},
|
||||
isRemote () {
|
||||
return this.userName !== this.userNameFull
|
||||
},
|
||||
shouldShowFullUserName () {
|
||||
const conf = this.mergedConfig.mentionLinkDisplay
|
||||
if (conf === 'short') {
|
||||
return false
|
||||
} else if (conf === 'full') {
|
||||
return true
|
||||
} else { // full_for_remote
|
||||
return this.isRemote
|
||||
}
|
||||
},
|
||||
shouldShowTooltip () {
|
||||
return this.mergedConfig.mentionLinkShowTooltip && this.mergedConfig.mentionLinkDisplay === 'short' && this.isRemote
|
||||
},
|
||||
shouldShowAvatar () {
|
||||
return this.mergedConfig.mentionLinkShowAvatar
|
||||
},
|
||||
shouldShowYous () {
|
||||
return this.mergedConfig.mentionLinkShowYous
|
||||
},
|
||||
shouldBoldenYou () {
|
||||
return this.mergedConfig.mentionLinkBoldenYou
|
||||
},
|
||||
shouldFadeDomain () {
|
||||
return this.mergedConfig.mentionLinkFadeDomain
|
||||
},
|
||||
...mapGetters(['mergedConfig']),
|
||||
...mapState({
|
||||
currentUser: state => state.users.currentUser
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.MentionLink {
|
||||
position: relative;
|
||||
white-space: normal;
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
color: var(--link);
|
||||
word-break: normal;
|
||||
|
||||
& .new,
|
||||
& .original {
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.mention-avatar {
|
||||
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
.full {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
|
@ -27,7 +39,8 @@
|
|||
user-select: all;
|
||||
}
|
||||
|
||||
.short {
|
||||
& .short.-with-tooltip,
|
||||
& .you {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
@ -36,6 +49,10 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shortName {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.new {
|
||||
&.-you {
|
||||
& .shortName,
|
||||
|
@ -48,7 +65,6 @@
|
|||
color: var(--link);
|
||||
opacity: 0.8;
|
||||
display: inline-block;
|
||||
height: 50%;
|
||||
line-height: 1;
|
||||
padding: 0 0.1em;
|
||||
vertical-align: -25%;
|
||||
|
@ -56,7 +72,7 @@
|
|||
}
|
||||
|
||||
&.-striped {
|
||||
& .userName,
|
||||
& .shortName,
|
||||
& .full {
|
||||
background-image:
|
||||
repeating-linear-gradient(
|
||||
|
@ -70,14 +86,14 @@
|
|||
}
|
||||
|
||||
&.-solid {
|
||||
& .userName,
|
||||
& .shortName,
|
||||
& .full {
|
||||
background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2));
|
||||
}
|
||||
}
|
||||
|
||||
&.-side {
|
||||
& .userName,
|
||||
& .shortName,
|
||||
& .userNameFull {
|
||||
box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor);
|
||||
}
|
||||
|
@ -88,4 +104,12 @@
|
|||
opacity: 1;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.serverName.-faded {
|
||||
color: var(--faintLink, $fallback--link);
|
||||
}
|
||||
|
||||
.full .-faded {
|
||||
color: var(--faint, $fallback--faint);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@
|
|||
class="original"
|
||||
target="_blank"
|
||||
v-html="content"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<span
|
||||
/><!-- eslint-enable vue/no-v-html --><span
|
||||
v-if="user"
|
||||
class="new"
|
||||
:style="style"
|
||||
|
@ -19,33 +17,54 @@
|
|||
>
|
||||
<a
|
||||
class="short button-unstyled"
|
||||
:class="{ '-with-tooltip': shouldShowTooltip }"
|
||||
:href="url"
|
||||
@click.prevent="onClick"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<FAIcon
|
||||
<UserAvatar
|
||||
v-if="shouldShowAvatar"
|
||||
class="mention-avatar"
|
||||
:user="user"
|
||||
/><span
|
||||
class="shortName"
|
||||
><FAIcon
|
||||
v-if="useAtIcon"
|
||||
size="sm"
|
||||
icon="at"
|
||||
class="at"
|
||||
/><span class="shortName"><span
|
||||
/>{{ !useAtIcon ? '@' : '' }}<span
|
||||
class="userName"
|
||||
v-html="userName"
|
||||
/></span>
|
||||
<span
|
||||
v-if="isYou"
|
||||
class="you"
|
||||
>{{ $t('status.you') }}</span>
|
||||
/><span
|
||||
v-if="shouldShowFullUserName"
|
||||
class="serverName"
|
||||
:class="{ '-faded': shouldFadeDomain }"
|
||||
v-html="'@' + serverName"
|
||||
/></span><span
|
||||
v-if="isYou && shouldShowYous"
|
||||
:class="{ '-you': shouldBoldenYou }"
|
||||
> {{ $t('status.you') }}</span>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</a>
|
||||
<span
|
||||
v-if="userName !== userNameFull"
|
||||
</a><span
|
||||
v-if="shouldShowTooltip"
|
||||
class="full popover-default"
|
||||
:class="[highlightType]"
|
||||
>
|
||||
<span
|
||||
class="userNameFull"
|
||||
v-text="'@' + userNameFull"
|
||||
/>
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
@<span
|
||||
class="userName"
|
||||
v-html="userName"
|
||||
/><span
|
||||
class="serverName"
|
||||
:class="{ '-faded': shouldFadeDomain }"
|
||||
v-html="'@' + serverName"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
.MentionsLine {
|
||||
word-break: break-all;
|
||||
|
||||
.mention-link:not(:first-child)::before {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.showMoreLess {
|
||||
margin-left: 0.5em;
|
||||
white-space: normal;
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
.fullExtraMentions,
|
||||
.mention-link:not(:last-child) {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,18 @@
|
|||
|
||||
// TODO Copypaste from Status, should unify it somehow
|
||||
.Notification {
|
||||
--emoji-size: 14px;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
--emoji-size: 14px;
|
||||
|
||||
&:hover {
|
||||
--_still-image-img-visibility: visible;
|
||||
--_still-image-canvas-visibility: hidden;
|
||||
--_still-image-label-visibility: hidden;
|
||||
}
|
||||
|
||||
&.-muted {
|
||||
padding: 0.25em 0.6em;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<Status
|
||||
v-if="notification.type === 'mention'"
|
||||
class="Notification"
|
||||
:compact="true"
|
||||
:statusoid="notification.status"
|
||||
/>
|
||||
|
@ -186,8 +187,9 @@
|
|||
</router-link>
|
||||
</div>
|
||||
<template v-else>
|
||||
<status-content
|
||||
<StatusContent
|
||||
class="faint"
|
||||
:compact="true"
|
||||
:status="notification.action"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -37,11 +37,6 @@
|
|||
|
||||
.notification {
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
||||
&:hover .animated.Avatar {
|
||||
canvas {
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.popover-trigger-button {
|
||||
display: block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.popover {
|
||||
|
|
|
@ -4,6 +4,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
|
|||
import EmojiInput from '../emoji_input/emoji_input.vue'
|
||||
import PollForm from '../poll/poll_form.vue'
|
||||
import Attachment from '../attachment/attachment.vue'
|
||||
import Gallery from 'src/components/gallery/gallery.vue'
|
||||
import StatusContent from '../status_content/status_content.vue'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||
|
@ -85,7 +86,8 @@ const PostStatusForm = {
|
|||
Checkbox,
|
||||
Select,
|
||||
Attachment,
|
||||
StatusContent
|
||||
StatusContent,
|
||||
Gallery
|
||||
},
|
||||
mounted () {
|
||||
this.updateIdempotencyKey()
|
||||
|
@ -390,6 +392,21 @@ const PostStatusForm = {
|
|||
this.newStatus.files.splice(index, 1)
|
||||
this.$emit('resize')
|
||||
},
|
||||
editAttachment (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)
|
||||
|
|
|
@ -287,32 +287,22 @@
|
|||
@click="clearError"
|
||||
/>
|
||||
</div>
|
||||
<div class="attachments">
|
||||
<div
|
||||
v-for="file in newStatus.files"
|
||||
:key="file.url"
|
||||
class="media-upload-wrapper"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled hider"
|
||||
@click="removeMediaFile(file)"
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
</button>
|
||||
<attachment
|
||||
:attachment="file"
|
||||
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
|
||||
size="small"
|
||||
allow-play="false"
|
||||
/>
|
||||
<input
|
||||
v-model="newStatus.mediaDescriptions[file.id]"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.media_description')"
|
||||
@keydown.enter.prevent=""
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<gallery
|
||||
v-if="newStatus.files && newStatus.files.length > 0"
|
||||
class="attachments"
|
||||
:grid="true"
|
||||
:nsfw="false"
|
||||
:attachments="newStatus.files"
|
||||
:descriptions="newStatus.mediaDescriptions"
|
||||
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
|
||||
: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)"
|
||||
/>
|
||||
<div
|
||||
v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox"
|
||||
class="upload_settings"
|
||||
|
@ -330,26 +320,13 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.tribute-container {
|
||||
ul {
|
||||
padding: 0px;
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
img {
|
||||
padding: 3px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: $fallback--avatarAltRadius;
|
||||
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
||||
}
|
||||
}
|
||||
|
||||
.post-status-form {
|
||||
position: relative;
|
||||
|
||||
.attachments {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.form-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -507,15 +484,6 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.attachments .media-upload-wrapper {
|
||||
position: relative;
|
||||
|
||||
.attachment {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -617,17 +585,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
// todo: unify with attachment.vue (otherwise the uploaded images are not minified unless a status with an attachment was displayed before)
|
||||
img.media-upload, .media-upload-container > video {
|
||||
line-height: 0;
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// todo: unify with attachment.vue (otherwise images the uploaded images are not minified unless a status with an attachment was displayed before)
|
||||
img.media-upload {
|
||||
line-height: 0;
|
||||
max-height: 200px;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -120,7 +120,8 @@ export default Vue.component('RichContent', {
|
|||
// don't include spaces when processing mentions - we'll include them
|
||||
// in MentionsLine
|
||||
lastSpacing = item
|
||||
return currentMentions !== null ? item.trim() : item
|
||||
// Don't remove last space in a container (fixes poast mentions)
|
||||
return (index !== array.length - 1) && (currentMentions !== null) ? item.trim() : item
|
||||
}
|
||||
|
||||
currentMentions = null
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
bottom: 0;
|
||||
right: 5px;
|
||||
height: 100%;
|
||||
width: 0.875em;
|
||||
color: $fallback--text;
|
||||
color: var(--inputText, $fallback--text);
|
||||
line-height: 28px;
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import { get, set } from 'lodash'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
import ServerSideIndicator from './server_side_indicator.vue'
|
||||
export default {
|
||||
components: {
|
||||
Checkbox,
|
||||
ModifiedIndicator
|
||||
ModifiedIndicator,
|
||||
ServerSideIndicator
|
||||
},
|
||||
props: [
|
||||
'path',
|
||||
'disabled'
|
||||
'disabled',
|
||||
'expert'
|
||||
],
|
||||
computed: {
|
||||
pathDefault () {
|
||||
|
@ -26,8 +29,14 @@ export default {
|
|||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isServerSide () {
|
||||
return this.path.startsWith('serverSide_')
|
||||
},
|
||||
isChanged () {
|
||||
return this.state !== this.defaultState
|
||||
return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<label
|
||||
v-if="matchesExpertLevel"
|
||||
class="BooleanSetting"
|
||||
>
|
||||
<Checkbox
|
||||
|
@ -13,8 +14,7 @@
|
|||
>
|
||||
<slot />
|
||||
</span>
|
||||
<ModifiedIndicator :changed="isChanged" />
|
||||
</Checkbox>
|
||||
<ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import { get, set } from 'lodash'
|
||||
import Select from 'src/components/select/select.vue'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
import ServerSideIndicator from './server_side_indicator.vue'
|
||||
export default {
|
||||
components: {
|
||||
Select,
|
||||
ModifiedIndicator
|
||||
ModifiedIndicator,
|
||||
ServerSideIndicator
|
||||
},
|
||||
props: [
|
||||
'path',
|
||||
'disabled',
|
||||
'options'
|
||||
'options',
|
||||
'expert'
|
||||
],
|
||||
computed: {
|
||||
pathDefault () {
|
||||
|
@ -27,8 +30,14 @@ export default {
|
|||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isServerSide () {
|
||||
return this.path.startsWith('serverSide_')
|
||||
},
|
||||
isChanged () {
|
||||
return this.state !== this.defaultState
|
||||
return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<label
|
||||
v-if="matchesExpertLevel"
|
||||
class="ChoiceSetting"
|
||||
>
|
||||
<slot />
|
||||
|
@ -18,6 +19,7 @@
|
|||
</option>
|
||||
</Select>
|
||||
<ModifiedIndicator :changed="isChanged" />
|
||||
<ServerSideIndicator :server-side="isServerSide" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
|
|
41
src/components/settings_modal/helpers/integer_setting.js
Normal file
41
src/components/settings_modal/helpers/integer_setting.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { get, set } from 'lodash'
|
||||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
export default {
|
||||
components: {
|
||||
ModifiedIndicator
|
||||
},
|
||||
props: {
|
||||
path: String,
|
||||
disabled: Boolean,
|
||||
min: Number,
|
||||
expert: Number
|
||||
},
|
||||
computed: {
|
||||
pathDefault () {
|
||||
const [firstSegment, ...rest] = this.path.split('.')
|
||||
return [firstSegment + 'DefaultValue', ...rest].join('.')
|
||||
},
|
||||
state () {
|
||||
const value = get(this.$parent, this.path)
|
||||
if (value === undefined) {
|
||||
return this.defaultState
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
defaultState () {
|
||||
return get(this.$parent, this.pathDefault)
|
||||
},
|
||||
isChanged () {
|
||||
return this.state !== this.defaultState
|
||||
},
|
||||
matchesExpertLevel () {
|
||||
return (this.expert || 0) <= this.$parent.expertLevel
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (e) {
|
||||
set(this.$parent, this.path, parseInt(e.target.value))
|
||||
}
|
||||
}
|
||||
}
|
23
src/components/settings_modal/helpers/integer_setting.vue
Normal file
23
src/components/settings_modal/helpers/integer_setting.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<span
|
||||
v-if="matchesExpertLevel"
|
||||
class="IntegerSetting"
|
||||
>
|
||||
<label :for="path">
|
||||
<slot />
|
||||
</label>
|
||||
<input
|
||||
:id="path"
|
||||
class="number-input"
|
||||
type="number"
|
||||
step="1"
|
||||
:disabled="disabled"
|
||||
:min="min || 0"
|
||||
:value="state"
|
||||
@change="update"
|
||||
>
|
||||
<ModifiedIndicator :changed="isChanged" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script src="./integer_setting.js"></script>
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<span
|
||||
v-if="serverSide"
|
||||
class="ServerSideIndicator"
|
||||
>
|
||||
<Popover
|
||||
trigger="hover"
|
||||
>
|
||||
<template v-slot:trigger>
|
||||
|
||||
<FAIcon
|
||||
icon="server"
|
||||
:aria-label="$t('settings.setting_server_side')"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<div class="serverside-tooltip">
|
||||
{{ $t('settings.setting_server_side') }}
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faServer } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faServer
|
||||
)
|
||||
|
||||
export default {
|
||||
components: { Popover },
|
||||
props: ['serverSide']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ServerSideIndicator {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.serverside-tooltip {
|
||||
margin: 0.5em 1em;
|
||||
min-width: 10em;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,4 +1,5 @@
|
|||
import { defaultState as configDefaultState } from 'src/modules/config.js'
|
||||
import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js'
|
||||
|
||||
const SharedComputedObject = () => ({
|
||||
user () {
|
||||
|
@ -22,6 +23,14 @@ const SharedComputedObject = () => ({
|
|||
}
|
||||
}])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||
...Object.keys(serverSideConfigDefaultState)
|
||||
.map(key => ['serverSide_' + key, {
|
||||
get () { return this.$store.state.serverSideConfig[key] },
|
||||
set (value) {
|
||||
this.$store.dispatch('setServerSideOption', { name: key, value })
|
||||
}
|
||||
}])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
|
||||
// Special cases (need to transform values or perform actions first)
|
||||
useStreamingApi: {
|
||||
get () { return this.$store.getters.mergedConfig.useStreamingApi },
|
||||
|
|
|
@ -3,6 +3,7 @@ import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
|
|||
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
|
||||
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
|
||||
import Popover from '../popover/popover.vue'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import {
|
||||
|
@ -51,6 +52,7 @@ const SettingsModal = {
|
|||
components: {
|
||||
Modal,
|
||||
Popover,
|
||||
Checkbox,
|
||||
SettingsModalContent: getResettableAsyncComponent(
|
||||
() => import('./settings_modal_content.vue'),
|
||||
{
|
||||
|
@ -159,6 +161,15 @@ const SettingsModal = {
|
|||
},
|
||||
modalPeeked () {
|
||||
return this.$store.state.interface.settingsModalState === 'minimized'
|
||||
},
|
||||
expertLevel: {
|
||||
get () {
|
||||
return this.$store.state.config.expertLevel > 0
|
||||
},
|
||||
set (value) {
|
||||
console.log(value)
|
||||
this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,4 +48,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-footer {
|
||||
display: flex;
|
||||
>* {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<div class="panel-body">
|
||||
<SettingsModalContent v-if="modalOpenedOnce" />
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<div class="panel-footer settings-footer">
|
||||
<Popover
|
||||
class="export"
|
||||
trigger="click"
|
||||
|
@ -108,6 +108,10 @@
|
|||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
|
||||
<Checkbox v-model="expertLevel">
|
||||
{{ $t("settings.expert_mode") }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { filter, trim } from 'lodash'
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
|
@ -17,7 +18,8 @@ const FilteringTab = {
|
|||
},
|
||||
components: {
|
||||
BooleanSetting,
|
||||
ChoiceSetting
|
||||
ChoiceSetting,
|
||||
IntegerSetting
|
||||
},
|
||||
computed: {
|
||||
...SharedComputedObject(),
|
||||
|
|
|
@ -1,73 +1,122 @@
|
|||
<template>
|
||||
<div :label="$t('settings.filtering')">
|
||||
<div class="setting-item">
|
||||
<div class="select-multiple">
|
||||
<span class="label">{{ $t('settings.notification_visibility') }}</span>
|
||||
<ul class="option-list">
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.likes">
|
||||
{{ $t('settings.notification_visibility_likes') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.repeats">
|
||||
{{ $t('settings.notification_visibility_repeats') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.follows">
|
||||
{{ $t('settings.notification_visibility_follows') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.mentions">
|
||||
{{ $t('settings.notification_visibility_mentions') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.moves">
|
||||
{{ $t('settings.notification_visibility_moves') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.emojiReactions">
|
||||
{{ $t('settings.notification_visibility_emoji_reactions') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ChoiceSetting
|
||||
id="replyVisibility"
|
||||
path="replyVisibility"
|
||||
:options="replyVisibilityOptions"
|
||||
>
|
||||
{{ $t('settings.replies_in_timeline') }}
|
||||
</ChoiceSetting>
|
||||
<div>
|
||||
<BooleanSetting path="hidePostStats">
|
||||
{{ $t('settings.hide_post_stats') }}
|
||||
</BooleanSetting>
|
||||
</div>
|
||||
<div>
|
||||
<BooleanSetting path="hideUserStats">
|
||||
{{ $t('settings.hide_user_stats') }}
|
||||
</BooleanSetting>
|
||||
</div>
|
||||
<h2>{{ $t('settings.posts') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="hideFilteredStatuses">
|
||||
{{ $t('settings.hide_filtered_statuses') }}
|
||||
</BooleanSetting>
|
||||
<ul
|
||||
class="setting-list suboptions"
|
||||
:class="[{disabled: !streaming}]"
|
||||
>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
:disabled="hideFilteredStatuses"
|
||||
path="hideWordFilteredPosts"
|
||||
>
|
||||
{{ $t('settings.hide_wordfiltered_statuses') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
v-if="user"
|
||||
:disabled="hideFilteredStatuses"
|
||||
path="hideMutedThreads"
|
||||
>
|
||||
{{ $t('settings.hide_muted_threads') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
v-if="user"
|
||||
:disabled="hideFilteredStatuses"
|
||||
path="hideMutedPosts"
|
||||
>
|
||||
{{ $t('settings.hide_muted_posts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="muteBotStatuses">
|
||||
{{ $t('settings.mute_bot_posts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="hidePostStats">
|
||||
{{ $t('settings.hide_post_stats') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="hideBotIndication">
|
||||
{{ $t('settings.hide_bot_indication') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<ChoiceSetting
|
||||
v-if="user"
|
||||
id="replyVisibility"
|
||||
path="replyVisibility"
|
||||
:options="replyVisibilityOptions"
|
||||
>
|
||||
{{ $t('settings.replies_in_timeline') }}
|
||||
</ChoiceSetting>
|
||||
<li>
|
||||
<h3>{{ $t('settings.wordfilter') }}</h3>
|
||||
<textarea
|
||||
id="muteWords"
|
||||
v-model="muteWordsString"
|
||||
class="resize-height"
|
||||
/>
|
||||
<div>{{ $t('settings.filtering_explanation') }}</div>
|
||||
</li>
|
||||
<h3>{{ $t('settings.attachments') }}</h3>
|
||||
<li v-if="expertLevel > 0">
|
||||
<label for="maxThumbnails">
|
||||
{{ $t('settings.max_thumbnails') }}
|
||||
</label>
|
||||
<input
|
||||
id="maxThumbnails"
|
||||
path.number="maxThumbnails"
|
||||
class="number-input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
path="maxThumbnails"
|
||||
:min="0"
|
||||
>
|
||||
{{ $t('settings.max_thumbnails') }}
|
||||
</IntegerSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="hideAttachments">
|
||||
{{ $t('settings.hide_attachments_in_tl') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="hideAttachmentsInConv">
|
||||
{{ $t('settings.hide_attachments_in_convo') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div>
|
||||
<p>{{ $t('settings.filtering_explanation') }}</p>
|
||||
<textarea
|
||||
id="muteWords"
|
||||
v-model="muteWordsString"
|
||||
class="resize-height"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<BooleanSetting path="hideFilteredStatuses">
|
||||
{{ $t('settings.hide_filtered_statuses') }}
|
||||
</BooleanSetting>
|
||||
</div>
|
||||
<div
|
||||
v-if="expertLevel > 0"
|
||||
class="setting-item"
|
||||
>
|
||||
<h2>{{ $t('settings.user_profiles') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="hideUserStats">
|
||||
{{ $t('settings.hide_user_stats') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import ServerSideIndicator from '../helpers/server_side_indicator.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faGlobe
|
||||
|
@ -20,7 +23,7 @@ const GeneralTab = {
|
|||
value: mode,
|
||||
label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`)
|
||||
})),
|
||||
conversationDisplayOptions: ['tree', 'simple_tree', 'linear'].map(mode => ({
|
||||
conversationDisplayOptions: ['tree', 'linear'].map(mode => ({
|
||||
key: mode,
|
||||
value: mode,
|
||||
label: this.$t(`settings.conversation_display_${mode}`)
|
||||
|
@ -30,6 +33,11 @@ const GeneralTab = {
|
|||
value: mode,
|
||||
label: this.$t(`settings.conversation_other_replies_button_${mode}`)
|
||||
})),
|
||||
mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({
|
||||
key: mode,
|
||||
value: mode,
|
||||
label: this.$t(`settings.mention_link_display_${mode}`)
|
||||
})),
|
||||
loopSilentAvailable:
|
||||
// Firefox
|
||||
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
||||
|
@ -42,7 +50,10 @@ const GeneralTab = {
|
|||
components: {
|
||||
BooleanSetting,
|
||||
ChoiceSetting,
|
||||
InterfaceLanguageSwitcher
|
||||
IntegerSetting,
|
||||
InterfaceLanguageSwitcher,
|
||||
ScopeSelector,
|
||||
ServerSideIndicator
|
||||
},
|
||||
computed: {
|
||||
postFormats () {
|
||||
|
@ -62,6 +73,11 @@ const GeneralTab = {
|
|||
},
|
||||
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
|
||||
...SharedComputedObject()
|
||||
},
|
||||
methods: {
|
||||
changeDefaultScope (value) {
|
||||
this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,13 +47,8 @@
|
|||
<h2>{{ $t('nav.timeline') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="hideMutedPosts">
|
||||
{{ $t('settings.hide_muted_posts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="collapseMessageWithSubject">
|
||||
{{ $t('settings.collapse_subject') }}
|
||||
<BooleanSetting path="stopGifs">
|
||||
{{ $t('settings.stop_gifs') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -75,24 +70,55 @@
|
|||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="useStreamingApi">
|
||||
<BooleanSetting
|
||||
path="useStreamingApi"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.useStreamingApi') }}
|
||||
<br>
|
||||
<small>
|
||||
{{ $t('settings.useStreamingApiWarning') }}
|
||||
</small>
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="emojiReactionsOnTimeline">
|
||||
{{ $t('settings.emoji_reactions_on_timeline') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="virtualScrolling">
|
||||
<BooleanSetting
|
||||
path="virtualScrolling"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.virtual_scrolling') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="alwaysShowNewPostButton"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.always_show_post_button') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="autohideFloatingPostButton"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.autohide_floating_post_button') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li v-if="instanceShoutboxPresent">
|
||||
<BooleanSetting
|
||||
path="hideShoutbox"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.hide_shoutbox') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="swapReacts">
|
||||
{{ $t('settings.swap_reacts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.post_look_feel') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
id="conversationDisplay"
|
||||
|
@ -107,120 +133,68 @@
|
|||
class="setting-list suboptions"
|
||||
>
|
||||
<li>
|
||||
<label for="maxDepthInThread">
|
||||
{{ $t('settings.max_depth_in_thread') }}
|
||||
</label>
|
||||
<input
|
||||
id="maxDepthInThread"
|
||||
path.number="maxDepthInThread"
|
||||
class="number-input"
|
||||
type="number"
|
||||
min="3"
|
||||
step="1"
|
||||
<BooleanSetting path="conversationTreeAdvanced">
|
||||
{{ $t('settings.tree_advanced') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="conversationTreeFadeAncestors"
|
||||
:expert="1"
|
||||
>
|
||||
{{ $t('settings.tree_fade_ancestors') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<IntegerSetting
|
||||
path="maxDepthInThread"
|
||||
:min="3"
|
||||
:expert="1"
|
||||
>
|
||||
{{ $t('settings.max_depth_in_thread') }}
|
||||
</IntegerSetting>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
id="conversationOtherRepliesButton"
|
||||
path="conversationOtherRepliesButton"
|
||||
:options="conversationOtherRepliesButtonOptions"
|
||||
:expert="1"
|
||||
>
|
||||
{{ $t('settings.conversation_other_replies_button') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.composing') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="scopeCopy">
|
||||
{{ $t('settings.scope_copy') }}
|
||||
<BooleanSetting path="collapseMessageWithSubject">
|
||||
{{ $t('settings.collapse_subject') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="alwaysShowSubjectInput">
|
||||
{{ $t('settings.subject_input_always_show') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
id="subjectLineBehavior"
|
||||
path="subjectLineBehavior"
|
||||
:options="subjectLineOptions"
|
||||
<BooleanSetting
|
||||
path="emojiReactionsOnTimeline"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.subject_line_behavior') }}
|
||||
</ChoiceSetting>
|
||||
{{ $t('settings.emoji_reactions_on_timeline') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li v-if="postFormats.length > 0">
|
||||
<ChoiceSetting
|
||||
id="postContentType"
|
||||
path="postContentType"
|
||||
:options="postContentOptions"
|
||||
<li>
|
||||
<BooleanSetting
|
||||
v-if="user"
|
||||
path="serverSide_stripRichContent"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.post_status_content_type') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="minimalScopesMode">
|
||||
{{ $t('settings.minimal_scopes_mode') }}
|
||||
{{ $t('settings.no_rich_text_description') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<h3>{{ $t('settings.attachments') }}</h3>
|
||||
<li>
|
||||
<BooleanSetting path="sensitiveByDefault">
|
||||
{{ $t('settings.sensitive_by_default') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="alwaysShowNewPostButton">
|
||||
{{ $t('settings.always_show_post_button') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="autohideFloatingPostButton">
|
||||
{{ $t('settings.autohide_floating_post_button') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="padEmoji">
|
||||
{{ $t('settings.pad_emoji') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="swapReacts">
|
||||
{{ $t('settings.swap_reacts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.attachments') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="hideAttachments">
|
||||
{{ $t('settings.hide_attachments_in_tl') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="hideAttachmentsInConv">
|
||||
{{ $t('settings.hide_attachments_in_convo') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<label for="maxThumbnails">
|
||||
{{ $t('settings.max_thumbnails') }}
|
||||
</label>
|
||||
<input
|
||||
id="maxThumbnails"
|
||||
path.number="maxThumbnails"
|
||||
class="number-input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
<BooleanSetting
|
||||
path="useContainFit"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.use_contain_fit') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="hideNsfw">
|
||||
|
@ -231,6 +205,7 @@
|
|||
<li>
|
||||
<BooleanSetting
|
||||
path="preloadImage"
|
||||
expert="1"
|
||||
:disabled="!hideNsfw"
|
||||
>
|
||||
{{ $t('settings.preload_images') }}
|
||||
|
@ -239,6 +214,7 @@
|
|||
<li>
|
||||
<BooleanSetting
|
||||
path="useOneClickNsfw"
|
||||
expert="1"
|
||||
:disabled="!hideNsfw"
|
||||
>
|
||||
{{ $t('settings.use_one_click_nsfw') }}
|
||||
|
@ -246,12 +222,10 @@
|
|||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<BooleanSetting path="stopGifs">
|
||||
{{ $t('settings.stop_gifs') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="loopVideo">
|
||||
<BooleanSetting
|
||||
path="loopVideo"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.loop_video') }}
|
||||
</BooleanSetting>
|
||||
<ul
|
||||
|
@ -261,6 +235,7 @@
|
|||
<li>
|
||||
<BooleanSetting
|
||||
path="loopVideoSilentOnly"
|
||||
expert="1"
|
||||
:disabled="!loopVideo || !loopSilentAvailable"
|
||||
>
|
||||
{{ $t('settings.loop_video_silent_only') }}
|
||||
|
@ -275,37 +250,177 @@
|
|||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="playVideosInModal">
|
||||
<BooleanSetting
|
||||
path="playVideosInModal"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.play_videos_in_modal') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<h3>{{ $t('settings.mention_links') }}</h3>
|
||||
<li>
|
||||
<BooleanSetting path="useContainFit">
|
||||
{{ $t('settings.use_contain_fit') }}
|
||||
<ChoiceSetting
|
||||
id="mentionLinkDisplay"
|
||||
path="mentionLinkDisplay"
|
||||
:options="mentionLinkDisplayOptions"
|
||||
>
|
||||
{{ $t('settings.mention_link_display') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<ul
|
||||
class="setting-list suboptions"
|
||||
>
|
||||
<li v-if="mentionLinkDisplay === 'short'">
|
||||
<BooleanSetting
|
||||
path="mentionLinkShowTooltip"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.mention_link_show_tooltip') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="useAtIcon"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.use_at_icon') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.notifications') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="webPushNotifications">
|
||||
{{ $t('settings.enable_web_push_notifications') }}
|
||||
<BooleanSetting path="mentionLinkShowAvatar">
|
||||
{{ $t('settings.mention_link_show_avatar') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.fun') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="greentext">
|
||||
<BooleanSetting
|
||||
path="mentionLinkFadeDomain"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.mention_link_fade_domain') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li v-if="user">
|
||||
<BooleanSetting
|
||||
path="mentionLinkBoldenYou"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.mention_link_bolden_you') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<h3 v-if="expertLevel > 0">
|
||||
{{ $t('settings.fun') }}
|
||||
</h3>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="greentext"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.greentext') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li v-if="user">
|
||||
<BooleanSetting
|
||||
path="mentionLinkShowYous"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.show_yous') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="user"
|
||||
class="setting-item"
|
||||
>
|
||||
<h2>{{ $t('settings.composing') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<label for="default-vis">
|
||||
{{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" />
|
||||
<ScopeSelector
|
||||
class="scope-selector"
|
||||
:show-all="true"
|
||||
:user-default="serverSide_defaultScope"
|
||||
:initial-scope="serverSide_defaultScope"
|
||||
:on-scope-change="changeDefaultScope"
|
||||
/>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<!-- <BooleanSetting path="serverSide_defaultNSFW"> -->
|
||||
<BooleanSetting path="sensitiveByDefault">
|
||||
{{ $t('settings.sensitive_by_default') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="scopeCopy"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.scope_copy') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="alwaysShowSubjectInput"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.subject_input_always_show') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
id="subjectLineBehavior"
|
||||
path="subjectLineBehavior"
|
||||
:options="subjectLineOptions"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.subject_line_behavior') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<li v-if="postFormats.length > 0">
|
||||
<ChoiceSetting
|
||||
id="postContentType"
|
||||
path="postContentType"
|
||||
:options="postContentOptions"
|
||||
>
|
||||
{{ $t('settings.post_status_content_type') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="minimalScopesMode"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.minimal_scopes_mode') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="alwaysShowNewPostButton"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.always_show_post_button') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="autohideFloatingPostButton"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.autohide_floating_post_button') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="padEmoji"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.pad_emoji') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
const NotificationsTab = {
|
||||
data () {
|
||||
|
@ -9,12 +10,13 @@ const NotificationsTab = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
Checkbox
|
||||
BooleanSetting
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
}
|
||||
},
|
||||
...SharedComputedObject()
|
||||
},
|
||||
methods: {
|
||||
updateNotificationSettings () {
|
||||
|
|
|
@ -2,30 +2,77 @@
|
|||
<div :label="$t('settings.notifications')">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
|
||||
<p>
|
||||
<Checkbox v-model="notificationSettings.block_from_strangers">
|
||||
{{ $t('settings.notification_setting_block_from_strangers') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_blockNotificationsFromStrangers">
|
||||
{{ $t('settings.notification_setting_block_from_strangers') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li class="select-multiple">
|
||||
<span class="label">{{ $t('settings.notification_visibility') }}</span>
|
||||
<ul class="option-list">
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.likes">
|
||||
{{ $t('settings.notification_visibility_likes') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.repeats">
|
||||
{{ $t('settings.notification_visibility_repeats') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.follows">
|
||||
{{ $t('settings.notification_visibility_follows') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.mentions">
|
||||
{{ $t('settings.notification_visibility_mentions') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.moves">
|
||||
{{ $t('settings.notification_visibility_moves') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="notificationVisibility.emojiReactions">
|
||||
{{ $t('settings.notification_visibility_emoji_reactions') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div
|
||||
v-if="expertLevel > 0"
|
||||
class="setting-item"
|
||||
>
|
||||
<h2>{{ $t('settings.notification_setting_privacy') }}</h2>
|
||||
<p>
|
||||
<Checkbox v-model="notificationSettings.hide_notification_contents">
|
||||
{{ $t('settings.notification_setting_hide_notification_contents') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="webPushNotifications"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.enable_web_push_notifications') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="serverSide_webPushHideContents"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.notification_setting_hide_notification_contents') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<p>{{ $t('settings.notification_mutes') }}</p>
|
||||
<p>{{ $t('settings.notification_blocks') }}</p>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="updateNotificationSettings"
|
||||
>
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -8,6 +8,9 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
|
|||
import suggestor from 'src/components/emoji_input/suggestor.js'
|
||||
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes,
|
||||
|
@ -27,18 +30,10 @@ const ProfileTab = {
|
|||
newName: this.$store.state.users.currentUser.name_unescaped,
|
||||
newBio: unescape(this.$store.state.users.currentUser.description),
|
||||
newLocked: this.$store.state.users.currentUser.locked,
|
||||
newNoRichText: this.$store.state.users.currentUser.no_rich_text,
|
||||
newDefaultScope: this.$store.state.users.currentUser.default_scope,
|
||||
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
|
||||
hideFollows: this.$store.state.users.currentUser.hide_follows,
|
||||
hideFollowers: this.$store.state.users.currentUser.hide_followers,
|
||||
hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count,
|
||||
hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count,
|
||||
showRole: this.$store.state.users.currentUser.show_role,
|
||||
role: this.$store.state.users.currentUser.role,
|
||||
discoverable: this.$store.state.users.currentUser.discoverable,
|
||||
bot: this.$store.state.users.currentUser.bot,
|
||||
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
|
||||
pickAvatarBtnVisible: true,
|
||||
bannerUploading: false,
|
||||
backgroundUploading: false,
|
||||
|
@ -54,12 +49,14 @@ const ProfileTab = {
|
|||
EmojiInput,
|
||||
Autosuggest,
|
||||
ProgressButton,
|
||||
Checkbox
|
||||
Checkbox,
|
||||
BooleanSetting
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
...SharedComputedObject(),
|
||||
emojiUserSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
|
@ -123,15 +120,7 @@ const ProfileTab = {
|
|||
/* eslint-disable camelcase */
|
||||
display_name: this.newName,
|
||||
fields_attributes: this.newFields.filter(el => el != null),
|
||||
default_scope: this.newDefaultScope,
|
||||
no_rich_text: this.newNoRichText,
|
||||
hide_follows: this.hideFollows,
|
||||
hide_followers: this.hideFollowers,
|
||||
discoverable: this.discoverable,
|
||||
bot: this.bot,
|
||||
allow_following_move: this.allowFollowingMove,
|
||||
hide_follows_count: this.hideFollowsCount,
|
||||
hide_followers_count: this.hideFollowersCount,
|
||||
show_role: this.showRole
|
||||
/* eslint-enable camelcase */
|
||||
} }).then((user) => {
|
||||
|
|
|
@ -25,61 +25,6 @@
|
|||
class="bio resize-height"
|
||||
/>
|
||||
</EmojiInput>
|
||||
<p>
|
||||
<Checkbox v-model="newLocked">
|
||||
{{ $t('settings.lock_account_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<div>
|
||||
<label for="default-vis">{{ $t('settings.default_vis') }}</label>
|
||||
<div
|
||||
id="default-vis"
|
||||
class="visibility-tray"
|
||||
>
|
||||
<scope-selector
|
||||
:show-all="true"
|
||||
:user-default="newDefaultScope"
|
||||
:initial-scope="newDefaultScope"
|
||||
:on-scope-change="changeVis"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<Checkbox v-model="newNoRichText">
|
||||
{{ $t('settings.no_rich_text_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p>
|
||||
<Checkbox v-model="hideFollows">
|
||||
{{ $t('settings.hide_follows_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p class="setting-subitem">
|
||||
<Checkbox
|
||||
v-model="hideFollowsCount"
|
||||
:disabled="!hideFollows"
|
||||
>
|
||||
{{ $t('settings.hide_follows_count_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p>
|
||||
<Checkbox v-model="hideFollowers">
|
||||
{{ $t('settings.hide_followers_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p class="setting-subitem">
|
||||
<Checkbox
|
||||
v-model="hideFollowersCount"
|
||||
:disabled="!hideFollowers"
|
||||
>
|
||||
{{ $t('settings.hide_followers_count_description') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p>
|
||||
<Checkbox v-model="allowFollowingMove">
|
||||
{{ $t('settings.allow_following_move') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p v-if="role === 'admin' || role === 'moderator'">
|
||||
<Checkbox v-model="showRole">
|
||||
<template v-if="role === 'admin'">
|
||||
|
@ -90,11 +35,6 @@
|
|||
</template>
|
||||
</Checkbox>
|
||||
</p>
|
||||
<p>
|
||||
<Checkbox v-model="discoverable">
|
||||
{{ $t('settings.discoverable') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<div v-if="maxFields > 0">
|
||||
<p>{{ $t('settings.profile_fields.label') }}</p>
|
||||
<div
|
||||
|
@ -269,6 +209,67 @@
|
|||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.account_privacy') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_locked">
|
||||
{{ $t('settings.lock_account_description') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_discoverable">
|
||||
{{ $t('settings.discoverable') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_allowFollowingMove">
|
||||
{{ $t('settings.allow_following_move') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_hideFavorites">
|
||||
{{ $t('settings.hide_favorites_description') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_hideFollowers">
|
||||
{{ $t('settings.hide_followers_description') }}
|
||||
</BooleanSetting>
|
||||
<ul
|
||||
class="setting-list suboptions"
|
||||
:class="[{disabled: !serverSide_hideFollowers}]"
|
||||
>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="serverSide_hideFollowersCount"
|
||||
:disabled="!serverSide_hideFollowers"
|
||||
>
|
||||
{{ $t('settings.hide_followers_count_description') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_hideFollows">
|
||||
{{ $t('settings.hide_follows_description') }}
|
||||
</BooleanSetting>
|
||||
<ul
|
||||
class="setting-list suboptions"
|
||||
:class="[{disabled: !serverSide_hideFollows}]"
|
||||
>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="serverSide_hideFollowsCount"
|
||||
:disabled="!serverSide_hideFollows"
|
||||
>
|
||||
{{ $t('settings.hide_follows_count_description') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -228,6 +228,12 @@ const Status = {
|
|||
muteWordHits () {
|
||||
return muteWordHits(this.status, this.muteWords)
|
||||
},
|
||||
botStatus () {
|
||||
return this.status.user.bot
|
||||
},
|
||||
botIndicator () {
|
||||
return this.botStatus && !this.hideBotIndication
|
||||
},
|
||||
mentionsLine () {
|
||||
if (!this.headTailLinks) return []
|
||||
const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url))
|
||||
|
@ -248,26 +254,34 @@ const Status = {
|
|||
return this.mentionsLine.length > 0
|
||||
},
|
||||
muted () {
|
||||
if (this.statusoid.user.id === this.currentUser.id) return false
|
||||
const reasonsToMute = this.userIsMuted ||
|
||||
// Thread is muted
|
||||
status.thread_muted ||
|
||||
// Wordfiltered
|
||||
this.muteWordHits.length > 0 ||
|
||||
// bot status
|
||||
(this.muteBotStatuses && this.botStatus && !this.compact)
|
||||
return !this.unmuted && !this.shouldNotMute && reasonsToMute
|
||||
},
|
||||
userIsMuted () {
|
||||
if (this.statusoid.user.id === this.currentUser.id) return false
|
||||
const { status } = this
|
||||
const { reblog } = status
|
||||
const relationship = this.$store.getters.relationship(status.user.id)
|
||||
const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id)
|
||||
const reasonsToMute = (
|
||||
// Post is muted according to BE
|
||||
status.muted ||
|
||||
return status.muted ||
|
||||
// Reprööt of a muted post according to BE
|
||||
(reblog && reblog.muted) ||
|
||||
// Muted user
|
||||
relationship.muting ||
|
||||
// Muted user of a reprööt
|
||||
(relationshipReblog && relationshipReblog.muting) ||
|
||||
// Thread is muted
|
||||
status.thread_muted ||
|
||||
// Wordfiltered
|
||||
this.muteWordHits.length > 0
|
||||
)
|
||||
const excusesNotToMute = (
|
||||
(relationshipReblog && relationshipReblog.muting)
|
||||
},
|
||||
shouldNotMute () {
|
||||
const { status } = this
|
||||
const { reblog } = status
|
||||
return (
|
||||
(
|
||||
this.inProfile && (
|
||||
// Don't mute user's posts on user timeline (except reblogs)
|
||||
|
@ -280,14 +294,26 @@ const Status = {
|
|||
(this.inConversation && status.thread_muted)
|
||||
// No excuses if post has muted words
|
||||
) && !this.muteWordHits.length > 0
|
||||
|
||||
return !this.unmuted && !excusesNotToMute && reasonsToMute
|
||||
},
|
||||
hideMutedUsers () {
|
||||
return this.mergedConfig.hideMutedPosts
|
||||
},
|
||||
hideMutedThreads () {
|
||||
return this.mergedConfig.hideMutedThreads
|
||||
},
|
||||
hideFilteredStatuses () {
|
||||
return this.mergedConfig.hideFilteredStatuses
|
||||
},
|
||||
hideWordFilteredPosts () {
|
||||
return this.mergedConfig.hideWordFilteredPosts
|
||||
},
|
||||
hideStatus () {
|
||||
return (this.muted && this.hideFilteredStatuses) || this.virtualHidden
|
||||
return (this.virtualHidden || !this.shouldNotMute) && (
|
||||
(this.muted && this.hideFilteredStatuses) ||
|
||||
(this.userIsMuted && this.hideMutedUsers) ||
|
||||
(this.status.thread_muted && this.hideMutedThreads) ||
|
||||
(this.muteWordHits.length > 0 && this.hideWordFilteredPosts)
|
||||
)
|
||||
},
|
||||
isFocused () {
|
||||
// retweet or root of an expanded conversation
|
||||
|
@ -337,6 +363,12 @@ const Status = {
|
|||
hidePostStats () {
|
||||
return this.mergedConfig.hidePostStats
|
||||
},
|
||||
muteBotStatuses () {
|
||||
return this.mergedConfig.muteBotStatuses
|
||||
},
|
||||
hideBotIndication () {
|
||||
return this.mergedConfig.hideBotIndication
|
||||
},
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
.Status {
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
||||
&:hover {
|
||||
--_still-image-img-visibility: visible;
|
||||
|
@ -25,7 +27,7 @@
|
|||
}
|
||||
|
||||
.gravestone {
|
||||
padding: $status-margin;
|
||||
padding: var(--status-margin, $status-margin);
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
display: flex;
|
||||
|
@ -38,7 +40,7 @@
|
|||
|
||||
.status-container {
|
||||
display: flex;
|
||||
padding: $status-margin;
|
||||
padding: var(--status-margin, $status-margin);
|
||||
|
||||
&.-repeat {
|
||||
padding-top: 0;
|
||||
|
@ -46,7 +48,7 @@
|
|||
}
|
||||
|
||||
.pin {
|
||||
padding: $status-margin $status-margin 0;
|
||||
padding: var(--status-margin, $status-margin) var(--status-margin, $status-margin) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
@ -62,7 +64,7 @@
|
|||
}
|
||||
|
||||
.left-side {
|
||||
margin-right: $status-margin;
|
||||
margin-right: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
.right-side {
|
||||
|
@ -71,7 +73,7 @@
|
|||
}
|
||||
|
||||
.usercard {
|
||||
margin-bottom: $status-margin;
|
||||
margin-bottom: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
.status-username {
|
||||
|
@ -155,18 +157,24 @@
|
|||
position: relative;
|
||||
align-content: baseline;
|
||||
font-size: 12px;
|
||||
line-height: 160%;
|
||||
margin-top: 0.2em;
|
||||
line-height: 130%;
|
||||
max-width: 100%;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
& .reply-to-popover,
|
||||
& .reply-to-no-popover {
|
||||
& .reply-to-no-popover,
|
||||
& .mentions {
|
||||
min-width: 0;
|
||||
margin-right: 0.4em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.reply-glued-label {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.reply-to-popover {
|
||||
.reply-to:hover::before {
|
||||
content: '';
|
||||
|
@ -200,7 +208,6 @@
|
|||
& .reply-to {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
& .mentions-text,
|
||||
|
@ -232,7 +239,7 @@
|
|||
}
|
||||
|
||||
.repeat-info {
|
||||
padding: 0.4em $status-margin;
|
||||
padding: 0.4em var(--status-margin, $status-margin);
|
||||
|
||||
.repeat-icon {
|
||||
color: $fallback--cGreen;
|
||||
|
@ -278,7 +285,7 @@
|
|||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: $status-margin;
|
||||
margin-top: var(--status-margin, $status-margin);
|
||||
|
||||
> * {
|
||||
max-width: 4em;
|
||||
|
@ -346,7 +353,7 @@
|
|||
}
|
||||
|
||||
.favs-repeated-users {
|
||||
margin-top: $status-margin;
|
||||
margin-top: var(--status-margin, $status-margin);
|
||||
}
|
||||
|
||||
.stats {
|
||||
|
@ -373,7 +380,7 @@
|
|||
}
|
||||
|
||||
.stat-count {
|
||||
margin-right: $status-margin;
|
||||
margin-right: var(--status-margin, $status-margin);
|
||||
user-select: none;
|
||||
|
||||
.stat-title {
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
<UserAvatar
|
||||
v-if="retweet"
|
||||
class="left-side repeater-avatar"
|
||||
:bot="botIndicator"
|
||||
:better-shadow="betterShadow"
|
||||
:user="statusoid.user"
|
||||
/>
|
||||
|
@ -124,6 +125,7 @@
|
|||
@click.stop.prevent.capture.native="toggleUserExpanded"
|
||||
>
|
||||
<UserAvatar
|
||||
:bot="botIndicator"
|
||||
:compact="compact"
|
||||
:better-shadow="betterShadow"
|
||||
:user="status.user"
|
||||
|
@ -252,7 +254,7 @@
|
|||
>
|
||||
<span
|
||||
v-if="isReply"
|
||||
class="glued-label"
|
||||
class="glued-label reply-glued-label"
|
||||
>
|
||||
<StatusPopover
|
||||
v-if="!isPreview"
|
||||
|
@ -455,7 +457,10 @@
|
|||
class="gravestone"
|
||||
>
|
||||
<div class="left-side">
|
||||
<UserAvatar :compact="compact" />
|
||||
<UserAvatar
|
||||
:compact="compact"
|
||||
:bot="botIndicator"
|
||||
/>
|
||||
</div>
|
||||
<div class="right-side">
|
||||
<div class="deleted-text">
|
||||
|
|
|
@ -21,6 +21,7 @@ library.add(
|
|||
const StatusContent = {
|
||||
name: 'StatusContent',
|
||||
props: [
|
||||
'compact',
|
||||
'status',
|
||||
'focused',
|
||||
'noHeading',
|
||||
|
@ -51,6 +52,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
|
||||
},
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.StatusBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.emoji {
|
||||
--_still_image-label-scale: 0.5;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
& .text,
|
||||
& .summary {
|
||||
font-family: var(--postFont, sans-serif);
|
||||
|
@ -115,4 +121,54 @@
|
|||
.cyantext {
|
||||
color: var(--postCyantext, $fallback--cBlue);
|
||||
}
|
||||
|
||||
&.-compact {
|
||||
align-items: top;
|
||||
flex-direction: row;
|
||||
|
||||
--emoji-size: 16px;
|
||||
|
||||
& .body,
|
||||
& .attachments {
|
||||
max-height: 3.25em;
|
||||
}
|
||||
|
||||
.body {
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
min-width: 5em;
|
||||
flex: 5 1 auto;
|
||||
mask-size: auto 3.5em, auto auto;
|
||||
mask-position: 0 0, 0 0;
|
||||
mask-repeat: repeat-x, repeat;
|
||||
mask-image: linear-gradient(to bottom, white 2em, transparent 3em);
|
||||
|
||||
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 0;
|
||||
flex: 1 1 0;
|
||||
min-width: 5em;
|
||||
height: 100%;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.summary-wrapper {
|
||||
.summary::after {
|
||||
content: ': ';
|
||||
}
|
||||
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div class="StatusBody">
|
||||
<div
|
||||
class="StatusBody"
|
||||
:class="{ '-compact': compact }"
|
||||
>
|
||||
<div class="body">
|
||||
<div
|
||||
v-if="status.summary_raw_html"
|
||||
|
@ -14,14 +17,14 @@
|
|||
<button
|
||||
v-if="longSubject && showingLongSubject"
|
||||
class="button-unstyled -link tall-subject-hider"
|
||||
@click.prevent="showingLongSubject=false"
|
||||
@click.prevent="toggleShowingLongSubject"
|
||||
>
|
||||
{{ $t("status.hide_full_subject") }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="longSubject"
|
||||
class="button-unstyled -link tall-subject-hider"
|
||||
@click.prevent="showingLongSubject=true"
|
||||
@click.prevent="toggleShowingLongSubject"
|
||||
>
|
||||
{{ $t("status.show_full_subject") }}
|
||||
</button>
|
||||
|
|
|
@ -3,7 +3,6 @@ import Poll from '../poll/poll.vue'
|
|||
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 fileType from 'src/services/file_type/file_type.service'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
@ -52,6 +51,7 @@ const StatusContent = {
|
|||
name: 'StatusContent',
|
||||
props: [
|
||||
'status',
|
||||
'compact',
|
||||
'focused',
|
||||
'noHeading',
|
||||
'fullContent',
|
||||
|
@ -85,33 +85,15 @@ const StatusContent = {
|
|||
return true
|
||||
},
|
||||
attachmentSize () {
|
||||
if ((this.mergedConfig.hideAttachments && !this.inConversation) ||
|
||||
if (this.compact) {
|
||||
return 'small'
|
||||
} else if ((this.mergedConfig.hideAttachments && !this.inConversation) ||
|
||||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation) ||
|
||||
(this.status.attachments.length > this.maxThumbnails)) {
|
||||
return 'hide'
|
||||
} else if (this.compact) {
|
||||
return 'small'
|
||||
}
|
||||
return 'normal'
|
||||
},
|
||||
galleryTypes () {
|
||||
if (this.attachmentSize === 'hide') {
|
||||
return []
|
||||
}
|
||||
return this.mergedConfig.playVideosInModal
|
||||
? ['image', 'video']
|
||||
: ['image']
|
||||
},
|
||||
galleryAttachments () {
|
||||
return this.status.attachments.filter(
|
||||
file => fileType.fileMatchesSomeType(this.galleryTypes, file)
|
||||
)
|
||||
},
|
||||
nonGalleryAttachments () {
|
||||
return this.status.attachments.filter(
|
||||
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
|
||||
)
|
||||
},
|
||||
maxThumbnails () {
|
||||
return this.mergedConfig.maxThumbnails
|
||||
},
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<template>
|
||||
<div class="StatusContent">
|
||||
<div
|
||||
class="StatusContent"
|
||||
:class="{ '-compact': compact }"
|
||||
>
|
||||
<slot name="header" />
|
||||
<StatusBody
|
||||
:status="status"
|
||||
:compact="compact"
|
||||
:single-line="singleLine"
|
||||
:showing-tall="showingTall"
|
||||
:expanding-subject="expandingSubject"
|
||||
|
@ -12,38 +16,33 @@
|
|||
:toggle-showing-long-subject="toggleShowingLongSubject"
|
||||
@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-if="status.attachments.length !== 0"
|
||||
class="attachments media-body"
|
||||
>
|
||||
<attachment
|
||||
v-for="attachment in nonGalleryAttachments"
|
||||
:key="attachment.id"
|
||||
class="non-gallery"
|
||||
:size="attachmentSize"
|
||||
:nsfw="nsfwClickthrough"
|
||||
:attachment="attachment"
|
||||
:allow-play="true"
|
||||
:set-media="setMedia()"
|
||||
@play="$emit('mediaplay', attachment.id)"
|
||||
@pause="$emit('mediapause', attachment.id)"
|
||||
/>
|
||||
<gallery
|
||||
v-if="galleryAttachments.length > 0"
|
||||
:nsfw="nsfwClickthrough"
|
||||
:attachments="galleryAttachments"
|
||||
:set-media="setMedia()"
|
||||
<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"
|
||||
:nsfw="nsfwClickthrough"
|
||||
:attachments="status.attachments"
|
||||
:limit="compact ? 1 : 0"
|
||||
:size="attachmentSize"
|
||||
@play="$emit('mediaplay', attachment.id)"
|
||||
@pause="$emit('mediapause', attachment.id)"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="status.card && !noHeading"
|
||||
v-if="status.card && !noHeading && !compact"
|
||||
class="link-preview media-body"
|
||||
>
|
||||
<link-preview
|
||||
|
@ -59,168 +58,8 @@
|
|||
|
||||
<script src="./status_content.js" ></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
$status-margin: 0.75em;
|
||||
|
||||
.StatusContent {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.status-tag {
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.status-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.tall-status {
|
||||
position: relative;
|
||||
height: 220px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
z-index: 1;
|
||||
.status-content {
|
||||
min-height: 0;
|
||||
mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
|
||||
linear-gradient(to top, white, white);
|
||||
/* Autoprefixed seem to ignore this one, and also syntax is different */
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
}
|
||||
|
||||
.tall-status-hider {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
position: absolute;
|
||||
height: 70px;
|
||||
margin-top: 150px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
line-height: 110px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.status-unhider, .cw-status-hider {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
|
||||
svg {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
img, video {
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
|
||||
&.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-wrapper {
|
||||
margin-bottom: 0.5em;
|
||||
border-style: solid;
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: var(--border, $fallback--border);
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-style: italic;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.tall-subject {
|
||||
position: relative;
|
||||
.summary {
|
||||
max-height: 2em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.tall-subject-hider {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
// position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
font-family: var(--postFont, sans-serif);
|
||||
line-height: 1.4em;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
||||
blockquote {
|
||||
margin: 0.2em 0 0.2em 2em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
code, samp, kbd, var, pre {
|
||||
font-family: var(--postCodeFont, monospace);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.2em;
|
||||
margin: 1.4em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.1em;
|
||||
margin: 1.0em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
margin: 1.2em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 1.1em 0;
|
||||
}
|
||||
|
||||
&.single-line {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
height: 1.4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.greentext {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--postGreentext, $fallback--cGreen);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,7 +5,9 @@ const StillImage = {
|
|||
'mimetype',
|
||||
'imageLoadError',
|
||||
'imageLoadHandler',
|
||||
'alt'
|
||||
'alt',
|
||||
'height',
|
||||
'width'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
|
@ -15,6 +17,13 @@ const StillImage = {
|
|||
computed: {
|
||||
animated () {
|
||||
return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif'))
|
||||
},
|
||||
style () {
|
||||
const appendPx = (str) => /\d$/.test(str) ? str + 'px' : str
|
||||
return {
|
||||
height: this.height ? appendPx(this.height) : null,
|
||||
width: this.width ? appendPx(this.width) : null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div
|
||||
class="still-image"
|
||||
:class="{ animated: animated }"
|
||||
:style="style"
|
||||
>
|
||||
<canvas
|
||||
v-if="animated"
|
||||
|
@ -18,6 +19,7 @@
|
|||
@load="onLoad"
|
||||
@error="onError"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -113,13 +113,12 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
.thread-tree-replies {
|
||||
margin-left: $status-margin;
|
||||
margin-left: var(--status-margin, $status-margin);
|
||||
border-left: 2px solid var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
.thread-tree-replies-hidden {
|
||||
padding: $status-margin;
|
||||
//border-top: 1px solid var(--border, $fallback--border);
|
||||
padding: var(--status-margin, $status-margin);
|
||||
/* Make the button stretch along the whole row */
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
|
|
@ -12,19 +12,6 @@ library.add(
|
|||
faCog
|
||||
)
|
||||
|
||||
export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
|
||||
const ids = []
|
||||
if (pinnedStatusIds && pinnedStatusIds.length > 0) {
|
||||
for (let status of statuses) {
|
||||
if (!pinnedStatusIds.includes(status.id)) {
|
||||
break
|
||||
}
|
||||
ids.push(status.id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
const Timeline = {
|
||||
props: [
|
||||
'timeline',
|
||||
|
@ -77,11 +64,6 @@ const Timeline = {
|
|||
}
|
||||
},
|
||||
// id map of statuses which need to be hidden in the main list due to pinning logic
|
||||
excludedStatusIdsObject () {
|
||||
const ids = getExcludedStatusIdsByPinning(this.timeline.visibleStatuses, this.pinnedStatusIds)
|
||||
// Convert id array to object
|
||||
return keyBy(ids)
|
||||
},
|
||||
pinnedStatusIdsObject () {
|
||||
return keyBy(this.pinnedStatusIds)
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
</template>
|
||||
<template v-for="status in timeline.visibleStatuses">
|
||||
<conversation
|
||||
v-if="!excludedStatusIdsObject[status.id]"
|
||||
v-if="timelineName !== 'user' || (status.id >= timeline.minId && status.id <= timeline.maxId)"
|
||||
:key="status.id"
|
||||
class="status-fadein"
|
||||
:status-id="status.id"
|
||||
|
|
|
@ -48,12 +48,18 @@ const TimelineQuickSettings = {
|
|||
}
|
||||
},
|
||||
hideMutedPosts: {
|
||||
get () { return this.mergedConfig.hideMutedPosts || this.mergedConfig.hideFilteredStatuses },
|
||||
get () { return this.mergedConfig.hideFilteredStatuses },
|
||||
set () {
|
||||
const value = !this.hideMutedPosts
|
||||
this.$store.dispatch('setOption', { name: 'hideMutedPosts', value })
|
||||
this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value })
|
||||
}
|
||||
},
|
||||
muteBotStatuses: {
|
||||
get () { return this.mergedConfig.muteBotStatuses },
|
||||
set () {
|
||||
const value = !this.muteBotStatuses
|
||||
this.$store.dispatch('setOption', { name: 'muteBotStatuses', value })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,15 @@
|
|||
class="dropdown-divider"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
@click="muteBotStatuses = !muteBotStatuses"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': muteBotStatuses }"
|
||||
/>{{ $t('settings.mute_bot_posts') }}
|
||||
</button>
|
||||
<button
|
||||
class="button-default dropdown-item"
|
||||
@click="hideMedia = !hideMedia"
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
import StillImage from '../still-image/still-image.vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
||||
import {
|
||||
faRobot
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faRobot
|
||||
)
|
||||
|
||||
const UserAvatar = {
|
||||
props: [
|
||||
'user',
|
||||
'betterShadow',
|
||||
'compact'
|
||||
'compact',
|
||||
'bot'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -7,7 +7,13 @@
|
|||
:src="imgSrc(user.profile_image_url_original)"
|
||||
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
|
||||
:image-load-error="imageLoadError"
|
||||
/>
|
||||
>
|
||||
<FAIcon
|
||||
v-if="bot"
|
||||
icon="robot"
|
||||
class="bot-indicator"
|
||||
/>
|
||||
</StillImage>
|
||||
<div
|
||||
v-else
|
||||
class="Avatar -placeholder"
|
||||
|
@ -36,6 +42,12 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
& > .bot-indicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.better-shadow {
|
||||
box-shadow: var(--_avatarShadowInset);
|
||||
filter: var(--_avatarShadowFilter);
|
||||
|
|
|
@ -166,7 +166,7 @@ export default {
|
|||
mimetype: 'image'
|
||||
}
|
||||
this.$store.dispatch('setMedia', [attachment])
|
||||
this.$store.dispatch('setCurrent', attachment)
|
||||
this.$store.dispatch('setCurrentMedia', attachment)
|
||||
},
|
||||
mentionUser () {
|
||||
this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
|
||||
|
|
|
@ -82,6 +82,12 @@
|
|||
@{{ user.screen_name_ui }}
|
||||
</router-link>
|
||||
<template v-if="!hideBio">
|
||||
<span
|
||||
v-if="user.deactivated"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t('user_card.deactivated') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!!visibleRole"
|
||||
class="alert user-role"
|
||||
|
@ -160,7 +166,10 @@
|
|||
class="user-interactions"
|
||||
>
|
||||
<div class="btn-group">
|
||||
<FollowButton :relationship="relationship" />
|
||||
<FollowButton
|
||||
:relationship="relationship"
|
||||
:user="user"
|
||||
/>
|
||||
<template v-if="relationship.following">
|
||||
<ProgressButton
|
||||
v-if="!relationship.subscribing"
|
||||
|
@ -195,6 +204,7 @@
|
|||
<button
|
||||
v-if="relationship.muting"
|
||||
class="btn button-default btn-block toggled"
|
||||
:disabled="user.deactivated"
|
||||
@click="unmuteUser"
|
||||
>
|
||||
{{ $t('user_card.muted') }}
|
||||
|
@ -202,6 +212,7 @@
|
|||
<button
|
||||
v-else
|
||||
class="btn button-default btn-block"
|
||||
:disabled="user.deactivated"
|
||||
@click="muteUser"
|
||||
>
|
||||
{{ $t('user_card.mute') }}
|
||||
|
@ -210,6 +221,7 @@
|
|||
<div>
|
||||
<button
|
||||
class="btn button-default btn-block"
|
||||
:disabled="user.deactivated"
|
||||
@click="mentionUser"
|
||||
>
|
||||
{{ $t('user_card.mention') }}
|
||||
|
@ -263,6 +275,7 @@
|
|||
class="user-card-bio"
|
||||
:html="user.description_html"
|
||||
:emoji="user.emoji"
|
||||
:handle-links="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,12 @@
|
|||
/>
|
||||
<div class="user-list-names">
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<span v-html="user.name_html" />
|
||||
<RichContent
|
||||
class="username"
|
||||
:title="'@'+user.screen_name_ui"
|
||||
:html="user.name_html"
|
||||
:emoji="user.emoji"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
|
||||
</div>
|
||||
|
@ -48,6 +53,8 @@
|
|||
.user-list-popover {
|
||||
padding: 0.5em;
|
||||
|
||||
--emoji-size: 16px;
|
||||
|
||||
.user-list-row {
|
||||
padding: 0.25em;
|
||||
display: flex;
|
||||
|
|
|
@ -323,7 +323,10 @@
|
|||
"play_videos_in_modal": "Reproduir vídeos en un marc emergent",
|
||||
"file_export_import": {
|
||||
"errors": {
|
||||
"invalid_file": "El fitxer seleccionat no és vàlid com a còpia de seguretat de la configuració. No s'ha realitzat cap canvi."
|
||||
"invalid_file": "El fitxer seleccionat no és vàlid com a còpia de seguretat de la configuració. No s'ha realitzat cap canvi.",
|
||||
"file_too_new": "Versió important incompatible: {fileMajor}, aquest PleromaFE (configuració versió {feMajor}) és massa antiga per gestionar-lo",
|
||||
"file_too_old": "Versió important incompatible: {fileMajor}, la versió del fitxer és massa antiga i no està implementada (s'ha establert un mínim ver. {feMajor})",
|
||||
"file_slightly_new": "La versió menor del fitxer és diferent, alguns paràmetres podrien no carregar-se"
|
||||
},
|
||||
"backup_settings": "Còpia de seguretat de la configuració a un fitxer",
|
||||
"backup_settings_theme": "Còpia de seguretat de la configuració i tema a un fitxer",
|
||||
|
@ -382,7 +385,8 @@
|
|||
"postCode": "Text monoespai en publicació (text enriquit)",
|
||||
"input": "Camps d'entrada",
|
||||
"interface": "Interfície"
|
||||
}
|
||||
},
|
||||
"weight": "Pes (negreta)"
|
||||
},
|
||||
"preview": {
|
||||
"input": "Acabo d'aterrar a Los Angeles.",
|
||||
|
@ -394,7 +398,9 @@
|
|||
"error": "Exemple d'error",
|
||||
"faint_link": "Manual d'ajuda",
|
||||
"checkbox": "He llegit els termes i condicions",
|
||||
"link": "un bonic enllaç"
|
||||
"link": "un bonic enllaç",
|
||||
"fine_print": "Llegiu el nostre {0} per no aprendre res útil!",
|
||||
"text": "Un grapat més de {0} i {1}"
|
||||
},
|
||||
"shadows": {
|
||||
"spread": "Difon",
|
||||
|
@ -438,7 +444,8 @@
|
|||
"snapshot_missing": "No hi havia cap instantània del tema al fitxer, per tant podria veure's diferent del previst originalment.",
|
||||
"upgraded_from_v2": "PleromaFE s'ha actualitzat, el tema pot veure's un poc diferent de com recordes.",
|
||||
"fe_downgraded": "Versió de PleromaFE revertida.",
|
||||
"older_version_imported": "El fitxer que has importat va ser creat en una versió del front-end més antiga."
|
||||
"older_version_imported": "El fitxer que has importat va ser creat en una versió del front-end més antiga.",
|
||||
"snapshot_present": "S'ha carregat la instantània del tema, de manera que tots els valors estan sobreescrits. En canvi, podeu carregar les dades reals del tema."
|
||||
},
|
||||
"keep_as_is": "Mantindre com està",
|
||||
"save_load_hint": "Les opcions \"Mantindre\" conserven les opcions configurades actualment al seleccionar o carregar temes, també emmagatzema aquestes opcions quan s'exporta un tema. Quan es desactiven totes les caselles de verificació, el tema exportat ho guardarà tot.",
|
||||
|
@ -532,7 +539,13 @@
|
|||
"notification_setting_hide_notification_contents": "Amagar el remitent i els continguts de les notificacions push",
|
||||
"notifications": "Notificacions",
|
||||
"notification_mutes": "Per a deixar de rebre notificacions d'un usuari en concret, silencia'l-ho.",
|
||||
"theme_help_v2_2": "Les icones per baix d'algunes entrades són indicadors del contrast del fons/text, desplaça el ratolí per a més informació. Tingues en compte que quan s'utilitzen indicadors de contrast de transparència es mostra el pitjor cas possible."
|
||||
"theme_help_v2_2": "Les icones per baix d'algunes entrades són indicadors del contrast del fons/text, desplaça el ratolí per a més informació. Tingues en compte que quan s'utilitzen indicadors de contrast de transparència es mostra el pitjor cas possible.",
|
||||
"hide_shoutbox": "Oculta la casella de gàbia de grills",
|
||||
"always_show_post_button": "Mostra sempre el botó flotant de publicació nova",
|
||||
"pad_emoji": "Acompanya els emojis amb espais en afegir des del selector",
|
||||
"mentions_new_style": "Enllaços d'esment més elegants",
|
||||
"mentions_new_place": "Posa les mencions en una línia separada",
|
||||
"post_status_content_type": "Format de publicació"
|
||||
},
|
||||
"time": {
|
||||
"day": "{0} dia",
|
||||
|
@ -617,10 +630,11 @@
|
|||
"disable_remote_subscription": "Deshabilita seguir algú des d'una instància remota",
|
||||
"delete_user": "Esborra la usuària",
|
||||
"grant_admin": "Concedir permisos d'Administració",
|
||||
"grant_moderator": "Concedir permisos de Moderació"
|
||||
"grant_moderator": "Concedir permisos de Moderació",
|
||||
"force_unlisted": "Força que les publicacions no estiguin llistades",
|
||||
"sandbox": "Força que els missatges siguin només seguidors"
|
||||
},
|
||||
"edit_profile": "Edita el perfil",
|
||||
"follow_again": "Envia de nou la petició?",
|
||||
"hidden": "Amagat",
|
||||
"follow_sent": "Petició enviada!",
|
||||
"unmute_progress": "Deixant de silenciar…",
|
||||
|
@ -643,7 +657,8 @@
|
|||
"solid": "Fons sòlid",
|
||||
"striped": "Fons a ratlles",
|
||||
"side": "Ratlla lateral"
|
||||
}
|
||||
},
|
||||
"media": "Media"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "Flux personal",
|
||||
|
@ -659,12 +674,14 @@
|
|||
},
|
||||
"remote_user_resolver": {
|
||||
"error": "No trobat.",
|
||||
"searching_for": "Cercant per"
|
||||
"searching_for": "Cercant per",
|
||||
"remote_user_resolver": "Resolució d'usuari remot"
|
||||
},
|
||||
"interactions": {
|
||||
"load_older": "Carrega antigues interaccions",
|
||||
"favs_repeats": "Repeticions i favorits",
|
||||
"follows": "Nous seguidors"
|
||||
"follows": "Nous seguidors",
|
||||
"moves": "Migració d'usuaris"
|
||||
},
|
||||
"emoji": {
|
||||
"stickers": "Adhesius",
|
||||
|
@ -776,7 +793,10 @@
|
|||
"pinned": "Destacat",
|
||||
"reply_to": "Contesta a",
|
||||
"pin": "Destaca al perfil",
|
||||
"unmute_conversation": "Deixa de silenciar la conversa"
|
||||
"unmute_conversation": "Deixa de silenciar la conversa",
|
||||
"mentions": "Mencions",
|
||||
"you": "(Tu)",
|
||||
"plus_more": "+{number} més"
|
||||
},
|
||||
"user_reporting": {
|
||||
"additional_comments": "Comentaris addicionals",
|
||||
|
@ -802,7 +822,8 @@
|
|||
"no_results": "No hi ha resultats",
|
||||
"people": "Persones",
|
||||
"hashtags": "Etiquetes",
|
||||
"people_talking": "{count} persones parlant"
|
||||
"people_talking": "{count} persones parlant",
|
||||
"person_talking": "{count} persones parlant"
|
||||
},
|
||||
"upload": {
|
||||
"file_size_units": {
|
||||
|
|
|
@ -407,7 +407,6 @@
|
|||
"follow": "Sledovat",
|
||||
"follow_sent": "Požadavek odeslán!",
|
||||
"follow_progress": "Odeslílám požadavek…",
|
||||
"follow_again": "Odeslat požadavek znovu?",
|
||||
"follow_unfollow": "Přestat sledovat",
|
||||
"followees": "Sledovaní",
|
||||
"followers": "Sledující",
|
||||
|
|
|
@ -569,7 +569,6 @@
|
|||
"follow": "Folgen",
|
||||
"follow_sent": "Anfrage gesendet!",
|
||||
"follow_progress": "Anfragen…",
|
||||
"follow_again": "Anfrage erneut senden?",
|
||||
"follow_unfollow": "Folgen beenden",
|
||||
"followees": "Folgt",
|
||||
"followers": "Folgende",
|
||||
|
|
|
@ -82,7 +82,10 @@
|
|||
"role": {
|
||||
"admin": "Admin",
|
||||
"moderator": "Moderator"
|
||||
}
|
||||
},
|
||||
"flash_content": "Click to show Flash content using Ruffle (Experimental, may not work).",
|
||||
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
|
||||
"flash_fail": "Failed to load flash content, see console for details."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "Crop picture",
|
||||
|
@ -115,7 +118,9 @@
|
|||
},
|
||||
"media_modal": {
|
||||
"previous": "Previous",
|
||||
"next": "Next"
|
||||
"next": "Next",
|
||||
"counter": "{current} / {total}",
|
||||
"hide": "Close media viewer"
|
||||
},
|
||||
"nav": {
|
||||
"about": "About",
|
||||
|
@ -255,12 +260,14 @@
|
|||
},
|
||||
"settings": {
|
||||
"app_name": "App name",
|
||||
"expert_mode": "Show advanced",
|
||||
"save": "Save changes",
|
||||
"security": "Security",
|
||||
"setting_changed": "Setting is different from default",
|
||||
"setting_server_side": "This setting is tied to your profile and affects all sessions and clients",
|
||||
"enter_current_password_to_confirm": "Enter your current password to confirm your identity",
|
||||
"mentions_new_style": "Fancier mention links",
|
||||
"mentions_new_place": "Put mentions on a separate line",
|
||||
"post_look_feel": "Posts Look & Feel",
|
||||
"mention_links": "Mention links",
|
||||
"mfa": {
|
||||
"otp": "OTP",
|
||||
"setup_otp": "Setup OTP",
|
||||
|
@ -330,10 +337,10 @@
|
|||
"domain_mutes": "Domains",
|
||||
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
|
||||
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
||||
"swap_reacts": "Swap Reactions with Favorite Button",
|
||||
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
|
||||
"export_theme": "Save preset",
|
||||
"filtering": "Filtering",
|
||||
"wordfilter": "Wordfilter",
|
||||
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
||||
"word_filter": "Word filter",
|
||||
"follow_export": "Follow export",
|
||||
|
@ -348,12 +355,11 @@
|
|||
"hide_attachments_in_tl": "Hide attachments in timeline",
|
||||
"hide_media_previews": "Hide media previews",
|
||||
"hide_muted_posts": "Hide posts of muted users",
|
||||
"mute_bot_posts": "Mute bot posts",
|
||||
"hide_bot_indication": "Hide bot indication in posts",
|
||||
"hide_all_muted_posts": "Hide muted posts",
|
||||
"max_thumbnails": "Maximum amount of thumbnails per post",
|
||||
"max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)",
|
||||
"hide_isp": "Hide instance-specific panel",
|
||||
"show_third_column": "Move Notifications to a seperate column",
|
||||
"compact_nav_panel": "Compact navigation panel",
|
||||
"compact_user_panel": "Compact user panel",
|
||||
"hide_shoutbox": "Hide instance shoutbox",
|
||||
"right_sidebar": "Show sidebar on the right side",
|
||||
"always_show_post_button": "Always show floating New Post button",
|
||||
|
@ -362,7 +368,9 @@
|
|||
"use_one_click_nsfw": "Open NSFW attachments with just one click",
|
||||
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
|
||||
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
|
||||
"hide_filtered_statuses": "Hide filtered statuses",
|
||||
"hide_filtered_statuses": "Hide all filtered posts",
|
||||
"hide_wordfiltered_statuses": "Hide word-filtered statuses",
|
||||
"hide_muted_threads": "Hide muted threads",
|
||||
"import_blocks_from_a_csv_file": "Import blocks from a csv file",
|
||||
"import_followers_from_a_csv_file": "Import follows from a csv file",
|
||||
"import_theme": "Load preset",
|
||||
|
@ -398,11 +406,14 @@
|
|||
"name": "Label",
|
||||
"value": "Content"
|
||||
},
|
||||
"account_privacy": "Privacy",
|
||||
"use_contain_fit": "Don't crop the attachment in thumbnails",
|
||||
"name": "Name",
|
||||
"name_bio": "Name & bio",
|
||||
"new_email": "New email",
|
||||
"new_password": "New password",
|
||||
"posts": "Posts",
|
||||
"user_profiles": "User Profiles",
|
||||
"notification_visibility": "Types of notifications to show",
|
||||
"notification_visibility_follows": "Follows",
|
||||
"notification_visibility_likes": "Favorites",
|
||||
|
@ -413,20 +424,21 @@
|
|||
"no_rich_text_description": "Strip rich text formatting from all posts",
|
||||
"no_blocks": "No blocks",
|
||||
"no_mutes": "No mutes",
|
||||
"hide_favorites_description": "Don't show list of my favorites (people still get notified)",
|
||||
"hide_follows_description": "Don't show who I'm following",
|
||||
"hide_followers_description": "Don't show who's following me",
|
||||
"hide_follows_count_description": "Don't show follow count",
|
||||
"hide_followers_count_description": "Don't show follower count",
|
||||
"show_admin_badge": "Show \"Admin\" badge in my profile",
|
||||
"show_moderator_badge": "Show \"Moderator\" badge in my profile",
|
||||
"nsfw_clickthrough": "Enable clickthrough attachment and link preview image hiding for NSFW statuses",
|
||||
"nsfw_clickthrough": "Hide sensitive/NSFW media",
|
||||
"oauth_tokens": "OAuth tokens",
|
||||
"token": "Token",
|
||||
"refresh_token": "Refresh token",
|
||||
"valid_until": "Valid until",
|
||||
"revoke_token": "Revoke",
|
||||
"panelRadius": "Panels",
|
||||
"pause_on_unfocused": "Pause streaming when tab is not focused",
|
||||
"pause_on_unfocused": "Pause when tab is not focused",
|
||||
"presets": "Presets",
|
||||
"profile_background": "Profile background",
|
||||
"profile_banner": "Profile banner",
|
||||
|
@ -463,7 +475,8 @@
|
|||
"subject_line_noop": "Do not copy",
|
||||
"conversation_display": "Conversation display style",
|
||||
"conversation_display_tree": "Tree-style",
|
||||
"conversation_display_simple_tree": "Simplified tree-style",
|
||||
"tree_advanced": "Allow more flexible navigation in tree view",
|
||||
"tree_fade_ancestors": "Display ancestors of the current status in faint text",
|
||||
"conversation_display_linear": "Linear-style",
|
||||
"conversation_other_replies_button": "Show the \"other replies\" button",
|
||||
"conversation_other_replies_button_below": "Below statuses",
|
||||
|
@ -471,11 +484,10 @@
|
|||
"max_depth_in_thread": "Maximum number of levels in thread to display by default",
|
||||
"post_status_content_type": "Post status content type",
|
||||
"sensitive_by_default": "Mark posts as sensitive by default",
|
||||
"stop_gifs": "Play-on-hover GIFs",
|
||||
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
|
||||
"stop_gifs": "Pause animated images until you hover on them",
|
||||
"streaming": "Automatically show new posts when scrolled to the top",
|
||||
"user_mutes": "Users",
|
||||
"useStreamingApi": "Receive posts and notifications real-time",
|
||||
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
|
||||
"text": "Text",
|
||||
"theme": "Theme",
|
||||
"theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
|
||||
|
@ -490,8 +502,18 @@
|
|||
"true": "yes"
|
||||
},
|
||||
"virtual_scrolling": "Optimize timeline rendering",
|
||||
"use_at_icon": "Display @ symbol as an icon instead of text",
|
||||
"mention_link_display": "Display mention links",
|
||||
"mention_link_display_short": "always as short names (e.g. @foo)",
|
||||
"mention_link_display_full_for_remote": "as full names only for remote users (e.g. @foo@example.org)",
|
||||
"mention_link_display_full": "always as full names (e.g. @foo@example.org)",
|
||||
"mention_link_show_tooltip": "Show full user names as tooltip for remote users",
|
||||
"mention_link_show_avatar": "Show user avatar beside the link",
|
||||
"mention_link_fade_domain": "Fade domains (e.g. @example.org in @foo@example.org)",
|
||||
"mention_link_bolden_you": "Highlight mention of you when you are mentioned",
|
||||
"fun": "Fun",
|
||||
"greentext": "Meme arrows",
|
||||
"show_yous": "Show (You)s",
|
||||
"notifications": "Notifications",
|
||||
"notification_setting_filters": "Filters",
|
||||
"notification_setting_block_from_strangers": "Block notifications from users who you do not follow",
|
||||
|
@ -732,6 +754,17 @@
|
|||
"expand": "Expand",
|
||||
"you": "(You)",
|
||||
"plus_more": "+{number} more",
|
||||
"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",
|
||||
"thread_hide": "Hide this thread",
|
||||
"thread_show": "Show this thread",
|
||||
"thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})",
|
||||
|
@ -748,13 +781,14 @@
|
|||
"approve": "Approve",
|
||||
"block": "Block",
|
||||
"blocked": "Blocked!",
|
||||
"deactivated": "Deactivated",
|
||||
"deny": "Deny",
|
||||
"edit_profile": "Edit profile",
|
||||
"favorites": "Favorites",
|
||||
"follow": "Follow",
|
||||
"follow_cancel": "Cancel request",
|
||||
"follow_sent": "Request sent!",
|
||||
"follow_progress": "Requesting…",
|
||||
"follow_again": "Send request again?",
|
||||
"follow_unfollow": "Unfollow",
|
||||
"followees": "Following",
|
||||
"followers": "Followers",
|
||||
|
|
|
@ -553,7 +553,10 @@
|
|||
},
|
||||
"right_sidebar": "Montri flankan breton dekstre",
|
||||
"save": "Konservi ŝanĝojn",
|
||||
"hide_shoutbox": "Kaŝi kriujon de nodo"
|
||||
"hide_shoutbox": "Kaŝi kriujon de nodo",
|
||||
"always_show_post_button": "Ĉiam montri ŝvebantan butonon por nova afiŝo",
|
||||
"mentions_new_style": "Pli mojosaj menciligiloj",
|
||||
"mentions_new_place": "Meti menciojn sur apartan linion"
|
||||
},
|
||||
"timeline": {
|
||||
"collapse": "Maletendi",
|
||||
|
@ -580,7 +583,6 @@
|
|||
"follow": "Aboni",
|
||||
"follow_sent": "Peto sendiĝis!",
|
||||
"follow_progress": "Petante…",
|
||||
"follow_again": "Ĉu sendi peton ree?",
|
||||
"follow_unfollow": "Malaboni",
|
||||
"followees": "Abonatoj",
|
||||
"followers": "Abonantoj",
|
||||
|
@ -632,7 +634,8 @@
|
|||
"striped": "Stria fono",
|
||||
"solid": "Unueca fono",
|
||||
"disabled": "Senemfaze"
|
||||
}
|
||||
},
|
||||
"edit_profile": "Redakti profilon"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "Historio de uzanto",
|
||||
|
@ -783,7 +786,10 @@
|
|||
"status_deleted": "Ĉi tiu afiŝo foriĝis",
|
||||
"nsfw": "Konsterna",
|
||||
"expand": "Etendi",
|
||||
"external_source": "Ekstera fonto"
|
||||
"external_source": "Ekstera fonto",
|
||||
"mentions": "Mencioj",
|
||||
"you": "(Vi)",
|
||||
"plus_more": "+{number} pli"
|
||||
},
|
||||
"time": {
|
||||
"years_short": "{0}j",
|
||||
|
|
|
@ -599,7 +599,10 @@
|
|||
"backup_restore": "Copia de seguridad de la configuración"
|
||||
},
|
||||
"hide_shoutbox": "Ocultar cuadro de diálogo de la instancia",
|
||||
"right_sidebar": "Mostrar la barra lateral a la derecha"
|
||||
"right_sidebar": "Mostrar la barra lateral a la derecha",
|
||||
"always_show_post_button": "Muestra siempre el botón flotante de Nueva Plubicación",
|
||||
"mentions_new_style": "Enlaces de menciones más elegantes",
|
||||
"mentions_new_place": "Situa las menciones en una línea separada"
|
||||
},
|
||||
"time": {
|
||||
"day": "{0} día",
|
||||
|
@ -676,7 +679,10 @@
|
|||
"status_deleted": "Esta publicación ha sido eliminada",
|
||||
"nsfw": "NSFW (No apropiado para el trabajo)",
|
||||
"expand": "Expandir",
|
||||
"external_source": "Fuente externa"
|
||||
"external_source": "Fuente externa",
|
||||
"mentions": "Menciones",
|
||||
"you": "(Tú)",
|
||||
"plus_more": "+{number} más"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "Aprobar",
|
||||
|
@ -687,7 +693,6 @@
|
|||
"follow": "Seguir",
|
||||
"follow_sent": "¡Solicitud enviada!",
|
||||
"follow_progress": "Solicitando…",
|
||||
"follow_again": "¿Enviar solicitud de nuevo?",
|
||||
"follow_unfollow": "Dejar de seguir",
|
||||
"followees": "Siguiendo",
|
||||
"followers": "Seguidores",
|
||||
|
|
|
@ -569,7 +569,6 @@
|
|||
"follow": "Jarraitu",
|
||||
"follow_sent": "Eskaera bidalita!",
|
||||
"follow_progress": "Eskatzen…",
|
||||
"follow_again": "Eskaera berriro bidali?",
|
||||
"follow_unfollow": "Jarraitzeari utzi",
|
||||
"followees": "Jarraitzen",
|
||||
"followers": "Jarraitzaileak",
|
||||
|
|
|
@ -590,7 +590,6 @@
|
|||
"follow": "Seuraa",
|
||||
"follow_sent": "Pyyntö lähetetty!",
|
||||
"follow_progress": "Pyydetään…",
|
||||
"follow_again": "Lähetä pyyntö uudestaan?",
|
||||
"follow_unfollow": "Älä seuraa",
|
||||
"followees": "Seuraa",
|
||||
"followers": "Seuraajat",
|
||||
|
|
|
@ -624,7 +624,6 @@
|
|||
"follow": "Suivre",
|
||||
"follow_sent": "Demande envoyée !",
|
||||
"follow_progress": "Demande en cours…",
|
||||
"follow_again": "Renvoyer la demande ?",
|
||||
"follow_unfollow": "Désabonner",
|
||||
"followees": "Suivis",
|
||||
"followers": "Vous suivent",
|
||||
|
|
|
@ -312,7 +312,6 @@
|
|||
"follow": "עקוב",
|
||||
"follow_sent": "בקשה נשלחה!",
|
||||
"follow_progress": "מבקש…",
|
||||
"follow_again": "שלח בקשה שוב?",
|
||||
"follow_unfollow": "בטל עקיבה",
|
||||
"followees": "נעקבים",
|
||||
"followers": "עוקבים",
|
||||
|
|
|
@ -208,7 +208,13 @@
|
|||
"enable_web_push_notifications": "Aktifkan notifikasi push web",
|
||||
"more_settings": "Lebih banyak pengaturan",
|
||||
"reply_visibility_all": "Tampilkan semua balasan",
|
||||
"reply_visibility_self": "Hanya tampilkan balasan yang ditujukan kepada saya"
|
||||
"reply_visibility_self": "Hanya tampilkan balasan yang ditujukan kepada saya",
|
||||
"hide_muted_posts": "Sembunyikan postingan-postingan dari pengguna yang dibisukan",
|
||||
"import_blocks_from_a_csv_file": "Impor blokiran dari berkas csv",
|
||||
"domain_mutes": "Domain",
|
||||
"composing": "Menulis",
|
||||
"no_blocks": "Tidak ada yang diblokir",
|
||||
"no_mutes": "Tidak ada yang dibisukan"
|
||||
},
|
||||
"about": {
|
||||
"mrf": {
|
||||
|
@ -222,7 +228,9 @@
|
|||
"reject_desc": "Instansi ini tidak akan menerima pesan dari instansi-instansi berikut:",
|
||||
"reject": "Tolak",
|
||||
"accept_desc": "Instansi ini hanya menerima pesan dari instansi-instansi berikut:",
|
||||
"accept": "Terima"
|
||||
"accept": "Terima",
|
||||
"media_removal": "Penghapusan Media",
|
||||
"media_removal_desc": "Instansi ini menghapus media dari postingan yang berasal dari instansi-instansi berikut:"
|
||||
},
|
||||
"federation": "Federasi",
|
||||
"mrf_policies": "Kebijakan MRF yang diaktifkan"
|
||||
|
@ -322,7 +330,6 @@
|
|||
"delete_user": "Hapus pengguna",
|
||||
"delete_user_confirmation": "Apakah Anda benar-benar yakin? Tindakan ini tidak dapat dibatalkan."
|
||||
},
|
||||
"follow_again": "Kirim permintaan lagi?",
|
||||
"follow_unfollow": "Berhenti mengikuti",
|
||||
"followees": "Mengikuti",
|
||||
"followers": "Pengikut",
|
||||
|
@ -335,7 +342,9 @@
|
|||
"message": "Kirimkan pesan"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "Linimasa pengguna"
|
||||
"timeline_title": "Linimasa pengguna",
|
||||
"profile_does_not_exist": "Maaf, profil ini tidak ada.",
|
||||
"profile_loading_error": "Maaf, terjadi kesalahan ketika memuat profil ini."
|
||||
},
|
||||
"user_reporting": {
|
||||
"title": "Melaporkan {0}",
|
||||
|
|
|
@ -448,7 +448,10 @@
|
|||
"backup_restore": "Archiviazione impostazioni"
|
||||
},
|
||||
"right_sidebar": "Mostra barra laterale a destra",
|
||||
"hide_shoutbox": "Nascondi muro dei graffiti"
|
||||
"hide_shoutbox": "Nascondi muro dei graffiti",
|
||||
"mentions_new_style": "Menzioni abbreviate",
|
||||
"mentions_new_place": "Segrega le menzioni",
|
||||
"always_show_post_button": "Non nascondere il pulsante di composizione"
|
||||
},
|
||||
"timeline": {
|
||||
"error_fetching": "Errore nell'aggiornamento",
|
||||
|
@ -516,7 +519,6 @@
|
|||
"its_you": "Sei tu!",
|
||||
"hidden": "Nascosto",
|
||||
"follow_unfollow": "Disconosci",
|
||||
"follow_again": "Reinvio richiesta?",
|
||||
"follow_progress": "Richiedo…",
|
||||
"follow_sent": "Richiesta inviata!",
|
||||
"favorites": "Preferiti",
|
||||
|
@ -758,7 +760,10 @@
|
|||
"status_deleted": "Questo messagio è stato cancellato",
|
||||
"nsfw": "DISDICEVOLE",
|
||||
"external_source": "Vai all'origine",
|
||||
"expand": "Espandi"
|
||||
"expand": "Espandi",
|
||||
"mentions": "Menzioni",
|
||||
"you": "(Tu)",
|
||||
"plus_more": "+{number} altri"
|
||||
},
|
||||
"time": {
|
||||
"years_short": "{0} a",
|
||||
|
@ -775,8 +780,8 @@
|
|||
"second": "{0} secondo",
|
||||
"now_short": "adesso",
|
||||
"now": "adesso",
|
||||
"months_short": "{0} ms",
|
||||
"month_short": "{0} ms",
|
||||
"months_short": "{0} mes",
|
||||
"month_short": "{0} mes",
|
||||
"months": "{0} mesi",
|
||||
"month": "{0} mese",
|
||||
"minutes_short": "{0} min",
|
||||
|
|
|
@ -567,7 +567,6 @@
|
|||
"follow": "フォロー",
|
||||
"follow_sent": "リクエストを、おくりました!",
|
||||
"follow_progress": "リクエストしています…",
|
||||
"follow_again": "ふたたびリクエストをおくりますか?",
|
||||
"follow_unfollow": "フォローをやめる",
|
||||
"followees": "フォロー",
|
||||
"followers": "フォロワー",
|
||||
|
|
|
@ -43,7 +43,10 @@
|
|||
"role": {
|
||||
"moderator": "モデレーター",
|
||||
"admin": "管理者"
|
||||
}
|
||||
},
|
||||
"flash_security": "Flashコンテンツが任意の命令を実行させることにより、コンピューターが危険にさらされることがあります。",
|
||||
"flash_fail": "Flashコンテンツの読み込みに失敗しました。コンソールで詳細を確認できます。",
|
||||
"flash_content": "(試験的機能)クリックしてFlashコンテンツを再生します。"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop_picture": "画像を切り抜く",
|
||||
|
@ -586,14 +589,18 @@
|
|||
"word_filter": "単語フィルタ",
|
||||
"file_export_import": {
|
||||
"errors": {
|
||||
"invalid_file": "これはPleromaの設定をバックアップしたファイルではありません。"
|
||||
"invalid_file": "これはPleromaの設定をバックアップしたファイルではありません。",
|
||||
"file_slightly_new": "ファイルのマイナーバージョンが異なり、一部の設定が読み込まれないことがあります"
|
||||
},
|
||||
"restore_settings": "設定をファイルから復元する",
|
||||
"backup_settings_theme": "テーマを含む設定をファイルにバックアップする",
|
||||
"backup_settings": "設定をファイルにバックアップする",
|
||||
"backup_restore": "設定をバックアップ"
|
||||
},
|
||||
"save": "変更を保存"
|
||||
"save": "変更を保存",
|
||||
"hide_shoutbox": "Shoutboxを表示しない",
|
||||
"always_show_post_button": "投稿ボタンを常に表示",
|
||||
"right_sidebar": "サイドバーを右に表示"
|
||||
},
|
||||
"time": {
|
||||
"day": "{0}日",
|
||||
|
@ -641,7 +648,9 @@
|
|||
"no_more_statuses": "これで終わりです",
|
||||
"no_statuses": "ステータスはありません",
|
||||
"reload": "再読み込み",
|
||||
"error": "タイムラインの読み込みに失敗しました: {0}"
|
||||
"error": "タイムラインの読み込みに失敗しました: {0}",
|
||||
"socket_reconnected": "リアルタイム接続が確立されました",
|
||||
"socket_broke": "コード{0}によりリアルタイム接続が切断されました"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "お気に入り",
|
||||
|
@ -668,7 +677,10 @@
|
|||
"copy_link": "リンクをコピー",
|
||||
"status_unavailable": "利用できません",
|
||||
"unbookmark": "ブックマーク解除",
|
||||
"bookmark": "ブックマーク"
|
||||
"bookmark": "ブックマーク",
|
||||
"mentions": "メンション",
|
||||
"you": "(あなた)",
|
||||
"plus_more": "ほか{number}件"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "受け入れ",
|
||||
|
@ -679,7 +691,6 @@
|
|||
"follow": "フォロー",
|
||||
"follow_sent": "リクエストを送りました!",
|
||||
"follow_progress": "リクエストしています…",
|
||||
"follow_again": "再びリクエストを送りますか?",
|
||||
"follow_unfollow": "フォローをやめる",
|
||||
"followees": "フォロー",
|
||||
"followers": "フォロワー",
|
||||
|
@ -735,7 +746,8 @@
|
|||
"striped": "背景を縞模様にする",
|
||||
"side": "端に線を付ける",
|
||||
"disabled": "強調しない"
|
||||
}
|
||||
},
|
||||
"edit_profile": "プロフィールを編集"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "ユーザータイムライン",
|
||||
|
|
|
@ -428,7 +428,6 @@
|
|||
"follow": "팔로우",
|
||||
"follow_sent": "요청 보내짐!",
|
||||
"follow_progress": "요청 중…",
|
||||
"follow_again": "요청을 다시 보낼까요?",
|
||||
"follow_unfollow": "팔로우 중지",
|
||||
"followees": "팔로우 중",
|
||||
"followers": "팔로워",
|
||||
|
@ -492,7 +491,9 @@
|
|||
"votes_count": "{count} 표 | {count} 표",
|
||||
"people_voted_count": "{count} 명 투표 | {count} 명 투표",
|
||||
"option": "선택지",
|
||||
"add_option": "선택지 추가"
|
||||
"add_option": "선택지 추가",
|
||||
"expired": "투표는 {0} 전에 마감되었습니다",
|
||||
"expires_in": "투표는 {0}에 마감됩니다"
|
||||
},
|
||||
"media_modal": {
|
||||
"next": "다음",
|
||||
|
|
|
@ -516,7 +516,6 @@
|
|||
"follow": "Følg",
|
||||
"follow_sent": "Forespørsel sendt!",
|
||||
"follow_progress": "Forespør…",
|
||||
"follow_again": "Gjenta forespørsel?",
|
||||
"follow_unfollow": "Avfølg",
|
||||
"followees": "Følger",
|
||||
"followers": "Følgere",
|
||||
|
|
|
@ -565,9 +565,9 @@
|
|||
"deny": "Weigeren",
|
||||
"favorites": "Favorieten",
|
||||
"follow": "Volgen",
|
||||
"follow_cancel": "Aanvraag annuleren",
|
||||
"follow_sent": "Aanvraag verzonden!",
|
||||
"follow_progress": "Aanvragen…",
|
||||
"follow_again": "Aanvraag opnieuw zenden?",
|
||||
"follow_unfollow": "Stop volgen",
|
||||
"followees": "Aan het volgen",
|
||||
"followers": "Volgers",
|
||||
|
|
|
@ -465,7 +465,6 @@
|
|||
"follow": "Seguir",
|
||||
"follow_sent": "Demanda enviada !",
|
||||
"follow_progress": "Demanda…",
|
||||
"follow_again": "Tornar enviar la demanda ?",
|
||||
"follow_unfollow": "Quitar de seguir",
|
||||
"followees": "Abonaments",
|
||||
"followers": "Seguidors",
|
||||
|
|
|
@ -721,7 +721,6 @@
|
|||
"follow": "Obserwuj",
|
||||
"follow_sent": "Wysłano prośbę!",
|
||||
"follow_progress": "Wysyłam prośbę…",
|
||||
"follow_again": "Wysłać prośbę ponownie?",
|
||||
"follow_unfollow": "Przestań obserwować",
|
||||
"followees": "Obserwowani",
|
||||
"followers": "Obserwujący",
|
||||
|
|
|
@ -575,7 +575,6 @@
|
|||
"follow": "Seguir",
|
||||
"follow_sent": "Pedido enviado!",
|
||||
"follow_progress": "Enviando…",
|
||||
"follow_again": "Enviar solicitação novamente?",
|
||||
"follow_unfollow": "Deixar de seguir",
|
||||
"followees": "Seguindo",
|
||||
"followers": "Seguidores",
|
||||
|
|
|
@ -550,7 +550,6 @@
|
|||
"follow": "Читать",
|
||||
"follow_sent": "Запрос отправлен!",
|
||||
"follow_progress": "Запрашиваем…",
|
||||
"follow_again": "Запросить еще раз?",
|
||||
"follow_unfollow": "Перестать читать",
|
||||
"followees": "Читаемые",
|
||||
"followers": "Читатели",
|
||||
|
|
|
@ -310,7 +310,6 @@
|
|||
"user_card.follow": "Follow",
|
||||
"user_card.follow_sent": "Request sent!",
|
||||
"user_card.follow_progress": "Requesting…",
|
||||
"user_card.follow_again": "Send request again?",
|
||||
"user_card.follow_unfollow": "Unfollow",
|
||||
"user_card.followees": "Following",
|
||||
"user_card.followers": "Followers",
|
||||
|
|
|
@ -748,7 +748,6 @@
|
|||
"message": "Повідомлення",
|
||||
"follow": "Підписатись",
|
||||
"follow_unfollow": "Відписатись",
|
||||
"follow_again": "Відправити запит знову?",
|
||||
"follow_sent": "Запит відправлено!",
|
||||
"blocked": "Заблоковано!",
|
||||
"admin_menu": {
|
||||
|
|
445
src/i18n/vi.json
445
src/i18n/vi.json
|
@ -51,7 +51,7 @@
|
|||
"scope_options": "Đa dạng kiểu đăng"
|
||||
},
|
||||
"finder": {
|
||||
"error_fetching_user": "Lỗi người dùng",
|
||||
"error_fetching_user": "Lỗi khi nạp người dùng",
|
||||
"find_user": "Tìm người dùng"
|
||||
},
|
||||
"shoutbox": {
|
||||
|
@ -149,7 +149,7 @@
|
|||
"no_more_notifications": "Không còn thông báo nào",
|
||||
"migrated_to": "chuyển sang",
|
||||
"reacted_with": "chạm tới {0}",
|
||||
"error": "Lỗi xử lý thông báo: {0}"
|
||||
"error": "Lỗi khi nạp thông báo {0}"
|
||||
},
|
||||
"polls": {
|
||||
"add_poll": "Tạo bình chọn",
|
||||
|
@ -197,7 +197,7 @@
|
|||
"text/bbcode": "BBCode"
|
||||
},
|
||||
"content_warning": "Tiêu đề (tùy chọn)",
|
||||
"default": "Just landed in L.A.",
|
||||
"default": "Đời người con gái không muốn yêu ai được không?",
|
||||
"direct_warning_to_first_only": "Người đầu tiên được nhắc đến mới có thể thấy tút này.",
|
||||
"posting": "Đang đăng tút",
|
||||
"post": "Đăng",
|
||||
|
@ -427,9 +427,446 @@
|
|||
"no_rich_text_description": "Không hiện rich text trong các tút",
|
||||
"hide_follows_count_description": "Ẩn số lượng người tôi theo dõi",
|
||||
"nsfw_clickthrough": "Cho phép nhấn vào xem các tút nhạy cảm",
|
||||
"reply_visibility_following": "Chỉ hiện những trả lời có nhắc tới tôi hoặc từ những người mà tôi theo dõi"
|
||||
"reply_visibility_following": "Chỉ hiện những trả lời có nhắc tới tôi hoặc từ những người mà tôi theo dõi",
|
||||
"autohide_floating_post_button": "Ẩn nút viết tút khi xem bảng tin (di động)",
|
||||
"saving_err": "Thiết lập lỗi lưu",
|
||||
"saving_ok": "Đã lưu các thay đổi",
|
||||
"search_user_to_block": "Tìm người bạn muốn chặn",
|
||||
"search_user_to_mute": "Tìm người bạn muốn ẩn",
|
||||
"security_tab": "Bảo mật",
|
||||
"scope_copy": "Chép phạm vi khi trả lời (tin nhắn luôn được chép sẵn)",
|
||||
"minimal_scopes_mode": "Tùy chọn thu nhỏ phạm vi tút",
|
||||
"set_new_avatar": "Đổi ảnh đại diện",
|
||||
"set_new_profile_background": "Đổi ảnh nền",
|
||||
"set_new_profile_banner": "Đổi ảnh bìa",
|
||||
"reset_profile_background": "Đặt lại ảnh nền",
|
||||
"reset_profile_banner": "Đặt lại ảnh bìa",
|
||||
"reset_banner_confirm": "Bạn có chắc chắn muốn đặt lại ảnh bìa?",
|
||||
"reset_background_confirm": "Bạn có chắc chắn muốn đặt lại ảnh nền?",
|
||||
"settings": "Cài đặt",
|
||||
"subject_input_always_show": "Luôn hiện vùng tiêu đề",
|
||||
"subject_line_behavior": "Chép tiêu đề khi trả lời",
|
||||
"subject_line_email": "Giống email: \"re: subject\"",
|
||||
"subject_line_mastodon": "Giống Mastodon: copy as is",
|
||||
"subject_line_noop": "Đừng chép",
|
||||
"sensitive_by_default": "Mặc định tút là nhạy cảm",
|
||||
"stop_gifs": "Chỉ phát GIF khi chạm vào",
|
||||
"streaming": "Tự động tải tút mới khi cuộn lên trên",
|
||||
"user_mutes": "Người dùng",
|
||||
"useStreamingApiWarning": "(Tính năng thử nghiệm, không đề xuất sử dụng)",
|
||||
"text": "Văn bản",
|
||||
"theme": "Theme",
|
||||
"theme_help": "Dùng mã màu hex (#rrggbb) để tự chế theme.",
|
||||
"tooltipRadius": "Tooltips/alerts",
|
||||
"type_domains_to_mute": "Tìm máy chủ để ẩn",
|
||||
"upload_a_photo": "Tải ảnh lên",
|
||||
"user_settings": "Thiết lập người dùng",
|
||||
"values": {
|
||||
"false": "không",
|
||||
"true": "có"
|
||||
},
|
||||
"virtual_scrolling": "Render bảng tin",
|
||||
"fun": "Vui nhộn",
|
||||
"greentext": "Mũi tên meme",
|
||||
"notifications": "Thông báo",
|
||||
"notification_setting_filters": "Bộ lọc",
|
||||
"notification_setting_block_from_strangers": "Chặn thông báo từ những người bạn không theo dõi",
|
||||
"notification_setting_privacy": "Riêng tư",
|
||||
"notification_setting_hide_notification_contents": "Ẩn người gửi và nội dung thông báo đẩy",
|
||||
"notification_mutes": "Sử dụng ẩn nếu muốn dừng nhận thông báo từ một người cụ thể.",
|
||||
"notification_blocks": "Chặn một người ngừng toàn bộ thông báo cũng giống như hủy đăng ký họ.",
|
||||
"more_settings": "Cài đặt khác",
|
||||
"style": {
|
||||
"switcher": {
|
||||
"keep_shadows": "Giữ bóng đổ",
|
||||
"keep_color": "Giữ màu",
|
||||
"keep_opacity": "Giữ trong suốt",
|
||||
"keep_roundness": "Giữ bo tròn góc",
|
||||
"reset": "Đặt lại",
|
||||
"clear_all": "Xóa hết",
|
||||
"clear_opacity": "Xóa trong suốt",
|
||||
"load_theme": "Tải theme",
|
||||
"keep_as_is": "Giữ như là",
|
||||
"use_snapshot": "Bản cũ",
|
||||
"use_source": "Bản mới",
|
||||
"help": {
|
||||
"upgraded_from_v2": "PleromaFE đã được nâng cấp, theme có thể khác hơn một chút so với bản cũ.",
|
||||
"v2_imported": "Tập tin bạn nhập là từ phiên bản PleromaFE cũ. Chúng tôi sẽ cố làm nó tương thích nhưng có thể sẽ có xung đột.",
|
||||
"older_version_imported": "Tập tin bạn vừa nhập được tạo ra từ phiên bản PleromaFE cũ.",
|
||||
"snapshot_present": "Đã tải theme snapshot, mọi giá trị sẽ bị chép đè. Thay vào đó, bạn có thể tải dữ liệu chắc chắn của theme.",
|
||||
"fe_upgraded": "Theme của PleromaFE được nâng cấp sau mỗi phiên bản.",
|
||||
"fe_downgraded": "Theme của phiên bản PleromaFE đã được hạ cấp.",
|
||||
"migration_snapshot_ok": "Theme snapshot đã tải xong. Bạn có thể thử tải dữ liệu theme.",
|
||||
"migration_napshot_gone": "Nếu thiếu snapshot, một số thứ sẽ khác với ban đầu.",
|
||||
"future_version_imported": "Tập tin bạn vừa nhập được tạo ra từ phiên bản PleromaFE mới.",
|
||||
"snapshot_missing": "Không có theme snapshot trong tập tin cho nên có thể nó sẽ khác với bản gốc đôi chút.",
|
||||
"snapshot_source_mismatch": "Xung đột phiên bản: hầu hết Pleroma FE đã hạ cấp và cập nhật lại, nếu bạn đổi theme sử dụng phiên bản cũ hơn của FE, bạn gần như muốn sử dụng phiên bản cũ, thay vào đó sử dụng phiên bản mới."
|
||||
},
|
||||
"keep_fonts": "Giữ phông chữ",
|
||||
"save_load_hint": "Giúp giữ nguyên các tùy chọn hiện tại khi chọn hoặc tải theme khác, nó cũng lưu trữ các tùy chọn đã nói khi xuất một theme. Khi tất cả các hộp kiểm bị bỏ trống, việc xuất theme sẽ lưu mọi thứ."
|
||||
},
|
||||
"common": {
|
||||
"color": "Màu sắc",
|
||||
"opacity": "Trong suốt",
|
||||
"contrast": {
|
||||
"hint": "Tỉ lệ tương phản là {ratio}, nó {level} {context}",
|
||||
"level": {
|
||||
"aa": "đạt mức AA (tối thiểu)",
|
||||
"aaa": "đạt mức AAA (đề xuất)",
|
||||
"bad": "không đạt yêu cầu"
|
||||
},
|
||||
"context": {
|
||||
"18pt": "cỡ chữ lớn (18pt+)",
|
||||
"text": "cho chữ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_colors": {
|
||||
"_tab_label": "Chung",
|
||||
"main": "Màu sắc chung",
|
||||
"foreground_hint": "Mở tab \"Nâng cao\" để có nhiều tùy chọn hơn",
|
||||
"rgbo": "Icons, accents, badges"
|
||||
},
|
||||
"advanced_colors": {
|
||||
"_tab_label": "Nâng cao",
|
||||
"alert": "Nền cảnh báo",
|
||||
"alert_error": "Lỗi",
|
||||
"alert_warning": "Cảnh báo",
|
||||
"alert_neutral": "Neutral",
|
||||
"post": "Tút/Tiểu sử",
|
||||
"badge": "Nền huy hiệu",
|
||||
"popover": "Tooltips, menus, popovers",
|
||||
"badge_notification": "Thông báo",
|
||||
"panel_header": "Tiêu đề panel",
|
||||
"top_bar": "Thanh trên cùng",
|
||||
"borders": "Đường biên",
|
||||
"buttons": "Nút bấm",
|
||||
"faint_text": "Chữ mờ",
|
||||
"underlay": "Lớp dưới",
|
||||
"wallpaper": "Wallpaper",
|
||||
"poll": "Biểu đồ cuộc bình chọn",
|
||||
"icons": "Biểu tượng",
|
||||
"highlight": "Những thành phần nổi bật",
|
||||
"pressed": "Khi nhấn xuống",
|
||||
"selectedPost": "Chọn tút",
|
||||
"selectedMenu": "Chọn menu",
|
||||
"toggled": "Toggled",
|
||||
"tabs": "Tab",
|
||||
"chat": {
|
||||
"incoming": "Tin nhắn đến",
|
||||
"outgoing": "Tin nhắn đi",
|
||||
"border": "Đường biên"
|
||||
},
|
||||
"inputs": "Khung soạn thảo",
|
||||
"disabled": "Vô hiệu hóa"
|
||||
},
|
||||
"radii": {
|
||||
"_tab_label": "Góc bo tròn"
|
||||
},
|
||||
"shadows": {
|
||||
"component": "Thành phần",
|
||||
"shadow_id": "Đổ bóng #{value}",
|
||||
"blur": "Làm mờ",
|
||||
"spread": "Mở rộng",
|
||||
"inset": "Thu vào",
|
||||
"filter_hint": {
|
||||
"always_drop_shadow": "Chú ý, màu bóng đổ này luôn sử dụng {0} nếu trình duyệt hỗ trợ.",
|
||||
"drop_shadow_syntax": "{0} không hỗ trợ {1} phần và từ khóa {2}.",
|
||||
"spread_zero": "Bóng đổ > 0 sẽ xuất hiện nếu chọn nó thành không",
|
||||
"inset_classic": "Bóng đổ inset sẽ sử dụng {0}",
|
||||
"avatar_inset": "Nếu trộn lẫn bóng đổ inset và non-inset trên ảnh đại diện có thể khiến ảnh đại diện biến thành trong suốt."
|
||||
},
|
||||
"components": {
|
||||
"panel": "Panel",
|
||||
"panelHeader": "Panel ảnh bìa",
|
||||
"topBar": "Thanh trên cùng",
|
||||
"avatar": "Ảnh đại diện (ở trang cá nhân)",
|
||||
"avatarStatus": "Ảnh đại diện (ở tút)",
|
||||
"popup": "Popups và tooltips",
|
||||
"button": "Nút bấm",
|
||||
"buttonHover": "Nút bấm (khi rê chuột)",
|
||||
"buttonPressed": "Nút bấm (khi nhấn chuột)",
|
||||
"buttonPressedHover": "Nút bấm (khi nhấn+giữ)",
|
||||
"input": "Khung soạn thảo"
|
||||
},
|
||||
"_tab_label": "Đổ bóng và tô sáng",
|
||||
"override": "Chép đè",
|
||||
"hintV3": "Với bóng đổ, bạn có thể sử dụng ký hiệu {0} để dùng slot màu khác."
|
||||
},
|
||||
"fonts": {
|
||||
"_tab_label": "Phông chữ",
|
||||
"components": {
|
||||
"interface": "Giao diện chung",
|
||||
"input": "Khung soạn thảo",
|
||||
"post": "Tút",
|
||||
"postCode": "Chữ monospaced (rich text)"
|
||||
},
|
||||
"family": "Tên phông",
|
||||
"size": "Kích cỡ (px)",
|
||||
"weight": "Độ đậm",
|
||||
"custom": "Tùy chỉnh",
|
||||
"help": "Chọn phông chữ hiển thị. Để \"tùy chọn\", bạn phải nhập chính xác tên phông chữ trên hệ thống."
|
||||
},
|
||||
"preview": {
|
||||
"header": "Xem trước",
|
||||
"content": "Nội dung",
|
||||
"error": "Lỗi mẫu ví dụ",
|
||||
"button": "Nút bấm",
|
||||
"text": "Một đống {0} và {1}",
|
||||
"mono": "nội dung",
|
||||
"input": "Đời người con gái không muốn yêu ai được không?",
|
||||
"faint_link": "tài liệu hướng dẫn",
|
||||
"checkbox": "Tôi đã đọc lướt qua quy tắc và chính sách bảo mật",
|
||||
"link": "Link đẹp đó em yêu",
|
||||
"fine_print": "Đọc {0} để tìm hiểu thêm!",
|
||||
"header_faint": "OK nè"
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
"title": "Phiên bản",
|
||||
"frontend_version": "Frontend",
|
||||
"backend_version": "Backend"
|
||||
},
|
||||
"reset_avatar": "Đặt lại ảnh đại diện",
|
||||
"reset_avatar_confirm": "Bạn có chắc chắn muốn đặt lại ảnh đại diện?",
|
||||
"post_status_content_type": "Loại tút đăng",
|
||||
"useStreamingApi": "Nhận tút và thông báo theo thời gian thực",
|
||||
"theme_help_v2_1": "Bạn cũng có thể xóa hết màu thành phần và làm theme trong suốt, chọn nút \"Xóa hết\".",
|
||||
"theme_help_v2_2": "Các biểu tượng bên dưới các mục có độ tương phản nền/văn bản, hãy rê chuột qua để biết thông tin chi tiết. Xin lưu ý rằng, khi sử dụng các độ tương phản trong suốt có thể khiến đọc chữ không ra.",
|
||||
"enable_web_push_notifications": "Cho phép thông báo đẩy trên web",
|
||||
"mentions_new_style": "Lượt nhắc màu mè",
|
||||
"mentions_new_place": "Đặt lượt nhắc ở dòng riêng",
|
||||
"always_show_post_button": "Luôn hiện nút viết tút mới"
|
||||
},
|
||||
"errors": {
|
||||
"storage_unavailable": "Pleroma không thể truy cập lưu trữ trình duyệt. Thông tin đăng nhập và những thiết lập tạm thời sẽ bị mất. Hãy cho phép cookies."
|
||||
},
|
||||
"time": {
|
||||
"day": "{0} ngày",
|
||||
"days": "{0} ngày",
|
||||
"day_short": "{0} ngày",
|
||||
"days_short": "{0} ngày",
|
||||
"hour": "{0} giờ",
|
||||
"hours": "{0} giờ",
|
||||
"hour_short": "{0} giờ",
|
||||
"hours_short": "{0} giờ",
|
||||
"in_future": "lúc {0}",
|
||||
"in_past": "{0} trước",
|
||||
"minute": "{0} phút",
|
||||
"minutes": "{0} phút",
|
||||
"minute_short": "{0} phút",
|
||||
"minutes_short": "{0} phút",
|
||||
"month": "{0} tháng",
|
||||
"months": "{0} tháng",
|
||||
"month_short": "{0} tháng",
|
||||
"months_short": "{0} tháng",
|
||||
"now": "vừa xong",
|
||||
"second": "{0} giây",
|
||||
"seconds": "{0} giây",
|
||||
"second_short": "{0}s",
|
||||
"seconds_short": "{0}s",
|
||||
"week": "{0} tuần",
|
||||
"weeks": "{0} tuần",
|
||||
"week_short": "{0} tuần",
|
||||
"weeks_short": "{0} tuần",
|
||||
"year": "{0} năm",
|
||||
"years": "{0} năm",
|
||||
"year_short": "{0} năm",
|
||||
"years_short": "{0} năm",
|
||||
"now_short": "vừa xong"
|
||||
},
|
||||
"timeline": {
|
||||
"collapse": "Thu gọn",
|
||||
"error": "Lỗi khi nạp bảng tin {0}",
|
||||
"load_older": "Xem tút cũ hơn",
|
||||
"repeated": "chia sẻ",
|
||||
"show_new": "Hiện mới",
|
||||
"reload": "Tải lại",
|
||||
"up_to_date": "Đã tải những tút mới nhất",
|
||||
"no_more_statuses": "Không còn tút nào",
|
||||
"no_statuses": "Trống trơn!",
|
||||
"socket_reconnected": "Thiết lập kết nối thời gian thực",
|
||||
"conversation": "Thảo luận",
|
||||
"no_retweet_hint": "Không thể chia sẻ tin nhắn và những tút riêng tư",
|
||||
"socket_broke": "Mất kết nối thời gian thực: CloseEvent {0}"
|
||||
},
|
||||
"status": {
|
||||
"repeats": "Chia sẻ",
|
||||
"delete": "Xóa tút",
|
||||
"unpin": "Bỏ ghim trên trang cá nhân",
|
||||
"pin": "Ghim trên trang cá nhân",
|
||||
"pinned": "Tút được ghim",
|
||||
"bookmark": "Lưu",
|
||||
"unbookmark": "Bỏ lưu",
|
||||
"reply_to": "Trả lời",
|
||||
"replies_list": "Những trả lời:",
|
||||
"mute_conversation": "Không quan tâm nữa",
|
||||
"unmute_conversation": "Quan tâm",
|
||||
"status_unavailable": "Không tìm thấy tút",
|
||||
"copy_link": "Sao chép URL",
|
||||
"external_source": "Nguồn bên ngoài",
|
||||
"thread_muted": "Đã ẩn chủ đề",
|
||||
"thread_muted_and_words": ", có từ:",
|
||||
"hide_full_subject": "Ẩn tiêu đề",
|
||||
"show_content": "Hiện nội dung",
|
||||
"hide_content": "Ẩn nội dung",
|
||||
"status_deleted": "Tút này đã bị xóa",
|
||||
"nsfw": "Nhạy cảm",
|
||||
"expand": "Xem nguyên văn",
|
||||
"favorites": "Thích",
|
||||
"delete_confirm": "Bạn có chắc chắn muốn xóa tút này?",
|
||||
"show_full_subject": "Hiện đầy đủ tiêu đề",
|
||||
"you": "(Bạn)",
|
||||
"mentions": "Lượt nhắc",
|
||||
"plus_more": "+{number} nhiều hơn"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "Chấp nhận",
|
||||
"block": "Chặn",
|
||||
"blocked": "Đã chặn!",
|
||||
"deny": "Từ chối",
|
||||
"edit_profile": "Chỉnh sửa trang cá nhân",
|
||||
"favorites": "Thích",
|
||||
"follow": "Theo dõi",
|
||||
"follow_progress": "Đang yêu cầu…",
|
||||
"follow_again": "Gửi lại yêu cầu?",
|
||||
"follow_unfollow": "Ngưng theo dõi",
|
||||
"followees": "Đang theo dõi",
|
||||
"followers": "Người theo dõi",
|
||||
"following": "Đang theo dõi!",
|
||||
"follows_you": "Theo dõi bạn!",
|
||||
"hidden": "Ẩn",
|
||||
"media": "Media",
|
||||
"mention": "Lượt nhắc",
|
||||
"message": "Tin nhắn",
|
||||
"mute": "Ẩn",
|
||||
"muted": "Đã ẩn",
|
||||
"per_day": "tút mỗi ngày",
|
||||
"remote_follow": "Theo dõi từ xa",
|
||||
"report": "Báo cáo",
|
||||
"statuses": "Tút",
|
||||
"subscribe": "Đăng ký",
|
||||
"unsubscribe": "Hủy đăng ký",
|
||||
"unblock": "Bỏ chặn",
|
||||
"unblock_progress": "Đang bỏ chặn…",
|
||||
"block_progress": "Đang chặn…",
|
||||
"unmute": "Bỏ ẩn",
|
||||
"unmute_progress": "Đang bỏ ẩn…",
|
||||
"mute_progress": "Đang ẩn…",
|
||||
"hide_repeats": "Ẩn lượt chia sẻ",
|
||||
"show_repeats": "Hiện lượt chia sẻ",
|
||||
"bot": "Bot",
|
||||
"admin_menu": {
|
||||
"moderation": "Kiểm duyệt",
|
||||
"grant_admin": "Chỉ định Quản trị viên",
|
||||
"revoke_admin": "Gỡ bỏ Quản trị viên",
|
||||
"grant_moderator": "Chỉ định Kiểm duyệt viên",
|
||||
"activate_account": "Xác thực người dùng",
|
||||
"deactivate_account": "Vô hiệu hóa người dùng",
|
||||
"delete_account": "Xóa người dùng",
|
||||
"force_nsfw": "Đánh dấu tất cả tút là nhạy cảm",
|
||||
"strip_media": "Gỡ bỏ media trong tút",
|
||||
"sandbox": "Đánh dấu tất cả tút là riêng tư",
|
||||
"disable_remote_subscription": "Không cho phép theo dõi từ máy chủ khác",
|
||||
"disable_any_subscription": "Không cho phép theo dõi bất cứ ai",
|
||||
"quarantine": "Không cho phép tút liên hợp",
|
||||
"delete_user": "Xóa người dùng",
|
||||
"revoke_moderator": "Gỡ bỏ Quản trị viên",
|
||||
"force_unlisted": "Đánh dấu tất cả tút là hạn chế",
|
||||
"delete_user_confirmation": "Bạn chắc chắn chưa? Hành động này không thể phục hồi."
|
||||
},
|
||||
"highlight": {
|
||||
"disabled": "Không nổi bật",
|
||||
"solid": "Nền 1 màu",
|
||||
"striped": "Nền 2 màu",
|
||||
"side": "Sọc bên"
|
||||
},
|
||||
"follow_sent": "Đã gửi yêu cầu!",
|
||||
"its_you": "Đó là bạn!"
|
||||
},
|
||||
"user_profile": {
|
||||
"timeline_title": "Bảng tin người dùng",
|
||||
"profile_does_not_exist": "Xin lỗi, tài khoản này không tồn tại.",
|
||||
"profile_loading_error": "Xin lỗi, có lỗi xảy ra khi xem trang cá nhân này."
|
||||
},
|
||||
"user_reporting": {
|
||||
"title": "Báo cáo {0}",
|
||||
"additional_comments": "Ghi chú",
|
||||
"forward_description": "Người này thuộc máy chủ khác. Gửi một báo cáo ẩn danh tới máy chủ đó?",
|
||||
"forward_to": "Chuyển cho {0}",
|
||||
"submit": "Gửi",
|
||||
"generic_error": "Có lỗi xảy ra khi xử lý yêu cầu của bạn.",
|
||||
"add_comment_description": "Hãy cho quản trị viên biết lý do vì sao bạn báo cáo người này:"
|
||||
},
|
||||
"who_to_follow": {
|
||||
"more": "Nhiều hơn nữa",
|
||||
"who_to_follow": "Những người dùng nổi bật"
|
||||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "Tải lên media",
|
||||
"repeat": "Chia sẻ",
|
||||
"reply": "Trả lời",
|
||||
"favorite": "Thích",
|
||||
"add_reaction": "Thêm tương tác",
|
||||
"accept_follow_request": "Phê duyệt yêu cầu theo dõi",
|
||||
"reject_follow_request": "Từ chối yêu cầu theo dõi",
|
||||
"bookmark": "Lưu",
|
||||
"user_settings": "Thiết lập người dùng"
|
||||
},
|
||||
"upload": {
|
||||
"error": {
|
||||
"base": "Tải lên thất bại.",
|
||||
"message": "Tải lên thất bại: {0}",
|
||||
"file_too_big": "Tập tin quá lớn [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
|
||||
"default": "Hãy thử lại sau"
|
||||
},
|
||||
"file_size_units": {
|
||||
"KiB": "KB",
|
||||
"MiB": "MB",
|
||||
"GiB": "GB",
|
||||
"B": "byte",
|
||||
"TiB": "TB"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"people": "Người",
|
||||
"hashtags": "Hashtag",
|
||||
"person_talking": "{count} người đang trò chuyện",
|
||||
"people_talking": "{count} người đang trò chuyện",
|
||||
"no_results": "Không tìm thấy"
|
||||
},
|
||||
"password_reset": {
|
||||
"forgot_password": "Quên mật khẩu",
|
||||
"password_reset": "Đổi mật khẩu",
|
||||
"placeholder": "Email hoặc tên người dùng",
|
||||
"check_email": "Kiểm tra email của bạn.",
|
||||
"return_home": "Quay lại Pleroma",
|
||||
"too_many_requests": "Bạn đã vượt giới hạn cho phép, hãy thử lại sau.",
|
||||
"password_reset_disabled": "Reset mật khẩu bị tắt. Hãy liên hệ quản trị viên máy chủ.",
|
||||
"password_reset_required": "Bạn phải đổi mật khẩu để đăng nhập.",
|
||||
"instruction": "Nhập email hoặc tên người dùng. Chúng tôi sẽ gửi email reset mật khẩu cho bạn.",
|
||||
"password_reset_required_but_mailer_is_disabled": "Bạn cần phải đổi mật khẩu, nhưng tính năng bị tắt. Hãy liên hệ quản trị viên máy chủ."
|
||||
},
|
||||
"chats": {
|
||||
"you": "Bạn:",
|
||||
"message_user": "Nhắn tin {nickname}",
|
||||
"delete": "Xóa",
|
||||
"chats": "Chat",
|
||||
"new": "Chat mới",
|
||||
"empty_message_error": "Không thể gửi tin nhắn trống",
|
||||
"more": "Nhiều hơn",
|
||||
"delete_confirm": "Bạn có chắc chắn muốn xóa tin nhắn này?",
|
||||
"error_loading_chat": "Có vấn đề khi tải giao diện chat.",
|
||||
"error_sending_message": "Có vấn đề khi gửi tin nhắn.",
|
||||
"empty_chat_list_placeholder": "Bạn không có tin nhắn. Hãy bắt đầu nhắn cho ai đó!"
|
||||
},
|
||||
"file_type": {
|
||||
"audio": "Âm thanh",
|
||||
"video": "Video",
|
||||
"image": "Hình ảnh",
|
||||
"file": "Tập tin"
|
||||
},
|
||||
"display_date": {
|
||||
"today": "Hôm nay"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -677,7 +677,6 @@
|
|||
"follow": "关注",
|
||||
"follow_sent": "请求已发送!",
|
||||
"follow_progress": "请求中…",
|
||||
"follow_again": "再次发送请求?",
|
||||
"follow_unfollow": "取消关注",
|
||||
"followees": "正在关注",
|
||||
"followers": "关注者",
|
||||
|
|
|
@ -771,7 +771,6 @@
|
|||
"follow": "關注",
|
||||
"follow_sent": "請求已發送!",
|
||||
"follow_progress": "請求中…",
|
||||
"follow_again": "再次發送請求?",
|
||||
"follow_unfollow": "取消關注",
|
||||
"followees": "正在關注",
|
||||
"followers": "關注者",
|
||||
|
|
|
@ -11,6 +11,7 @@ import statusesModule from './modules/statuses.js'
|
|||
import usersModule from './modules/users.js'
|
||||
import apiModule from './modules/api.js'
|
||||
import configModule from './modules/config.js'
|
||||
import serverSideConfigModule from './modules/serverSideConfig.js'
|
||||
import shoutModule from './modules/shout.js'
|
||||
import oauthModule from './modules/oauth.js'
|
||||
import authFlowModule from './modules/auth_flow.js'
|
||||
|
@ -90,6 +91,7 @@ const persistedStateOptions = {
|
|||
users: usersModule,
|
||||
api: apiModule,
|
||||
config: configModule,
|
||||
serverSideConfig: serverSideConfigModule,
|
||||
shout: shoutModule,
|
||||
oauth: oauthModule,
|
||||
authFlow: authFlowModule,
|
||||
|
|
|
@ -13,10 +13,12 @@ export const multiChoiceProperties = [
|
|||
'postContentType',
|
||||
'subjectLineBehavior',
|
||||
'conversationDisplay', // tree | linear
|
||||
'conversationOtherRepliesButton' // below | inside
|
||||
'conversationOtherRepliesButton', // below | inside
|
||||
'mentionLinkDisplay' // short | full_for_remote | full
|
||||
]
|
||||
|
||||
export const defaultState = {
|
||||
expertLevel: 0, // used to track which settings to show and hide
|
||||
colors: {},
|
||||
theme: undefined,
|
||||
customTheme: undefined,
|
||||
|
@ -29,6 +31,9 @@ export const defaultState = {
|
|||
hideShoutbox: false,
|
||||
// bad name: actually hides posts of muted USERS
|
||||
hideMutedPosts: undefined, // instance default
|
||||
hideMutedThreads: undefined, // instance default
|
||||
hideWordFilteredPosts: undefined, // instance default
|
||||
muteBotStatuses: undefined, // instance default
|
||||
collapseMessageWithSubject: undefined, // instance default
|
||||
padEmoji: true,
|
||||
swapReacts: true,
|
||||
|
@ -44,7 +49,7 @@ export const defaultState = {
|
|||
alwaysShowNewPostButton: false,
|
||||
autohideFloatingPostButton: false,
|
||||
pauseOnUnfocused: true,
|
||||
stopGifs: false,
|
||||
stopGifs: true,
|
||||
replyVisibility: 'all',
|
||||
notificationVisibility: {
|
||||
follows: true,
|
||||
|
@ -72,15 +77,25 @@ export const defaultState = {
|
|||
hideFilteredStatuses: undefined, // instance default
|
||||
playVideosInModal: false,
|
||||
useOneClickNsfw: false,
|
||||
useContainFit: false,
|
||||
useContainFit: true,
|
||||
greentext: undefined, // instance default
|
||||
useAtIcon: undefined, // instance default
|
||||
mentionLinkDisplay: undefined, // instance default
|
||||
mentionLinkShowTooltip: undefined, // instance default
|
||||
mentionLinkShowAvatar: undefined, // instance default
|
||||
mentionLinkFadeDomain: undefined, // instance default
|
||||
mentionLinkShowYous: undefined, // instance default
|
||||
mentionLinkBoldenYou: undefined, // instance default
|
||||
hidePostStats: undefined, // instance default
|
||||
hideBotIndication: undefined, // instance default
|
||||
hideUserStats: undefined, // instance default
|
||||
virtualScrolling: undefined, // instance default
|
||||
sensitiveByDefault: undefined, // instance default
|
||||
conversationDisplay: undefined, // instance default
|
||||
conversationTreeAdvanced: undefined, // instance default
|
||||
conversationOtherRepliesButton: undefined, // instance default
|
||||
maxDepthInThread: 6
|
||||
conversationTreeFadeAncestors: undefined, // instance default
|
||||
maxDepthInThread: undefined // instance default
|
||||
}
|
||||
|
||||
// caching the instance default properties
|
||||
|
|
|
@ -20,11 +20,23 @@ const defaultState = {
|
|||
background: '/static/aurora_borealis.jpg',
|
||||
collapseMessageWithSubject: false,
|
||||
greentext: false,
|
||||
useAtIcon: false,
|
||||
mentionLinkDisplay: 'short',
|
||||
mentionLinkShowTooltip: true,
|
||||
mentionLinkShowAvatar: false,
|
||||
mentionLinkFadeDomain: true,
|
||||
mentionLinkShowYous: false,
|
||||
mentionLinkBoldenYou: true,
|
||||
hideFilteredStatuses: false,
|
||||
// bad name: actually hides posts of muted USERS
|
||||
hideMutedPosts: false,
|
||||
hideMutedThreads: true,
|
||||
hideWordFilteredPosts: false,
|
||||
hidePostStats: false,
|
||||
hideBotIndication: false,
|
||||
hideSitename: false,
|
||||
hideUserStats: false,
|
||||
muteBotStatuses: false,
|
||||
loginMethod: 'password',
|
||||
logo: '/static/logo.svg',
|
||||
logoMargin: '.2em',
|
||||
|
@ -44,7 +56,9 @@ const defaultState = {
|
|||
virtualScrolling: true,
|
||||
sensitiveByDefault: false,
|
||||
conversationDisplay: 'simple_tree',
|
||||
conversationTreeAdvanced: false,
|
||||
conversationOtherRepliesButton: 'below',
|
||||
conversationTreeFadeAncestors: false,
|
||||
maxDepthInThread: 6,
|
||||
|
||||
// Nasty stuff
|
||||
|
@ -100,6 +114,9 @@ const instance = {
|
|||
return instanceDefaultProperties
|
||||
.map(key => [key, state[key]])
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
},
|
||||
instanceDomain (state) {
|
||||
return new URL(state.server).hostname
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import fileTypeService from '../services/file_type/file_type.service.js'
|
||||
const supportedTypes = new Set(['image', 'video', 'audio', 'flash'])
|
||||
|
||||
const mediaViewer = {
|
||||
state: {
|
||||
|
@ -10,7 +11,7 @@ const mediaViewer = {
|
|||
setMedia (state, media) {
|
||||
state.media = media
|
||||
},
|
||||
setCurrent (state, index) {
|
||||
setCurrentMedia (state, index) {
|
||||
state.activated = true
|
||||
state.currentIndex = index
|
||||
},
|
||||
|
@ -22,13 +23,13 @@ const mediaViewer = {
|
|||
setMedia ({ commit }, attachments) {
|
||||
const media = attachments.filter(attachment => {
|
||||
const type = fileTypeService.fileType(attachment.mimetype)
|
||||
return type === 'image' || type === 'video' || type === 'audio'
|
||||
return supportedTypes.has(type)
|
||||
})
|
||||
commit('setMedia', media)
|
||||
},
|
||||
setCurrent ({ commit, state }, current) {
|
||||
setCurrentMedia ({ commit, state }, current) {
|
||||
const index = state.media.indexOf(current)
|
||||
commit('setCurrent', index || 0)
|
||||
commit('setCurrentMedia', index || 0)
|
||||
},
|
||||
closeMediaViewer ({ commit }) {
|
||||
commit('close')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue