Merge remote-tracking branch 'origin/develop' into vue3-again
* origin/develop: (475 commits) Apply 1 suggestion(s) to 1 file(s) Update dependency @ungap/event-target to v0.2.3 Update package.json fix broken icons after FA upgrade Update Font Awesome Update dependency webpack-dev-middleware to v3.7.3 Update dependency vuelidate to v0.7.7 Pin dependency @kazvmoe-infra/pinch-zoom-element to 1.2.0 lint Make media modal buttons larger Add English translation for hide tooltip Add hide button to media modal Lint Prevent hiding media viewer if swiped over SwipeClick Fix webkit image blurs Fix video in media modal not displaying properly Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403 Remove image box-shadow in media modal Clean up debug code for image pinch zoom Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm ...
This commit is contained in:
commit
cd4ad2df11
184 changed files with 11589 additions and 3790 deletions
2
.babelrc
2
.babelrc
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"presets": ["@babel/preset-env", "@vue/babel-preset-jsx"],
|
||||
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
|
||||
"comments": false
|
||||
}
|
||||
|
|
58
CHANGELOG.md
58
CHANGELOG.md
|
@ -3,11 +3,67 @@ 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]
|
||||
## 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
|
||||
- Added option to mark posts as sensitive by default
|
||||
- Added quick filters for notifications
|
||||
- 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
|
||||
|
||||
### Fixed
|
||||
- Fixed follow request count showing in the wrong location in mobile view
|
||||
|
||||
|
||||
## [2.3.0] - 2021-03-01
|
||||
|
|
|
@ -3,6 +3,7 @@ Contributors of this project.
|
|||
- Constance Variable (lambadalambda@social.heldscal.la): Code
|
||||
- Coco Snuss (cocosnuss@social.heldscal.la): Code
|
||||
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
|
||||
- eris (eris@disqordia.space): Code
|
||||
- dtluna (dtluna@social.heldscal.la): Code
|
||||
- sonyam (sonyam@social.heldscal.la): Background images
|
||||
- hakui (hakui@freezepeach.xyz): CSS and styling
|
||||
|
|
|
@ -21,6 +21,7 @@ var compiler = webpack(webpackConfig)
|
|||
|
||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
writeToDisk: true,
|
||||
stats: {
|
||||
colors: true,
|
||||
chunks: false
|
||||
|
|
|
@ -4,6 +4,7 @@ var utils = require('./utils')
|
|||
var projectRoot = path.resolve(__dirname, '../')
|
||||
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
|
||||
var { VueLoaderPlugin } = require('vue-loader')
|
||||
var CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
var env = process.env.NODE_ENV
|
||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
||||
|
@ -95,6 +96,19 @@ module.exports = {
|
|||
entry: path.join(__dirname, '..', 'src/sw.js'),
|
||||
filename: 'sw-pleroma.js'
|
||||
}),
|
||||
new VueLoaderPlugin()
|
||||
new VueLoaderPlugin(),
|
||||
// This copies Ruffle's WASM to a directory so that JS side can access it
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: "node_modules/ruffle-mirror/*",
|
||||
to: "static/ruffle",
|
||||
flatten: true
|
||||
},
|
||||
],
|
||||
options: {
|
||||
concurrency: 100,
|
||||
},
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
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": "^2.0.0-beta",
|
||||
"@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": "^3.0.0-3",
|
||||
"@vue/compiler-sfc": "^3.0.7",
|
||||
"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",
|
||||
"punycode.js": "^2.1.0",
|
||||
"qrcode": "^1.4.4",
|
||||
"v-click-outside": "^2.1.1",
|
||||
"vue": "^3.0.7",
|
||||
"vue-i18n": "^9.0.0-beta.18",
|
||||
"vue-router": "^4.0.5",
|
||||
"vuelidate": "^0.7.6",
|
||||
"vuex": "^4.0.0"
|
||||
"@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.0.0",
|
||||
"@vue/babel-plugin-jsx": "^1.0.3",
|
||||
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
|
||||
"@vue/test-utils": "^2.0.0-beta.8",
|
||||
"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": "^16.1.2",
|
||||
"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"
|
||||
]
|
||||
}
|
14
src/App.js
14
src/App.js
|
@ -4,7 +4,7 @@ import Notifications from './components/notifications/notifications.vue'
|
|||
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
||||
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
||||
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||
import ChatPanel from './components/chat_panel/chat_panel.vue'
|
||||
import ShoutPanel from './components/shout_panel/shout_panel.vue'
|
||||
import SettingsModal from './components/settings_modal/settings_modal.vue'
|
||||
import MediaModal from './components/media_modal/media_modal.vue'
|
||||
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
||||
|
@ -26,7 +26,7 @@ export default {
|
|||
InstanceSpecificPanel,
|
||||
FeaturesPanel,
|
||||
WhoToFollowPanel,
|
||||
ChatPanel,
|
||||
ShoutPanel,
|
||||
MediaModal,
|
||||
SideDrawer,
|
||||
MobilePostStatusButton,
|
||||
|
@ -65,7 +65,7 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
||||
shout () { return this.$store.state.shout.channel.state === 'joined' },
|
||||
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
||||
showInstanceSpecificPanel () {
|
||||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
||||
|
@ -73,11 +73,17 @@ export default {
|
|||
this.$store.state.instance.instanceSpecificPanelContent
|
||||
},
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||
shoutboxPosition () {
|
||||
return this.$store.getters.mergedConfig.showNewPostButton || false
|
||||
},
|
||||
hideShoutbox () {
|
||||
return this.$store.getters.mergedConfig.hideShoutbox
|
||||
},
|
||||
isMobileLayout () { return this.$store.state.interface.mobileLayout },
|
||||
privateMode () { return this.$store.state.instance.private },
|
||||
sidebarAlign () {
|
||||
return {
|
||||
'order': this.$store.state.instance.sidebarRight ? 99 : 0
|
||||
'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
|
||||
}
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
|
|
51
src/App.scss
51
src/App.scss
|
@ -88,6 +88,10 @@ a {
|
|||
font-family: sans-serif;
|
||||
font-family: var(--interfaceFont, sans-serif);
|
||||
|
||||
&.-sublime {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
i[class*=icon-],
|
||||
.svg-inline--fa {
|
||||
color: $fallback--text;
|
||||
|
@ -187,7 +191,7 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
input, textarea, .select, .input {
|
||||
input, textarea, .input {
|
||||
|
||||
&.unstyled {
|
||||
border-radius: 0;
|
||||
|
@ -217,47 +221,11 @@ input, textarea, .select, .input {
|
|||
hyphens: none;
|
||||
padding: 8px .5em;
|
||||
|
||||
&.select {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:disabled, &[disabled=disabled] {
|
||||
&:disabled, &[disabled=disabled], &.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.select-down-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 5px;
|
||||
height: 100%;
|
||||
color: $fallback--text;
|
||||
color: var(--inputText, $fallback--text);
|
||||
line-height: 28px;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $fallback--text;
|
||||
color: var(--inputText, --text, $fallback--text);
|
||||
margin: 0;
|
||||
padding: 0 2em 0 .2em;
|
||||
font-family: sans-serif;
|
||||
font-family: var(--inputFont, sans-serif);
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
height: 28px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&[type=range] {
|
||||
background: none;
|
||||
border: none;
|
||||
|
@ -830,13 +798,6 @@ nav {
|
|||
}
|
||||
}
|
||||
|
||||
.select-multiple {
|
||||
display: flex;
|
||||
.option-list {
|
||||
margin: 0;
|
||||
padding-left: .5em;
|
||||
}
|
||||
}
|
||||
.setting-list,
|
||||
.option-list{
|
||||
list-style-type: none;
|
||||
|
|
|
@ -49,10 +49,11 @@
|
|||
</div>
|
||||
<media-modal />
|
||||
</div>
|
||||
<chat-panel
|
||||
v-if="currentUser && chat"
|
||||
<shout-panel
|
||||
v-if="currentUser && shout && !hideShoutbox"
|
||||
:floating="true"
|
||||
class="floating-chat mobile-hidden"
|
||||
class="floating-shout mobile-hidden"
|
||||
:class="{ 'left': shoutboxPosition }"
|
||||
/>
|
||||
<MobilePostStatusButton />
|
||||
<UserReportingModal />
|
||||
|
|
|
@ -30,3 +30,5 @@ $fallback--attachmentRadius: 10px;
|
|||
$fallback--chatMessageRadius: 10px;
|
||||
|
||||
$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
|
||||
|
||||
$status-margin: 0.75em;
|
||||
|
|
|
@ -121,6 +121,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
|||
copyInstanceOption('nsfwCensorImage')
|
||||
copyInstanceOption('background')
|
||||
copyInstanceOption('hidePostStats')
|
||||
copyInstanceOption('hideBotIndication')
|
||||
copyInstanceOption('hideUserStats')
|
||||
copyInstanceOption('hideFilteredStatuses')
|
||||
copyInstanceOption('logo')
|
||||
|
@ -246,7 +247,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
|
||||
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
||||
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
||||
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
|
||||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||
|
|
|
@ -16,7 +16,7 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
|||
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
||||
import Notifications from 'components/notifications/notifications.vue'
|
||||
import AuthForm from 'components/auth_form/auth_form.js'
|
||||
import ChatPanel from 'components/chat_panel/chat_panel.vue'
|
||||
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
|
||||
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
||||
import About from 'components/about/about.vue'
|
||||
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
||||
|
@ -64,7 +64,7 @@ export default (store) => {
|
|||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
|
||||
{ name: 'login', path: '/login', component: AuthForm },
|
||||
{ name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) },
|
||||
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
|
||||
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
||||
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
|
||||
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import StillImage from '../still-image/still-image.vue'
|
||||
import Flash from '../flash/flash.vue'
|
||||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||
import nsfwImage from '../../assets/nsfw.png'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
|
@ -10,7 +11,12 @@ import {
|
|||
faImage,
|
||||
faVideo,
|
||||
faPlayCircle,
|
||||
faTimes
|
||||
faTimes,
|
||||
faStop,
|
||||
faSearchPlus,
|
||||
faTrashAlt,
|
||||
faPencilAlt,
|
||||
faAlignRight
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
|
@ -19,36 +25,64 @@ 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: {
|
||||
Flash,
|
||||
StillImage,
|
||||
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 +106,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 +141,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 +198,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 |