Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
Henry Jameson | 02bf733328 | |
Henry Jameson | d1ade90a1c | |
Henry Jameson | b17768baa6 |
4
.babelrc
4
.babelrc
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"presets": ["@babel/preset-env"],
|
"presets": ["@babel/preset-env"],
|
||||||
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
|
"plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-transform-vue-jsx"],
|
||||||
"comments": true
|
"comments": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: '@babel/eslint-parser',
|
parser: 'babel-eslint',
|
||||||
sourceType: 'module'
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
||||||
|
@ -21,7 +21,6 @@ module.exports = {
|
||||||
'generator-star-spacing': 0,
|
'generator-star-spacing': 0,
|
||||||
// allow debugger during development
|
// allow debugger during development
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||||
'vue/require-prop-types': 0,
|
'vue/require-prop-types': 0
|
||||||
'vue/multi-word-component-names': 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
/build/webpack.prod.conf.js export-subst
|
|
|
@ -7,4 +7,3 @@ test/e2e/reports
|
||||||
selenium-debug.log
|
selenium-debug.log
|
||||||
.idea/
|
.idea/
|
||||||
config/local.json
|
config/local.json
|
||||||
static/emoji.json
|
|
||||||
|
|
|
@ -1,39 +1,14 @@
|
||||||
# This file is a template, and might need editing before it works on your project.
|
# This file is a template, and might need editing before it works on your project.
|
||||||
# Official framework image. Look for the different tagged releases at:
|
# Official framework image. Look for the different tagged releases at:
|
||||||
# https://hub.docker.com/r/library/node/tags/
|
# https://hub.docker.com/r/library/node/tags/
|
||||||
image: node:16
|
image: node:10
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- check-changelog
|
|
||||||
- lint
|
- lint
|
||||||
- build
|
- build
|
||||||
- test
|
- test
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
# https://git.pleroma.social/help/ci/yaml/workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines
|
|
||||||
workflow:
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
|
||||||
when: never
|
|
||||||
- if: $CI_COMMIT_BRANCH
|
|
||||||
|
|
||||||
check-changelog:
|
|
||||||
stage: check-changelog
|
|
||||||
image: alpine
|
|
||||||
rules:
|
|
||||||
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate/
|
|
||||||
when: never
|
|
||||||
- if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma-fe' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate'
|
|
||||||
when: never
|
|
||||||
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop"
|
|
||||||
before_script: ''
|
|
||||||
after_script: ''
|
|
||||||
cache: {}
|
|
||||||
script:
|
|
||||||
- apk add git
|
|
||||||
- sh ./tools/check-changelog
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
stage: lint
|
stage: lint
|
||||||
script:
|
script:
|
||||||
|
@ -43,8 +18,6 @@ lint:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
tags:
|
|
||||||
- amd64
|
|
||||||
variables:
|
variables:
|
||||||
APT_CACHE_DIR: apt-cache
|
APT_CACHE_DIR: apt-cache
|
||||||
script:
|
script:
|
||||||
|
@ -56,8 +29,6 @@ test:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
tags:
|
|
||||||
- amd64
|
|
||||||
script:
|
script:
|
||||||
- yarn
|
- yarn
|
||||||
- npm run build
|
- npm run build
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Environment info
|
|
||||||
|
|
||||||
<!-- Everything is optional and where applicable but the more information the better. -->
|
|
||||||
* Browser, version, OS, platform:
|
|
||||||
* Instance URL:
|
|
||||||
* Frontend version (see settings -> about):
|
|
||||||
* Backend version (see settings -> about):
|
|
||||||
* Browser extensions (ublock, rikaichamp etc):
|
|
||||||
* Known instance/user customizations (i.e. pleromafe mods/forks, instance styles etc)
|
|
||||||
|
|
||||||
# Bug description & reproduction steps
|
|
||||||
|
|
||||||
<!-- Type out here how to reproduce the bug, what goes wrong and what should go right -->
|
|
||||||
<!-- Screenshots and videos help a lot ;) any observations might also help -->
|
|
||||||
<!-- Also mention if there any errors in browser's console if relevant -->
|
|
||||||
|
|
||||||
# Bug seriousness
|
|
||||||
|
|
||||||
<!-- Everything is optional and free-form -->
|
|
||||||
* How annoying it is:
|
|
||||||
* How often does it happen:
|
|
||||||
* How many people does it affect:
|
|
||||||
* Is there a workaround for it:
|
|
||||||
|
|
||||||
/label ~Bug
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Behavior suggestion/Feature request
|
|
||||||
<!--
|
|
||||||
Type out what you want to see changed or what feature you want to see added to
|
|
||||||
PleormaFE. Please also explain how it would benefit users (or admins/moderators)
|
|
||||||
and what intended usecase is. Any background information (i.e. porting behavior
|
|
||||||
from other frontends/services, specific situations, personal preferences etc.)
|
|
||||||
as well as examples would be greatly appreciated.
|
|
||||||
-->
|
|
||||||
|
|
||||||
/label ~suggestion
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<!--
|
|
||||||
please use one of the templates if applicable, otherwise - type out here
|
|
||||||
in free-form
|
|
||||||
-->
|
|
||||||
|
|
||||||
/label ~needs-triage
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
<!--
|
|
||||||
Feel free to submit merge requests that are work-in-progress, but mark them as
|
|
||||||
Draft: or WIP:.
|
|
||||||
Merge requests that have Draft or WIP status will not be merged and have less chances
|
|
||||||
of being reviewed, but you can still ask people to take a look if you need advice.
|
|
||||||
-->
|
|
||||||
# Changes
|
|
||||||
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
|
|
||||||
<!-- List what your merge request changes and how -->
|
|
||||||
<!--
|
|
||||||
Try to not to break existing behavior, if your changes do break existing behavior
|
|
||||||
make it configurable to toggle between old behavior and new. Which one should be
|
|
||||||
default is up to discussion.
|
|
||||||
-->
|
|
||||||
<!-- If your merge request resolves some issue link it like so: "Closes #99999" -->
|
|
||||||
<!--
|
|
||||||
If merge request adds some new feature that depends on backend:
|
|
||||||
|
|
||||||
1. Make sure it gracefully degrades if backend hasn't been updated to support the feature,
|
|
||||||
we try to make PleromaFE compatible with older versions of BE so that people can still
|
|
||||||
update frontend safely without updating backend since it's costly and much riskier.
|
|
||||||
2. Link related BE merge request here
|
|
||||||
-->
|
|
||||||
<!-- Screenshots are welcome -->
|
|
||||||
|
|
||||||
/label ~needs-review
|
|
|
@ -1 +1 @@
|
||||||
16.18.1
|
7.2.1
|
||||||
|
|
|
@ -1,41 +1,19 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"stylelint-rscss/config",
|
"stylelint-rscss/config",
|
||||||
"stylelint-config-standard",
|
"stylelint-config-recommended",
|
||||||
"stylelint-config-recommended-scss",
|
"stylelint-config-standard"
|
||||||
"stylelint-config-html",
|
|
||||||
"stylelint-config-recommended-vue/scss"
|
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"declaration-no-important": true,
|
"declaration-no-important": true,
|
||||||
"rscss/no-descendant-combinator": false,
|
"rscss/no-descendant-combinator": false,
|
||||||
"rscss/class-format": [
|
"rscss/class-format": [
|
||||||
false,
|
true,
|
||||||
{
|
{
|
||||||
"component": "pascal-case",
|
"component": "pascal-case",
|
||||||
"variant": "^-[a-z]\\w+",
|
"variant": "^-[a-z]\\w+",
|
||||||
"element": "^[a-z]\\w+"
|
"element": "^[a-z]\\w+"
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"selector-class-pattern": null,
|
|
||||||
"import-notation": null,
|
|
||||||
"custom-property-pattern": null,
|
|
||||||
"keyframes-name-pattern": null,
|
|
||||||
"scss/operator-no-newline-after": null,
|
|
||||||
"declaration-block-no-redundant-longhand-properties": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignoreShorthands": [
|
|
||||||
"grid-template",
|
|
||||||
"margin",
|
|
||||||
"padding",
|
|
||||||
"border",
|
|
||||||
"border-width",
|
|
||||||
"border-style",
|
|
||||||
"border-color",
|
|
||||||
"border-radius"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
162
CHANGELOG.md
162
CHANGELOG.md
|
@ -3,163 +3,10 @@ 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/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## 2.6.1
|
## [Unreleased]
|
||||||
### Fixed
|
|
||||||
- fix admin dashboard not having any feedback on frontend installation
|
|
||||||
- Fix frontend admin tab crashing when no primary frontend is set
|
|
||||||
- Add aria attributes to react and extra buttons
|
|
||||||
|
|
||||||
## 2.6.0
|
|
||||||
### Added
|
|
||||||
- add the initial i18n translation file for Taiwanese (Hokkien), and modify some related files.
|
|
||||||
- Implemented a very basic instance administration screen
|
|
||||||
- Implement quoting
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Keep aspect ratio of custom emoji reaction in notification
|
|
||||||
- Fix openSettingsModalTab so that it correctly opens Settings modal instead of Admin modal
|
|
||||||
- Add alt text to emoji picker buttons
|
|
||||||
- Use export-subst gitattribute to allow tarball builds
|
|
||||||
- fix reports now showing reason/content
|
|
||||||
- Fix HTML attribute parsing, discard attributes not strating with a letter
|
|
||||||
- Make MentionsLine aware of line breaking by non-br elements
|
|
||||||
- Fix a bug where mentioning a user twice will not fill the mention into the textarea
|
|
||||||
- Fix parsing non-ascii tags
|
|
||||||
- Fix OAuth2 token lingering after revocation
|
|
||||||
- fix regex issue in HTML parser/renderer
|
|
||||||
- don't display quoted status twice
|
|
||||||
- fix typo in code that prevented cards from showing at all
|
|
||||||
- Fix react button not working if reaction accounts are not loaded
|
|
||||||
- Fix react button misalignment on safari ios
|
|
||||||
- Fix pinned statuses gone when reloading user timeline
|
|
||||||
- Fix scrolling emoji selector in modal in safari ios
|
|
||||||
|
|
||||||
## 2.5.1
|
|
||||||
### Fixed
|
|
||||||
- Checkboxes in settings can now work with screenreaders
|
|
||||||
- Autocomplete in edit boxes can now work with screenreaders
|
|
||||||
- Status interact buttons now have focus indicator for anonymous users
|
|
||||||
- Top bar buttons now correctly have text labels
|
|
||||||
- It is now possible to register if the site admin requires birthday to register
|
|
||||||
- User cards from search results will correctly popup
|
|
||||||
- Fix notification attachment icon overflow
|
|
||||||
- Editing mute words is less laggy
|
|
||||||
- Repeater's name will no longer mess up with the directionality of the text sitting on the same line
|
|
||||||
- Unauthenticated access will give better error messages
|
|
||||||
- It is now easier to close the media viewer with a mouse when there is only one image
|
|
||||||
- Deleting profile fields can work properly
|
|
||||||
- Clicking the react button will correctly focus the search box
|
|
||||||
- Clicking buttons on the top-bar will no longer bring you to the top of the page
|
|
||||||
- Emoji picker is much faster to load
|
|
||||||
- `blockquote`s have a better display style
|
|
||||||
- Announcements posting and editing are now available to everyone with such a privilege, not just admins
|
|
||||||
- Adding or removing list members will actually work
|
|
||||||
- Emojis without a pack are now correctly displayed in emoji picker
|
|
||||||
- Changing notification settings will actually work
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- You can now set and see birthdays
|
|
||||||
- Optional confirmation dialogs when performing various actions
|
|
||||||
- You can now set fallback languages
|
|
||||||
|
|
||||||
## 2.5.0 - 23.12.2022
|
|
||||||
### Fixed
|
|
||||||
- UI no longer lags when switching between mobile and desktop mode
|
|
||||||
- Popovers no longer constrained by DOM hierarchy, shouldn't be cut off by anything
|
|
||||||
- Emoji autocomplete popover and picker popover stick to the text cursor.
|
|
||||||
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
|
|
||||||
- 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
|
|
||||||
- "Always show mobile button" is working now
|
|
||||||
- Inline images now respect their intended width/height attributes
|
|
||||||
- Links with `&` in them work properly now
|
|
||||||
- Attachment description is prefilled with backend-provided default when uploading
|
|
||||||
- Proper visual feedback that next image is loading when browsing
|
|
||||||
- Additional HTML sanitization on frontend side in case backend sanitization fails
|
|
||||||
- Interaction list popovers now properly emojify names
|
|
||||||
- AdminFE button no longer scrolls page to top when clicked
|
|
||||||
- User handles with non-ascii domains now have less intrusive indicator for the domain name
|
|
||||||
- Completely hidden posts still no longer have 1px border
|
|
||||||
- A lot of accessibility improvements
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Using Vue 3 now
|
|
||||||
- A lot of internal dependencies updated
|
|
||||||
- "(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 thoroughly rearranged to make more sense and make navigation 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. (You can expand them to full if need be)
|
|
||||||
- Slight width/spacing adjustments
|
|
||||||
- More sizing stuff is font-size dependent now
|
|
||||||
- Scrollbars are styled/colorized now
|
|
||||||
- Scrollbars are toggleable (for stuff that didn't have visible scrollbars before) (opt-in)
|
|
||||||
- Updated localization files
|
|
||||||
- Top bar is more useful in mobile mode now.
|
|
||||||
- "Show new" button is way more compact in mobile mode
|
|
||||||
- Slightly adjusted placement and spacing of the topbar buttons so it's less easy to accidentally log yourself out
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- 3 column mode: only enables when there's space for it (opt-out, customizable)
|
|
||||||
- Apologetic pleroma-tan
|
|
||||||
- New button on timeline header to change some of the new and often-used settings
|
|
||||||
- Support for lists
|
|
||||||
- Added ability to edit posts and view post edit history etc.
|
|
||||||
- Added ability to add personal note to users
|
|
||||||
- Added initial support for admin announcements
|
|
||||||
- Added ui for account migration
|
|
||||||
- Added ui for backups
|
|
||||||
- Added ability to force-unfollow a user from you
|
|
||||||
- Emoji are now grouped by pack
|
|
||||||
- Ability to pin navigation items and collapse the navigation menu
|
|
||||||
- Ability to rearrange order of attachments when uploading
|
|
||||||
- Ability to scroll column (or page) to top via panel header button
|
|
||||||
- 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
|
|
||||||
- Option to customize what clicking user avatar does in user popover
|
|
||||||
- Notifications for poll results
|
|
||||||
- "Favorites" link in navigation
|
|
||||||
- Very early and somewhat experimental system for automatic settings sync (used only for pinned navigation and apologetic pleroma-tan)
|
|
||||||
- Implemented remote interaction with statuses for anon visitors
|
|
||||||
- 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)
|
|
||||||
- Enabled users to zoom and pan images in media viewer with mouse and touch
|
|
||||||
- Timelines/panels and conversations have sticky headers now (a bit glitchy on some browsers like safari) (opt-out)
|
|
||||||
|
|
||||||
|
|
||||||
## [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
|
||||||
- Added a quick settings to timeline header for easier access
|
- Added a quick settings to timeline header for easier access
|
||||||
- Added option to mark posts as sensitive by default
|
- 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
|
## [2.3.0] - 2021-03-01
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -173,8 +20,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Display 'people voted' instead of 'votes' for multi-choice polls
|
- Display 'people voted' instead of 'votes' for multi-choice polls
|
||||||
- Changed the "Timelines" link in side panel to toggle show all timeline options inside the panel
|
|
||||||
- Renamed "Timeline" to "Home Timeline" to be more clear
|
|
||||||
- Optimized chat to not get horrible performance after keeping the same chat open for a long time
|
- Optimized chat to not get horrible performance after keeping the same chat open for a long time
|
||||||
- When opening emoji picker or react picker, it automatically focuses the search field
|
- When opening emoji picker or react picker, it automatically focuses the search field
|
||||||
- Language picker now uses native language names
|
- Language picker now uses native language names
|
||||||
|
@ -183,7 +28,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Added reason field for registration when approval is required
|
- Added reason field for registration when approval is required
|
||||||
- Group staff members by role in the About page
|
- Group staff members by role in the About page
|
||||||
|
|
||||||
|
|
||||||
## [2.2.3] - 2021-01-18
|
## [2.2.3] - 2021-01-18
|
||||||
### Added
|
### Added
|
||||||
- Added Report button to status ellipsis menu for easier reporting
|
- Added Report button to status ellipsis menu for easier reporting
|
||||||
|
@ -191,10 +35,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Fixed
|
### Fixed
|
||||||
- Follows/Followers tabs on user profiles now display the content properly.
|
- Follows/Followers tabs on user profiles now display the content properly.
|
||||||
- Handle punycode in screen names
|
- Handle punycode in screen names
|
||||||
- Fixed local dev mode having non-functional websockets in some cases
|
|
||||||
- Show notices for websocket events (errors, abnormal closures, reconnections)
|
|
||||||
- Fix not being able to re-enable websocket until page refresh
|
|
||||||
- Fix annoying issue where timeline might have few posts when streaming is enabled
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Don't filter own posts when they hit your wordfilter
|
- Don't filter own posts when they hit your wordfilter
|
||||||
|
|
|
@ -3,12 +3,9 @@ Contributors of this project.
|
||||||
- Constance Variable (lambadalambda@social.heldscal.la): Code
|
- Constance Variable (lambadalambda@social.heldscal.la): Code
|
||||||
- Coco Snuss (cocosnuss@social.heldscal.la): Code
|
- Coco Snuss (cocosnuss@social.heldscal.la): Code
|
||||||
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
|
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
|
||||||
- eris (eris@disqordia.space): Code
|
|
||||||
- dtluna (dtluna@social.heldscal.la): Code
|
- dtluna (dtluna@social.heldscal.la): Code
|
||||||
- sonyam (sonyam@social.heldscal.la): Background images
|
- sonyam (sonyam@social.heldscal.la): Background images
|
||||||
- hakui (hakui@freezepeach.xyz): CSS and styling
|
- hakui (hakui@freezepeach.xyz): CSS and styling
|
||||||
- shpuld (shpuld@shitposter.club): CSS and styling
|
- shpuld (shpuld@shitposter.club): CSS and styling
|
||||||
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
|
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
|
||||||
- hj (hj@shigusegubu.club): Code
|
- hj (hj@shigusegubu.club): Code
|
||||||
- Sean King (seanking@kazv.moe): Code
|
|
||||||
- tusooa (tusooa@kazv.moe): Code
|
|
||||||
|
|
27
README.md
27
README.md
|
@ -1,19 +1,18 @@
|
||||||
# Pleroma-FE
|
# Pleroma-FE
|
||||||
|
|
||||||
> Highly-customizable frontend designed for Pleroma.
|
> A single column frontend designed for Pleroma.
|
||||||
|
|
||||||
![screenshot](./image-1.png)
|
![screenshot](/uploads/796c5ecf985ed1e2b0943ee0df131ed0/DJVqSJ0.png)
|
||||||
|
|
||||||
# For Translators
|
# For Translators
|
||||||
|
|
||||||
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js).
|
To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
|
||||||
|
|
||||||
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
|
# FOR ADMINS
|
||||||
|
|
||||||
# For instance admins
|
You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box.
|
||||||
You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. Information of customizing PleromaFE settings/defaults is in our [guide](https://docs-develop.pleroma.social/frontend/CONFIGURATION/) and in case you want to build your own custom version there's [another](https://docs-develop.pleroma.social/frontend/HACKING/)
|
|
||||||
|
|
||||||
# Build Setup
|
## Build Setup
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
# install dependencies
|
# install dependencies
|
||||||
|
@ -21,13 +20,13 @@ npm install -g yarn
|
||||||
yarn
|
yarn
|
||||||
|
|
||||||
# serve with hot reload at localhost:8080
|
# serve with hot reload at localhost:8080
|
||||||
yarn dev
|
npm run dev
|
||||||
|
|
||||||
# build for production with minification
|
# build for production with minification
|
||||||
yarn build
|
npm run build
|
||||||
|
|
||||||
# run unit tests
|
# run unit tests
|
||||||
yarn unit
|
npm run unit
|
||||||
```
|
```
|
||||||
|
|
||||||
# For Contributors:
|
# For Contributors:
|
||||||
|
@ -41,4 +40,10 @@ FE Build process also leaves current commit hash in global variable `___pleromaf
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Set configuration settings in AdminFE, additionally you can edit config.json. For more details see [documentation](https://docs-develop.pleroma.social/frontend/CONFIGURATION/).
|
Edit config.json for configuration.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Login methods
|
||||||
|
|
||||||
|
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
|
||||||
|
|
|
@ -18,9 +18,6 @@ console.log(
|
||||||
var spinner = ora('building for production...')
|
var spinner = ora('building for production...')
|
||||||
spinner.start()
|
spinner.start()
|
||||||
|
|
||||||
var updateEmoji = require('./update-emoji').updateEmoji
|
|
||||||
updateEmoji()
|
|
||||||
|
|
||||||
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
|
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
|
||||||
rm('-rf', assetsPath)
|
rm('-rf', assetsPath)
|
||||||
mkdir('-p', assetsPath)
|
mkdir('-p', assetsPath)
|
||||||
|
@ -36,8 +33,4 @@ webpack(webpackConfig, function (err, stats) {
|
||||||
chunks: false,
|
chunks: false,
|
||||||
chunkModules: false
|
chunkModules: false
|
||||||
}) + '\n')
|
}) + '\n')
|
||||||
if (stats.hasErrors()) {
|
|
||||||
console.error('See above for errors.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,9 +10,6 @@ var webpackConfig = process.env.NODE_ENV === 'testing'
|
||||||
? require('./webpack.prod.conf')
|
? require('./webpack.prod.conf')
|
||||||
: require('./webpack.dev.conf')
|
: require('./webpack.dev.conf')
|
||||||
|
|
||||||
var updateEmoji = require('./update-emoji').updateEmoji
|
|
||||||
updateEmoji()
|
|
||||||
|
|
||||||
// default port where dev server listens for incoming traffic
|
// default port where dev server listens for incoming traffic
|
||||||
var port = process.env.PORT || config.dev.port
|
var port = process.env.PORT || config.dev.port
|
||||||
// Define HTTP proxies to your custom API backend
|
// Define HTTP proxies to your custom API backend
|
||||||
|
@ -24,7 +21,6 @@ var compiler = webpack(webpackConfig)
|
||||||
|
|
||||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||||
publicPath: webpackConfig.output.publicPath,
|
publicPath: webpackConfig.output.publicPath,
|
||||||
writeToDisk: true,
|
|
||||||
stats: {
|
stats: {
|
||||||
colors: true,
|
colors: true,
|
||||||
chunks: false
|
chunks: false
|
||||||
|
@ -32,20 +28,18 @@ var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||||
})
|
})
|
||||||
|
|
||||||
var hotMiddleware = require('webpack-hot-middleware')(compiler)
|
var hotMiddleware = require('webpack-hot-middleware')(compiler)
|
||||||
|
|
||||||
// FIXME: The statement below gives error about hooks being required in webpack 5.
|
|
||||||
// force page reload when html-webpack-plugin template changes
|
// force page reload when html-webpack-plugin template changes
|
||||||
// compiler.plugin('compilation', function (compilation) {
|
compiler.plugin('compilation', function (compilation) {
|
||||||
// compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
||||||
// // FIXME: This supposed to reload whole page when index.html is changed,
|
// FIXME: This supposed to reload whole page when index.html is changed,
|
||||||
// // however now it reloads entire page on every breath, i suppose the order
|
// however now it reloads entire page on every breath, i suppose the order
|
||||||
// // of plugins changed or something. It's a minor thing and douesn't hurt
|
// of plugins changed or something. It's a minor thing and douesn't hurt
|
||||||
// // disabling it, constant reloads hurt much more
|
// disabling it, constant reloads hurt much more
|
||||||
|
|
||||||
// // hotMiddleware.publish({ action: 'reload' })
|
// hotMiddleware.publish({ action: 'reload' })
|
||||||
// // cb()
|
// cb()
|
||||||
// })
|
})
|
||||||
// })
|
})
|
||||||
|
|
||||||
// proxy api requests
|
// proxy api requests
|
||||||
Object.keys(proxyTable).forEach(function (context) {
|
Object.keys(proxyTable).forEach(function (context) {
|
||||||
|
@ -53,7 +47,7 @@ Object.keys(proxyTable).forEach(function (context) {
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
options = { target: options }
|
options = { target: options }
|
||||||
}
|
}
|
||||||
app.use(proxyMiddleware.createProxyMiddleware(context, options))
|
app.use(proxyMiddleware(context, options))
|
||||||
})
|
})
|
||||||
|
|
||||||
// handle fallback for HTML5 history API
|
// handle fallback for HTML5 history API
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
updateEmoji () {
|
|
||||||
const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
Object.keys(emojis)
|
|
||||||
.map(k => {
|
|
||||||
emojis[k].map(e => {
|
|
||||||
delete e.unicode_version
|
|
||||||
delete e.emoji_version
|
|
||||||
delete e.skin_tone_support_unicode_version
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = {}
|
|
||||||
Object.keys(emojis)
|
|
||||||
.map(k => {
|
|
||||||
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
|
|
||||||
res[groupId] = emojis[k]
|
|
||||||
})
|
|
||||||
|
|
||||||
console.info('Updating emojis...')
|
|
||||||
fs.writeFileSync('static/emoji.json', JSON.stringify(res))
|
|
||||||
console.info('Done.')
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,8 @@ var path = require('path')
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var projectRoot = path.resolve(__dirname, '../')
|
var projectRoot = path.resolve(__dirname, '../')
|
||||||
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin')
|
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
|
||||||
var CopyPlugin = require('copy-webpack-plugin');
|
|
||||||
var { VueLoaderPlugin } = require('vue-loader')
|
var { VueLoaderPlugin } = require('vue-loader')
|
||||||
var ESLintPlugin = require('eslint-webpack-plugin');
|
|
||||||
var StylelintPlugin = require('stylelint-webpack-plugin');
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV
|
var env = process.env.NODE_ENV
|
||||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
||||||
|
@ -24,8 +21,7 @@ module.exports = {
|
||||||
output: {
|
output: {
|
||||||
path: config.build.assetsRoot,
|
path: config.build.assetsRoot,
|
||||||
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
|
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
|
||||||
filename: '[name].js',
|
filename: '[name].js'
|
||||||
chunkFilename: '[name].js'
|
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
|
@ -33,47 +29,38 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.mjs', '.js', '.jsx', '.vue'],
|
extensions: ['.js', '.vue'],
|
||||||
modules: [
|
modules: [
|
||||||
path.join(__dirname, '../node_modules')
|
path.join(__dirname, '../node_modules')
|
||||||
],
|
],
|
||||||
alias: {
|
alias: {
|
||||||
|
vue: "@vue/runtime-dom",
|
||||||
'static': path.resolve(__dirname, '../static'),
|
'static': path.resolve(__dirname, '../static'),
|
||||||
'src': path.resolve(__dirname, '../src'),
|
'src': path.resolve(__dirname, '../src'),
|
||||||
'assets': path.resolve(__dirname, '../src/assets'),
|
'assets': path.resolve(__dirname, '../src/assets'),
|
||||||
'components': path.resolve(__dirname, '../src/components'),
|
'components': path.resolve(__dirname, '../src/components')
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
|
|
||||||
},
|
|
||||||
fallback: {
|
|
||||||
'querystring': require.resolve('querystring-es3'),
|
|
||||||
'url': require.resolve('url/')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
noParse: /node_modules\/localforage\/dist\/localforage.js/,
|
noParse: /node_modules\/localforage\/dist\/localforage.js/,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
enforce: 'post',
|
enforce: 'pre',
|
||||||
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
|
test: /\.(js|vue)$/,
|
||||||
type: 'javascript/auto',
|
include: projectRoot,
|
||||||
loader: '@intlify/vue-i18n-loader',
|
exclude: /node_modules/,
|
||||||
include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled
|
use: {
|
||||||
path.resolve(__dirname, '../src/i18n')
|
loader: 'eslint-loader',
|
||||||
]
|
options: {
|
||||||
|
formatter: require('eslint-friendly-formatter'),
|
||||||
|
sourceMap: config.build.productionSourceMap,
|
||||||
|
extract: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.vue$/,
|
||||||
loader: 'vue-loader',
|
use: 'vue-loader'
|
||||||
options: {
|
|
||||||
compilerOptions: {
|
|
||||||
isCustomElement(tag) {
|
|
||||||
if (tag === 'pinch-zoom') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.jsx?$/,
|
||||||
|
@ -83,23 +70,24 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||||
type: 'asset',
|
use: {
|
||||||
generator: {
|
loader: 'url-loader',
|
||||||
filename: utils.assetsPath('img/[name].[hash:7][ext]')
|
options: {
|
||||||
|
limit: 10000,
|
||||||
|
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||||
type: 'asset',
|
use: {
|
||||||
generator: {
|
loader: 'url-loader',
|
||||||
filename: utils.assetsPath('fonts/[name].[hash:7][ext]')
|
options: {
|
||||||
|
limit: 10000,
|
||||||
|
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.mjs$/,
|
|
||||||
include: /node_modules/,
|
|
||||||
type: 'javascript/auto'
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -107,23 +95,6 @@ module.exports = {
|
||||||
entry: path.join(__dirname, '..', 'src/sw.js'),
|
entry: path.join(__dirname, '..', 'src/sw.js'),
|
||||||
filename: 'sw-pleroma.js'
|
filename: 'sw-pleroma.js'
|
||||||
}),
|
}),
|
||||||
new ESLintPlugin({
|
new VueLoaderPlugin()
|
||||||
extensions: ['js', 'vue'],
|
|
||||||
formatter: require('eslint-formatter-friendly')
|
|
||||||
}),
|
|
||||||
new StylelintPlugin({}),
|
|
||||||
new VueLoaderPlugin(),
|
|
||||||
// This copies Ruffle's WASM to a directory so that JS side can access it
|
|
||||||
new CopyPlugin({
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
from: "node_modules/@ruffle-rs/ruffle/**/*",
|
|
||||||
to: "static/ruffle/[name][ext]"
|
|
||||||
},
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
concurrency: 100,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,12 @@ module.exports = merge(baseWebpackConfig, {
|
||||||
},
|
},
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
// eval-source-map is faster for development
|
// eval-source-map is faster for development
|
||||||
devtool: 'eval-source-map',
|
devtool: '#eval-source-map',
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': config.dev.env,
|
'process.env': config.dev.env,
|
||||||
'COMMIT_HASH': JSON.stringify('DEV'),
|
'COMMIT_HASH': JSON.stringify('DEV'),
|
||||||
'DEV_OVERRIDES': JSON.stringify(config.dev.settings),
|
'DEV_OVERRIDES': JSON.stringify(config.dev.settings)
|
||||||
'__VUE_OPTIONS_API__': true,
|
|
||||||
'__VUE_PROD_DEVTOOLS__': false
|
|
||||||
}),
|
}),
|
||||||
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
|
|
@ -5,38 +5,26 @@ var webpack = require('webpack')
|
||||||
var merge = require('webpack-merge')
|
var merge = require('webpack-merge')
|
||||||
var baseWebpackConfig = require('./webpack.base.conf')
|
var baseWebpackConfig = require('./webpack.base.conf')
|
||||||
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
|
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
var env = process.env.NODE_ENV === 'testing'
|
var env = process.env.NODE_ENV === 'testing'
|
||||||
? require('../config/test.env')
|
? require('../config/test.env')
|
||||||
: config.build.env
|
: config.build.env
|
||||||
|
|
||||||
let commitHash = (() => {
|
let commitHash = require('child_process')
|
||||||
const subst = "$Format:%h$";
|
.execSync('git rev-parse --short HEAD')
|
||||||
if(!subst.match(/Format:/)) {
|
.toString();
|
||||||
return subst;
|
|
||||||
} else {
|
|
||||||
return require('child_process')
|
|
||||||
.execSync('git rev-parse --short HEAD')
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
var webpackConfig = merge(baseWebpackConfig, {
|
var webpackConfig = merge(baseWebpackConfig, {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
module: {
|
module: {
|
||||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true })
|
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true })
|
||||||
},
|
},
|
||||||
devtool: config.build.productionSourceMap ? 'source-map' : false,
|
devtool: config.build.productionSourceMap ? '#source-map' : false,
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: true,
|
minimize: true,
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
chunks: 'all'
|
chunks: 'all'
|
||||||
},
|
}
|
||||||
minimizer: [
|
|
||||||
`...`,
|
|
||||||
new CssMinimizerPlugin()
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: config.build.assetsRoot,
|
path: config.build.assetsRoot,
|
||||||
|
@ -48,9 +36,7 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': env,
|
'process.env': env,
|
||||||
'COMMIT_HASH': JSON.stringify(commitHash),
|
'COMMIT_HASH': JSON.stringify(commitHash),
|
||||||
'DEV_OVERRIDES': JSON.stringify(undefined),
|
'DEV_OVERRIDES': JSON.stringify(undefined)
|
||||||
'__VUE_OPTIONS_API__': true,
|
|
||||||
'__VUE_PROD_DEVTOOLS__': false
|
|
||||||
}),
|
}),
|
||||||
// extract css into its own file
|
// extract css into its own file
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
|
@ -72,7 +58,9 @@ var webpackConfig = merge(baseWebpackConfig, {
|
||||||
ignoreCustomComments: [/server-generated-meta/]
|
ignoreCustomComments: [/server-generated-meta/]
|
||||||
// more options:
|
// more options:
|
||||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||||
}
|
},
|
||||||
|
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||||
|
chunksSortMode: 'dependency'
|
||||||
}),
|
}),
|
||||||
// split vendor js into its own file
|
// split vendor js into its own file
|
||||||
// extract webpack runtime and module manifest to its own file in order to
|
// extract webpack runtime and module manifest to its own file in order to
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Make Pleroma FE to also view apng (Animated PNG) attachment.
|
|
|
@ -1 +0,0 @@
|
||||||
Added emoji pack management to the admin panel
|
|
|
@ -1 +0,0 @@
|
||||||
stop using that one runner for intensive tasks
|
|
|
@ -1 +0,0 @@
|
||||||
Create a link to the URL of the scrobble when it's present
|
|
|
@ -1 +0,0 @@
|
||||||
Fix native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
|
|
|
@ -1 +0,0 @@
|
||||||
Support showing extra notifications in the notifications column
|
|
|
@ -1 +0,0 @@
|
||||||
Focusing into a tab clears all current desktop notifications
|
|
|
@ -1 +0,0 @@
|
||||||
Support group actors
|
|
|
@ -1 +0,0 @@
|
||||||
Allow hiding custom emojis in picker.
|
|
|
@ -1 +0,0 @@
|
||||||
Fixed error that appeared on mobile Chrome(ium) (and derivatives) when native notifications are allowed
|
|
|
@ -1 +0,0 @@
|
||||||
Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
|
|
|
@ -1 +0,0 @@
|
||||||
Fixed being unable to set notification visibility for reports and follow requests
|
|
|
@ -1 +0,0 @@
|
||||||
Added ability to mute sensitive posts (ported from eintei)
|
|
|
@ -1 +0,0 @@
|
||||||
Added option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
|
|
|
@ -1 +0,0 @@
|
||||||
Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
|
|
|
@ -1 +0,0 @@
|
||||||
The expiry date indication won't be shown if the poll never expires
|
|
|
@ -1 +0,0 @@
|
||||||
Added option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
|
|
|
@ -1 +0,0 @@
|
||||||
Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
|
|
|
@ -1 +0,0 @@
|
||||||
Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
|
|
|
@ -1 +0,0 @@
|
||||||
Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
|
|
|
@ -1 +0,0 @@
|
||||||
Add poll end notifications to fetched types.
|
|
|
@ -1 +0,0 @@
|
||||||
Display public favorites on user profiles
|
|
|
@ -1 +0,0 @@
|
||||||
Display quotes count on posts and add quotes list page
|
|
|
@ -1 +0,0 @@
|
||||||
Show a dedicated registration notice page when further action is required after registering
|
|
|
@ -1 +0,0 @@
|
||||||
Option to only show scrobbles that are recent enough
|
|
|
@ -1 +0,0 @@
|
||||||
Notifications are now shown through a serviceworker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
|
|
|
@ -1 +0,0 @@
|
||||||
Shows the most recent scrobble under each post when available
|
|
|
@ -1 +0,0 @@
|
||||||
Display loading and error indicator for conversation page
|
|
|
@ -1 +0,0 @@
|
||||||
Add caching system for themes3
|
|
|
@ -1 +0,0 @@
|
||||||
fix color inputs and some in-development themes3 issues
|
|
|
@ -1 +0,0 @@
|
||||||
Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system.
|
|
|
@ -1 +0,0 @@
|
||||||
unread notifications should now properly catch up (eventually) in polling mode
|
|
|
@ -1 +0,0 @@
|
||||||
Video posters on Safari
|
|
|
@ -1 +0,0 @@
|
||||||
nothing
|
|
|
@ -1 +0,0 @@
|
||||||
Added option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
|
|
|
@ -3,11 +3,6 @@ const path = require('path')
|
||||||
let settings = {}
|
let settings = {}
|
||||||
try {
|
try {
|
||||||
settings = require('./local.json')
|
settings = require('./local.json')
|
||||||
if (settings.target && settings.target.endsWith('/')) {
|
|
||||||
// replacing trailing slash since it can conflict with some apis
|
|
||||||
// and that's how actual BE reports its url
|
|
||||||
settings.target = settings.target.replace(/\/$/, '')
|
|
||||||
}
|
|
||||||
console.log('Using local dev server settings (/config/local.json):')
|
console.log('Using local dev server settings (/config/local.json):')
|
||||||
console.log(JSON.stringify(settings, null, 2))
|
console.log(JSON.stringify(settings, null, 2))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -52,10 +47,7 @@ module.exports = {
|
||||||
target,
|
target,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
cookieDomainRewrite: 'localhost',
|
cookieDomainRewrite: 'localhost',
|
||||||
ws: true,
|
ws: true
|
||||||
headers: {
|
|
||||||
'Origin': target
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'/oauth/revoke': {
|
'/oauth/revoke': {
|
||||||
target,
|
target,
|
||||||
|
|
|
@ -25,17 +25,7 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build*
|
||||||
|
|
||||||
### Replacing your instance's frontend with custom FE build
|
### Replacing your instance's frontend with custom FE build
|
||||||
|
|
||||||
#### New way (via AdminFE, a bit janky but works)
|
This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
|
||||||
|
|
||||||
In backend's [static directory](../backend/configuration/static_dir.md) there should be a folder called `frontends` if you installed any frontends from AdminFE before, otherwise you can create it yourself (ensuring correct permissions). Backend will serve given frontend from path `frontends/{frontend}/{reference}`, where `{frontend}` is name of frontend (`pleroma-fe`) and `{reference}` is version. You could make a production build, move `dist` folder into `frontends/pleroma-fe` and rename it into something like `myCustomVersion`. To actually make backend serve this frontend by default, in AdminFE you'll need to set name/reference in Settings -> Frontend -> Frontends -> Primary.
|
|
||||||
|
|
||||||
You could also install from a zip file (i.e. CI build) but AdminFE UI is a bit buggy and lacking, so this approach is not recommended.
|
|
||||||
|
|
||||||
Take note that frontend management is in early development and currently there's no way for user to change frontend or version for themselves, primary frontend becomes default frontend for all users and visitors.
|
|
||||||
|
|
||||||
#### Old way (replaces everything, hard to maintain, not recommended)
|
|
||||||
|
|
||||||
Copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder, and this could remove emojis, other frontends etc. and therefore this approach is not recommended.
|
|
||||||
|
|
||||||
### Running production build locally or on a separate server
|
### Running production build locally or on a separate server
|
||||||
|
|
||||||
|
|
BIN
image-1.png
BIN
image-1.png
Binary file not shown.
Before Width: | Height: | Size: 883 KiB |
|
@ -3,14 +3,12 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
|
||||||
<!--server-generated-meta-->
|
<!--server-generated-meta-->
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
</head>
|
</head>
|
||||||
<body class="hidden">
|
<body class="hidden">
|
||||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<div id="modal"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
<div id="popovers" />
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
217
package.json
217
package.json
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "pleroma_fe",
|
"name": "pleroma_fe",
|
||||||
"version": "2.6.1",
|
"version": "1.0.0",
|
||||||
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
"description": "A Qvitter-style frontend for certain GS servers.",
|
||||||
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
"author": "Roger Braun <roger@rogerbraun.net>",
|
||||||
"private": false,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node build/dev-server.js",
|
"dev": "node build/dev-server.js",
|
||||||
"build": "node build/build.js",
|
"build": "node build/build.js",
|
||||||
|
@ -11,126 +11,113 @@
|
||||||
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
|
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
|
||||||
"e2e": "node test/e2e/runner.js",
|
"e2e": "node test/e2e/runner.js",
|
||||||
"test": "npm run unit && npm run e2e",
|
"test": "npm run unit && npm run e2e",
|
||||||
"stylelint": "npx stylelint '**/*.scss' '**/*.vue'",
|
"stylelint": "npx stylelint src/components/status/status.scss",
|
||||||
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
|
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
|
||||||
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.21.5",
|
"@babel/runtime": "^7.7.6",
|
||||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
"@chenfengyuan/vue-qrcode": "^2.0.0-beta",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
"@fortawesome/free-regular-svg-icons": "^5.15.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.3",
|
"@fortawesome/vue-fontawesome": "^3.0.0-3",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
"@vue/compiler-sfc": "^3.0.7",
|
||||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
"body-scroll-lock": "^2.6.4",
|
||||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.3.17",
|
"chromatism": "^3.0.0",
|
||||||
"@vuelidate/core": "2.0.3",
|
"cropperjs": "^1.4.3",
|
||||||
"@vuelidate/validators": "2.0.4",
|
"diff": "^3.0.1",
|
||||||
"body-scroll-lock": "3.1.5",
|
"escape-html": "^1.0.3",
|
||||||
"chromatism": "3.0.0",
|
"localforage": "^1.5.0",
|
||||||
"click-outside-vue3": "4.0.1",
|
"parse-link-header": "^1.0.1",
|
||||||
"cropperjs": "1.5.13",
|
"phoenix": "^1.3.0",
|
||||||
"escape-html": "1.0.3",
|
"punycode.js": "^2.1.0",
|
||||||
"hash-sum": "^2.0.0",
|
"v-click-outside": "^2.1.1",
|
||||||
"js-cookie": "3.0.5",
|
"vue": "^3.0.7",
|
||||||
"localforage": "1.10.0",
|
"vue-i18n": "^9.0.0-beta.18",
|
||||||
"parse-link-header": "2.0.0",
|
"vue-router": "^4.0.5",
|
||||||
"phoenix": "1.7.7",
|
"vuelidate": "^0.7.6",
|
||||||
"punycode.js": "2.3.0",
|
"vuex": "^4.0.0",
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "^1.4.4"
|
||||||
"querystring-es3": "0.2.1",
|
|
||||||
"url": "0.11.0",
|
|
||||||
"utf8": "3.0.0",
|
|
||||||
"vue": "3.2.45",
|
|
||||||
"vue-i18n": "9.2.2",
|
|
||||||
"vue-router": "4.1.6",
|
|
||||||
"vue-template-compiler": "2.7.14",
|
|
||||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
|
||||||
"vuex": "4.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.21.8",
|
"@babel/core": "^7.7.5",
|
||||||
"@babel/eslint-parser": "7.21.8",
|
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||||
"@babel/plugin-transform-runtime": "7.21.4",
|
"@babel/preset-env": "^7.7.6",
|
||||||
"@babel/preset-env": "7.21.5",
|
"@babel/register": "^7.7.4",
|
||||||
"@babel/register": "7.21.0",
|
"@ungap/event-target": "^0.1.0",
|
||||||
"@intlify/vue-i18n-loader": "5.0.1",
|
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
|
||||||
"@ungap/event-target": "0.2.4",
|
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
"@vue/test-utils": "^2.0.0-beta.8",
|
||||||
"@vue/babel-plugin-jsx": "1.2.1",
|
"autoprefixer": "^6.4.0",
|
||||||
"@vue/compiler-sfc": "3.2.45",
|
"babel-eslint": "^7.0.0",
|
||||||
"@vue/test-utils": "2.2.8",
|
"babel-loader": "^8.0.6",
|
||||||
"autoprefixer": "10.4.19",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"babel-loader": "9.1.3",
|
"chai": "^3.5.0",
|
||||||
"babel-plugin-lodash": "3.3.4",
|
"chalk": "^1.1.3",
|
||||||
"chai": "4.3.7",
|
"chromedriver": "^87.0.1",
|
||||||
"chalk": "1.1.3",
|
"connect-history-api-fallback": "^1.1.0",
|
||||||
"chromedriver": "108.0.0",
|
"cross-spawn": "^4.0.2",
|
||||||
"connect-history-api-fallback": "2.0.0",
|
"css-loader": "^0.28.0",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"cross-spawn": "7.0.3",
|
"eslint": "^5.16.0",
|
||||||
"css-loader": "6.10.0",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"css-minimizer-webpack-plugin": "4.2.2",
|
"eslint-friendly-formatter": "^2.0.5",
|
||||||
"custom-event-polyfill": "1.0.7",
|
"eslint-loader": "^2.1.0",
|
||||||
"eslint": "8.33.0",
|
"eslint-plugin-import": "^2.13.0",
|
||||||
"eslint-config-standard": "17.0.0",
|
"eslint-plugin-node": "^7.0.0",
|
||||||
"eslint-formatter-friendly": "7.0.0",
|
"eslint-plugin-promise": "^4.0.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"eslint-plugin-n": "15.6.1",
|
"eslint-plugin-vue": "^5.2.2",
|
||||||
"eslint-plugin-promise": "6.1.1",
|
"eventsource-polyfill": "^0.9.6",
|
||||||
"eslint-plugin-vue": "9.9.0",
|
"express": "^4.13.3",
|
||||||
"eslint-webpack-plugin": "3.2.0",
|
"file-loader": "^3.0.1",
|
||||||
"eventsource-polyfill": "0.9.6",
|
"function-bind": "^1.0.2",
|
||||||
"express": "4.18.2",
|
"html-webpack-plugin": "^3.0.0",
|
||||||
"function-bind": "1.1.1",
|
"http-proxy-middleware": "^0.17.2",
|
||||||
"html-webpack-plugin": "5.5.1",
|
"inject-loader": "^2.0.1",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"iso-639-1": "^2.0.3",
|
||||||
"iso-639-1": "2.1.15",
|
"isparta-loader": "^2.0.0",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "^0.5.4",
|
||||||
"karma": "6.4.2",
|
"karma": "^3.0.0",
|
||||||
"karma-coverage": "2.2.0",
|
"karma-coverage": "^1.1.1",
|
||||||
"karma-firefox-launcher": "2.1.2",
|
"karma-firefox-launcher": "^1.1.0",
|
||||||
"karma-mocha": "2.0.1",
|
"karma-mocha": "^1.2.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "^2.2.1",
|
||||||
"karma-sinon-chai": "2.0.2",
|
"karma-sinon-chai": "^2.0.2",
|
||||||
"karma-sourcemap-loader": "0.3.8",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-spec-reporter": "0.0.36",
|
"karma-spec-reporter": "0.0.26",
|
||||||
"karma-webpack": "5.0.0",
|
"karma-webpack": "^4.0.0-rc.3",
|
||||||
"lodash": "4.17.21",
|
"lodash": "^4.16.4",
|
||||||
"mini-css-extract-plugin": "2.7.6",
|
"lolex": "^1.4.0",
|
||||||
"mocha": "10.2.0",
|
"mini-css-extract-plugin": "^0.5.0",
|
||||||
"nightwatch": "2.6.25",
|
"mocha": "^3.1.0",
|
||||||
"opn": "5.5.0",
|
"nightwatch": "^0.9.8",
|
||||||
"ora": "0.4.1",
|
"opn": "^4.0.2",
|
||||||
"postcss": "8.4.23",
|
"ora": "^0.3.0",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-loader": "7.0.2",
|
"raw-loader": "^0.5.1",
|
||||||
"postcss-scss": "^4.0.6",
|
"sass": "^1.17.3",
|
||||||
"sass": "1.60.0",
|
"sass-loader": "git://github.com/webpack-contrib/sass-loader",
|
||||||
"sass-loader": "13.2.2",
|
|
||||||
"selenium-server": "2.53.1",
|
"selenium-server": "2.53.1",
|
||||||
"semver": "7.3.8",
|
"semver": "^5.3.0",
|
||||||
"serviceworker-webpack5-plugin": "2.0.0",
|
"serviceworker-webpack-plugin": "^1.0.0",
|
||||||
"shelljs": "0.8.5",
|
"shelljs": "^0.8.4",
|
||||||
"sinon": "15.0.4",
|
"sinon": "^2.1.0",
|
||||||
"sinon-chai": "3.7.0",
|
"sinon-chai": "^2.8.0",
|
||||||
"stylelint": "14.16.1",
|
"stylelint": "^13.6.1",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"stylelint-config-recommended-scss": "^8.0.0",
|
"stylelint-rscss": "^0.4.0",
|
||||||
"stylelint-config-recommended-vue": "^1.4.0",
|
"url-loader": "^1.1.2",
|
||||||
"stylelint-config-standard": "29.0.0",
|
"vue-loader": "^16.1.2",
|
||||||
"stylelint-rscss": "0.4.0",
|
"vue-style-loader": "^4.0.0",
|
||||||
"stylelint-webpack-plugin": "^3.3.0",
|
"webpack": "^4.0.0",
|
||||||
"vue-loader": "17.0.1",
|
"webpack-dev-middleware": "^3.6.0",
|
||||||
"vue-style-loader": "4.1.3",
|
"webpack-hot-middleware": "^2.12.2",
|
||||||
"webpack": "5.75.0",
|
"webpack-merge": "^0.14.1"
|
||||||
"webpack-dev-middleware": "3.7.3",
|
|
||||||
"webpack-hot-middleware": "2.25.3",
|
|
||||||
"webpack-merge": "0.20.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16.0.0",
|
"node": ">= 4.0.0",
|
||||||
"npm": ">= 3.0.0"
|
"npm": ">= 3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
]
|
|
||||||
}
|
|
77
src/App.js
77
src/App.js
|
@ -1,44 +1,40 @@
|
||||||
import UserPanel from './components/user_panel/user_panel.vue'
|
import UserPanel from './components/user_panel/user_panel.vue'
|
||||||
import NavPanel from './components/nav_panel/nav_panel.vue'
|
import NavPanel from './components/nav_panel/nav_panel.vue'
|
||||||
|
import Notifications from './components/notifications/notifications.vue'
|
||||||
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
||||||
import FeaturesPanel from './components/features_panel/features_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 WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||||
import ShoutPanel from './components/shout_panel/shout_panel.vue'
|
import ChatPanel from './components/chat_panel/chat_panel.vue'
|
||||||
|
import SettingsModal from './components/settings_modal/settings_modal.vue'
|
||||||
import MediaModal from './components/media_modal/media_modal.vue'
|
import MediaModal from './components/media_modal/media_modal.vue'
|
||||||
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
||||||
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
|
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
|
||||||
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
||||||
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
||||||
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
||||||
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
|
|
||||||
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
||||||
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
|
|
||||||
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
||||||
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { defineAsyncComponent } from 'vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
UserPanel,
|
UserPanel,
|
||||||
NavPanel,
|
NavPanel,
|
||||||
Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
|
Notifications,
|
||||||
InstanceSpecificPanel,
|
InstanceSpecificPanel,
|
||||||
FeaturesPanel,
|
FeaturesPanel,
|
||||||
WhoToFollowPanel,
|
WhoToFollowPanel,
|
||||||
ShoutPanel,
|
ChatPanel,
|
||||||
MediaModal,
|
MediaModal,
|
||||||
SideDrawer,
|
SideDrawer,
|
||||||
MobilePostStatusButton,
|
MobilePostStatusButton,
|
||||||
MobileNav,
|
MobileNav,
|
||||||
DesktopNav,
|
DesktopNav,
|
||||||
SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
|
SettingsModal,
|
||||||
UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
|
|
||||||
UserReportingModal,
|
UserReportingModal,
|
||||||
PostStatusModal,
|
PostStatusModal,
|
||||||
EditStatusModal,
|
|
||||||
StatusHistoryModal,
|
|
||||||
GlobalNoticeList
|
GlobalNoticeList
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
@ -50,27 +46,10 @@ export default {
|
||||||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||||
window.addEventListener('resize', this.updateMobileState)
|
window.addEventListener('resize', this.updateMobileState)
|
||||||
},
|
},
|
||||||
unmounted () {
|
destroyed () {
|
||||||
window.removeEventListener('resize', this.updateMobileState)
|
window.removeEventListener('resize', this.updateMobileState)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classes () {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'-reverse': this.reverseLayout,
|
|
||||||
'-no-sticky-headers': this.noSticky,
|
|
||||||
'-has-new-post-button': this.newPostButtonShown
|
|
||||||
},
|
|
||||||
'-' + this.layoutType
|
|
||||||
]
|
|
||||||
},
|
|
||||||
navClasses () {
|
|
||||||
const { navbarColumnStretch } = this.$store.getters.mergedConfig
|
|
||||||
return [
|
|
||||||
'-' + this.layoutType,
|
|
||||||
...(navbarColumnStretch ? ['-column-stretch'] : [])
|
|
||||||
]
|
|
||||||
},
|
|
||||||
currentUser () { return this.$store.state.users.currentUser },
|
currentUser () { return this.$store.state.users.currentUser },
|
||||||
userBackground () { return this.currentUser.background_image },
|
userBackground () { return this.currentUser.background_image },
|
||||||
instanceBackground () {
|
instanceBackground () {
|
||||||
|
@ -86,50 +65,32 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shout () { return this.$store.state.shout.joined },
|
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
||||||
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
||||||
showInstanceSpecificPanel () {
|
showInstanceSpecificPanel () {
|
||||||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
return this.$store.state.instance.showInstanceSpecificPanel &&
|
||||||
!this.$store.getters.mergedConfig.hideISP &&
|
!this.$store.getters.mergedConfig.hideISP &&
|
||||||
this.$store.state.instance.instanceSpecificPanelContent
|
this.$store.state.instance.instanceSpecificPanelContent
|
||||||
},
|
},
|
||||||
isChats () {
|
|
||||||
return this.$route.name === 'chat' || this.$route.name === 'chats'
|
|
||||||
},
|
|
||||||
isListEdit () {
|
|
||||||
return this.$route.name === 'lists-edit'
|
|
||||||
},
|
|
||||||
newPostButtonShown () {
|
|
||||||
if (this.isChats) return false
|
|
||||||
if (this.isListEdit) return false
|
|
||||||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
|
|
||||||
},
|
|
||||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
isMobileLayout () { return this.$store.state.interface.mobileLayout },
|
||||||
shoutboxPosition () {
|
|
||||||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
|
|
||||||
},
|
|
||||||
hideShoutbox () {
|
|
||||||
return this.$store.getters.mergedConfig.hideShoutbox
|
|
||||||
},
|
|
||||||
layoutType () { return this.$store.state.interface.layoutType },
|
|
||||||
privateMode () { return this.$store.state.instance.private },
|
privateMode () { return this.$store.state.instance.private },
|
||||||
reverseLayout () {
|
sidebarAlign () {
|
||||||
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
|
return {
|
||||||
if (this.layoutType !== 'wide') {
|
'order': this.$store.state.instance.sidebarRight ? 99 : 0
|
||||||
return reverseSetting
|
|
||||||
} else {
|
|
||||||
return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
|
|
||||||
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
|
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMobileState () {
|
updateMobileState () {
|
||||||
this.$store.dispatch('setLayoutWidth', windowWidth())
|
const mobileLayout = windowWidth() <= 800
|
||||||
this.$store.dispatch('setLayoutHeight', windowHeight())
|
const layoutHeight = windowHeight()
|
||||||
|
const changed = mobileLayout !== this.isMobileLayout
|
||||||
|
if (changed) {
|
||||||
|
this.$store.dispatch('setMobileLayout', mobileLayout)
|
||||||
|
}
|
||||||
|
this.$store.dispatch('setLayoutHeight', layoutHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1288
src/App.scss
1288
src/App.scss
File diff suppressed because it is too large
Load Diff
69
src/App.vue
69
src/App.vue
|
@ -1,44 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-show="$store.state.interface.themeApplied"
|
id="app"
|
||||||
id="app-loaded"
|
|
||||||
:style="bgStyle"
|
:style="bgStyle"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="app_bg_wrapper"
|
id="app_bg_wrapper"
|
||||||
class="app-bg-wrapper"
|
class="app-bg-wrapper"
|
||||||
/>
|
/>
|
||||||
<MobileNav v-if="layoutType === 'mobile'" />
|
<MobileNav v-if="isMobileLayout" />
|
||||||
<DesktopNav
|
<DesktopNav v-else />
|
||||||
v-else
|
<div class="app-bg-wrapper app-container-wrapper" />
|
||||||
:class="navClasses"
|
|
||||||
/>
|
|
||||||
<Notifications v-if="currentUser" />
|
|
||||||
<div
|
<div
|
||||||
id="content"
|
id="content"
|
||||||
class="app-layout container"
|
class="container underlay"
|
||||||
:class="classes"
|
|
||||||
>
|
>
|
||||||
<div class="underlay" />
|
|
||||||
<div
|
<div
|
||||||
id="sidebar"
|
class="sidebar-flexer mobile-hidden"
|
||||||
class="column -scrollable"
|
:style="sidebarAlign"
|
||||||
:class="{ '-show-scrollbar': showScrollbars }"
|
|
||||||
>
|
>
|
||||||
<user-panel />
|
<div class="sidebar-bounds">
|
||||||
<template v-if="layoutType !== 'mobile'">
|
<div class="sidebar-scroller">
|
||||||
<nav-panel />
|
<div class="sidebar">
|
||||||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
<user-panel />
|
||||||
<features-panel v-if="!currentUser && showFeaturesPanel" />
|
<div v-if="!isMobileLayout">
|
||||||
<who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
|
<nav-panel />
|
||||||
<div id="notifs-sidebar" />
|
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||||
</template>
|
<features-panel v-if="!currentUser && showFeaturesPanel" />
|
||||||
|
<who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
|
||||||
|
<notifications v-if="currentUser" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<main
|
<div class="main">
|
||||||
id="main-scroller"
|
|
||||||
class="column main"
|
|
||||||
:class="{ '-full-height': isChats || isListEdit }"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-if="!currentUser"
|
v-if="!currentUser"
|
||||||
class="login-hint panel panel-default"
|
class="login-hint panel panel-default"
|
||||||
|
@ -51,27 +46,19 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<router-view />
|
<router-view />
|
||||||
</main>
|
</div>
|
||||||
<div
|
<media-modal />
|
||||||
id="notifs-column"
|
|
||||||
class="column -scrollable"
|
|
||||||
:class="{ '-show-scrollbar': showScrollbars }"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<MediaModal />
|
<chat-panel
|
||||||
<shout-panel
|
v-if="currentUser && chat"
|
||||||
v-if="currentUser && shout && !hideShoutbox"
|
|
||||||
:floating="true"
|
:floating="true"
|
||||||
class="floating-shout mobile-hidden"
|
class="floating-chat mobile-hidden"
|
||||||
:class="{ '-left': shoutboxPosition }"
|
|
||||||
/>
|
/>
|
||||||
<MobilePostStatusButton />
|
<MobilePostStatusButton />
|
||||||
<UserReportingModal />
|
<UserReportingModal />
|
||||||
<PostStatusModal />
|
<PostStatusModal />
|
||||||
<EditStatusModal v-if="editingAvailable" />
|
|
||||||
<StatusHistoryModal v-if="editingAvailable" />
|
|
||||||
<SettingsModal />
|
<SettingsModal />
|
||||||
<UpdateNotification />
|
<portal-target name="modal" />
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
@mixin unfocused-style {
|
|
||||||
@content;
|
|
||||||
|
|
||||||
&:focus:not(:focus-visible, :hover) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin focused-style {
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
$main-color: #f58d2c;
|
||||||
|
$main-background: white;
|
||||||
|
$darkened-background: whitesmoke;
|
||||||
|
|
||||||
|
$fallback--bg: #121a24;
|
||||||
|
$fallback--fg: #182230;
|
||||||
|
$fallback--faint: rgba(185, 185, 186, .5);
|
||||||
|
$fallback--text: #b9b9ba;
|
||||||
|
$fallback--link: #d8a070;
|
||||||
|
$fallback--icon: #666;
|
||||||
|
$fallback--lightBg: rgb(21, 30, 42);
|
||||||
|
$fallback--lightText: #b9b9ba;
|
||||||
|
$fallback--border: #222;
|
||||||
|
$fallback--cRed: #ff0000;
|
||||||
|
$fallback--cBlue: #0095ff;
|
||||||
|
$fallback--cGreen: #0fa00f;
|
||||||
|
$fallback--cOrange: orange;
|
||||||
|
|
||||||
|
$fallback--alertError: rgba(211,16,20,.5);
|
||||||
|
$fallback--alertWarning: rgba(111,111,20,.5);
|
||||||
|
|
||||||
|
$fallback--panelRadius: 10px;
|
||||||
|
$fallback--checkboxRadius: 2px;
|
||||||
|
$fallback--btnRadius: 4px;
|
||||||
|
$fallback--inputRadius: 4px;
|
||||||
|
$fallback--tooltipRadius: 5px;
|
||||||
|
$fallback--avatarRadius: 4px;
|
||||||
|
$fallback--avatarAltRadius: 10px;
|
||||||
|
$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;
|
Binary file not shown.
Before Width: | Height: | Size: 396 KiB |
Binary file not shown.
Before Width: | Height: | Size: 521 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.3 KiB |
|
@ -1,8 +1,6 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import vClickOutside from 'click-outside-vue3'
|
import VueClickOutside from 'v-click-outside'
|
||||||
import VueVirtualScroller from 'vue-virtual-scroller'
|
|
||||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
|
||||||
|
|
||||||
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
|
@ -10,13 +8,12 @@ import App from '../App.vue'
|
||||||
import routes from './routes'
|
import routes from './routes'
|
||||||
import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
||||||
|
|
||||||
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
|
import { windowWidth } from '../services/window_utils/window_utils'
|
||||||
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||||
import { applyTheme, applyConfig, tryLoadCache } from '../services/style_setter/style_setter.js'
|
import { applyTheme } from '../services/style_setter/style_setter.js'
|
||||||
import FaviconService from '../services/favicon_service/favicon_service.js'
|
import FaviconService from '../services/favicon_service/favicon_service.js'
|
||||||
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
|
|
||||||
|
|
||||||
let staticInitialResults = null
|
let staticInitialResults = null
|
||||||
|
|
||||||
|
@ -61,8 +58,6 @@ const getInstanceConfig = async ({ store }) => {
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
||||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
||||||
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
|
|
||||||
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
|
|
||||||
|
|
||||||
if (vapidPublicKey) {
|
if (vapidPublicKey) {
|
||||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
||||||
|
@ -126,7 +121,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
copyInstanceOption('nsfwCensorImage')
|
copyInstanceOption('nsfwCensorImage')
|
||||||
copyInstanceOption('background')
|
copyInstanceOption('background')
|
||||||
copyInstanceOption('hidePostStats')
|
copyInstanceOption('hidePostStats')
|
||||||
copyInstanceOption('hideBotIndication')
|
|
||||||
copyInstanceOption('hideUserStats')
|
copyInstanceOption('hideUserStats')
|
||||||
copyInstanceOption('hideFilteredStatuses')
|
copyInstanceOption('hideFilteredStatuses')
|
||||||
copyInstanceOption('logo')
|
copyInstanceOption('logo')
|
||||||
|
@ -161,7 +155,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
copyInstanceOption('hideSitename')
|
copyInstanceOption('hideSitename')
|
||||||
copyInstanceOption('sidebarRight')
|
copyInstanceOption('sidebarRight')
|
||||||
|
|
||||||
return store.dispatch('setTheme', config.theme)
|
return store.dispatch('setTheme', config['theme'])
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTOS = async ({ store }) => {
|
const getTOS = async ({ store }) => {
|
||||||
|
@ -202,7 +196,7 @@ const getStickers = async ({ store }) => {
|
||||||
const stickers = (await Promise.all(
|
const stickers = (await Promise.all(
|
||||||
Object.entries(values).map(async ([name, path]) => {
|
Object.entries(values).map(async ([name, path]) => {
|
||||||
const resPack = await window.fetch(path + 'pack.json')
|
const resPack = await window.fetch(path + 'pack.json')
|
||||||
let meta = {}
|
var meta = {}
|
||||||
if (resPack.ok) {
|
if (resPack.ok) {
|
||||||
meta = await resPack.json()
|
meta = await resPack.json()
|
||||||
}
|
}
|
||||||
|
@ -252,16 +246,12 @@ const getNodeInfo = async ({ store }) => {
|
||||||
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
|
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
|
||||||
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
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: 'safeDM', value: features.includes('safe_dm_mentions') })
|
||||||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
|
|
||||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
|
||||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
||||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||||
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
|
|
||||||
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
|
|
||||||
|
|
||||||
const uploadLimits = metadata.uploadLimits
|
const uploadLimits = metadata.uploadLimits
|
||||||
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
||||||
|
@ -328,24 +318,23 @@ const setConfig = async ({ store }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkOAuthToken = async ({ store }) => {
|
const checkOAuthToken = async ({ store }) => {
|
||||||
if (store.getters.getUserToken()) {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
if (store.getters.getUserToken()) {
|
||||||
await store.dispatch('loginUser', store.getters.getUserToken())
|
try {
|
||||||
} catch (e) {
|
await store.dispatch('loginUser', store.getters.getUserToken())
|
||||||
console.error(e)
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
resolve()
|
||||||
return Promise.resolve()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const afterStoreSetup = async ({ store, i18n }) => {
|
const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
store.dispatch('setLayoutWidth', windowWidth())
|
const width = windowWidth()
|
||||||
store.dispatch('setLayoutHeight', windowHeight())
|
store.dispatch('setMobileLayout', width <= 800)
|
||||||
|
|
||||||
FaviconService.initFaviconService()
|
FaviconService.initFaviconService()
|
||||||
initServiceWorker(store)
|
|
||||||
|
|
||||||
window.addEventListener('focus', () => updateFocus())
|
|
||||||
|
|
||||||
const overrides = window.___pleromafe_dev_overrides || {}
|
const overrides = window.___pleromafe_dev_overrides || {}
|
||||||
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
|
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
|
||||||
|
@ -353,29 +342,22 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
|
||||||
await setConfig({ store })
|
await setConfig({ store })
|
||||||
|
|
||||||
const { customTheme, customThemeSource, forceThemeRecompilation } = store.state.config
|
const { customTheme, customThemeSource } = store.state.config
|
||||||
const { theme } = store.state.instance
|
const { theme } = store.state.instance
|
||||||
const customThemePresent = customThemeSource || customTheme
|
const customThemePresent = customThemeSource || customTheme
|
||||||
|
|
||||||
if (!forceThemeRecompilation && tryLoadCache()) {
|
if (customThemePresent) {
|
||||||
store.commit('setThemeApplied')
|
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
|
||||||
} else {
|
applyTheme(customThemeSource)
|
||||||
if (customThemePresent) {
|
|
||||||
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
|
|
||||||
applyTheme(customThemeSource)
|
|
||||||
} else {
|
|
||||||
applyTheme(customTheme)
|
|
||||||
}
|
|
||||||
store.commit('setThemeApplied')
|
|
||||||
} else if (theme) {
|
|
||||||
// do nothing, it will load asynchronously
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to load any theme!')
|
applyTheme(customTheme)
|
||||||
}
|
}
|
||||||
|
} else if (theme) {
|
||||||
|
// do nothing, it will load asynchronously
|
||||||
|
} else {
|
||||||
|
console.error('Failed to load any theme!')
|
||||||
}
|
}
|
||||||
|
|
||||||
applyConfig(store.state.config)
|
|
||||||
|
|
||||||
// Now we can try getting the server settings and logging in
|
// Now we can try getting the server settings and logging in
|
||||||
// Most of these are preloaded into the index.html so blocking is minimized
|
// Most of these are preloaded into the index.html so blocking is minimized
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -387,7 +369,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
|
||||||
// Start fetching things that don't need to block the UI
|
// Start fetching things that don't need to block the UI
|
||||||
store.dispatch('fetchMutes')
|
store.dispatch('fetchMutes')
|
||||||
store.dispatch('startFetchingAnnouncements')
|
|
||||||
getTOS({ store })
|
getTOS({ store })
|
||||||
getStickers({ store })
|
getStickers({ store })
|
||||||
|
|
||||||
|
@ -408,16 +389,12 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
app.use(store)
|
app.use(store)
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
app.use(vClickOutside)
|
app.use(VueClickOutside)
|
||||||
app.use(VBodyScrollLock)
|
app.use(VBodyScrollLock)
|
||||||
app.use(VueVirtualScroller)
|
|
||||||
|
|
||||||
app.component('FAIcon', FontAwesomeIcon)
|
app.component('FAIcon', FontAwesomeIcon)
|
||||||
app.component('FALayers', FontAwesomeLayers)
|
app.component('FALayers', FontAwesomeLayers)
|
||||||
|
|
||||||
// remove after vue 3.3
|
|
||||||
app.config.unwrapInjectedRef = true
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -16,16 +16,10 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
||||||
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
||||||
import Notifications from 'components/notifications/notifications.vue'
|
import Notifications from 'components/notifications/notifications.vue'
|
||||||
import AuthForm from 'components/auth_form/auth_form.js'
|
import AuthForm from 'components/auth_form/auth_form.js'
|
||||||
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
|
import ChatPanel from 'components/chat_panel/chat_panel.vue'
|
||||||
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
||||||
import About from 'components/about/about.vue'
|
import About from 'components/about/about.vue'
|
||||||
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
||||||
import Lists from 'components/lists/lists.vue'
|
|
||||||
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
|
|
||||||
import ListsEdit from 'components/lists_edit/lists_edit.vue'
|
|
||||||
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
|
||||||
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
|
|
||||||
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
|
|
||||||
|
|
||||||
export default (store) => {
|
export default (store) => {
|
||||||
const validateAuthenticatedRoute = (to, from, next) => {
|
const validateAuthenticatedRoute = (to, from, next) => {
|
||||||
|
@ -37,8 +31,7 @@ export default (store) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let routes = [
|
let routes = [
|
||||||
{
|
{ name: 'root',
|
||||||
name: 'root',
|
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: _to => {
|
redirect: _to => {
|
||||||
return (store.state.users.currentUser
|
return (store.state.users.currentUser
|
||||||
|
@ -52,41 +45,31 @@ export default (store) => {
|
||||||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||||
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
|
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
|
||||||
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
||||||
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
|
{ name: 'remote-user-profile-acct',
|
||||||
{
|
path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)',
|
||||||
name: 'remote-user-profile-acct',
|
|
||||||
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
|
|
||||||
component: RemoteUserResolver,
|
component: RemoteUserResolver,
|
||||||
beforeEnter: validateAuthenticatedRoute
|
beforeEnter: validateAuthenticatedRoute
|
||||||
},
|
},
|
||||||
{
|
{ name: 'remote-user-profile',
|
||||||
name: 'remote-user-profile',
|
|
||||||
path: '/remote-users/:hostname/:username',
|
path: '/remote-users/:hostname/:username',
|
||||||
component: RemoteUserResolver,
|
component: RemoteUserResolver,
|
||||||
beforeEnter: validateAuthenticatedRoute
|
beforeEnter: validateAuthenticatedRoute
|
||||||
},
|
},
|
||||||
{ name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
|
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
|
||||||
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'registration', path: '/registration', component: Registration },
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
|
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
|
||||||
{ name: 'registration-token', path: '/registration/:token', component: Registration },
|
{ name: 'registration-token', path: '/registration/:token', component: Registration },
|
||||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
|
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'login', path: '/login', component: AuthForm },
|
{ name: 'login', path: '/login', component: AuthForm },
|
||||||
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
|
{ name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) },
|
||||||
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
{ 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: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
|
||||||
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'about', path: '/about', component: About },
|
{ name: 'about', path: '/about', component: About },
|
||||||
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
|
{ name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
|
||||||
{ name: 'user-profile', path: '/users/:name', component: UserProfile },
|
|
||||||
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
|
|
||||||
{ name: 'lists', path: '/lists', component: Lists },
|
|
||||||
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
|
|
||||||
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
|
|
||||||
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
|
|
||||||
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if (store.state.instance.pleromaChatMessagesAvailable) {
|
if (store.state.instance.pleromaChatMessagesAvailable) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="column-inner">
|
<div class="sidebar">
|
||||||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||||
<staff-panel />
|
<staff-panel />
|
||||||
<terms-of-service-panel />
|
<terms-of-service-panel />
|
||||||
|
@ -8,4 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./about.js"></script>
|
<script src="./about.js" ></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import ProgressButton from '../progress_button/progress_button.vue'
|
import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
|
||||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faEllipsisV
|
faEllipsisV
|
||||||
|
@ -17,30 +15,13 @@ const AccountActions = {
|
||||||
'user', 'relationship'
|
'user', 'relationship'
|
||||||
],
|
],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return { }
|
||||||
showingConfirmBlock: false,
|
|
||||||
showingConfirmRemoveFollower: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ProgressButton,
|
ProgressButton,
|
||||||
Popover,
|
Popover
|
||||||
UserListMenu,
|
|
||||||
ConfirmModal
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showConfirmBlock () {
|
|
||||||
this.showingConfirmBlock = true
|
|
||||||
},
|
|
||||||
hideConfirmBlock () {
|
|
||||||
this.showingConfirmBlock = false
|
|
||||||
},
|
|
||||||
showConfirmRemoveUserFromFollowers () {
|
|
||||||
this.showingConfirmRemoveFollower = true
|
|
||||||
},
|
|
||||||
hideConfirmRemoveUserFromFollowers () {
|
|
||||||
this.showingConfirmRemoveFollower = false
|
|
||||||
},
|
|
||||||
showRepeats () {
|
showRepeats () {
|
||||||
this.$store.dispatch('showReblogs', this.user.id)
|
this.$store.dispatch('showReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
|
@ -48,47 +29,22 @@ const AccountActions = {
|
||||||
this.$store.dispatch('hideReblogs', this.user.id)
|
this.$store.dispatch('hideReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
blockUser () {
|
blockUser () {
|
||||||
if (!this.shouldConfirmBlock) {
|
|
||||||
this.doBlockUser()
|
|
||||||
} else {
|
|
||||||
this.showConfirmBlock()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doBlockUser () {
|
|
||||||
this.$store.dispatch('blockUser', this.user.id)
|
this.$store.dispatch('blockUser', this.user.id)
|
||||||
this.hideConfirmBlock()
|
|
||||||
},
|
},
|
||||||
unblockUser () {
|
unblockUser () {
|
||||||
this.$store.dispatch('unblockUser', this.user.id)
|
this.$store.dispatch('unblockUser', this.user.id)
|
||||||
},
|
},
|
||||||
removeUserFromFollowers () {
|
|
||||||
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
|
||||||
this.doRemoveUserFromFollowers()
|
|
||||||
} else {
|
|
||||||
this.showConfirmRemoveUserFromFollowers()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doRemoveUserFromFollowers () {
|
|
||||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
|
||||||
this.hideConfirmRemoveUserFromFollowers()
|
|
||||||
},
|
|
||||||
reportUser () {
|
reportUser () {
|
||||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||||
},
|
},
|
||||||
openChat () {
|
openChat () {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
|
params: { recipient_id: this.user.id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
shouldConfirmBlock () {
|
|
||||||
return this.$store.getters.mergedConfig.modalOnBlock
|
|
||||||
},
|
|
||||||
shouldConfirmRemoveUserFromFollowers () {
|
|
||||||
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
|
||||||
},
|
|
||||||
...mapState({
|
...mapState({
|
||||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,19 +6,22 @@
|
||||||
:bound-to="{ x: 'container' }"
|
:bound-to="{ x: 'container' }"
|
||||||
remove-padding
|
remove-padding
|
||||||
>
|
>
|
||||||
<template #content>
|
<div
|
||||||
|
slot="content"
|
||||||
|
class="account-tools-popover"
|
||||||
|
>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<template v-if="relationship.following">
|
<template v-if="relationship.following">
|
||||||
<button
|
<button
|
||||||
v-if="relationship.showing_reblogs"
|
v-if="relationship.showing_reblogs"
|
||||||
class="dropdown-item menu-item"
|
class="btn button-default dropdown-item"
|
||||||
@click="hideRepeats"
|
@click="hideRepeats"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.hide_repeats') }}
|
{{ $t('user_card.hide_repeats') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!relationship.showing_reblogs"
|
v-if="!relationship.showing_reblogs"
|
||||||
class="dropdown-item menu-item"
|
class="btn button-default dropdown-item"
|
||||||
@click="showRepeats"
|
@click="showRepeats"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.show_repeats') }}
|
{{ $t('user_card.show_repeats') }}
|
||||||
|
@ -28,106 +31,68 @@
|
||||||
class="dropdown-divider"
|
class="dropdown-divider"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<UserListMenu :user="user" />
|
|
||||||
<button
|
|
||||||
v-if="relationship.followed_by"
|
|
||||||
class="dropdown-item menu-item"
|
|
||||||
@click="removeUserFromFollowers"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.remove_follower') }}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
v-if="relationship.blocking"
|
v-if="relationship.blocking"
|
||||||
class="dropdown-item menu-item"
|
class="btn button-default btn-block dropdown-item"
|
||||||
@click="unblockUser"
|
@click="unblockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.unblock') }}
|
{{ $t('user_card.unblock') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="dropdown-item menu-item"
|
class="btn button-default btn-block dropdown-item"
|
||||||
@click="blockUser"
|
@click="blockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.block') }}
|
{{ $t('user_card.block') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="dropdown-item menu-item"
|
class="btn button-default btn-block dropdown-item"
|
||||||
@click="reportUser"
|
@click="reportUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.report') }}
|
{{ $t('user_card.report') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="pleromaChatMessagesAvailable"
|
v-if="pleromaChatMessagesAvailable"
|
||||||
class="dropdown-item menu-item"
|
class="btn button-default btn-block dropdown-item"
|
||||||
@click="openChat"
|
@click="openChat"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.message') }}
|
{{ $t('user_card.message') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template #trigger>
|
<div
|
||||||
<button class="button-unstyled ellipsis-button">
|
slot="trigger"
|
||||||
<FAIcon
|
class="ellipsis-button"
|
||||||
class="icon"
|
>
|
||||||
icon="ellipsis-v"
|
<FAIcon
|
||||||
/>
|
class="icon"
|
||||||
</button>
|
icon="ellipsis-v"
|
||||||
</template>
|
/>
|
||||||
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
<teleport to="#modal">
|
|
||||||
<confirm-modal
|
|
||||||
v-if="showingConfirmBlock"
|
|
||||||
:title="$t('user_card.block_confirm_title')"
|
|
||||||
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
|
||||||
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
|
||||||
@accepted="doBlockUser"
|
|
||||||
@cancelled="hideConfirmBlock"
|
|
||||||
>
|
|
||||||
<i18n-t
|
|
||||||
keypath="user_card.block_confirm"
|
|
||||||
tag="span"
|
|
||||||
>
|
|
||||||
<template #user>
|
|
||||||
<span
|
|
||||||
v-text="user.screen_name_ui"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</confirm-modal>
|
|
||||||
</teleport>
|
|
||||||
<teleport to="#modal">
|
|
||||||
<confirm-modal
|
|
||||||
v-if="showingConfirmRemoveFollower"
|
|
||||||
:title="$t('user_card.remove_follower_confirm_title')"
|
|
||||||
:confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
|
|
||||||
:cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
|
|
||||||
@accepted="doRemoveUserFromFollowers"
|
|
||||||
@cancelled="hideConfirmRemoveUserFromFollowers"
|
|
||||||
>
|
|
||||||
<i18n-t
|
|
||||||
keypath="user_card.remove_follower_confirm"
|
|
||||||
tag="span"
|
|
||||||
>
|
|
||||||
<template #user>
|
|
||||||
<span
|
|
||||||
v-text="user.screen_name_ui"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</confirm-modal>
|
|
||||||
</teleport>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./account_actions.js"></script>
|
<script src="./account_actions.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
.AccountActions {
|
.AccountActions {
|
||||||
|
button.dropdown-item {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ellipsis-button {
|
.ellipsis-button {
|
||||||
|
cursor: pointer;
|
||||||
width: 2.5em;
|
width: 2.5em;
|
||||||
margin: -0.5em 0;
|
margin: -0.5em 0;
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
&:not(:hover) .icon {
|
||||||
|
color: $fallback--lightText;
|
||||||
|
color: var(--lightText, $fallback--lightText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'Alert',
|
|
||||||
selector: '.alert',
|
|
||||||
validInnerComponents: [
|
|
||||||
'Text',
|
|
||||||
'Icon',
|
|
||||||
'Link',
|
|
||||||
'Border',
|
|
||||||
'ButtonUnstyled'
|
|
||||||
],
|
|
||||||
variants: {
|
|
||||||
normal: '.neutral',
|
|
||||||
error: '.error',
|
|
||||||
warning: '.warning',
|
|
||||||
success: '.success'
|
|
||||||
},
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
directives: {
|
|
||||||
background: '--text',
|
|
||||||
opacity: 0.5,
|
|
||||||
blur: '9px'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parent: {
|
|
||||||
component: 'Alert'
|
|
||||||
},
|
|
||||||
component: 'Border',
|
|
||||||
textColor: '--parent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
variant: 'error',
|
|
||||||
directives: {
|
|
||||||
background: '--cRed'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
variant: 'warning',
|
|
||||||
directives: {
|
|
||||||
background: '--cOrange'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
variant: 'success',
|
|
||||||
directives: {
|
|
||||||
background: '--cGreen'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
import { mapState } from 'vuex'
|
|
||||||
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
|
|
||||||
import RichContent from '../rich_content/rich_content.jsx'
|
|
||||||
import localeService from '../../services/locale/locale.service.js'
|
|
||||||
|
|
||||||
const Announcement = {
|
|
||||||
components: {
|
|
||||||
AnnouncementEditor,
|
|
||||||
RichContent
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
editing: false,
|
|
||||||
editedAnnouncement: {
|
|
||||||
content: '',
|
|
||||||
startsAt: undefined,
|
|
||||||
endsAt: undefined,
|
|
||||||
allDay: undefined
|
|
||||||
},
|
|
||||||
editError: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
announcement: Object
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
currentUser: state => state.users.currentUser
|
|
||||||
}),
|
|
||||||
canEditAnnouncement () {
|
|
||||||
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
|
||||||
},
|
|
||||||
content () {
|
|
||||||
return this.announcement.content
|
|
||||||
},
|
|
||||||
isRead () {
|
|
||||||
return this.announcement.read
|
|
||||||
},
|
|
||||||
publishedAt () {
|
|
||||||
const time = this.announcement.published_at
|
|
||||||
if (!time) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
|
|
||||||
},
|
|
||||||
startsAt () {
|
|
||||||
const time = this.announcement.starts_at
|
|
||||||
if (!time) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
|
|
||||||
},
|
|
||||||
endsAt () {
|
|
||||||
const time = this.announcement.ends_at
|
|
||||||
if (!time) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
|
|
||||||
},
|
|
||||||
inactive () {
|
|
||||||
return this.announcement.inactive
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
markAsRead () {
|
|
||||||
if (!this.isRead) {
|
|
||||||
return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteAnnouncement () {
|
|
||||||
return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
|
|
||||||
},
|
|
||||||
formatTimeOrDate (time, locale) {
|
|
||||||
const d = new Date(time)
|
|
||||||
return this.announcement.all_day ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
|
|
||||||
},
|
|
||||||
enterEditMode () {
|
|
||||||
this.editedAnnouncement.content = this.announcement.pleroma.raw_content
|
|
||||||
this.editedAnnouncement.startsAt = this.announcement.starts_at
|
|
||||||
this.editedAnnouncement.endsAt = this.announcement.ends_at
|
|
||||||
this.editedAnnouncement.allDay = this.announcement.all_day
|
|
||||||
this.editing = true
|
|
||||||
},
|
|
||||||
submitEdit () {
|
|
||||||
this.$store.dispatch('editAnnouncement', {
|
|
||||||
id: this.announcement.id,
|
|
||||||
...this.editedAnnouncement
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.editing = false
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
this.editError = error.error
|
|
||||||
})
|
|
||||||
},
|
|
||||||
cancelEdit () {
|
|
||||||
this.editing = false
|
|
||||||
},
|
|
||||||
clearError () {
|
|
||||||
this.editError = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Announcement
|
|
|
@ -1,134 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="announcement">
|
|
||||||
<div class="heading">
|
|
||||||
<h4>{{ $t('announcements.title') }}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
|
||||||
<rich-content
|
|
||||||
v-if="!editing"
|
|
||||||
:html="content"
|
|
||||||
:emoji="announcement.emojis"
|
|
||||||
:handle-links="true"
|
|
||||||
/>
|
|
||||||
<announcement-editor
|
|
||||||
v-else
|
|
||||||
:announcement="editedAnnouncement"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<div
|
|
||||||
v-if="!editing"
|
|
||||||
class="times"
|
|
||||||
>
|
|
||||||
<span v-if="publishedAt">
|
|
||||||
{{ $t('announcements.published_time_display', { time: publishedAt }) }}
|
|
||||||
</span>
|
|
||||||
<span v-if="startsAt">
|
|
||||||
{{ $t('announcements.start_time_display', { time: startsAt }) }}
|
|
||||||
</span>
|
|
||||||
<span v-if="endsAt">
|
|
||||||
{{ $t('announcements.end_time_display', { time: endsAt }) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="!editing"
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="currentUser"
|
|
||||||
class="btn button-default"
|
|
||||||
:class="{ toggled: isRead }"
|
|
||||||
:disabled="inactive"
|
|
||||||
:title="inactive ? $t('announcements.inactive_message') : ''"
|
|
||||||
@click="markAsRead"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.mark_as_read_action') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="canEditAnnouncement"
|
|
||||||
class="btn button-default"
|
|
||||||
@click="enterEditMode"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.edit_action') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="canEditAnnouncement"
|
|
||||||
class="btn button-default"
|
|
||||||
@click="deleteAnnouncement"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.delete_action') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn button-default"
|
|
||||||
@click="submitEdit"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.submit_edit_action') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="btn button-default"
|
|
||||||
@click="cancelEdit"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.cancel_edit_action') }}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-if="editing && editError"
|
|
||||||
class="alert error"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.edit_error', { error }) }}
|
|
||||||
<button
|
|
||||||
class="button-unstyled"
|
|
||||||
@click="clearError"
|
|
||||||
>
|
|
||||||
<FAIcon
|
|
||||||
class="fa-scale-110 fa-old-padding"
|
|
||||||
icon="times"
|
|
||||||
:title="$t('announcements.close_error')"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./announcement.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.announcement {
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
border-radius: 0;
|
|
||||||
padding: var(--status-margin);
|
|
||||||
|
|
||||||
.heading,
|
|
||||||
.body {
|
|
||||||
margin-bottom: var(--status-margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.times {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer .actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
flex: 1;
|
|
||||||
margin: 1em;
|
|
||||||
max-width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,13 +0,0 @@
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
|
||||||
|
|
||||||
const AnnouncementEditor = {
|
|
||||||
components: {
|
|
||||||
Checkbox
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
announcement: Object,
|
|
||||||
disabled: Boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnnouncementEditor
|
|
|
@ -1,62 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="announcement-editor">
|
|
||||||
<textarea
|
|
||||||
ref="textarea"
|
|
||||||
v-model="announcement.content"
|
|
||||||
class="input post-textarea"
|
|
||||||
rows="1"
|
|
||||||
cols="1"
|
|
||||||
:placeholder="$t('announcements.post_placeholder')"
|
|
||||||
:disabled="disabled"
|
|
||||||
/>
|
|
||||||
<span class="announcement-metadata">
|
|
||||||
<label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
|
|
||||||
<input
|
|
||||||
id="announcement-start-time"
|
|
||||||
v-model="announcement.startsAt"
|
|
||||||
class="input"
|
|
||||||
:type="announcement.allDay ? 'date' : 'datetime-local'"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<span class="announcement-metadata">
|
|
||||||
<label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
|
|
||||||
<input
|
|
||||||
id="announcement-end-time"
|
|
||||||
v-model="announcement.endsAt"
|
|
||||||
class="input"
|
|
||||||
:type="announcement.allDay ? 'date' : 'datetime-local'"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<span class="announcement-metadata">
|
|
||||||
<Checkbox
|
|
||||||
id="announcement-all-day"
|
|
||||||
v-model="announcement.allDay"
|
|
||||||
:disabled="disabled"
|
|
||||||
/>
|
|
||||||
<label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./announcement_editor.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.announcement-editor {
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.announcement-metadata {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-textarea {
|
|
||||||
resize: vertical;
|
|
||||||
height: 10em;
|
|
||||||
overflow: none;
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { mapState } from 'vuex'
|
|
||||||
import Announcement from '../announcement/announcement.vue'
|
|
||||||
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
|
|
||||||
|
|
||||||
const AnnouncementsPage = {
|
|
||||||
components: {
|
|
||||||
Announcement,
|
|
||||||
AnnouncementEditor
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
newAnnouncement: {
|
|
||||||
content: '',
|
|
||||||
startsAt: undefined,
|
|
||||||
endsAt: undefined,
|
|
||||||
allDay: false
|
|
||||||
},
|
|
||||||
posting: false,
|
|
||||||
error: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this.$store.dispatch('fetchAnnouncements')
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
currentUser: state => state.users.currentUser
|
|
||||||
}),
|
|
||||||
announcements () {
|
|
||||||
return this.$store.state.announcements.announcements
|
|
||||||
},
|
|
||||||
canPostAnnouncement () {
|
|
||||||
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
postAnnouncement () {
|
|
||||||
this.posting = true
|
|
||||||
this.$store.dispatch('postAnnouncement', this.newAnnouncement)
|
|
||||||
.then(() => {
|
|
||||||
this.newAnnouncement.content = ''
|
|
||||||
this.startsAt = undefined
|
|
||||||
this.endsAt = undefined
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
this.error = error.error
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.posting = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearError () {
|
|
||||||
this.error = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AnnouncementsPage
|
|
|
@ -1,78 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="panel panel-default announcements-page">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<span>
|
|
||||||
{{ $t('announcements.page_header') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<section
|
|
||||||
v-if="canPostAnnouncement"
|
|
||||||
>
|
|
||||||
<div class="post-form">
|
|
||||||
<div class="heading">
|
|
||||||
<h4>{{ $t('announcements.post_form_header') }}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
|
||||||
<announcement-editor
|
|
||||||
:announcement="newAnnouncement"
|
|
||||||
:disabled="posting"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<button
|
|
||||||
class="btn button-default post-button"
|
|
||||||
:disabled="posting"
|
|
||||||
@click.prevent="postAnnouncement"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.post_action') }}
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
v-if="error"
|
|
||||||
class="alert error"
|
|
||||||
>
|
|
||||||
{{ $t('announcements.post_error', { error }) }}
|
|
||||||
<button
|
|
||||||
class="button-unstyled"
|
|
||||||
@click="clearError"
|
|
||||||
>
|
|
||||||
<FAIcon
|
|
||||||
class="fa-scale-110 fa-old-padding"
|
|
||||||
icon="times"
|
|
||||||
:title="$t('announcements.close_error')"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section
|
|
||||||
v-for="announcement in announcements"
|
|
||||||
:key="announcement.id"
|
|
||||||
>
|
|
||||||
<announcement
|
|
||||||
:announcement="announcement"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./announcements_page.js"></script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.announcements-page {
|
|
||||||
.post-form {
|
|
||||||
padding: var(--status-margin);
|
|
||||||
|
|
||||||
.heading,
|
|
||||||
.body {
|
|
||||||
margin-bottom: var(--status-margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-button {
|
|
||||||
min-width: 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
emits: ['resetAsyncComponent'],
|
|
||||||
methods: {
|
methods: {
|
||||||
retry () {
|
retry () {
|
||||||
this.$emit('resetAsyncComponent')
|
this.$emit('resetAsyncComponent')
|
||||||
|
@ -34,10 +33,9 @@ export default {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin: 0.5em;
|
margin: .5em;
|
||||||
padding: 0.5em 2em;
|
padding: .5em 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import StillImage from '../still-image/still-image.vue'
|
import StillImage from '../still-image/still-image.vue'
|
||||||
import Flash from '../flash/flash.vue'
|
|
||||||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||||
import nsfwImage from '../../assets/nsfw.png'
|
import nsfwImage from '../../assets/nsfw.png'
|
||||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||||
|
@ -11,12 +10,7 @@ import {
|
||||||
faImage,
|
faImage,
|
||||||
faVideo,
|
faVideo,
|
||||||
faPlayCircle,
|
faPlayCircle,
|
||||||
faTimes,
|
faTimes
|
||||||
faStop,
|
|
||||||
faSearchPlus,
|
|
||||||
faTrashAlt,
|
|
||||||
faPencilAlt,
|
|
||||||
faAlignRight
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
@ -25,66 +19,36 @@ library.add(
|
||||||
faImage,
|
faImage,
|
||||||
faVideo,
|
faVideo,
|
||||||
faPlayCircle,
|
faPlayCircle,
|
||||||
faTimes,
|
faTimes
|
||||||
faStop,
|
|
||||||
faSearchPlus,
|
|
||||||
faTrashAlt,
|
|
||||||
faPencilAlt,
|
|
||||||
faAlignRight
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const Attachment = {
|
const Attachment = {
|
||||||
props: [
|
props: [
|
||||||
'attachment',
|
'attachment',
|
||||||
'compact',
|
|
||||||
'description',
|
|
||||||
'hideDescription',
|
|
||||||
'nsfw',
|
'nsfw',
|
||||||
'size',
|
'size',
|
||||||
|
'allowPlay',
|
||||||
'setMedia',
|
'setMedia',
|
||||||
'remove',
|
'naturalSizeLoad'
|
||||||
'shiftUp',
|
|
||||||
'shiftDn',
|
|
||||||
'edit'
|
|
||||||
],
|
],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
localDescription: this.description || this.attachment.description,
|
|
||||||
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
|
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
|
||||||
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
|
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
|
||||||
preloadImage: this.$store.getters.mergedConfig.preloadImage,
|
preloadImage: this.$store.getters.mergedConfig.preloadImage,
|
||||||
loading: false,
|
loading: false,
|
||||||
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
|
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
|
||||||
modalOpen: false,
|
modalOpen: false,
|
||||||
showHidden: false,
|
showHidden: false
|
||||||
flashLoaded: false,
|
|
||||||
showDescription: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Flash,
|
|
||||||
StillImage,
|
StillImage,
|
||||||
VideoAttachment
|
VideoAttachment
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classNames () {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'-loading': this.loading,
|
|
||||||
'-nsfw-placeholder': this.hidden,
|
|
||||||
'-editable': this.edit !== undefined,
|
|
||||||
'-compact': this.compact
|
|
||||||
},
|
|
||||||
'-type-' + this.type,
|
|
||||||
this.size && '-size-' + this.size,
|
|
||||||
`-${this.useContainFit ? 'contain' : 'cover'}-fit`
|
|
||||||
]
|
|
||||||
},
|
|
||||||
usePlaceholder () {
|
usePlaceholder () {
|
||||||
return this.size === 'hide'
|
return this.size === 'hide' || this.type === 'unknown'
|
||||||
},
|
|
||||||
useContainFit () {
|
|
||||||
return this.$store.getters.mergedConfig.useContainFit
|
|
||||||
},
|
},
|
||||||
placeholderName () {
|
placeholderName () {
|
||||||
if (this.attachment.description === '' || !this.attachment.description) {
|
if (this.attachment.description === '' || !this.attachment.description) {
|
||||||
|
@ -108,36 +72,24 @@ const Attachment = {
|
||||||
return this.nsfw && this.hideNsfwLocal && !this.showHidden
|
return this.nsfw && this.hideNsfwLocal && !this.showHidden
|
||||||
},
|
},
|
||||||
isEmpty () {
|
isEmpty () {
|
||||||
return (this.type === 'html' && !this.attachment.oembed)
|
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'
|
||||||
},
|
},
|
||||||
useModal () {
|
useModal () {
|
||||||
let modalTypes = []
|
const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio']
|
||||||
switch (this.size) {
|
: this.mergedConfig.playVideosInModal
|
||||||
case 'hide':
|
? ['image', 'video']
|
||||||
case 'small':
|
: ['image']
|
||||||
modalTypes = ['image', 'video', 'audio', 'flash']
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
modalTypes = this.mergedConfig.playVideosInModal
|
|
||||||
? ['image', 'video', 'flash']
|
|
||||||
: ['image']
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return modalTypes.includes(this.type)
|
return modalTypes.includes(this.type)
|
||||||
},
|
},
|
||||||
videoTag () {
|
|
||||||
return this.useModal ? 'button' : 'span'
|
|
||||||
},
|
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig'])
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
'attachment.description' (newVal) {
|
|
||||||
this.localDescription = newVal
|
|
||||||
},
|
|
||||||
localDescription (newVal) {
|
|
||||||
this.onEdit(newVal)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
linkClicked ({ target }) {
|
linkClicked ({ target }) {
|
||||||
if (target.tagName === 'A') {
|
if (target.tagName === 'A') {
|
||||||
|
@ -146,37 +98,12 @@ const Attachment = {
|
||||||
},
|
},
|
||||||
openModal (event) {
|
openModal (event) {
|
||||||
if (this.useModal) {
|
if (this.useModal) {
|
||||||
this.$emit('setMedia')
|
event.stopPropagation()
|
||||||
this.$store.dispatch('setCurrentMedia', this.attachment)
|
event.preventDefault()
|
||||||
} else if (this.type === 'unknown') {
|
this.setMedia()
|
||||||
window.open(this.attachment.url)
|
this.$store.dispatch('setCurrent', this.attachment)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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) {
|
toggleHidden (event) {
|
||||||
if (
|
if (
|
||||||
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
|
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
|
||||||
|
@ -203,7 +130,7 @@ const Attachment = {
|
||||||
onImageLoad (image) {
|
onImageLoad (image) {
|
||||||
const width = image.naturalWidth
|
const width = image.naturalWidth
|
||||||
const height = image.naturalHeight
|
const height = image.naturalHeight
|
||||||
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
|
this.naturalSizeLoad && this.naturalSizeLoad({ width, height })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,266 +0,0 @@
|
||||||
.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: var(--roundness);
|
|
||||||
border-color: var(--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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-container {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color: inherit;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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: rgb(255 255 255 / 75%);
|
|
||||||
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
|
|
||||||
|
|
||||||
&::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: var(--roundness);
|
|
||||||
text-align: center;
|
|
||||||
width: 2em;
|
|
||||||
height: 2em;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-contain-fit {
|
|
||||||
img,
|
|
||||||
canvas {
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-cover-fit {
|
|
||||||
img,
|
|
||||||
canvas {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.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: 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
flex: 2;
|
|
||||||
margin: 8px;
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-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: var(--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;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-compact {
|
|
||||||
.placeholder-container {
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'Attachment',
|
|
||||||
selector: '.Attachment',
|
|
||||||
validInnerComponents: [
|
|
||||||
'Border',
|
|
||||||
'ButtonUnstyled',
|
|
||||||
'Input'
|
|
||||||
],
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
directives: {
|
|
||||||
roundness: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
parent: { component: 'Attachment' },
|
|
||||||
directives: {
|
|
||||||
background: '#FFFFFF',
|
|
||||||
opacity: 0.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<div
|
||||||
v-if="usePlaceholder"
|
v-if="usePlaceholder"
|
||||||
class="Attachment -placeholder button-unstyled"
|
:class="{ 'fullwidth': fullwidth }"
|
||||||
:class="classNames"
|
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -12,257 +11,312 @@
|
||||||
:href="attachment.url"
|
:href="attachment.url"
|
||||||
:alt="attachment.description"
|
:alt="attachment.description"
|
||||||
:title="attachment.description"
|
:title="attachment.description"
|
||||||
@click.prevent
|
|
||||||
>
|
>
|
||||||
<FAIcon :icon="placeholderIconClass" />
|
<FAIcon :icon="placeholderIconClass" />
|
||||||
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ edit ? '' : placeholderName }}
|
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
|
||||||
</a>
|
</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="input description-field"
|
|
||||||
:placeholder="$t('post_status.media_description')"
|
|
||||||
@keydown.enter.prevent=""
|
|
||||||
>
|
|
||||||
<p v-else>
|
|
||||||
{{ localDescription }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="Attachment"
|
v-show="!isEmpty"
|
||||||
:class="classNames"
|
class="attachment"
|
||||||
|
:class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
|
||||||
>
|
>
|
||||||
<div
|
<a
|
||||||
v-show="!isEmpty"
|
v-if="hidden"
|
||||||
class="attachment-wrapper"
|
class="image-attachment"
|
||||||
|
:href="attachment.url"
|
||||||
|
:alt="attachment.description"
|
||||||
|
:title="attachment.description"
|
||||||
|
@click.prevent.stop="toggleHidden"
|
||||||
>
|
>
|
||||||
<a
|
<img
|
||||||
v-if="hidden"
|
:key="nsfwImage"
|
||||||
class="image-container"
|
class="nsfw"
|
||||||
:href="attachment.url"
|
: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"
|
:alt="attachment.description"
|
||||||
:title="attachment.description"
|
/>
|
||||||
@click.prevent.stop="toggleHidden"
|
</a>
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<a
|
<a
|
||||||
v-if="type === 'image' && (!hidden || preloadImage)"
|
v-if="type === 'video' && !hidden"
|
||||||
class="image-container"
|
class="video-container"
|
||||||
:class="{'-hidden': hidden && preloadImage }"
|
:class="{'small': isSmall}"
|
||||||
:href="attachment.url"
|
:href="allowPlay ? undefined : attachment.url"
|
||||||
target="_blank"
|
@click="openModal"
|
||||||
@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="compact ? '2x' : '5x'"
|
|
||||||
:icon="placeholderIconClass"
|
|
||||||
:title="localDescription"
|
|
||||||
/>
|
|
||||||
<p v-if="!compact">
|
|
||||||
{{ localDescription }}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<component
|
|
||||||
:is="videoTag"
|
|
||||||
v-if="type === 'video' && !hidden"
|
|
||||||
class="video-container"
|
|
||||||
: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
|
<VideoAttachment
|
||||||
v-if="edit"
|
class="video"
|
||||||
v-model="localDescription"
|
:attachment="attachment"
|
||||||
type="text"
|
:controls="allowPlay"
|
||||||
class="input description-field"
|
@play="$emit('play')"
|
||||||
:placeholder="$t('post_status.media_description')"
|
@pause="$emit('pause')"
|
||||||
@keydown.enter.prevent=""
|
/>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="attachment.thumb_url"
|
||||||
|
class="image"
|
||||||
>
|
>
|
||||||
<p v-else>
|
<img :src="attachment.thumb_url">
|
||||||
{{ localDescription }}
|
</div>
|
||||||
</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./attachment.js"></script>
|
<script src="./attachment.js"></script>
|
||||||
|
|
||||||
<style src="./attachment.scss" lang="scss"></style>
|
<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>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { h, resolveComponent } from 'vue'
|
|
||||||
import LoginForm from '../login_form/login_form.vue'
|
import LoginForm from '../login_form/login_form.vue'
|
||||||
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
|
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
|
||||||
import MFATOTPForm from '../mfa_form/totp_form.vue'
|
import MFATOTPForm from '../mfa_form/totp_form.vue'
|
||||||
|
@ -6,8 +5,8 @@ import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
const AuthForm = {
|
const AuthForm = {
|
||||||
name: 'AuthForm',
|
name: 'AuthForm',
|
||||||
render () {
|
render (createElement) {
|
||||||
return h(resolveComponent(this.authForm))
|
return createElement('component', { is: this.authForm })
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
authForm () {
|
authForm () {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
<!-- FIXME THIS NEEDS TO BE REFACTORED TO USE POPOVER -->
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-click-outside="onClickOutside"
|
v-click-outside="onClickOutside"
|
||||||
|
@ -7,12 +6,12 @@
|
||||||
<input
|
<input
|
||||||
v-model="term"
|
v-model="term"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
class="input autosuggest-input"
|
class="autosuggest-input"
|
||||||
@click="onInputClick"
|
@click="onInputClick"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="resultsVisible && filtered.length > 0"
|
v-if="resultsVisible && filtered.length > 0"
|
||||||
class="panel autosuggest-results"
|
class="autosuggest-results"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
v-for="item in filtered"
|
v-for="item in filtered"
|
||||||
|
@ -25,6 +24,8 @@
|
||||||
<script src="./autosuggest.js"></script>
|
<script src="./autosuggest.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.autosuggest {
|
.autosuggest {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -39,15 +40,18 @@
|
||||||
top: 100%;
|
top: 100%;
|
||||||
right: 0;
|
right: 0;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
background-color: var(--bg);
|
background-color: $fallback--bg;
|
||||||
|
background-color: var(--bg, $fallback--bg);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: var(--border);
|
border-color: $fallback--border;
|
||||||
border-radius: var(--roundness);
|
border-color: var(--border, $fallback--border);
|
||||||
|
border-radius: $fallback--inputRadius;
|
||||||
|
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
|
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--panelShadow);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,11 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./avatar_list.js"></script>
|
<script src="./avatar_list.js" ></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.avatars {
|
.avatars {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -34,7 +36,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-small {
|
.avatar-small {
|
||||||
border-radius: var(--roundness);
|
border-radius: $fallback--avatarAltRadius;
|
||||||
|
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'Badge',
|
|
||||||
selector: '.badge',
|
|
||||||
validInnerComponents: [
|
|
||||||
'Text',
|
|
||||||
'Icon'
|
|
||||||
],
|
|
||||||
variants: {
|
|
||||||
notification: '.-notification'
|
|
||||||
},
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
component: 'Root',
|
|
||||||
directives: {
|
|
||||||
'--badgeNotification': 'color | --cRed'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
directives: {
|
|
||||||
background: '--cGreen'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
variant: 'notification',
|
|
||||||
directives: {
|
|
||||||
background: '--cRed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,20 +1,24 @@
|
||||||
import UserPopover from '../user_popover/user_popover.vue'
|
import UserCard from '../user_card/user_card.vue'
|
||||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
import UserLink from '../user_link/user_link.vue'
|
|
||||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
|
||||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
const BasicUserCard = {
|
const BasicUserCard = {
|
||||||
props: [
|
props: [
|
||||||
'user'
|
'user'
|
||||||
],
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
userExpanded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
UserPopover,
|
UserCard,
|
||||||
UserAvatar,
|
UserAvatar
|
||||||
RichContent,
|
|
||||||
UserLink
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleUserExpanded () {
|
||||||
|
this.userExpanded = !this.userExpanded
|
||||||
|
},
|
||||||
userProfileLink (user) {
|
userProfileLink (user) {
|
||||||
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,49 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="basic-user-card">
|
<div class="basic-user-card">
|
||||||
<router-link
|
<router-link :to="userProfileLink(user)">
|
||||||
:to="userProfileLink(user)"
|
<UserAvatar
|
||||||
@click.prevent
|
class="avatar"
|
||||||
>
|
:user="user"
|
||||||
<UserPopover
|
@click.prevent.native="toggleUserExpanded"
|
||||||
:user-id="user.id"
|
/>
|
||||||
:overlay-centers="true"
|
|
||||||
overlay-centers-selector=".avatar"
|
|
||||||
>
|
|
||||||
<UserAvatar
|
|
||||||
class="user-avatar avatar"
|
|
||||||
:user="user"
|
|
||||||
@click.prevent
|
|
||||||
/>
|
|
||||||
</UserPopover>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
<div
|
<div
|
||||||
|
v-if="userExpanded"
|
||||||
|
class="basic-user-card-expanded-content"
|
||||||
|
>
|
||||||
|
<UserCard
|
||||||
|
:user-id="user.id"
|
||||||
|
:rounded="true"
|
||||||
|
:bordered="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
class="basic-user-card-collapsed-content"
|
class="basic-user-card-collapsed-content"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:title="user.name"
|
:title="user.name"
|
||||||
class="basic-user-card-user-name"
|
class="basic-user-card-user-name"
|
||||||
>
|
>
|
||||||
<RichContent
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<span
|
||||||
|
v-if="user.name_html"
|
||||||
class="basic-user-card-user-name-value"
|
class="basic-user-card-user-name-value"
|
||||||
:html="user.name"
|
v-html="user.name_html"
|
||||||
:emoji="user.emoji"
|
|
||||||
/>
|
/>
|
||||||
|
<!-- eslint-enable vue/no-v-html -->
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="basic-user-card-user-name-value"
|
||||||
|
>{{ user.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<user-link
|
<router-link
|
||||||
class="basic-user-card-screen-name"
|
class="basic-user-card-screen-name"
|
||||||
:user="user"
|
:to="userProfileLink(user)"
|
||||||
/>
|
>
|
||||||
|
@{{ user.screen_name_ui }}
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,8 +57,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0.6em 1em;
|
||||||
--emoji-size: 14px;
|
|
||||||
|
|
||||||
&-collapsed-content {
|
&-collapsed-content {
|
||||||
margin-left: 0.7em;
|
margin-left: 0.7em;
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
.block-card-content-container {
|
.block-card-content-container {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const Bookmarks = {
|
||||||
components: {
|
components: {
|
||||||
Timeline
|
Timeline
|
||||||
},
|
},
|
||||||
unmounted () {
|
destroyed () {
|
||||||
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'Border',
|
|
||||||
selector: '/*border*/',
|
|
||||||
virtual: true,
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
directives: {
|
|
||||||
textColor: '$mod(--parent, 10)',
|
|
||||||
textAuto: 'no-auto'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'Button', // Name of the component
|
|
||||||
selector: '.button-default', // CSS selector/prefix
|
|
||||||
// outOfTreeSelector: '' // out-of-tree selector is used when other components are laid over it but it's not part of the tree, see Underlay component
|
|
||||||
// States, system witll calculate ALL possible combinations of those and prepend "normal" to them + standalone "normal" state
|
|
||||||
states: {
|
|
||||||
// States are a bit expensive - the amount of combinations generated is about (1/6)n^3+n, so adding more state increased number of combination by an order of magnitude!
|
|
||||||
// All states inherit from "normal" state, there is no other inheirtance, i.e. hover+disabled only inherits from "normal", not from hover nor disabled.
|
|
||||||
// However, cascading still works, so resulting state will be result of merging of all relevant states/variants
|
|
||||||
// normal: '' // normal state is implicitly added, it is always included
|
|
||||||
toggled: '.toggled',
|
|
||||||
pressed: ':active',
|
|
||||||
hover: ':hover:not(:disabled)',
|
|
||||||
focused: ':focus-within',
|
|
||||||
disabled: ':disabled'
|
|
||||||
},
|
|
||||||
// Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it.
|
|
||||||
variants: {
|
|
||||||
// Variants save on computation time since adding new variant just adds one more "set".
|
|
||||||
// normal: '', // you can override normal variant, it will be appenended to the main class
|
|
||||||
danger: '.danger'
|
|
||||||
// Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants.
|
|
||||||
// This (currently) is further multipled by number of places where component can exist.
|
|
||||||
},
|
|
||||||
// This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever).
|
|
||||||
validInnerComponents: [
|
|
||||||
'Text',
|
|
||||||
'Icon'
|
|
||||||
],
|
|
||||||
// Default rules, used as "default theme", essentially.
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
component: 'Root',
|
|
||||||
directives: {
|
|
||||||
'--defaultButtonHoverGlow': 'shadow | 0 0 4 --text',
|
|
||||||
'--defaultButtonShadow': 'shadow | 0 0 2 #000000',
|
|
||||||
'--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF, top, 0.2) | $borderSide(#000000, bottom, 0.2)',
|
|
||||||
'--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// component: 'Button', // no need to specify components every time unless you're specifying how other component should look
|
|
||||||
// like within it
|
|
||||||
directives: {
|
|
||||||
background: '--fg',
|
|
||||||
shadow: ['--defaultButtonShadow', '--defaultButtonBevel'],
|
|
||||||
roundness: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: ['hover'],
|
|
||||||
directives: {
|
|
||||||
shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: ['pressed'],
|
|
||||||
directives: {
|
|
||||||
shadow: ['--defaultButtonShadow', '--pressedButtonBevel']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: ['hover', 'pressed'],
|
|
||||||
directives: {
|
|
||||||
shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: ['toggled'],
|
|
||||||
directives: {
|
|
||||||
background: '--inheritedBackground,-14.2',
|
|
||||||
shadow: ['--defaultButtonShadow', '--pressedButtonBevel']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: ['toggled', 'hover'],
|
|
||||||
directives: {
|
|
||||||
background: '--inheritedBackground,-14.2',
|
|
||||||
shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
state: ['disabled'],
|
|
||||||
directives: {
|
|
||||||
background: '$blend(--inheritedBackground, 0.25, --parent)',
|
|
||||||
shadow: ['--defaultButtonBevel']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Text',
|
|
||||||
parent: {
|
|
||||||
component: 'Button',
|
|
||||||
state: ['disabled']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textOpacity: 0.25,
|
|
||||||
textOpacityMode: 'blend'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'ButtonUnstyled',
|
|
||||||
selector: '.button-unstyled',
|
|
||||||
states: {
|
|
||||||
toggled: '.toggled',
|
|
||||||
disabled: ':disabled',
|
|
||||||
hover: ':hover:not(:disabled)',
|
|
||||||
focused: ':focus-within'
|
|
||||||
},
|
|
||||||
validInnerComponents: [
|
|
||||||
'Text',
|
|
||||||
'Icon',
|
|
||||||
'Badge'
|
|
||||||
],
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
directives: {
|
|
||||||
background: '#ffffff',
|
|
||||||
opacity: 0,
|
|
||||||
shadow: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Icon',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['hover']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textColor: '--parent--text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Icon',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['toggled']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textColor: '--parent--text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Icon',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['toggled', 'hover']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textColor: '--parent--text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Icon',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['toggled', 'focused']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textColor: '--parent--text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Icon',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['toggled', 'focused', 'hover']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textColor: '--parent--text'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Text',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['disabled']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textOpacity: 0.25,
|
|
||||||
textOpacityMode: 'blend'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'Icon',
|
|
||||||
parent: {
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
state: ['disabled']
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
textOpacity: 0.25,
|
|
||||||
textOpacityMode: 'blend'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue