Merge remote-tracking branch 'origin/develop' into checkbox-goes-brrr
7
.browserslistrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
>0.2%
|
||||||
|
not op_mini all
|
||||||
|
Safari > 15
|
||||||
|
Firefox >= 115
|
||||||
|
Firefox ESR
|
||||||
|
Android > 4
|
||||||
|
not dead
|
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build/webpack.prod.conf.js export-subst
|
1
.gitignore
vendored
|
@ -8,3 +8,4 @@ selenium-debug.log
|
||||||
.idea/
|
.idea/
|
||||||
config/local.json
|
config/local.json
|
||||||
static/emoji.json
|
static/emoji.json
|
||||||
|
logs/
|
||||||
|
|
|
@ -4,11 +4,36 @@
|
||||||
image: node:16
|
image: node:16
|
||||||
|
|
||||||
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:
|
||||||
|
@ -18,6 +43,9 @@ lint:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
|
tags:
|
||||||
|
- amd64
|
||||||
|
- himem
|
||||||
variables:
|
variables:
|
||||||
APT_CACHE_DIR: apt-cache
|
APT_CACHE_DIR: apt-cache
|
||||||
script:
|
script:
|
||||||
|
@ -29,6 +57,9 @@ test:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
|
tags:
|
||||||
|
- amd64
|
||||||
|
- himem
|
||||||
script:
|
script:
|
||||||
- yarn
|
- yarn
|
||||||
- npm run build
|
- npm run build
|
||||||
|
|
127
CHANGELOG.md
|
@ -3,6 +3,133 @@ 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.7.1
|
||||||
|
Bugfix release. Added small optimizations to emoji picker that should make it a bit more responsive, however it needs rather large change to make it more performant which might come in a major release.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Instance default theme not respected
|
||||||
|
- Nested panel header having wrong sticky position if navbar height != panel header height
|
||||||
|
- Toggled buttons having bad contrast (when using v2 theme)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Simplify the OAuth client_name to 'PleromaFE'
|
||||||
|
- Small optimizations to emoji picker
|
||||||
|
|
||||||
|
|
||||||
|
## 2.7.0
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
We got some reports related to emoji picker performance, this hopefully will be fixed in 2.7.1.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
This release overhauls how themes work, themes now need to be "compiled", which can cause some delay when loading for the first time and temporarily look "wrong" in some places (popups, menus, dialogs). Please do report any issues, especially if your theme looks wrong or breaks interface when loading. Also report issues if you're experiencing constant performance issues.
|
||||||
|
|
||||||
|
To admins: remember that you can update PleromaFE to recent `master` or `develop` in admin dashboard in "Front-ends" tab, scroll down to find PleromaFE box and click "Reinstall `master`" or dropdown and then "Reinstall `develop`". Currently there is no mechanism to check if there is an update or not.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system aka "Themes 3".
|
||||||
|
- 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.
|
||||||
|
- 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.
|
||||||
|
- Reorganized Settings modal to move out visual stuff into Appearance tab
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Emoji pack management to the admin panel
|
||||||
|
- Support `status` notification type (subscriptions/bell, fixes PleromaFE on newer PleromaBE versions)
|
||||||
|
- Poll end notifications.
|
||||||
|
- Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
|
||||||
|
- Option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
|
||||||
|
- 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.
|
||||||
|
- 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)
|
||||||
|
- Ability to resize UI (and certain components) scale independent of browser/text scale
|
||||||
|
- Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
|
||||||
|
- Theme selector with visual previews of the theme
|
||||||
|
- Display loading and error indicator for conversation page
|
||||||
|
- Option to only show scrobbles that are recent enough
|
||||||
|
- 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.
|
||||||
|
- Support group actors
|
||||||
|
- Focusing into a tab clears all current desktop notifications
|
||||||
|
- Ability to change size of emoji
|
||||||
|
- Ability to view APNG (Animated PNG) attachments.
|
||||||
|
- Support showing extra notifications in the notifications column
|
||||||
|
- Create a link to the URL of the scrobble when it's present
|
||||||
|
- Allow hiding custom emojis in picker.
|
||||||
|
- Ability to mute sensitive posts (ported from eintei).
|
||||||
|
- Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
|
||||||
|
- Display public favorites on user profiles
|
||||||
|
- Display quotes count on posts and add quotes list page
|
||||||
|
- Show a dedicated registration notice page when further action is required after registering
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
|
||||||
|
- Error that appeared on mobile Chromium (and derivatives) when native notifications are allowed
|
||||||
|
- Being unable to set notification visibility for reports and follow requests
|
||||||
|
- Native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
|
||||||
|
- The expiry date indication won't be shown if the poll never expires
|
||||||
|
- Profile mentions causing a 422 error on newer PleromaBE versions.
|
||||||
|
- Color inputs are less ugly now
|
||||||
|
- Unread notifications should now properly catch up between sessions (eventually) in polling mode
|
||||||
|
- Video posters on Safari
|
||||||
|
|
||||||
|
|
||||||
|
## 2.6.1
|
||||||
|
### 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
|
## 2.5.0 - 23.12.2022
|
||||||
### Fixed
|
### Fixed
|
||||||
- UI no longer lags when switching between mobile and desktop mode
|
- UI no longer lags when switching between mobile and desktop mode
|
||||||
|
|
|
@ -11,9 +11,16 @@ var env = process.env.NODE_ENV === 'testing'
|
||||||
? require('../config/test.env')
|
? require('../config/test.env')
|
||||||
: config.build.env
|
: config.build.env
|
||||||
|
|
||||||
let commitHash = require('child_process')
|
let commitHash = (() => {
|
||||||
|
const subst = "$Format:%h$";
|
||||||
|
if(!subst.match(/Format:/)) {
|
||||||
|
return subst;
|
||||||
|
} else {
|
||||||
|
return require('child_process')
|
||||||
.execSync('git rev-parse --short HEAD')
|
.execSync('git rev-parse --short HEAD')
|
||||||
.toString();
|
.toString();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
var webpackConfig = merge(baseWebpackConfig, {
|
var webpackConfig = merge(baseWebpackConfig, {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
|
|
1
changelog.d/better-shadow-control.fix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name.
|
1
changelog.d/bookmark-folders.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Support bookmark folders
|
9
changelog.d/browsers-support.change
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Updated our build system to support browsers:
|
||||||
|
Safari >= 15
|
||||||
|
Firefox >= 115
|
||||||
|
Android > 4
|
||||||
|
no Opera Mini support
|
||||||
|
no IE support
|
||||||
|
no "dead" (unmaintained) browsers support
|
||||||
|
|
||||||
|
This does not guarantee that browsers will or will not work.
|
1
changelog.d/date-absolute.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Support displaying time in absolute format
|
1
changelog.d/multiple-status-mute-reasons.fix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix whitespaces for multiple status mute reasons, display bot status reason
|
1
changelog.d/non-anonymous-polls.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Inform users that Smithereen public polls are public
|
1
changelog.d/oauth-app-name.change
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Simplify the OAuth client_name to 'PleromaFE'
|
0
changelog.d/piss-fix.skip
Normal file
0
changelog.d/piss-serialization.skip
Normal file
1
changelog.d/splashscreen.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Splash screen + loading indicator to make process of identifying initialization issues and load performance
|
133
index.html
|
@ -3,12 +3,139 @@
|
||||||
<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">
|
||||||
<!--server-generated-meta-->
|
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
|
<!-- putting styles here to avoid having to wait for styles to load up -->
|
||||||
|
<style id="splashscreen">
|
||||||
|
#splash {
|
||||||
|
--scale: 1;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #0f161e;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #b9b9ba;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9999;
|
||||||
|
font-size: calc(1vw + 1vh + 1vmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-credit {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 14px;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-container {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mascot-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
perspective: 60em;
|
||||||
|
perspective-origin: 0 -15em;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mascot {
|
||||||
|
width: calc(10em * var(--scale));
|
||||||
|
height: calc(10em * var(--scale));
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: bottom;
|
||||||
|
transform: translateZ(-2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#throbber {
|
||||||
|
display: grid;
|
||||||
|
width: calc(5em * 0.5 * var(--scale));
|
||||||
|
height: calc(8em * 0.5 * var(--scale));
|
||||||
|
margin-left: 4.1em;
|
||||||
|
z-index: 2;
|
||||||
|
grid-template-rows: repeat(8, 1fr);
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-template-areas: "P P . L L"
|
||||||
|
"P P . L L"
|
||||||
|
"P P . L L"
|
||||||
|
"P P . L L"
|
||||||
|
"P P . . ."
|
||||||
|
"P P . . ."
|
||||||
|
"P P . E E"
|
||||||
|
"P P . E E";
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunk {
|
||||||
|
background-color: #e2b188;
|
||||||
|
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chunk-P {
|
||||||
|
grid-area: P;
|
||||||
|
border-top-left-radius: calc(var(--logoChunkSize) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chunk-L {
|
||||||
|
grid-area: L;
|
||||||
|
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chunk-E {
|
||||||
|
grid-area: E;
|
||||||
|
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
margin-top: 1em;
|
||||||
|
line-height: 2;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
#throbber {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style id="pleroma-eager-styles" type="text/css"></style>
|
||||||
|
<style id="pleroma-lazy-styles" type="text/css"></style>
|
||||||
|
<!--server-generated-meta-->
|
||||||
</head>
|
</head>
|
||||||
<body class="hidden">
|
<body style="margin: 0; padding: 0">
|
||||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||||
<div id="app"></div>
|
<div id="splash">
|
||||||
|
<!-- we are hiding entire graphic so no point showing credit -->
|
||||||
|
<div aria-hidden="true" id="splash-credit">
|
||||||
|
Art by pipivovott
|
||||||
|
</div>
|
||||||
|
<div id="splash-container">
|
||||||
|
<div aria-hidden="true" id="mascot-container">
|
||||||
|
<div id="throbber">
|
||||||
|
<div class="chunk" id="chunk-P">
|
||||||
|
</div>
|
||||||
|
<div class="chunk" id="chunk-L">
|
||||||
|
</div>
|
||||||
|
<div class="chunk" id="chunk-E">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img id="mascot" src="/static/pleromatan_apology.png">
|
||||||
|
</div>
|
||||||
|
<div id="status" class="css-ok">
|
||||||
|
<!-- (。>﹏<) -->
|
||||||
|
<!-- it's a pseudographic, don't want screenreader read out nonsense -->
|
||||||
|
<span aria-hidden="true" class="initial-text">(。>﹏<)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="app" class="hidden"></div>
|
||||||
<div id="modal"></div>
|
<div id="modal"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
<div id="popovers" />
|
<div id="popovers" />
|
||||||
|
|
74
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "pleroma_fe",
|
"name": "pleroma_fe",
|
||||||
"version": "2.5.0",
|
"version": "2.7.1",
|
||||||
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
||||||
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
||||||
"private": false,
|
"private": false,
|
||||||
|
@ -16,28 +16,29 @@
|
||||||
"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.20.7",
|
"@babel/runtime": "7.21.5",
|
||||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.2",
|
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
||||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12",
|
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.8.21",
|
||||||
"@vuelidate/core": "2.0.0",
|
"@vuelidate/core": "2.0.3",
|
||||||
"@vuelidate/validators": "2.0.0",
|
"@vuelidate/validators": "2.0.4",
|
||||||
"body-scroll-lock": "3.1.5",
|
"body-scroll-lock": "3.1.5",
|
||||||
"chromatism": "3.0.0",
|
"chromatism": "3.0.0",
|
||||||
"click-outside-vue3": "4.0.1",
|
"click-outside-vue3": "4.0.1",
|
||||||
"cropperjs": "1.5.12",
|
"cropperjs": "1.5.13",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"js-cookie": "3.0.1",
|
"hash-sum": "^2.0.0",
|
||||||
|
"js-cookie": "3.0.5",
|
||||||
"localforage": "1.10.0",
|
"localforage": "1.10.0",
|
||||||
"parse-link-header": "2.0.0",
|
"parse-link-header": "2.0.0",
|
||||||
"phoenix": "1.6.2",
|
"phoenix": "1.7.7",
|
||||||
"punycode.js": "2.3.0",
|
"punycode.js": "2.3.0",
|
||||||
"qrcode": "1.5.0",
|
"qrcode": "1.5.3",
|
||||||
"querystring-es3": "0.2.1",
|
"querystring-es3": "0.2.1",
|
||||||
"url": "0.11.0",
|
"url": "0.11.0",
|
||||||
"utf8": "3.0.0",
|
"utf8": "3.0.0",
|
||||||
|
@ -49,19 +50,19 @@
|
||||||
"vuex": "4.1.0"
|
"vuex": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.20.7",
|
"@babel/core": "7.21.8",
|
||||||
"@babel/eslint-parser": "7.19.1",
|
"@babel/eslint-parser": "7.21.8",
|
||||||
"@babel/plugin-transform-runtime": "7.19.6",
|
"@babel/plugin-transform-runtime": "7.21.4",
|
||||||
"@babel/preset-env": "7.20.2",
|
"@babel/preset-env": "7.21.5",
|
||||||
"@babel/register": "7.18.9",
|
"@babel/register": "7.21.0",
|
||||||
"@intlify/vue-i18n-loader": "5.0.0",
|
"@intlify/vue-i18n-loader": "5.0.1",
|
||||||
"@ungap/event-target": "0.2.3",
|
"@ungap/event-target": "0.2.4",
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||||
"@vue/babel-plugin-jsx": "1.1.1",
|
"@vue/babel-plugin-jsx": "1.2.2",
|
||||||
"@vue/compiler-sfc": "3.2.45",
|
"@vue/compiler-sfc": "3.2.45",
|
||||||
"@vue/test-utils": "2.2.7",
|
"@vue/test-utils": "2.2.8",
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.19",
|
||||||
"babel-loader": "9.1.2",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-lodash": "3.3.4",
|
"babel-plugin-lodash": "3.3.4",
|
||||||
"chai": "4.3.7",
|
"chai": "4.3.7",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
|
@ -69,10 +70,10 @@
|
||||||
"connect-history-api-fallback": "2.0.0",
|
"connect-history-api-fallback": "2.0.0",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
"copy-webpack-plugin": "11.0.0",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
"css-loader": "6.7.3",
|
"css-loader": "6.10.0",
|
||||||
"css-minimizer-webpack-plugin": "4.2.2",
|
"css-minimizer-webpack-plugin": "4.2.2",
|
||||||
"custom-event-polyfill": "1.0.7",
|
"custom-event-polyfill": "1.0.7",
|
||||||
"eslint": "8.32.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-standard": "17.0.0",
|
"eslint-config-standard": "17.0.0",
|
||||||
"eslint-formatter-friendly": "7.0.0",
|
"eslint-formatter-friendly": "7.0.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
|
@ -83,13 +84,13 @@
|
||||||
"eventsource-polyfill": "0.9.6",
|
"eventsource-polyfill": "0.9.6",
|
||||||
"express": "4.18.2",
|
"express": "4.18.2",
|
||||||
"function-bind": "1.1.1",
|
"function-bind": "1.1.1",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.1",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.6",
|
||||||
"iso-639-1": "2.1.15",
|
"iso-639-1": "2.1.15",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"karma": "6.4.1",
|
"karma": "6.4.4",
|
||||||
"karma-coverage": "2.2.0",
|
"karma-coverage": "2.2.0",
|
||||||
"karma-firefox-launcher": "2.1.2",
|
"karma-firefox-launcher": "2.1.3",
|
||||||
"karma-mocha": "2.0.1",
|
"karma-mocha": "2.0.1",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"karma-sinon-chai": "2.0.2",
|
"karma-sinon-chai": "2.0.2",
|
||||||
|
@ -97,22 +98,22 @@
|
||||||
"karma-spec-reporter": "0.0.36",
|
"karma-spec-reporter": "0.0.36",
|
||||||
"karma-webpack": "5.0.0",
|
"karma-webpack": "5.0.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mini-css-extract-plugin": "2.7.2",
|
"mini-css-extract-plugin": "2.7.6",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"nightwatch": "2.6.10",
|
"nightwatch": "2.6.25",
|
||||||
"opn": "5.5.0",
|
"opn": "5.5.0",
|
||||||
"ora": "0.4.1",
|
"ora": "0.4.1",
|
||||||
"postcss": "8.4.20",
|
"postcss": "8.4.23",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"postcss-loader": "7.0.2",
|
"postcss-loader": "7.0.2",
|
||||||
"postcss-scss": "^4.0.6",
|
"postcss-scss": "^4.0.6",
|
||||||
"sass": "1.57.1",
|
"sass": "1.60.0",
|
||||||
"sass-loader": "13.2.0",
|
"sass-loader": "13.2.2",
|
||||||
"selenium-server": "2.53.1",
|
"selenium-server": "2.53.1",
|
||||||
"semver": "7.3.8",
|
"semver": "7.3.8",
|
||||||
"serviceworker-webpack5-plugin": "2.0.0",
|
"serviceworker-webpack5-plugin": "2.0.0",
|
||||||
"shelljs": "0.8.5",
|
"shelljs": "0.8.5",
|
||||||
"sinon": "15.0.1",
|
"sinon": "15.0.4",
|
||||||
"sinon-chai": "3.7.0",
|
"sinon-chai": "3.7.0",
|
||||||
"stylelint": "14.16.1",
|
"stylelint": "14.16.1",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
|
@ -131,5 +132,6 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16.0.0",
|
"node": ">= 16.0.0",
|
||||||
"npm": ">= 3.0.0"
|
"npm": ">= 3.0.0"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|
0
preview.style.js
Normal file
22
src/App.js
|
@ -44,16 +44,29 @@ export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
mobileActivePanel: 'timeline'
|
mobileActivePanel: 'timeline'
|
||||||
}),
|
}),
|
||||||
|
watch: {
|
||||||
|
themeApplied (value) {
|
||||||
|
this.removeSplash()
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
// Load the locale from the storage
|
// Load the locale from the storage
|
||||||
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
||||||
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)
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.$store.state.interface.themeApplied) {
|
||||||
|
this.removeSplash()
|
||||||
|
}
|
||||||
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
window.removeEventListener('resize', this.updateMobileState)
|
window.removeEventListener('resize', this.updateMobileState)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
themeApplied () {
|
||||||
|
return this.$store.state.interface.themeApplied
|
||||||
|
},
|
||||||
classes () {
|
classes () {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -130,6 +143,15 @@ export default {
|
||||||
updateMobileState () {
|
updateMobileState () {
|
||||||
this.$store.dispatch('setLayoutWidth', windowWidth())
|
this.$store.dispatch('setLayoutWidth', windowWidth())
|
||||||
this.$store.dispatch('setLayoutHeight', windowHeight())
|
this.$store.dispatch('setLayoutHeight', windowHeight())
|
||||||
|
},
|
||||||
|
removeSplash () {
|
||||||
|
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
|
||||||
|
const splashscreenRoot = document.querySelector('#splash')
|
||||||
|
splashscreenRoot.addEventListener('transitionend', () => {
|
||||||
|
splashscreenRoot.remove()
|
||||||
|
})
|
||||||
|
splashscreenRoot.classList.add('hidden')
|
||||||
|
document.querySelector('#app').classList.remove('hidden')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
655
src/App.scss
|
@ -1,10 +1,9 @@
|
||||||
// stylelint-disable rscss/class-format
|
// stylelint-disable rscss/class-format
|
||||||
/* stylelint-disable no-descending-specificity */
|
/* stylelint-disable no-descending-specificity */
|
||||||
@import "./variables";
|
|
||||||
@import "./panel";
|
@import "./panel";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--navbar-height: 3.5rem;
|
--status-margin: 0.75em;
|
||||||
--post-line-height: 1.4;
|
--post-line-height: 1.4;
|
||||||
// Z-Index stuff
|
// Z-Index stuff
|
||||||
--ZI_media_modal: 9000;
|
--ZI_media_modal: 9000;
|
||||||
|
@ -13,19 +12,25 @@
|
||||||
--ZI_navbar_popovers: 7500;
|
--ZI_navbar_popovers: 7500;
|
||||||
--ZI_navbar: 7000;
|
--ZI_navbar: 7000;
|
||||||
--ZI_popovers: 6000;
|
--ZI_popovers: 6000;
|
||||||
|
|
||||||
|
// Fallback for when stuff is loading
|
||||||
|
--background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: 14px;
|
font-size: var(--textSize, 14px);
|
||||||
|
|
||||||
|
--navbar-height: var(--navbarSize, 3.5rem);
|
||||||
|
--emoji-size: var(--emojiSize, 32px);
|
||||||
|
--panel-header-height: var(--panelHeaderSize, 3.2rem);
|
||||||
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
|
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-family: var(--interfaceFont, sans-serif);
|
font-family: var(--font);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
|
@ -42,17 +47,35 @@ body {
|
||||||
// have a cursor/pointer to operate them
|
// have a cursor/pointer to operate them
|
||||||
@media (any-pointer: fine) {
|
@media (any-pointer: fine) {
|
||||||
* {
|
* {
|
||||||
scrollbar-color: var(--btn) transparent;
|
scrollbar-color: var(--fg) transparent;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-resizer {
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(
|
||||||
|
135deg,
|
||||||
|
transparent calc(50% - 1px),
|
||||||
|
var(--textFaint) 50%,
|
||||||
|
transparent calc(50% + 1px),
|
||||||
|
transparent calc(75% - 1px),
|
||||||
|
var(--textFaint) 75%,
|
||||||
|
transparent calc(75% + 1px),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-button,
|
&::-webkit-scrollbar-button,
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background-color: var(--btn);
|
box-shadow: var(--shadow);
|
||||||
box-shadow: var(--buttonShadow);
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--btnRadius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// horizontal/vertical/increment/decrement are webkit-specific stuff
|
// horizontal/vertical/increment/decrement are webkit-specific stuff
|
||||||
|
@ -61,7 +84,7 @@ body {
|
||||||
&::-webkit-scrollbar-button {
|
&::-webkit-scrollbar-button {
|
||||||
--___bgPadding: 2px;
|
--___bgPadding: 2px;
|
||||||
|
|
||||||
color: var(--btnText);
|
color: var(--text);
|
||||||
background-repeat: no-repeat, no-repeat;
|
background-repeat: no-repeat, no-repeat;
|
||||||
|
|
||||||
&:horizontal {
|
&:horizontal {
|
||||||
|
@ -69,15 +92,15 @@ body {
|
||||||
|
|
||||||
&:increment {
|
&:increment {
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(45deg, var(--btnText) 50%, transparent 51%),
|
linear-gradient(45deg, var(--text) 50%, transparent 51%),
|
||||||
linear-gradient(-45deg, transparent 50%, var(--btnText) 51%);
|
linear-gradient(-45deg, transparent 50%, var(--text) 51%);
|
||||||
background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding);
|
background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:decrement {
|
&:decrement {
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(45deg, transparent 50%, var(--btnText) 51%),
|
linear-gradient(45deg, transparent 50%, var(--text) calc(50% + 1px)),
|
||||||
linear-gradient(-45deg, var(--btnText) 50%, transparent 51%);
|
linear-gradient(-45deg, var(--text) 50%, transparent 51%);
|
||||||
background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
|
background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,15 +110,15 @@ body {
|
||||||
|
|
||||||
&:increment {
|
&:increment {
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(-45deg, transparent 50%, var(--btnText) 51%),
|
linear-gradient(-45deg, transparent 50%, var(--text) 51%),
|
||||||
linear-gradient(45deg, transparent 50%, var(--btnText) 51%);
|
linear-gradient(45deg, transparent 50%, var(--text) 51%);
|
||||||
background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%;
|
background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:decrement {
|
&:decrement {
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(-45deg, var(--btnText) 50%, transparent 51%),
|
linear-gradient(-45deg, var(--text) 50%, transparent 51%),
|
||||||
linear-gradient(45deg, var(--btnText) 50%, transparent 51%);
|
linear-gradient(45deg, var(--text) 50%, transparent 51%);
|
||||||
background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
|
background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,15 +127,14 @@ body {
|
||||||
}
|
}
|
||||||
// Body should have background to scrollbar otherwise it will use white (body color?)
|
// Body should have background to scrollbar otherwise it will use white (body color?)
|
||||||
html {
|
html {
|
||||||
scrollbar-color: var(--selectedMenu) var(--wallpaper);
|
scrollbar-color: var(--fg) var(--wallpaper);
|
||||||
background: var(--wallpaper);
|
background: var(--wallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $fallback--link;
|
color: var(--link);
|
||||||
color: var(--link, $fallback--link);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
@ -128,29 +150,15 @@ h4 {
|
||||||
i[class*="icon-"],
|
i[class*="icon-"],
|
||||||
.svg-inline--fa,
|
.svg-inline--fa,
|
||||||
.iconLetter {
|
.iconLetter {
|
||||||
color: $fallback--icon;
|
color: var(--icon);
|
||||||
color: var(--icon, $fallback--icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-unstyled:hover,
|
|
||||||
a:hover {
|
|
||||||
> i[class*="icon-"],
|
|
||||||
> .svg-inline--fa,
|
|
||||||
> .iconLetter {
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
z-index: var(--ZI_navbar);
|
z-index: var(--ZI_navbar);
|
||||||
background-color: $fallback--fg;
|
box-shadow: var(--shadow);
|
||||||
background-color: var(--topBar, $fallback--fg);
|
|
||||||
color: $fallback--faint;
|
|
||||||
color: var(--faint, $fallback--faint);
|
|
||||||
box-shadow: 0 0 4px rgb(0 0 0 / 60%);
|
|
||||||
box-shadow: var(--topBarShadow);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: var(--navbar-height);
|
height: var(--navbar-height);
|
||||||
|
font-size: calc(var(--navbar-height) / 3.5);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,16 +203,14 @@ nav {
|
||||||
grid-column: 1 / span 3;
|
grid-column: 1 / span 3;
|
||||||
grid-row: 1 / 1;
|
grid-row: 1 / 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: rgb(0 0 0 / 15%);
|
background-color: var(--underlay);
|
||||||
background-color: var(--underlay, rgb(0 0 0 / 15%));
|
|
||||||
z-index: -1000;
|
z-index: -1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-layout {
|
.app-layout {
|
||||||
--miniColumn: 25rem;
|
--miniColumn: 25rem;
|
||||||
--maxiColumn: 45rem;
|
--maxiColumn: 45rem;
|
||||||
--columnGap: 1em;
|
--columnGap: 1rem;
|
||||||
--status-margin: 0.75em;
|
|
||||||
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
|
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
|
||||||
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
|
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
|
||||||
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
|
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
|
||||||
|
@ -366,106 +372,112 @@ nav {
|
||||||
|
|
||||||
.button-default {
|
.button-default {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--btnText, $fallback--text);
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--btn, $fallback--fg);
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $fallback--btnRadius;
|
|
||||||
border-radius: var(--btnRadius, $fallback--btnRadius);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: $fallback--buttonShadow;
|
background-color: var(--background);
|
||||||
box-shadow: var(--buttonShadow);
|
box-shadow: var(--shadow);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-family: var(--interfaceFont, sans-serif);
|
font-family: var(--font);
|
||||||
|
|
||||||
&.-sublime {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
i[class*="icon-"],
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnText, $fallback--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
&::-moz-focus-inner {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 0 4px rgb(255 255 255 / 30%);
|
|
||||||
box-shadow: var(--buttonHoverShadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
box-shadow:
|
|
||||||
0 0 4px 0 rgb(255 255 255 / 30%),
|
|
||||||
0 1px 0 0 rgb(0 0 0 / 20%) inset,
|
|
||||||
0 -1px 0 0 rgb(255 255 255 / 20%) inset;
|
|
||||||
box-shadow: var(--buttonPressedShadow);
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnPressedText, $fallback--text);
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--btnPressed, $fallback--fg);
|
|
||||||
|
|
||||||
svg,
|
|
||||||
i {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnPressedText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnDisabledText, $fallback--text);
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--btnDisabled, $fallback--fg);
|
|
||||||
|
|
||||||
svg,
|
|
||||||
i {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnDisabledText, $fallback--text);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item,
|
||||||
|
.list-item {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
text-align: initial;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
clear: both;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-color: var(--border);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
border-top-width: 1px;
|
||||||
|
width: 100%;
|
||||||
|
line-height: var(--__line-height);
|
||||||
|
padding: var(--__vertical-gap) var(--__horizontal-gap);
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
--__line-height: 1.5em;
|
||||||
|
--__horizontal-gap: 0.75em;
|
||||||
|
--__vertical-gap: 0.5em;
|
||||||
|
|
||||||
|
&.-non-interactive {
|
||||||
|
cursor: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.toggled {
|
&.-active,
|
||||||
color: $fallback--text;
|
&:hover {
|
||||||
color: var(--btnToggledText, $fallback--text);
|
border-top-width: 1px;
|
||||||
background-color: $fallback--fg;
|
border-bottom-width: 1px;
|
||||||
background-color: var(--btnToggled, $fallback--fg);
|
|
||||||
box-shadow:
|
|
||||||
0 0 4px 0 rgb(255 255 255 / 30%),
|
|
||||||
0 1px 0 0 rgb(0 0 0 / 20%) inset,
|
|
||||||
0 -1px 0 0 rgb(255 255 255 / 20%) inset;
|
|
||||||
box-shadow: var(--buttonPressedShadow);
|
|
||||||
|
|
||||||
svg,
|
|
||||||
i {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnToggledText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.danger {
|
&.-active + &,
|
||||||
// TODO: add better color variable
|
&:hover + & {
|
||||||
color: $fallback--text;
|
border-top-width: 0;
|
||||||
color: var(--alertErrorPanelText, $fallback--text);
|
}
|
||||||
background-color: $fallback--alertError;
|
|
||||||
background-color: var(--alertError, $fallback--alertError);
|
&:hover + .menu-item-collapsible:not(.-expanded) + &,
|
||||||
|
&.-active + .menu-item-collapsible:not(.-expanded) + & {
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[aria-expanded="true"] {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
button:not(.button-default) {
|
||||||
|
text-align: initial;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: inline;
|
||||||
|
font-size: 100%;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: unset;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-right-radius: var(--roundness);
|
||||||
|
border-top-left-radius: var(--roundness);
|
||||||
|
border-top-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-right-radius: var(--roundness);
|
||||||
|
border-bottom-left-radius: var(--roundness);
|
||||||
|
border-bottom-width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-unstyled {
|
.button-unstyled {
|
||||||
background: none;
|
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
display: inline;
|
display: inline;
|
||||||
text-align: initial;
|
text-align: initial;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
background-color: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: unset;
|
line-height: unset;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -473,28 +485,23 @@ nav {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
||||||
&.-link {
|
&.-link {
|
||||||
color: $fallback--link;
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
color: var(--link, $fallback--link);
|
color: var(--link) !important;
|
||||||
}
|
|
||||||
|
|
||||||
&.-fullwidth {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-hover-highlight {
|
|
||||||
&:hover svg {
|
|
||||||
color: $fallback--lightText;
|
|
||||||
color: var(--lightText, $fallback--lightText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea {
|
||||||
|
border: none;
|
||||||
|
display: inline-block;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
&.unstyled {
|
&.unstyled {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: none;
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
background: none !important;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
|
@ -502,19 +509,10 @@ textarea,
|
||||||
--_padding: 0.5em;
|
--_padding: 0.5em;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $fallback--inputRadius;
|
background-color: var(--background);
|
||||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
color: var(--text);
|
||||||
box-shadow:
|
box-shadow: var(--shadow);
|
||||||
0 1px 0 0 rgb(0 0 0 / 20%) inset,
|
font-family: var(--font);
|
||||||
0 -1px 0 0 rgb(255 255 255 / 20%) inset,
|
|
||||||
0 0 2px 0 rgb(0 0 0 / 100%) inset;
|
|
||||||
box-shadow: var(--inputShadow);
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--input, $fallback--fg);
|
|
||||||
color: $fallback--lightText;
|
|
||||||
color: var(--inputText, $fallback--lightText);
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-family: var(--inputFont, sans-serif);
|
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -528,7 +526,6 @@ textarea,
|
||||||
&[disabled="disabled"],
|
&[disabled="disabled"],
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type="range"] {
|
&[type="range"] {
|
||||||
|
@ -543,9 +540,9 @@ textarea,
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
&:checked + label::before {
|
&:checked + label::before {
|
||||||
box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset;
|
box-shadow: var(--shadow);
|
||||||
box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset;
|
background-color: var(--background);
|
||||||
background-color: var(--accent, $fallback--link);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
|
@ -559,16 +556,14 @@ textarea,
|
||||||
+ label::before {
|
+ label::before {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
content: "";
|
content: "•";
|
||||||
transition: box-shadow 200ms;
|
transition: box-shadow 200ms;
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
border-radius: 100%; // Radio buttons should always be circle
|
border-radius: 100%; // Radio buttons should always be circle
|
||||||
box-shadow: 0 0 2px black inset;
|
background-color: var(--background);
|
||||||
box-shadow: var(--inputShadow);
|
box-shadow: var(--shadow);
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--input, $fallback--fg);
|
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
|
@ -580,11 +575,10 @@ textarea,
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type="checkbox"] {
|
&[type="checkbox"] {
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:checked + label::before {
|
&:checked + label::before {
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--inputText, $fallback--text);
|
background-color: var(--background);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
|
@ -602,13 +596,9 @@ textarea,
|
||||||
transition: color 200ms;
|
transition: color 200ms;
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
height: 1.1em;
|
height: 1.1em;
|
||||||
border-radius: $fallback--checkboxRadius;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
|
box-shadow: var(--shadow);
|
||||||
box-shadow: 0 0 2px black inset;
|
|
||||||
box-shadow: var(--inputShadow);
|
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--input, $fallback--fg);
|
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
|
@ -624,17 +614,26 @@ textarea,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.button-default {
|
||||||
|
--_roundness-left: var(--roundness);
|
||||||
|
--_roundness-right: var(--roundness);
|
||||||
|
|
||||||
|
border-top-left-radius: var(--_roundness-left);
|
||||||
|
border-bottom-left-radius: var(--_roundness-left);
|
||||||
|
border-top-right-radius: var(--_roundness-right);
|
||||||
|
border-bottom-right-radius: var(--_roundness-right);
|
||||||
|
}
|
||||||
|
|
||||||
// Textareas should have stock line-height + vertical padding instead of huge line-height
|
// Textareas should have stock line-height + vertical padding instead of huge line-height
|
||||||
textarea {
|
textarea.input {
|
||||||
padding: var(--_padding);
|
padding: var(--_padding);
|
||||||
line-height: var(--post-line-height);
|
line-height: var(--post-line-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
option {
|
option {
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--text, $fallback--text);
|
background-color: var(--background);
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-number-spinner {
|
.hide-number-spinner {
|
||||||
|
@ -647,6 +646,20 @@ option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cards-list {
|
||||||
|
list-style: none;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: row dense;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
|
li {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn-block {
|
.btn-block {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -657,19 +670,23 @@ option {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
button {
|
> *,
|
||||||
|
> * .button-default {
|
||||||
|
--_roundness-left: 0;
|
||||||
|
--_roundness-right: 0;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:first-child) {
|
> *:first-child,
|
||||||
border-top-left-radius: 0;
|
> *:first-child .button-default {
|
||||||
border-bottom-left-radius: 0;
|
--_roundness-left: var(--roundness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> *:last-child,
|
||||||
|
> *:last-child .button-default {
|
||||||
|
--_roundness-right: var(--roundness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,74 +716,58 @@ option {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&.badge-notification {
|
&.-dot,
|
||||||
background-color: $fallback--cRed;
|
&.-counter {
|
||||||
background-color: var(--badgeNotification, $fallback--cRed);
|
margin: 0;
|
||||||
color: white;
|
position: absolute;
|
||||||
color: var(--badgeNotificationText, white);
|
}
|
||||||
|
|
||||||
|
&.-dot {
|
||||||
|
min-height: 8px;
|
||||||
|
max-height: 8px;
|
||||||
|
min-width: 8px;
|
||||||
|
max-width: 8px;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 0;
|
||||||
|
font-size: 0;
|
||||||
|
left: calc(50% - 4px);
|
||||||
|
top: calc(50% - 4px);
|
||||||
|
margin-left: 6px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.-counter {
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: right;
|
||||||
|
padding: 0.2em;
|
||||||
|
min-width: 0;
|
||||||
|
left: calc(50% - 0.5em);
|
||||||
|
top: calc(50% - 0.4em);
|
||||||
|
margin-left: 0.7em;
|
||||||
|
margin-top: -1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
margin: 0 0.35em;
|
margin: 0 0.35em;
|
||||||
padding: 0 0.25em;
|
padding: 0 0.25em;
|
||||||
border-radius: $fallback--tooltipRadius;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
border: 1px solid var(--border);
|
||||||
|
|
||||||
&.error {
|
|
||||||
background-color: $fallback--alertError;
|
|
||||||
background-color: var(--alertError, $fallback--alertError);
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--alertErrorText, $fallback--text);
|
|
||||||
|
|
||||||
.panel-heading & {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--alertErrorPanelText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.warning {
|
|
||||||
background-color: $fallback--alertWarning;
|
|
||||||
background-color: var(--alertWarning, $fallback--alertWarning);
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--alertWarningText, $fallback--text);
|
|
||||||
|
|
||||||
.panel-heading & {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--alertWarningPanelText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.success {
|
|
||||||
background-color: var(--alertSuccess, $fallback--alertWarning);
|
|
||||||
color: var(--alertSuccessText, $fallback--text);
|
|
||||||
|
|
||||||
.panel-heading & {
|
|
||||||
color: var(--alertSuccessPanelText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.faint {
|
.faint {
|
||||||
color: $fallback--faint;
|
--text: var(--textFaint);
|
||||||
color: var(--faint, $fallback--faint);
|
--link: var(--linkFaint);
|
||||||
}
|
|
||||||
|
|
||||||
.faint-link {
|
color: var(--text);
|
||||||
color: $fallback--faint;
|
|
||||||
color: var(--faint, $fallback--faint);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.visibility-notice {
|
.visibility-notice {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border: 1px solid $fallback--faint;
|
border: 1px solid var(--textFaint);
|
||||||
border: 1px solid var(--faint, $fallback--faint);
|
border-radius: var(--roundness);
|
||||||
border-radius: $fallback--inputRadius;
|
|
||||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-dismissible {
|
.notice-dismissible {
|
||||||
|
@ -787,6 +788,10 @@ option {
|
||||||
&.iconLetter {
|
&.iconLetter {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.svg-inline--fa {
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa-old-padding {
|
.fa-old-padding {
|
||||||
|
@ -801,6 +806,11 @@ option {
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeago {
|
||||||
|
--link: var(--text);
|
||||||
|
--linkFaint: var(--textFaint);
|
||||||
|
}
|
||||||
|
|
||||||
.login-hint {
|
.login-hint {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@ -887,3 +897,186 @@ option {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
/* stylelint-enable no-descending-specificity */
|
/* stylelint-enable no-descending-specificity */
|
||||||
|
|
||||||
|
.visible-for-screenreader-only {
|
||||||
|
display: block;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: visible;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::selection {
|
||||||
|
color: var(--selectionText);
|
||||||
|
background-color: var(--selectionBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash {
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 2s;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
&.css-ok {
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: "CSS OK";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.initial-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#throbber {
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-name: bounce;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-direction: normal;
|
||||||
|
transform-origin: bottom center;
|
||||||
|
|
||||||
|
&.dead {
|
||||||
|
animation-name: dead;
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dead {
|
||||||
|
0% {
|
||||||
|
transform: rotateX(0) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
5% {
|
||||||
|
transform: rotateX(0) rotateY(0) rotateZ(1deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
transform: rotateX(0) rotateY(0) rotateZ(-2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
15% {
|
||||||
|
transform: rotateX(0) rotateY(0) rotateZ(3deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
20% {
|
||||||
|
transform: rotateX(0) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: rotateX(0) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: rotateX(10deg) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
35% {
|
||||||
|
transform: rotateX(-10deg) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: rotateX(10deg) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
45% {
|
||||||
|
transform: rotateX(-10deg) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotateX(10deg) rotateY(0) rotateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
|
||||||
|
transition-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); /* easeInQuint */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0% {
|
||||||
|
scale: 1 1;
|
||||||
|
translate: 0 0;
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
scale: 1.2 0.8;
|
||||||
|
translate: 0 0;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
scale: 0.9 1.1;
|
||||||
|
translate: 0 -40%;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
scale: 1.1 0.9;
|
||||||
|
translate: 0 -50%;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
45% {
|
||||||
|
scale: 0.9 1.1;
|
||||||
|
translate: 0 -45%;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
scale: 1.05 0.95;
|
||||||
|
translate: 0 -40%;
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
55% {
|
||||||
|
scale: 0.985 1.025;
|
||||||
|
translate: 0 -35%;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
scale: 1.0125 0.9985;
|
||||||
|
translate: 0 -30%;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
scale: 1.0063 0.9938;
|
||||||
|
translate: 0 -10%;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-in-ou;
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
scale: 1.2 0.8;
|
||||||
|
translate: 0 0;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
scale: 1 1;
|
||||||
|
translate: 0 0;
|
||||||
|
transform: rotateZ(var(--defaultZ));
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
v-show="$store.state.interface.themeApplied"
|
||||||
id="app-loaded"
|
id="app-loaded"
|
||||||
:style="bgStyle"
|
:style="bgStyle"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
$main-color: #f58d2c;
|
|
||||||
$main-background: white;
|
|
||||||
$darkened-background: whitesmoke;
|
|
||||||
|
|
||||||
$fallback--bg: #121a24;
|
|
||||||
$fallback--fg: #182230;
|
|
||||||
$fallback--faint: rgb(185 185 186 / 50%);
|
|
||||||
$fallback--text: #b9b9ba;
|
|
||||||
$fallback--link: #d8a070;
|
|
||||||
$fallback--icon: #666;
|
|
||||||
$fallback--lightBg: rgb(21 30 42);
|
|
||||||
$fallback--lightText: #b9b9ba;
|
|
||||||
$fallback--border: #222;
|
|
||||||
$fallback--cRed: #f00;
|
|
||||||
$fallback--cBlue: #0095ff;
|
|
||||||
$fallback--cGreen: #0fa00f;
|
|
||||||
$fallback--cOrange: orange;
|
|
||||||
|
|
||||||
$fallback--alertError: rgb(211 16 20 / 50%);
|
|
||||||
$fallback--alertWarning: rgb(111 111 20 / 50%);
|
|
||||||
|
|
||||||
$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: 0 0 2px 0 rgb(0 0 0 / 100%),
|
|
||||||
0 1px 0 0 rgb(255 255 255 / 20%) inset,
|
|
||||||
0 -1px 0 0 rgb(0 0 0 / 20%) inset;
|
|
||||||
|
|
||||||
$status-margin: 0.75em;
|
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 35 B |
1
src/assets/pleromatan_apology.png
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../static/pleromatan_apology.png
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 35 B |
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 39 B |
1
src/assets/pleromatan_apology_fox.png
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../static/pleromatan_apology_fox.png
|
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 39 B |
|
@ -13,9 +13,9 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
||||||
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
|
import { windowWidth, windowHeight } 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 { applyConfig } from '../services/style_setter/style_setter.js'
|
||||||
import { applyTheme, applyConfig } 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
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
store.dispatch('setInstanceOption', { name, value: config[name] })
|
store.dispatch('setInstanceOption', { name, value: config[name] })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyInstanceOption('theme')
|
||||||
copyInstanceOption('nsfwCensorImage')
|
copyInstanceOption('nsfwCensorImage')
|
||||||
copyInstanceOption('background')
|
copyInstanceOption('background')
|
||||||
copyInstanceOption('hidePostStats')
|
copyInstanceOption('hidePostStats')
|
||||||
|
@ -159,8 +160,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
copyInstanceOption('showFeaturesPanel')
|
copyInstanceOption('showFeaturesPanel')
|
||||||
copyInstanceOption('hideSitename')
|
copyInstanceOption('hideSitename')
|
||||||
copyInstanceOption('sidebarRight')
|
copyInstanceOption('sidebarRight')
|
||||||
|
|
||||||
return store.dispatch('setTheme', config.theme)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTOS = async ({ store }) => {
|
const getTOS = async ({ store }) => {
|
||||||
|
@ -253,11 +252,15 @@ const getNodeInfo = async ({ store }) => {
|
||||||
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: 'shoutAvailable', 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: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
|
||||||
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: '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) })
|
||||||
|
@ -324,17 +327,10 @@ const setConfig = async ({ store }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkOAuthToken = async ({ store }) => {
|
const checkOAuthToken = async ({ store }) => {
|
||||||
// eslint-disable-next-line no-async-promise-executor
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
if (store.getters.getUserToken()) {
|
if (store.getters.getUserToken()) {
|
||||||
try {
|
return store.dispatch('loginUser', store.getters.getUserToken())
|
||||||
await store.dispatch('loginUser', store.getters.getUserToken())
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
}
|
return Promise.resolve()
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const afterStoreSetup = async ({ store, i18n }) => {
|
const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
@ -342,39 +338,34 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
store.dispatch('setLayoutHeight', windowHeight())
|
store.dispatch('setLayoutHeight', windowHeight())
|
||||||
|
|
||||||
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
|
||||||
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
||||||
|
|
||||||
|
document.querySelector('#status').textContent = i18n.global.t('splash.settings')
|
||||||
await setConfig({ store })
|
await setConfig({ store })
|
||||||
|
document.querySelector('#status').textContent = i18n.global.t('splash.theme')
|
||||||
const { customTheme, customThemeSource } = store.state.config
|
try {
|
||||||
const { theme } = store.state.instance
|
await store.dispatch('setTheme').catch((e) => { console.error('Error setting theme', e) })
|
||||||
const customThemePresent = customThemeSource || customTheme
|
} catch (e) {
|
||||||
|
return Promise.reject(e)
|
||||||
if (customThemePresent) {
|
|
||||||
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
|
|
||||||
applyTheme(customThemeSource)
|
|
||||||
} else {
|
|
||||||
applyTheme(customTheme)
|
|
||||||
}
|
|
||||||
} else if (theme) {
|
|
||||||
// do nothing, it will load asynchronously
|
|
||||||
} else {
|
|
||||||
console.error('Failed to load any theme!')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applyConfig(store.state.config)
|
applyConfig(store.state.config, i18n.global)
|
||||||
|
|
||||||
// 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
|
||||||
|
document.querySelector('#status').textContent = i18n.global.t('splash.instance')
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
checkOAuthToken({ store }),
|
checkOAuthToken({ store }),
|
||||||
getInstancePanel({ store }),
|
getInstancePanel({ store }),
|
||||||
getNodeInfo({ store }),
|
getNodeInfo({ store }),
|
||||||
getInstanceConfig({ store })
|
getInstanceConfig({ store })
|
||||||
])
|
]).catch(e => Promise.reject(e))
|
||||||
|
|
||||||
// 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')
|
||||||
|
@ -408,9 +399,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
|
||||||
// remove after vue 3.3
|
// remove after vue 3.3
|
||||||
app.config.unwrapInjectedRef = true
|
app.config.unwrapInjectedRef = true
|
||||||
|
document.querySelector('#status').textContent = i18n.global.t('splash.almost')
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
|
||||||
import ListsEdit from 'components/lists_edit/lists_edit.vue'
|
import ListsEdit from 'components/lists_edit/lists_edit.vue'
|
||||||
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
||||||
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
|
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
|
||||||
|
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
|
||||||
|
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
|
||||||
|
import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
|
||||||
|
|
||||||
export default (store) => {
|
export default (store) => {
|
||||||
const validateAuthenticatedRoute = (to, from, next) => {
|
const validateAuthenticatedRoute = (to, from, next) => {
|
||||||
|
@ -51,6 +54,7 @@ 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',
|
name: 'remote-user-profile-acct',
|
||||||
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
|
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
|
||||||
|
@ -84,7 +88,11 @@ export default (store) => {
|
||||||
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
|
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
|
||||||
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
|
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
|
||||||
{ name: 'lists-new', path: '/lists/new', 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 }
|
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute },
|
||||||
|
{ name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders },
|
||||||
|
{ name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit },
|
||||||
|
{ name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline },
|
||||||
|
{ name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit }
|
||||||
]
|
]
|
||||||
|
|
||||||
if (store.state.instance.pleromaChatMessagesAvailable) {
|
if (store.state.instance.pleromaChatMessagesAvailable) {
|
||||||
|
|
|
@ -11,14 +11,14 @@
|
||||||
<template v-if="relationship.following">
|
<template v-if="relationship.following">
|
||||||
<button
|
<button
|
||||||
v-if="relationship.showing_reblogs"
|
v-if="relationship.showing_reblogs"
|
||||||
class="btn button-default dropdown-item"
|
class="dropdown-item menu-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="btn button-default dropdown-item"
|
class="dropdown-item menu-item"
|
||||||
@click="showRepeats"
|
@click="showRepeats"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.show_repeats') }}
|
{{ $t('user_card.show_repeats') }}
|
||||||
|
@ -31,34 +31,34 @@
|
||||||
<UserListMenu :user="user" />
|
<UserListMenu :user="user" />
|
||||||
<button
|
<button
|
||||||
v-if="relationship.followed_by"
|
v-if="relationship.followed_by"
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="dropdown-item menu-item"
|
||||||
@click="removeUserFromFollowers"
|
@click="removeUserFromFollowers"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.remove_follower') }}
|
{{ $t('user_card.remove_follower') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="relationship.blocking"
|
v-if="relationship.blocking"
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="dropdown-item menu-item"
|
||||||
@click="unblockUser"
|
@click="unblockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.unblock') }}
|
{{ $t('user_card.unblock') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="dropdown-item menu-item"
|
||||||
@click="blockUser"
|
@click="blockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.block') }}
|
{{ $t('user_card.block') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="dropdown-item menu-item"
|
||||||
@click="reportUser"
|
@click="reportUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.report') }}
|
{{ $t('user_card.report') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="pleromaChatMessagesAvailable"
|
v-if="pleromaChatMessagesAvailable"
|
||||||
class="btn button-default btn-block dropdown-item"
|
class="dropdown-item menu-item"
|
||||||
@click="openChat"
|
@click="openChat"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.message') }}
|
{{ $t('user_card.message') }}
|
||||||
|
@ -122,19 +122,12 @@
|
||||||
<script src="./account_actions.js"></script>
|
<script src="./account_actions.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.AccountActions {
|
.AccountActions {
|
||||||
.ellipsis-button {
|
.ellipsis-button {
|
||||||
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>
|
||||||
|
|
53
src/components/alert.style.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
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',
|
||||||
|
directives: {
|
||||||
|
textColor: '--parent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'error',
|
||||||
|
directives: {
|
||||||
|
background: '--cRed'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'warning',
|
||||||
|
directives: {
|
||||||
|
background: '--cOrange'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'success',
|
||||||
|
directives: {
|
||||||
|
background: '--cGreen'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -99,16 +99,14 @@
|
||||||
<script src="./announcement.js"></script>
|
<script src="./announcement.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.announcement {
|
.announcement {
|
||||||
border-bottom: 1px solid var(--border, $fallback--border);
|
border-bottom: 1px solid var(--border);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin);
|
||||||
|
|
||||||
.heading,
|
.heading,
|
||||||
.body {
|
.body {
|
||||||
margin-bottom: var(--status-margin, $status-margin);
|
margin-bottom: var(--status-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<textarea
|
<textarea
|
||||||
ref="textarea"
|
ref="textarea"
|
||||||
v-model="announcement.content"
|
v-model="announcement.content"
|
||||||
class="post-textarea"
|
class="input post-textarea"
|
||||||
rows="1"
|
rows="1"
|
||||||
cols="1"
|
cols="1"
|
||||||
:placeholder="$t('announcements.post_placeholder')"
|
:placeholder="$t('announcements.post_placeholder')"
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
<input
|
<input
|
||||||
id="announcement-start-time"
|
id="announcement-start-time"
|
||||||
v-model="announcement.startsAt"
|
v-model="announcement.startsAt"
|
||||||
|
class="input"
|
||||||
:type="announcement.allDay ? 'date' : 'datetime-local'"
|
:type="announcement.allDay ? 'date' : 'datetime-local'"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
<input
|
<input
|
||||||
id="announcement-end-time"
|
id="announcement-end-time"
|
||||||
v-model="announcement.endsAt"
|
v-model="announcement.endsAt"
|
||||||
|
class="input"
|
||||||
:type="announcement.allDay ? 'date' : 'datetime-local'"
|
:type="announcement.allDay ? 'date' : 'datetime-local'"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
|
|
|
@ -61,15 +61,13 @@
|
||||||
<script src="./announcements_page.js"></script>
|
<script src="./announcements_page.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.announcements-page {
|
.announcements-page {
|
||||||
.post-form {
|
.post-form {
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin);
|
||||||
|
|
||||||
.heading,
|
.heading,
|
||||||
.body {
|
.body {
|
||||||
margin-bottom: var(--status-margin, $status-margin);
|
margin-bottom: var(--status-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-button {
|
.post-button {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.Attachment {
|
.Attachment {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -9,10 +7,8 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-radius: $fallback--attachmentRadius;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
border-color: var(--border);
|
||||||
border-color: $fallback--border;
|
|
||||||
border-color: var(--border, $fallback--border);
|
|
||||||
|
|
||||||
.attachment-wrapper {
|
.attachment-wrapper {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -84,6 +80,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.audio-container {
|
.audio-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
@ -126,23 +129,12 @@
|
||||||
|
|
||||||
.attachment-button {
|
.attachment-button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: $fallback--tooltipRadius;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
// TODO: theming? hard to theme with unknown background image color
|
|
||||||
background: rgb(230 230 230 / 70%);
|
|
||||||
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: rgb(0 0 0 / 60%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .svg-inline--fa {
|
|
||||||
color: rgb(0 0 0 / 90%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,8 +209,7 @@
|
||||||
|
|
||||||
&.-placeholder {
|
&.-placeholder {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: $fallback--link;
|
color: var(--link);
|
||||||
color: var(--postLink, $fallback--link);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
24
src/components/attachment/attachment.style.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -38,7 +38,7 @@
|
||||||
v-if="edit"
|
v-if="edit"
|
||||||
v-model="localDescription"
|
v-model="localDescription"
|
||||||
type="text"
|
type="text"
|
||||||
class="description-field"
|
class="input description-field"
|
||||||
:placeholder="$t('post_status.media_description')"
|
:placeholder="$t('post_status.media_description')"
|
||||||
@keydown.enter.prevent=""
|
@keydown.enter.prevent=""
|
||||||
>
|
>
|
||||||
|
@ -175,7 +175,6 @@
|
||||||
:is="videoTag"
|
:is="videoTag"
|
||||||
v-if="type === 'video' && !hidden"
|
v-if="type === 'video' && !hidden"
|
||||||
class="video-container"
|
class="video-container"
|
||||||
:class="{ 'button-unstyled': 'isModal' }"
|
|
||||||
:href="attachment.url"
|
:href="attachment.url"
|
||||||
@click.stop.prevent="openModal"
|
@click.stop.prevent="openModal"
|
||||||
>
|
>
|
||||||
|
@ -253,7 +252,7 @@
|
||||||
v-if="edit"
|
v-if="edit"
|
||||||
v-model="localDescription"
|
v-model="localDescription"
|
||||||
type="text"
|
type="text"
|
||||||
class="description-field"
|
class="input description-field"
|
||||||
:placeholder="$t('post_status.media_description')"
|
:placeholder="$t('post_status.media_description')"
|
||||||
@keydown.enter.prevent=""
|
@keydown.enter.prevent=""
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!-- FIXME THIS NEEDS TO BE REFACTORED TO USE POPOVER -->
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-click-outside="onClickOutside"
|
v-click-outside="onClickOutside"
|
||||||
|
@ -6,12 +7,12 @@
|
||||||
<input
|
<input
|
||||||
v-model="term"
|
v-model="term"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
class="autosuggest-input"
|
class="input autosuggest-input"
|
||||||
@click="onInputClick"
|
@click="onInputClick"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="resultsVisible && filtered.length > 0"
|
v-if="resultsVisible && filtered.length > 0"
|
||||||
class="autosuggest-results"
|
class="panel autosuggest-results"
|
||||||
>
|
>
|
||||||
<slot
|
<slot
|
||||||
v-for="item in filtered"
|
v-for="item in filtered"
|
||||||
|
@ -24,8 +25,6 @@
|
||||||
<script src="./autosuggest.js"></script>
|
<script src="./autosuggest.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.autosuggest {
|
.autosuggest {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -40,18 +39,15 @@
|
||||||
top: 100%;
|
top: 100%;
|
||||||
right: 0;
|
right: 0;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
background-color: $fallback--bg;
|
background-color: var(--bg);
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: $fallback--border;
|
border-color: var(--border);
|
||||||
border-color: var(--border, $fallback--border);
|
border-radius: var(--roundness);
|
||||||
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 rgb(0 0 0 / 60%);
|
||||||
box-shadow: var(--panelShadow);
|
box-shadow: var(--shadow);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
<script src="./avatar_list.js"></script>
|
<script src="./avatar_list.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.avatars {
|
.avatars {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -36,8 +34,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-small {
|
.avatar-small {
|
||||||
border-radius: $fallback--avatarAltRadius;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
30
src/components/badge.style.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -47,7 +47,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.6em 1em;
|
|
||||||
|
|
||||||
--emoji-size: 14px;
|
--emoji-size: 14px;
|
||||||
|
|
||||||
|
|
22
src/components/bookmark_folder_card/bookmark_folder_card.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faEllipsisH
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faEllipsisH
|
||||||
|
)
|
||||||
|
|
||||||
|
const BookmarkFolderCard = {
|
||||||
|
props: [
|
||||||
|
'folder',
|
||||||
|
'allBookmarks'
|
||||||
|
],
|
||||||
|
computed: {
|
||||||
|
firstLetter () {
|
||||||
|
return this.folder ? this.folder.name[0] : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BookmarkFolderCard
|
111
src/components/bookmark_folder_card/bookmark_folder_card.vue
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="allBookmarks"
|
||||||
|
class="bookmark-folder-card"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'bookmarks' }"
|
||||||
|
class="bookmark-folder-name"
|
||||||
|
>
|
||||||
|
<span class="icon">
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
class="fa-scale-110 menu-icon"
|
||||||
|
icon="bookmark"
|
||||||
|
/>
|
||||||
|
</span>{{ $t('nav.all_bookmarks') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="bookmark-folder-card"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'bookmark-folder', params: { id: folder.id } }"
|
||||||
|
class="bookmark-folder-name"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="folder.emoji_url"
|
||||||
|
class="iconEmoji iconEmoji-image"
|
||||||
|
:src="folder.emoji_url"
|
||||||
|
:alt="folder.emoji"
|
||||||
|
:title="folder.emoji"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else-if="folder.emoji"
|
||||||
|
class="iconEmoji"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ folder.emoji }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else-if="firstLetter"
|
||||||
|
class="icon iconLetter fa-scale-110"
|
||||||
|
>{{ firstLetter }}</span>{{ folder.name }}
|
||||||
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'bookmark-folder-edit', params: { id: folder.id } }"
|
||||||
|
class="button-folder-edit"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
class="fa-scale-110 fa-old-padding"
|
||||||
|
icon="ellipsis-h"
|
||||||
|
/>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./bookmark_folder_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.bookmark-folder-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.bookmark-folder-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
.iconLetter,
|
||||||
|
.iconEmoji {
|
||||||
|
display: inline-block;
|
||||||
|
height: 2.5rem;
|
||||||
|
width: 2.5rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
.iconLetter {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconEmoji {
|
||||||
|
text-align: center;
|
||||||
|
object-fit: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img.iconEmoji {
|
||||||
|
padding: 0.25em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-folder-name,
|
||||||
|
.button-folder-edit {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1em;
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
</style>
|
80
src/components/bookmark_folder_edit/bookmark_folder_edit.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
||||||
|
import apiService from '../../services/api/api.service'
|
||||||
|
|
||||||
|
const BookmarkFolderEdit = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
nameDraft: '',
|
||||||
|
emoji: '',
|
||||||
|
emojiUrl: null,
|
||||||
|
emojiDraft: '',
|
||||||
|
emojiUrlDraft: null,
|
||||||
|
emojiPickerExpanded: false,
|
||||||
|
reallyDelete: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
EmojiPicker
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (!this.id) return
|
||||||
|
const credentials = this.$store.state.users.currentUser.credentials
|
||||||
|
apiService.fetchBookmarkFolders({ credentials })
|
||||||
|
.then((folders) => {
|
||||||
|
const folder = folders.find(folder => folder.id === this.id)
|
||||||
|
if (!folder) return
|
||||||
|
|
||||||
|
this.nameDraft = this.name = folder.name
|
||||||
|
this.emojiDraft = this.emoji = folder.emoji
|
||||||
|
this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
|
||||||
|
})
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
id () {
|
||||||
|
return this.$route.params.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectEmoji (event) {
|
||||||
|
this.emojiDraft = event.insertion
|
||||||
|
this.emojiUrlDraft = event.insertionUrl
|
||||||
|
},
|
||||||
|
showEmojiPicker () {
|
||||||
|
if (!this.emojiPickerExpanded) {
|
||||||
|
this.$refs.picker.showPicker()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShowPicker () {
|
||||||
|
this.emojiPickerExpanded = true
|
||||||
|
},
|
||||||
|
onClosePicker () {
|
||||||
|
this.emojiPickerExpanded = false
|
||||||
|
},
|
||||||
|
updateFolder () {
|
||||||
|
this.$store.dispatch('setBookmarkFolder', { folderId: this.id, name: this.nameDraft, emoji: this.emojiDraft })
|
||||||
|
.then(() => {
|
||||||
|
this.$router.push({ name: 'bookmark-folders' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createFolder () {
|
||||||
|
this.$store.dispatch('createBookmarkFolder', { name: this.nameDraft, emoji: this.emojiDraft })
|
||||||
|
.then(() => {
|
||||||
|
this.$router.push({ name: 'bookmark-folders' })
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.$store.dispatch('pushGlobalNotice', {
|
||||||
|
messageKey: 'bookmark_folders.error',
|
||||||
|
messageArgs: [e.message],
|
||||||
|
level: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteFolder () {
|
||||||
|
this.$store.dispatch('deleteBookmarkFolder', { folderId: this.id })
|
||||||
|
this.$router.push({ name: 'bookmark-folders' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BookmarkFolderEdit
|
198
src/components/bookmark_folder_edit/bookmark_folder_edit.vue
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel-default panel BookmarkFolderEdit">
|
||||||
|
<div
|
||||||
|
ref="header"
|
||||||
|
class="panel-heading folder-edit-heading"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="button-unstyled go-back-button"
|
||||||
|
@click="$router.back"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
size="lg"
|
||||||
|
icon="chevron-left"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div class="title">
|
||||||
|
<i18n-t
|
||||||
|
v-if="id"
|
||||||
|
keypath="bookmark_folders.editing_folder"
|
||||||
|
>
|
||||||
|
<template #folderName>
|
||||||
|
{{ name }}
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
<i18n-t
|
||||||
|
v-else
|
||||||
|
keypath="bookmark_folders.creating_folder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="input-wrap">
|
||||||
|
<label for="folder-edit-title">{{ $t('bookmark_folders.emoji') }}</label>
|
||||||
|
<button
|
||||||
|
class="input input-emoji"
|
||||||
|
:title="$t('bookmark_folder.emoji_pick')"
|
||||||
|
@click="showEmojiPicker"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="emojiUrlDraft"
|
||||||
|
class="iconEmoji iconEmoji-image"
|
||||||
|
:src="emojiUrlDraft"
|
||||||
|
:alt="emojiDraft"
|
||||||
|
:title="emojiDraft"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else-if="emojiDraft"
|
||||||
|
class="iconEmoji"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{ emojiDraft }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<EmojiPicker
|
||||||
|
ref="picker"
|
||||||
|
class="emoji-picker-panel"
|
||||||
|
@emoji="selectEmoji"
|
||||||
|
@show="onShowPicker"
|
||||||
|
@close="onClosePicker"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="input-wrap">
|
||||||
|
<label for="folder-edit-title">{{ $t('bookmark_folders.name') }}</label>
|
||||||
|
<input
|
||||||
|
id="folder-edit-title"
|
||||||
|
ref="name"
|
||||||
|
v-model="nameDraft"
|
||||||
|
class="input"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<span class="spacer" />
|
||||||
|
<button
|
||||||
|
v-if="!id"
|
||||||
|
class="btn button-default footer-button"
|
||||||
|
@click="createFolder"
|
||||||
|
>
|
||||||
|
{{ $t('bookmark_folders.create') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else-if="!reallyDelete"
|
||||||
|
class="btn button-default footer-button"
|
||||||
|
@click="reallyDelete = true"
|
||||||
|
>
|
||||||
|
{{ $t('bookmark_folders.delete') }}
|
||||||
|
</button>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t('bookmark_folders.really_delete') }}
|
||||||
|
<button
|
||||||
|
class="btn button-default footer-button"
|
||||||
|
@click="deleteFolder"
|
||||||
|
>
|
||||||
|
{{ $t('general.yes') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn button-default footer-button"
|
||||||
|
@click="reallyDelete = false"
|
||||||
|
>
|
||||||
|
{{ $t('general.no') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="id && !reallyDelete"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn button-default follow-button"
|
||||||
|
@click="updateFolder"
|
||||||
|
>
|
||||||
|
{{ $t('bookmark_folders.update_folder') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./bookmark_folder_edit.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.BookmarkFolderEdit {
|
||||||
|
--panel-body-padding: 0.5em;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.folder-edit-heading {
|
||||||
|
grid-template-columns: auto minmax(50%, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-body {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-panel {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-emoji {
|
||||||
|
height: 2.5em;
|
||||||
|
width: 2.5em;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.iconEmoji {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
object-fit: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 2.5em;
|
||||||
|
width: 2.5em;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img.iconEmoji {
|
||||||
|
padding: 0.25em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.go-back-button {
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
height: 100%;
|
||||||
|
align-self: start;
|
||||||
|
width: var(--__panel-heading-height-inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-footer {
|
||||||
|
grid-template-columns: minmax(10%, 1fr);
|
||||||
|
|
||||||
|
.footer-button {
|
||||||
|
min-width: 9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
27
src/components/bookmark_folders/bookmark_folders.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue'
|
||||||
|
|
||||||
|
const BookmarkFolders = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isNew: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BookmarkFolderCard
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
bookmarkFolders () {
|
||||||
|
return this.$store.state.bookmarkFolders.allFolders
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
cancelNewFolder () {
|
||||||
|
this.isNew = false
|
||||||
|
},
|
||||||
|
newFolder () {
|
||||||
|
this.isNew = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BookmarkFolders
|
37
src/components/bookmark_folders/bookmark_folders.vue
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div class="Bookmark-folders panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<div class="title">
|
||||||
|
{{ $t('nav.bookmark_folders') }}
|
||||||
|
</div>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'bookmark-folder-new' }"
|
||||||
|
class="button-default btn new-folder-button"
|
||||||
|
>
|
||||||
|
{{ $t("bookmark_folders.new") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<BookmarkFolderCard
|
||||||
|
:all-bookmarks="true"
|
||||||
|
class="list-item"
|
||||||
|
/>
|
||||||
|
<BookmarkFolderCard
|
||||||
|
v-for="folder in bookmarkFolders.slice().reverse()"
|
||||||
|
:key="folder"
|
||||||
|
:folder="folder"
|
||||||
|
class="list-item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./bookmark_folders.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.Bookmark-folders {
|
||||||
|
.new-folder-button {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
|
||||||
|
import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
|
||||||
|
|
||||||
|
export const BookmarkFoldersMenuContent = {
|
||||||
|
components: {
|
||||||
|
NavigationEntry
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
folders: getBookmarkFolderEntries
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BookmarkFoldersMenuContent
|
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<NavigationEntry
|
||||||
|
:item="{
|
||||||
|
name: 'bookmarks',
|
||||||
|
routeObject: { name: 'bookmarks' },
|
||||||
|
label: 'nav.all_bookmarks',
|
||||||
|
icon: 'bookmark'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<NavigationEntry
|
||||||
|
v-for="item in folders"
|
||||||
|
:key="item.id"
|
||||||
|
:item="item"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./bookmark_folders_menu_content.js"></script>
|
|
@ -1,16 +1,31 @@
|
||||||
import Timeline from '../timeline/timeline.vue'
|
import Timeline from '../timeline/timeline.vue'
|
||||||
|
|
||||||
const Bookmarks = {
|
const Bookmarks = {
|
||||||
computed: {
|
created () {
|
||||||
timeline () {
|
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
||||||
return this.$store.state.statuses.timelines.bookmarks
|
this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
|
||||||
}
|
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Timeline
|
Timeline
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
folderId () {
|
||||||
|
return this.$route.params.id
|
||||||
|
},
|
||||||
|
timeline () {
|
||||||
|
return this.$store.state.statuses.timelines.bookmarks
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
folderId () {
|
||||||
|
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
||||||
|
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
|
||||||
|
this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
|
||||||
|
}
|
||||||
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
|
||||||
|
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
:title="$t('nav.bookmarks')"
|
:title="$t('nav.bookmarks')"
|
||||||
:timeline="timeline"
|
:timeline="timeline"
|
||||||
:timeline-name="'bookmarks'"
|
:timeline-name="'bookmarks'"
|
||||||
|
:bookmark-folder-id="folderId"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
13
src/components/border.style.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export default {
|
||||||
|
name: 'Border',
|
||||||
|
selector: '/*border*/',
|
||||||
|
virtual: true,
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
directives: {
|
||||||
|
textColor: '$mod(--parent, 10)',
|
||||||
|
textAuto: 'no-auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
112
src/components/button.style.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Icon',
|
||||||
|
parent: {
|
||||||
|
component: 'Button',
|
||||||
|
state: ['disabled']
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
textOpacity: 0.25,
|
||||||
|
textOpacityMode: 'blend'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
96
src/components/button_unstyled.style.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -11,15 +11,15 @@
|
||||||
|
|
||||||
.chat-view-body {
|
.chat-view-body {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: var(--chatBg, $fallback--bg);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
min-height: calc(100vh - var(--navbar-height));
|
min-height: calc(100vh - var(--navbar-height));
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -37,8 +37,6 @@
|
||||||
.footer {
|
.footer {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +59,6 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 1.3em;
|
right: 1.3em;
|
||||||
top: -3.2em;
|
top: -3.2em;
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--btn, $fallback--fg);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -79,12 +75,6 @@
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 1em;
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.unread-message-count {
|
.unread-message-count {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
|
19
src/components/chat/chat.style.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export default {
|
||||||
|
name: 'Chat',
|
||||||
|
selector: '.chat-message-list',
|
||||||
|
validInnerComponents: [
|
||||||
|
'Text',
|
||||||
|
'Link',
|
||||||
|
'Icon',
|
||||||
|
'Avatar',
|
||||||
|
'ChatMessage'
|
||||||
|
],
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
directives: {
|
||||||
|
background: '--bg',
|
||||||
|
blur: '5px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -26,7 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="message-list"
|
class="chat-message-list message-list"
|
||||||
:style="{ height: scrollableContainerHeight }"
|
:style="{ height: scrollableContainerHeight }"
|
||||||
>
|
>
|
||||||
<template v-if="!errorLoadingChat">
|
<template v-if="!errorLoadingChat">
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
<FAIcon icon="chevron-down" />
|
<FAIcon icon="chevron-down" />
|
||||||
<div
|
<div
|
||||||
v-if="newMessageCount"
|
v-if="newMessageCount"
|
||||||
class="badge badge-notification unread-chat-count unread-message-count"
|
class="badge -notification unread-chat-count unread-message-count"
|
||||||
>
|
>
|
||||||
{{ newMessageCount }}
|
{{ newMessageCount }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,6 +95,5 @@
|
||||||
|
|
||||||
<script src="./chat.js"></script>
|
<script src="./chat.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
@import "./chat";
|
@import "./chat";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -45,8 +45,6 @@
|
||||||
<script src="./chat_list.js"></script>
|
<script src="./chat_list.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.chat-list {
|
.chat-list {
|
||||||
min-height: 25em;
|
min-height: 25em;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -57,8 +55,7 @@
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: $fallback--text;
|
color: var(--textFaint);
|
||||||
color: var(--faint, $fallback--text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
.chat-list-item {
|
.chat-list-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: 0.75em;
|
|
||||||
height: 5em;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -11,11 +9,6 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--selectedPost, $fallback--lightBg);
|
|
||||||
box-shadow: 0 0 3px 1px rgb(0 0 0 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-list-item-left {
|
.chat-list-item-left {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +22,7 @@
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
@ -47,18 +40,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-preview {
|
.chat-preview {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin: 0.35em 0;
|
margin: 0.35em 0;
|
||||||
color: $fallback--text;
|
color: var(--textFaint);
|
||||||
color: var(--faint, $fallback--text);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--faintLink, $fallback--link);
|
color: var(--linkFaint);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -73,11 +65,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Avatar {
|
|
||||||
border-radius: $fallback--avatarAltRadius;
|
|
||||||
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-preview-body {
|
.chat-preview-body {
|
||||||
--emoji-size: 1.4em;
|
--emoji-size: 1.4em;
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="chat.unread > 0"
|
v-if="chat.unread > 0"
|
||||||
class="badge badge-notification unread-chat-count"
|
class="badge -notification unread-chat-count"
|
||||||
>
|
>
|
||||||
{{ chat.unread }}
|
{{ chat.unread }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,5 @@
|
||||||
<script src="./chat_list_item.js"></script>
|
<script src="./chat_list_item.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
@import "./chat_list_item";
|
@import "./chat_list_item";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.chat-message-wrapper {
|
.chat-message-wrapper {
|
||||||
&.hovered-message-chain {
|
&.hovered-message-chain {
|
||||||
.animated.Avatar {
|
.animated.Avatar {
|
||||||
|
@ -27,12 +25,6 @@
|
||||||
|
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover,
|
|
||||||
.extra-button-popover.open & {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
|
@ -61,10 +53,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
border-radius: $fallback--chatMessageRadius;
|
background-color: var(--background);
|
||||||
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
|
color: var(--text);
|
||||||
|
border-radius: var(--roundness);
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.created-at {
|
.created-at {
|
||||||
|
@ -97,8 +91,7 @@
|
||||||
.error {
|
.error {
|
||||||
.status-content.media-body,
|
.status-content.media-body,
|
||||||
.created-at {
|
.created-at {
|
||||||
color: $fallback--cRed;
|
color: var(--badgeNotification);
|
||||||
color: var(--badgeNotification, $fallback--cRed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,16 +110,6 @@
|
||||||
align-content: end;
|
align-content: end;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--chatMessageOutgoingLink, $fallback--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
color: var(--chatMessageOutgoingText, $fallback--text);
|
|
||||||
background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
|
|
||||||
border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-inner {
|
.chat-message-inner {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
@ -137,22 +120,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.incoming {
|
.incoming {
|
||||||
a {
|
|
||||||
color: var(--chatMessageIncomingLink, $fallback--link);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
color: var(--chatMessageIncomingText, $fallback--text);
|
|
||||||
background-color: var(--chatMessageIncomingBg, $fallback--bg);
|
|
||||||
border: 1px solid var(--chatMessageIncomingBorder, --border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.created-at {
|
|
||||||
a {
|
|
||||||
color: var(--chatMessageIncomingText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-message-menu {
|
.chat-message-menu {
|
||||||
left: 0.4rem;
|
left: 0.4rem;
|
||||||
}
|
}
|
||||||
|
@ -176,6 +143,5 @@
|
||||||
margin: 1.4em 0;
|
margin: 1.4em 0;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: $fallback--text;
|
color: var(--textFaint);
|
||||||
color: var(--faintedText, $fallback--text);
|
|
||||||
}
|
}
|
||||||
|
|
30
src/components/chat_message/chat_message.style.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export default {
|
||||||
|
name: 'ChatMessage',
|
||||||
|
selector: '.chat-message',
|
||||||
|
variants: {
|
||||||
|
outgoing: '.outgoing'
|
||||||
|
},
|
||||||
|
validInnerComponents: [
|
||||||
|
'Text',
|
||||||
|
'Icon',
|
||||||
|
'Border',
|
||||||
|
'Button',
|
||||||
|
'RichContent',
|
||||||
|
'Attachment',
|
||||||
|
'PollGraph'
|
||||||
|
],
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
directives: {
|
||||||
|
background: '--bg, 2',
|
||||||
|
backgroundNoCssColor: 'yes'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'outgoing',
|
||||||
|
directives: {
|
||||||
|
background: '--bg, 5'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -53,7 +53,7 @@
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
@click="deleteMessage"
|
@click="deleteMessage"
|
||||||
>
|
>
|
||||||
<FAIcon icon="times" /> {{ $t("chats.delete") }}
|
<FAIcon icon="times" /> {{ $t("chats.delete") }}
|
||||||
|
|
|
@ -16,11 +16,6 @@
|
||||||
padding-bottom: 0.7rem;
|
padding-bottom: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.basic-user-card:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--selectedPost, $fallback--lightBg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.go-back-button {
|
.go-back-button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
<div class="input-search">
|
<div class="input-search">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
<input
|
<input
|
||||||
ref="search"
|
ref="search"
|
||||||
v-model="query"
|
v-model="query"
|
||||||
|
class="input"
|
||||||
placeholder="Search people"
|
placeholder="Search people"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
>
|
>
|
||||||
|
@ -34,9 +36,9 @@
|
||||||
<div
|
<div
|
||||||
v-for="user in availableUsers"
|
v-for="user in availableUsers"
|
||||||
:key="user.id"
|
:key="user.id"
|
||||||
class="member"
|
class="list-item"
|
||||||
|
@click.capture.prevent="goToChat(user)"
|
||||||
>
|
>
|
||||||
<div @click.capture.prevent="goToChat(user)">
|
|
||||||
<BasicUserCard :user="user" />
|
<BasicUserCard :user="user" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,6 +48,5 @@
|
||||||
|
|
||||||
<script src="./chat_new.js"></script>
|
<script src="./chat_new.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
@import "./chat_new";
|
@import "./chat_new";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,8 +26,6 @@
|
||||||
<script src="./chat_title.js"></script>
|
<script src="./chat_title.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.chat-title {
|
.chat-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -54,8 +52,7 @@
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
border-radius: $fallback--avatarAltRadius;
|
border-radius: var(--roundness);
|
||||||
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
|
|
||||||
|
|
||||||
&.animated::before {
|
&.animated::before {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<label
|
<label
|
||||||
class="checkbox"
|
class="checkbox"
|
||||||
:class="{ disabled, indeterminate }"
|
:class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
v-if="!!$slots.before"
|
||||||
|
class="label -before"
|
||||||
|
:class="{ faint: disabled }"
|
||||||
|
>
|
||||||
|
<slot name="before" />
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
class="visible-for-screenreader-only"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:checked="modelValue"
|
:checked="modelValue"
|
||||||
:indeterminate="indeterminate"
|
:indeterminate="indeterminate"
|
||||||
@change="$emit('update:modelValue', $event.target.checked)"
|
@change="$emit('update:modelValue', $event.target.checked)"
|
||||||
>
|
>
|
||||||
<i class="checkbox-indicator" />
|
<i
|
||||||
|
class="input -checkbox checkbox-indicator"
|
||||||
|
:aria-hidden="true"
|
||||||
|
:class="{ disabled }"
|
||||||
|
@transitionend.capture="onTransitionEnd"
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="!!$slots.default"
|
v-if="!!$slots.default"
|
||||||
class="label"
|
class="label -after"
|
||||||
|
:class="{ faint: disabled }"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
|
@ -27,12 +41,29 @@ export default {
|
||||||
'indeterminate',
|
'indeterminate',
|
||||||
'disabled'
|
'disabled'
|
||||||
],
|
],
|
||||||
emits: ['update:modelValue']
|
emits: ['update:modelValue'],
|
||||||
|
data: (vm) => ({
|
||||||
|
indeterminateTransitionFix: vm.indeterminate
|
||||||
|
}),
|
||||||
|
watch: {
|
||||||
|
indeterminate (e) {
|
||||||
|
if (e) {
|
||||||
|
this.indeterminateTransitionFix = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onTransitionEnd (e) {
|
||||||
|
if (!this.indeterminate) {
|
||||||
|
this.indeterminateTransitionFix = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
@import "../../mixins";
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -44,11 +75,16 @@ export default {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-indicator {
|
& > &-indicator {
|
||||||
display: inline-block;
|
/* Reset .input stuff */
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
line-height: inherit;
|
||||||
|
display: inline-block;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-indicator::before {
|
&-indicator::before {
|
||||||
|
@ -57,12 +93,11 @@ export default {
|
||||||
display: block;
|
display: block;
|
||||||
content: "✓";
|
content: "✓";
|
||||||
transition: color 200ms;
|
transition: color 200ms;
|
||||||
border-radius: $fallback--checkboxRadius;
|
width: 1.1em;
|
||||||
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
|
height: 1.1em;
|
||||||
box-shadow: 0 0 2px black inset;
|
border-radius: var(--roundness);
|
||||||
box-shadow: var(--inputShadow);
|
box-shadow: var(--shadow);
|
||||||
background-color: $fallback--fg;
|
background-color: var(--background);
|
||||||
background-color: var(--input, $fallback--fg);
|
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.1em;
|
line-height: 1.1em;
|
||||||
|
@ -72,35 +107,37 @@ export default {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
.disabled {
|
||||||
.checkbox-indicator::before,
|
.checkbox-indicator::before {
|
||||||
.label {
|
background-color: var(--background);
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: $fallback--faint;
|
|
||||||
color: var(--faint, $fallback--faint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:checked + .checkbox-indicator::before {
|
&:checked + .checkbox-indicator::before {
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--inputText, $fallback--text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:indeterminate + .checkbox-indicator::before {
|
&:indeterminate + .checkbox-indicator::before {
|
||||||
content: "–";
|
content: "–";
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--inputText, $fallback--text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
&.indeterminate-fix {
|
||||||
|
input[type="checkbox"] + .checkbox-indicator::before {
|
||||||
|
content: "–";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .label {
|
||||||
|
&.-after {
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.-before {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.color-input {
|
.color-input {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
&-field.input {
|
&-field.input {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex: 0 0 0;
|
flex: 0 0 0;
|
||||||
max-width: 9em;
|
max-width: 9em;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: 0.2em 8px;
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
color: var(--text);
|
||||||
background: none;
|
background: none;
|
||||||
color: $fallback--lightText;
|
|
||||||
color: var(--inputText, $fallback--lightText);
|
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -23,21 +23,39 @@
|
||||||
min-width: 3em;
|
min-width: 3em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.nativeColor {
|
.nativeColor {
|
||||||
flex: 0 0 2em;
|
cursor: pointer;
|
||||||
min-width: 2em;
|
flex: 0 0 auto;
|
||||||
align-self: stretch;
|
padding: 0;
|
||||||
min-height: 100%;
|
|
||||||
|
input {
|
||||||
|
appearance: none;
|
||||||
|
max-width: 0;
|
||||||
|
min-width: 0;
|
||||||
|
max-height: 0;
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.computedIndicator,
|
.computedIndicator,
|
||||||
|
.validIndicator,
|
||||||
|
.invalidIndicator,
|
||||||
.transparentIndicator {
|
.transparentIndicator {
|
||||||
flex: 0 0 2em;
|
flex: 0 0 2em;
|
||||||
|
margin: 0.2em 0.5em;
|
||||||
min-width: 2em;
|
min-width: 2em;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
min-height: 100%;
|
min-height: 1.1em;
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalidIndicator {
|
||||||
|
background: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid var(--cRed);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transparentIndicator {
|
.transparentIndicator {
|
||||||
|
@ -58,16 +76,26 @@
|
||||||
&::after {
|
&::after {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
border-top-left-radius: var(--roundness);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
border-bottom-right-radius: var(--roundness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
&.disabled,
|
||||||
flex: 1 1 auto;
|
&:disabled {
|
||||||
|
.nativeColor input,
|
||||||
|
.computedIndicator,
|
||||||
|
.validIndicator,
|
||||||
|
.invalidIndicator,
|
||||||
|
.transparentIndicator {
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
opacity: 0.25 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<label
|
<label
|
||||||
:for="name"
|
:for="name"
|
||||||
class="label"
|
class="label"
|
||||||
|
:class="{ faint: !present || disabled }"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -14,41 +15,68 @@
|
||||||
:model-value="present"
|
:model-value="present"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
class="opt"
|
class="opt"
|
||||||
@update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
@update:modelValue="update(typeof modelValue === 'undefined' ? fallback : undefined)"
|
||||||
/>
|
/>
|
||||||
<div class="input color-input-field">
|
<div
|
||||||
|
class="input color-input-field"
|
||||||
|
:class="{ disabled: !present || disabled }"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
:id="name + '-t'"
|
:id="name + '-t'"
|
||||||
class="textColor unstyled"
|
class="textColor unstyled"
|
||||||
|
:class="{ disabled: !present || disabled }"
|
||||||
type="text"
|
type="text"
|
||||||
:value="modelValue || fallback"
|
:value="modelValue || fallback"
|
||||||
:disabled="!present || disabled"
|
:disabled="!present || disabled"
|
||||||
@input="$emit('update:modelValue', $event.target.value)"
|
@input="updateValue($event.target.value)"
|
||||||
>
|
|
||||||
<input
|
|
||||||
v-if="validColor"
|
|
||||||
:id="name"
|
|
||||||
class="nativeColor unstyled"
|
|
||||||
type="color"
|
|
||||||
:value="modelValue || fallback"
|
|
||||||
:disabled="!present || disabled"
|
|
||||||
@input="$emit('update:modelValue', $event.target.value)"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="transparentColor"
|
v-if="validColor"
|
||||||
|
class="validIndicator"
|
||||||
|
:style="{backgroundColor: modelValue || fallback}"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-else-if="transparentColor"
|
||||||
class="transparentIndicator"
|
class="transparentIndicator"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="computedColor"
|
v-else-if="computedColor"
|
||||||
class="computedIndicator"
|
class="computedIndicator"
|
||||||
:style="{backgroundColor: fallback}"
|
:style="{backgroundColor: fallback}"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="invalidIndicator"
|
||||||
|
/>
|
||||||
|
<label class="nativeColor">
|
||||||
|
<FAIcon icon="eye-dropper" />
|
||||||
|
<input
|
||||||
|
:id="name"
|
||||||
|
class="unstyled"
|
||||||
|
type="color"
|
||||||
|
:value="modelValue || fallback"
|
||||||
|
:disabled="!present || disabled"
|
||||||
|
:class="{ disabled: !present || disabled }"
|
||||||
|
@input="updateValue($event.target.value)"
|
||||||
|
>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
import { hex2rgb } from '../../services/color_convert/color_convert.js'
|
import { hex2rgb } from '../../services/color_convert/color_convert.js'
|
||||||
|
import { throttle } from 'lodash'
|
||||||
|
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faEyeDropper
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faEyeDropper
|
||||||
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Checkbox
|
Checkbox
|
||||||
|
@ -104,16 +132,12 @@ export default {
|
||||||
computedColor () {
|
computedColor () {
|
||||||
return this.modelValue && this.modelValue.startsWith('--')
|
return this.modelValue && this.modelValue.startsWith('--')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateValue: throttle(function (value) {
|
||||||
|
this.$emit('update:modelValue', value)
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" src="./color_input.scss"></style>
|
<style lang="scss" src="./color_input.scss"></style>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.color-control {
|
|
||||||
input.text-input {
|
|
||||||
max-width: 7em;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
212
src/components/component_preview/component_preview.vue
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ComponentPreview"
|
||||||
|
:class="{ '-shadow-controls': shadowControl }"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="header"
|
||||||
|
v-show="shadowControl"
|
||||||
|
:class="{ faint: disabled }"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.shadows.offset') }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-show="shadowControl"
|
||||||
|
:value="shadow?.y"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="{ disabled }"
|
||||||
|
class="input input-number y-shift-number"
|
||||||
|
type="number"
|
||||||
|
@input="e => updateProperty('y', e.target.value)"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-show="shadowControl"
|
||||||
|
:value="shadow?.y"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="{ disabled }"
|
||||||
|
class="input input-range y-shift-slider"
|
||||||
|
type="range"
|
||||||
|
max="20"
|
||||||
|
min="-20"
|
||||||
|
@input="e => updateProperty('y', e.target.value)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="preview-window"
|
||||||
|
:class="{ '-light-grid': lightGrid }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="preview-block"
|
||||||
|
:style="previewStyle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
v-show="shadowControl"
|
||||||
|
:value="shadow?.x"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="{ disabled }"
|
||||||
|
class="input input-number x-shift-number"
|
||||||
|
type="number"
|
||||||
|
@input="e => updateProperty('x', e.target.value)"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-show="shadowControl"
|
||||||
|
:value="shadow?.x"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="{ disabled }"
|
||||||
|
class="input input-range x-shift-slider"
|
||||||
|
type="range"
|
||||||
|
max="20"
|
||||||
|
min="-20"
|
||||||
|
@input="e => updateProperty('x', e.target.value)"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id="lightGrid"
|
||||||
|
v-model="lightGrid"
|
||||||
|
:disabled="shadow == null"
|
||||||
|
name="lightGrid"
|
||||||
|
class="input-light-grid"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.shadows.light_grid') }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ComponentPreview {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3em 1fr 3em;
|
||||||
|
grid-template-rows: 2em 1fr 2em;
|
||||||
|
grid-template-areas:
|
||||||
|
". header y-num "
|
||||||
|
". preview y-slide"
|
||||||
|
"x-num x-slide . "
|
||||||
|
"options options options";
|
||||||
|
grid-gap: 0.5em;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
grid-area: header;
|
||||||
|
justify-self: center;
|
||||||
|
align-self: baseline;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-light-grid {
|
||||||
|
grid-area: options;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-number {
|
||||||
|
min-width: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-shift-number {
|
||||||
|
grid-area: x-num;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-shift-slider {
|
||||||
|
grid-area: x-slide;
|
||||||
|
height: auto;
|
||||||
|
align-self: start;
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.y-shift-number {
|
||||||
|
grid-area: y-num;
|
||||||
|
}
|
||||||
|
|
||||||
|
.y-shift-slider {
|
||||||
|
grid-area: y-slide;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
justify-self: left;
|
||||||
|
min-height: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x-shift-slider,
|
||||||
|
.y-shift-slider {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-window {
|
||||||
|
--__grid-color1: rgb(102 102 102);
|
||||||
|
--__grid-color2: rgb(153 153 153);
|
||||||
|
--__grid-color1-disabled: rgba(102 102 102 / 20%);
|
||||||
|
--__grid-color2-disabled: rgba(153 153 153 / 20%);
|
||||||
|
|
||||||
|
&.-light-grid {
|
||||||
|
--__grid-color1: rgb(205 205 205);
|
||||||
|
--__grid-color2: rgb(255 255 255);
|
||||||
|
--__grid-color1-disabled: rgba(205 205 205 / 20%);
|
||||||
|
--__grid-color2-disabled: rgba(255 255 255 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
grid-area: preview;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 10em;
|
||||||
|
min-height: 10em;
|
||||||
|
background-color: var(--__grid-color2);
|
||||||
|
background-image:
|
||||||
|
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
|
||||||
|
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background-color: var(--__grid-color2-disabled);
|
||||||
|
background-image:
|
||||||
|
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
|
||||||
|
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
|
||||||
|
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-block {
|
||||||
|
background: var(--background, var(--bg));
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 33%;
|
||||||
|
min-height: 33%;
|
||||||
|
max-width: 80%;
|
||||||
|
max-height: 80%;
|
||||||
|
border-width: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--border);
|
||||||
|
border-radius: var(--roundness);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'shadow',
|
||||||
|
'shadowControl',
|
||||||
|
'previewClass',
|
||||||
|
'previewStyle',
|
||||||
|
'disabled'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
lightGrid: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:shadow'],
|
||||||
|
components: {
|
||||||
|
Checkbox
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateProperty (axis, value) {
|
||||||
|
this.$emit('update:shadow', { axis, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -56,7 +56,8 @@ const conversation = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
|
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
|
||||||
statusContentPropertiesObject: {},
|
statusContentPropertiesObject: {},
|
||||||
inlineDivePosition: null
|
inlineDivePosition: null,
|
||||||
|
loadStatusError: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
|
@ -392,11 +393,15 @@ const conversation = {
|
||||||
this.setHighlight(this.originalStatusId)
|
this.setHighlight(this.originalStatusId)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
this.loadStatusError = null
|
||||||
this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
|
this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
|
||||||
.then((status) => {
|
.then((status) => {
|
||||||
this.$store.dispatch('addNewStatuses', { statuses: [status] })
|
this.$store.dispatch('addNewStatuses', { statuses: [status] })
|
||||||
this.fetchConversation()
|
this.fetchConversation()
|
||||||
})
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.loadStatusError = error
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getReplies (id) {
|
getReplies (id) {
|
||||||
|
|
|
@ -28,7 +28,27 @@
|
||||||
class="rightside-button"
|
class="rightside-button"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="conversation-body panel-body">
|
<div
|
||||||
|
v-if="isPage && !status"
|
||||||
|
class="conversation-body"
|
||||||
|
:class="{ 'panel-body': isExpanded }"
|
||||||
|
>
|
||||||
|
<p v-if="!loadStatusError">
|
||||||
|
<FAIcon
|
||||||
|
spin
|
||||||
|
icon="circle-notch"
|
||||||
|
/>
|
||||||
|
{{ $t('status.loading') }}
|
||||||
|
</p>
|
||||||
|
<p v-else>
|
||||||
|
{{ $t('status.load_error', { error: loadStatusError }) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="conversation-body"
|
||||||
|
:class="{ 'panel-body': isExpanded }"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isTreeView"
|
v-if="isTreeView"
|
||||||
class="thread-body"
|
class="thread-body"
|
||||||
|
@ -203,6 +223,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
class="Conversation -hidden"
|
||||||
:style="hiddenStyle"
|
:style="hiddenStyle"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -210,14 +231,17 @@
|
||||||
<script src="./conversation.js"></script>
|
<script src="./conversation.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.Conversation {
|
.Conversation {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
|
&.-hidden {
|
||||||
|
background: var(--__panel-background);
|
||||||
|
backdrop-filter: var(--__panel-backdrop-filter);
|
||||||
|
}
|
||||||
|
|
||||||
.conversation-dive-to-top-level-box {
|
.conversation-dive-to-top-level-box {
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin);
|
||||||
border-bottom: 1px solid var(--border, $fallback--border);
|
border-bottom: 1px solid var(--border);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
/* Make the button stretch along the whole row */
|
/* Make the button stretch along the whole row */
|
||||||
|
@ -227,20 +251,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestors {
|
.thread-ancestors {
|
||||||
margin-left: var(--status-margin, $status-margin);
|
margin-left: var(--status-margin);
|
||||||
border-left: 2px solid var(--border, $fallback--border);
|
border-left: 2px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor.-faded .StatusContent {
|
.thread-ancestor.-faded .RichContent {
|
||||||
--link: var(--faintLink);
|
/* stylelint-disable declaration-no-important */
|
||||||
--text: var(--faint);
|
--text: var(--textFaint) !important;
|
||||||
|
--link: var(--linkFaint) !important;
|
||||||
color: var(--text);
|
--funtextGreentext: var(--funtextGreentextFaint) !important;
|
||||||
|
--funtextCyantext: var(--funtextCyantextFaint) !important;
|
||||||
|
/* stylelint-enable declaration-no-important */
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor-dive-box {
|
.thread-ancestor-dive-box {
|
||||||
padding-left: var(--status-margin, $status-margin);
|
padding-left: var(--status-margin);
|
||||||
border-bottom: 1px solid var(--border, $fallback--border);
|
border-bottom: 1px solid var(--border);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
/* Make the button stretch along the whole row */
|
/* Make the button stretch along the whole row */
|
||||||
|
@ -253,16 +279,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor-dive-box-inner {
|
.thread-ancestor-dive-box-inner {
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-status {
|
.conversation-status {
|
||||||
border-bottom: 1px solid var(--border, $fallback--border);
|
border-bottom: 1px solid var(--border);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestor-has-other-replies .conversation-status,
|
.thread-ancestor-has-other-replies .conversation-status,
|
||||||
&:last-child .conversation-status,
|
&:last-child:not(.-expanded) .conversation-status,
|
||||||
|
&.-expanded .conversation-status:last-child,
|
||||||
.thread-ancestor:last-child .conversation-status,
|
.thread-ancestor:last-child .conversation-status,
|
||||||
.thread-ancestor:last-child .thread-ancestor-dive-box,
|
.thread-ancestor:last-child .thread-ancestor-dive-box,
|
||||||
&.-expanded .thread-tree .conversation-status {
|
&.-expanded .thread-tree .conversation-status {
|
||||||
|
@ -270,20 +297,36 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-ancestors + .thread-tree > .conversation-status {
|
.thread-ancestors + .thread-tree > .conversation-status {
|
||||||
border-top: 1px solid var(--border, $fallback--border);
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* expanded conversation in timeline */
|
/* expanded conversation in timeline */
|
||||||
&.status-fadein.-expanded .thread-body {
|
&.status-fadein.-expanded .thread-body {
|
||||||
border-left: 4px solid $fallback--cRed;
|
border-left: 4px solid var(--cRed);
|
||||||
border-left-color: var(--cRed, $fallback--cRed);
|
border-radius: var(--roundness);
|
||||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
border-top-left-radius: 0;
|
||||||
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
border-top-right-radius: 0;
|
||||||
border-bottom: 1px solid var(--border, $fallback--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-expanded.status-fadein {
|
&.-expanded.status-fadein {
|
||||||
margin: calc(var(--status-margin, $status-margin) / 2);
|
--___margin: calc(var(--status-margin) / 2);
|
||||||
|
|
||||||
|
background: var(--background);
|
||||||
|
margin: var(--___margin);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
z-index: -1;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--___margin) * -1);
|
||||||
|
bottom: calc(var(--___margin) * -1);
|
||||||
|
left: calc(var(--___margin) * -1);
|
||||||
|
right: calc(var(--___margin) * -1);
|
||||||
|
background: var(--background);
|
||||||
|
backdrop-filter: var(--__panel-backdrop-filter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -107,7 +107,10 @@ export default {
|
||||||
this.searchBarHidden = hidden
|
this.searchBarHidden = hidden
|
||||||
},
|
},
|
||||||
openSettingsModal () {
|
openSettingsModal () {
|
||||||
this.$store.dispatch('openSettingsModal')
|
this.$store.dispatch('openSettingsModal', 'user')
|
||||||
|
},
|
||||||
|
openAdminModal () {
|
||||||
|
this.$store.dispatch('openSettingsModal', 'admin')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.DesktopNav {
|
.DesktopNav {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: var(--ZI_navbar);
|
z-index: var(--ZI_navbar);
|
||||||
|
@ -9,7 +7,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--topBarLink, $fallback--link);
|
color: var(--link);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner-nav {
|
.inner-nav {
|
||||||
|
@ -54,27 +52,7 @@
|
||||||
.button-default {
|
.button-default {
|
||||||
&,
|
&,
|
||||||
svg {
|
svg {
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--btnTopBarText, $fallback--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--btnPressedTopBar, $fallback--fg);
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnPressedTopBarText, $fallback--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnDisabledTopBarText, $fallback--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toggled {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--btnToggledTopBarText, $fallback--text);
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--btnToggledTopBar, $fallback--fg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,8 +72,7 @@
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
background-color: $fallback--fg;
|
background-color: var(--text);
|
||||||
background-color: var(--topBarText, $fallback--fg);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -116,8 +93,7 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
color: $fallback--link;
|
color: var(--link);
|
||||||
color: var(--topBarLink, $fallback--link);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
class="logo"
|
class="logo"
|
||||||
:to="{ name: 'root' }"
|
:to="{ name: 'root' }"
|
||||||
:style="logoBgStyle"
|
:style="logoBgStyle"
|
||||||
|
:title="sitename"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mask"
|
class="mask"
|
||||||
|
@ -38,40 +39,39 @@
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="button-unstyled nav-icon"
|
class="button-unstyled nav-icon"
|
||||||
|
:title="$t('nav.preferences')"
|
||||||
@click.stop="openSettingsModal"
|
@click.stop="openSettingsModal"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="cog"
|
icon="cog"
|
||||||
:title="$t('nav.preferences')"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<a
|
<button
|
||||||
v-if="currentUser && currentUser.role === 'admin'"
|
v-if="currentUser && currentUser.role === 'admin'"
|
||||||
href="/pleroma/admin/#/login-pleroma"
|
class="button-unstyled nav-icon"
|
||||||
class="nav-icon"
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@click.stop
|
:title="$t('nav.administration')"
|
||||||
|
@click.stop="openAdminModal"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="tachometer-alt"
|
icon="tachometer-alt"
|
||||||
:title="$t('nav.administration')"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</button>
|
||||||
<span class="spacer" />
|
<span class="spacer" />
|
||||||
<button
|
<button
|
||||||
v-if="currentUser"
|
v-if="currentUser"
|
||||||
class="button-unstyled nav-icon"
|
class="button-unstyled nav-icon"
|
||||||
|
:title="$t('login.logout')"
|
||||||
@click.stop.prevent="logout"
|
@click.stop.prevent="logout"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
fixed-width
|
fixed-width
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
icon="sign-out-alt"
|
icon="sign-out-alt"
|
||||||
:title="$t('login.logout')"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-modal-content">
|
<div class="panel-body dialog-modal-content">
|
||||||
<slot name="default" />
|
<slot name="default" />
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-modal-footer user-interactions panel-footer">
|
<div class="dialog-modal-footer user-interactions panel-footer">
|
||||||
|
@ -25,8 +25,6 @@
|
||||||
<script src="./dialog_modal.js"></script>
|
<script src="./dialog_modal.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
// TODO: unify with other modals.
|
// TODO: unify with other modals.
|
||||||
.dark-overlay {
|
.dark-overlay {
|
||||||
&::before {
|
&::before {
|
||||||
|
@ -54,8 +52,6 @@
|
||||||
z-index: 2001;
|
z-index: 2001;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
|
|
||||||
.dialog-modal-heading {
|
.dialog-modal-heading {
|
||||||
.title {
|
.title {
|
||||||
|
@ -66,18 +62,13 @@
|
||||||
.dialog-modal-content {
|
.dialog-modal-content {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-modal-footer {
|
.dialog-modal-footer {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
background-color: $fallback--bg;
|
border-top: 1px solid var(--border);
|
||||||
background-color: var(--bg, $fallback--bg);
|
|
||||||
border-top: 1px solid $fallback--border;
|
|
||||||
border-top: 1px solid var(--border, $fallback--border);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import Completion from '../../services/completion/completion.js'
|
import Completion from '../../services/completion/completion.js'
|
||||||
|
import genRandomSeed from '../../services/random_seed/random_seed.service.js'
|
||||||
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
||||||
import Popover from 'src/components/popover/popover.vue'
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
|
import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
|
||||||
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||||
import { take } from 'lodash'
|
import { take } from 'lodash'
|
||||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||||
|
@ -109,9 +111,10 @@ const EmojiInput = {
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
randomSeed: genRandomSeed(),
|
||||||
input: undefined,
|
input: undefined,
|
||||||
caretEl: undefined,
|
caretEl: undefined,
|
||||||
highlighted: 0,
|
highlighted: -1,
|
||||||
caret: 0,
|
caret: 0,
|
||||||
focused: false,
|
focused: false,
|
||||||
blurTimeout: null,
|
blurTimeout: null,
|
||||||
|
@ -125,12 +128,16 @@ const EmojiInput = {
|
||||||
components: {
|
components: {
|
||||||
Popover,
|
Popover,
|
||||||
EmojiPicker,
|
EmojiPicker,
|
||||||
UnicodeDomainIndicator
|
UnicodeDomainIndicator,
|
||||||
|
ScreenReaderNotice
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
padEmoji () {
|
padEmoji () {
|
||||||
return this.$store.getters.mergedConfig.padEmoji
|
return this.$store.getters.mergedConfig.padEmoji
|
||||||
},
|
},
|
||||||
|
defaultCandidateIndex () {
|
||||||
|
return this.$store.getters.mergedConfig.autocompleteSelect ? 0 : -1
|
||||||
|
},
|
||||||
preText () {
|
preText () {
|
||||||
return this.modelValue.slice(0, this.caret)
|
return this.modelValue.slice(0, this.caret)
|
||||||
},
|
},
|
||||||
|
@ -203,6 +210,12 @@ const EmojiInput = {
|
||||||
top: this.input.scrollTop,
|
top: this.input.scrollTop,
|
||||||
left: this.input.scrollLeft
|
left: this.input.scrollLeft
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
suggestionListId () {
|
||||||
|
return `suggestions-${this.randomSeed}`
|
||||||
|
},
|
||||||
|
suggestionItemId () {
|
||||||
|
return (index) => `suggestion-item-${index}-${this.randomSeed}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
@ -278,6 +291,11 @@ const EmojiInput = {
|
||||||
...rest,
|
...rest,
|
||||||
img: imageUrl || ''
|
img: imageUrl || ''
|
||||||
}))
|
}))
|
||||||
|
this.highlighted = this.defaultCandidateIndex
|
||||||
|
this.$refs.screenReaderNotice.announce(
|
||||||
|
this.$tc('tool_tip.autocomplete_available',
|
||||||
|
this.suggestions.length,
|
||||||
|
{ number: this.suggestions.length }))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -374,26 +392,27 @@ const EmojiInput = {
|
||||||
},
|
},
|
||||||
cycleBackward (e) {
|
cycleBackward (e) {
|
||||||
const len = this.suggestions.length || 0
|
const len = this.suggestions.length || 0
|
||||||
if (len > 1) {
|
|
||||||
this.highlighted -= 1
|
this.highlighted -= 1
|
||||||
if (this.highlighted < 0) {
|
if (this.highlighted === -1) {
|
||||||
this.highlighted = this.suggestions.length - 1
|
this.input.focus()
|
||||||
|
} else if (this.highlighted < -1) {
|
||||||
|
this.highlighted = len - 1
|
||||||
}
|
}
|
||||||
|
if (len > 0) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
} else {
|
|
||||||
this.highlighted = 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cycleForward (e) {
|
cycleForward (e) {
|
||||||
const len = this.suggestions.length || 0
|
const len = this.suggestions.length || 0
|
||||||
if (len > 1) {
|
|
||||||
this.highlighted += 1
|
this.highlighted += 1
|
||||||
if (this.highlighted >= len) {
|
if (this.highlighted >= len) {
|
||||||
this.highlighted = 0
|
this.highlighted = -1
|
||||||
|
this.input.focus()
|
||||||
}
|
}
|
||||||
|
if (len > 0) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
} else {
|
|
||||||
this.highlighted = 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollIntoView () {
|
scrollIntoView () {
|
||||||
|
@ -540,6 +559,13 @@ const EmojiInput = {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resize () {
|
resize () {
|
||||||
|
},
|
||||||
|
autoCompleteItemLabel (suggestion) {
|
||||||
|
if (suggestion.user) {
|
||||||
|
return suggestion.displayText + ' ' + suggestion.detailText
|
||||||
|
} else {
|
||||||
|
return this.maybeLocalizedEmojiName(suggestion)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="root"
|
ref="root"
|
||||||
class="emoji-input"
|
class="input emoji-input"
|
||||||
:class="{ 'with-picker': !hideEmojiButton }"
|
:class="{ 'with-picker': !hideEmojiButton }"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot
|
||||||
|
:id="'textbox-' + randomSeed"
|
||||||
|
:aria-owns="suggestionListId"
|
||||||
|
aria-autocomplete="both"
|
||||||
|
:aria-expanded="showSuggestions"
|
||||||
|
:aria-activedescendant="(!showSuggestions || highlighted === -1) ? '' : suggestionItemId(highlighted)"
|
||||||
|
/>
|
||||||
<!-- TODO: make the 'x' disappear if at the end maybe? -->
|
<!-- TODO: make the 'x' disappear if at the end maybe? -->
|
||||||
<div
|
<div
|
||||||
ref="hiddenOverlay"
|
ref="hiddenOverlay"
|
||||||
class="hidden-overlay"
|
class="hidden-overlay"
|
||||||
:style="overlayStyle"
|
:style="overlayStyle"
|
||||||
|
:aria-hidden="true"
|
||||||
>
|
>
|
||||||
<span>{{ preText }}</span>
|
<span>{{ preText }}</span>
|
||||||
<span
|
<span
|
||||||
|
@ -18,11 +25,16 @@
|
||||||
>x</span>
|
>x</span>
|
||||||
<span>{{ postText }}</span>
|
<span>{{ postText }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<screen-reader-notice
|
||||||
|
ref="screenReaderNotice"
|
||||||
|
aria-live="assertive"
|
||||||
|
/>
|
||||||
<template v-if="enableEmojiPicker">
|
<template v-if="enableEmojiPicker">
|
||||||
<button
|
<button
|
||||||
v-if="!hideEmojiButton"
|
v-if="!hideEmojiButton"
|
||||||
class="button-unstyled emoji-picker-icon"
|
class="button-unstyled emoji-picker-icon"
|
||||||
type="button"
|
type="button"
|
||||||
|
:title="$t('emoji.add_emoji')"
|
||||||
@click.prevent="togglePicker"
|
@click.prevent="togglePicker"
|
||||||
>
|
>
|
||||||
<FAIcon :icon="['far', 'smile-beam']" />
|
<FAIcon :icon="['far', 'smile-beam']" />
|
||||||
|
@ -43,17 +55,24 @@
|
||||||
ref="suggestorPopover"
|
ref="suggestorPopover"
|
||||||
class="autocomplete-panel"
|
class="autocomplete-panel"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
:trigger-attrs="{ 'aria-hidden': true }"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div
|
<div
|
||||||
|
:id="suggestionListId"
|
||||||
ref="panel-body"
|
ref="panel-body"
|
||||||
class="autocomplete-panel-body"
|
class="autocomplete-panel-body"
|
||||||
|
role="listbox"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(suggestion, index) in suggestions"
|
v-for="(suggestion, index) in suggestions"
|
||||||
|
:id="suggestionItemId(index)"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="autocomplete-item"
|
class="menu-item autocomplete-item"
|
||||||
:class="{ highlighted: index === highlighted }"
|
role="option"
|
||||||
|
:class="{ '-active': index === highlighted }"
|
||||||
|
:aria-label="autoCompleteItemLabel(suggestion)"
|
||||||
|
:aria-selected="index === highlighted"
|
||||||
@click.stop.prevent="onClick($event, suggestion)"
|
@click.stop.prevent="onClick($event, suggestion)"
|
||||||
>
|
>
|
||||||
<span class="image">
|
<span class="image">
|
||||||
|
@ -91,9 +110,8 @@
|
||||||
<script src="./emoji_input.js"></script>
|
<script src="./emoji_input.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
.input.emoji-input {
|
||||||
|
padding: 0;
|
||||||
.emoji-input {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -108,8 +126,7 @@
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
|
||||||
&:hover i {
|
&:hover i {
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +143,12 @@
|
||||||
input,
|
input,
|
||||||
textarea {
|
textarea {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
|
color: inherit;
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
background: none !important;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.with-picker input {
|
&.with-picker input {
|
||||||
|
@ -160,26 +183,28 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item.menu-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
padding-top: 0;
|
||||||
padding: 0.2em 0.4em;
|
padding-bottom: 0;
|
||||||
border-bottom: 1px solid rgb(0 0 0 / 40%);
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
width: 32px;
|
width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
|
||||||
height: 32px;
|
height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
|
||||||
line-height: 32px;
|
line-height: var(--__line-height);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 32px;
|
margin-right: var(--__horizontal-gap);
|
||||||
margin-right: 4px;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 32px;
|
width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
|
||||||
height: 32px;
|
height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: var(--__line-height);
|
||||||
|
line-height: var(--__line-height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
@ -197,17 +222,6 @@
|
||||||
line-height: 9px;
|
line-height: 9px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.highlighted {
|
|
||||||
background-color: $fallback--fg;
|
|
||||||
background-color: var(--selectedMenuPopover, $fallback--fg);
|
|
||||||
color: var(--selectedMenuPopoverText, $fallback--text);
|
|
||||||
|
|
||||||
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
|
|
||||||
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
|
||||||
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
|
||||||
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -94,8 +94,9 @@ export const suggestUsers = ({ dispatch, state }) => {
|
||||||
|
|
||||||
const newSuggestions = state.users.users.filter(
|
const newSuggestions = state.users.users.filter(
|
||||||
user =>
|
user =>
|
||||||
|
user.screen_name && user.name && (
|
||||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||||
user.name.toLowerCase().startsWith(noPrefix)
|
user.name.toLowerCase().startsWith(noPrefix))
|
||||||
).slice(0, 20).sort((a, b) => {
|
).slice(0, 20).sort((a, b) => {
|
||||||
let aScore = 0
|
let aScore = 0
|
||||||
let bScore = 0
|
let bScore = 0
|
||||||
|
|
|
@ -98,8 +98,14 @@ const EmojiPicker = {
|
||||||
required: false,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
hideCustomEmoji: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
inject: ['popoversZLayer'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
keyword: '',
|
keyword: '',
|
||||||
|
@ -108,11 +114,13 @@ const EmojiPicker = {
|
||||||
groupsScrolledClass: 'scrolled-top',
|
groupsScrolledClass: 'scrolled-top',
|
||||||
keepOpen: false,
|
keepOpen: false,
|
||||||
customEmojiTimeout: null,
|
customEmojiTimeout: null,
|
||||||
|
hideCustomEmojiInPicker: false,
|
||||||
// Lazy-load only after the first time `showing` becomes true.
|
// Lazy-load only after the first time `showing` becomes true.
|
||||||
contentLoaded: false,
|
contentLoaded: false,
|
||||||
groupRefs: {},
|
groupRefs: {},
|
||||||
emojiRefs: {},
|
emojiRefs: {},
|
||||||
filteredEmojiGroups: [],
|
filteredEmojiGroups: [],
|
||||||
|
emojiSize: 0,
|
||||||
width: 0
|
width: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -123,9 +131,28 @@ const EmojiPicker = {
|
||||||
Popover
|
Popover
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateEmojiSize () {
|
||||||
|
const css = window.getComputedStyle(this.$refs.popover.$el)
|
||||||
|
const emojiSize = css.getPropertyValue('--emojiSize')
|
||||||
|
const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '')
|
||||||
|
const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, ''))
|
||||||
|
const fontSize = css.getPropertyValue('font-size').replace(/[^0-9,.]+/, '')
|
||||||
|
|
||||||
|
let emojiSizeReal
|
||||||
|
if (emojiSizeUnit.endsWith('em')) {
|
||||||
|
emojiSizeReal = emojiSizeValue * fontSize
|
||||||
|
} else {
|
||||||
|
emojiSizeReal = emojiSizeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSize)
|
||||||
|
this.emojiSize = fullEmojiSize
|
||||||
|
},
|
||||||
showPicker () {
|
showPicker () {
|
||||||
this.$refs.popover.showPopover()
|
this.$refs.popover.showPopover()
|
||||||
|
this.$nextTick(() => {
|
||||||
this.onShowing()
|
this.onShowing()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
hidePicker () {
|
hidePicker () {
|
||||||
this.$refs.popover.hidePopover()
|
this.$refs.popover.hidePopover()
|
||||||
|
@ -153,7 +180,7 @@ const EmojiPicker = {
|
||||||
if (!this.keepOpen) {
|
if (!this.keepOpen) {
|
||||||
this.$refs.popover.hidePopover()
|
this.$refs.popover.hidePopover()
|
||||||
}
|
}
|
||||||
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
|
this.$emit('emoji', { insertion: value, insertionUrl: emoji.imageUrl, keepOpen: this.keepOpen })
|
||||||
},
|
},
|
||||||
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
|
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
|
||||||
const target = this.$refs['emoji-groups'].$el
|
const target = this.$refs['emoji-groups'].$el
|
||||||
|
@ -217,6 +244,7 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
onShowing () {
|
onShowing () {
|
||||||
const oldContentLoaded = this.contentLoaded
|
const oldContentLoaded = this.contentLoaded
|
||||||
|
this.updateEmojiSize()
|
||||||
this.recalculateItemPerRow()
|
this.recalculateItemPerRow()
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.search.focus()
|
this.$refs.search.focus()
|
||||||
|
@ -259,16 +287,20 @@ const EmojiPicker = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
minItemSize () {
|
minItemSize () {
|
||||||
return this.emojiHeight
|
return this.emojiSize
|
||||||
|
},
|
||||||
|
// used to watch it
|
||||||
|
fontSize () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateEmojiSize()
|
||||||
|
})
|
||||||
|
return this.$store.getters.mergedConfig.fontSize
|
||||||
},
|
},
|
||||||
emojiHeight () {
|
emojiHeight () {
|
||||||
return 32 + 4
|
return this.emojiSize
|
||||||
},
|
|
||||||
emojiWidth () {
|
|
||||||
return 32 + 4
|
|
||||||
},
|
},
|
||||||
itemPerRow () {
|
itemPerRow () {
|
||||||
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
|
return this.width ? Math.floor(this.width / this.emojiSize) : 6
|
||||||
},
|
},
|
||||||
activeGroupView () {
|
activeGroupView () {
|
||||||
return this.showingStickers ? '' : this.activeGroup
|
return this.showingStickers ? '' : this.activeGroup
|
||||||
|
@ -280,6 +312,9 @@ const EmojiPicker = {
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
allCustomGroups () {
|
allCustomGroups () {
|
||||||
|
if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
const emojis = this.$store.getters.groupedCustomEmojis
|
const emojis = this.$store.getters.groupedCustomEmojis
|
||||||
if (emojis.unpacked) {
|
if (emojis.unpacked) {
|
||||||
emojis.unpacked.text = this.$t('emoji.unpacked')
|
emojis.unpacked.text = this.$t('emoji.unpacked')
|
||||||
|
@ -342,6 +377,9 @@ const EmojiPicker = {
|
||||||
|
|
||||||
return emoji.displayText
|
return emoji.displayText
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isInModal () {
|
||||||
|
return this.popoversZLayer === 'modals'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,55 @@
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
$emoji-picker-header-height: 36px;
|
|
||||||
$emoji-picker-header-picture-width: 32px;
|
|
||||||
$emoji-picker-header-picture-height: 32px;
|
|
||||||
$emoji-picker-emoji-size: 32px;
|
|
||||||
|
|
||||||
.emoji-picker {
|
.emoji-picker {
|
||||||
|
--__emoji-picker-header: 2.2em;
|
||||||
|
|
||||||
width: 25em;
|
width: 25em;
|
||||||
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
|
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: $fallback--bg;
|
|
||||||
background-color: var(--popover, $fallback--bg);
|
|
||||||
color: $fallback--link;
|
|
||||||
color: var(--popoverText, $fallback--link);
|
|
||||||
|
|
||||||
--faint: var(--popoverFaintText, $fallback--faint);
|
|
||||||
--faintLink: var(--popoverFaintLink, $fallback--faint);
|
|
||||||
--lightText: var(--popoverLightText, $fallback--lightText);
|
|
||||||
--icon: var(--popoverIcon, $fallback--icon);
|
|
||||||
|
|
||||||
&-header-image {
|
&-header-image {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: $emoji-picker-header-picture-width;
|
width: var(--__emoji-picker-header);
|
||||||
max-width: $emoji-picker-header-picture-width;
|
max-width: var(--__emoji-picker-header);
|
||||||
height: $emoji-picker-header-picture-height;
|
height: var(--__emoji-picker-header);
|
||||||
max-height: $emoji-picker-header-picture-height;
|
max-height: var(--__emoji-picker-header);
|
||||||
|
|
||||||
.still-image {
|
.still-image {
|
||||||
max-width: 100%;
|
width: var(--__emoji-picker-header);
|
||||||
max-height: 100%;
|
max-width: var(--__emoji-picker-header);
|
||||||
height: 100%;
|
height: var(--__emoji-picker-header);
|
||||||
width: 100%;
|
max-height: var(--__emoji-picker-header);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|
||||||
|
--_still_image-label-scale: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keep-open,
|
.keep-open,
|
||||||
.too-many-emoji {
|
.too-many-emoji,
|
||||||
padding: 7px;
|
.hide-custom-emoji {
|
||||||
|
padding: 0.5em;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide-custom-emoji {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.too-many-emoji {
|
.too-many-emoji {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.keep-open-label {
|
.keep-open-label {
|
||||||
padding: 0 7px;
|
padding: 0 0.5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px 7px 5px;
|
padding: 0.7em 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -71,14 +64,14 @@ $emoji-picker-emoji-size: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.additional-tabs {
|
.additional-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-left: 1px solid;
|
border-left: 1px solid;
|
||||||
border-left-color: $fallback--icon;
|
border-left-color: var(--border);
|
||||||
border-left-color: var(--icon, $fallback--icon);
|
padding-left: 0.5em;
|
||||||
padding-left: 7px;
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,30 +80,29 @@ $emoji-picker-emoji-size: 32px;
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
padding: 0 7px;
|
padding: 0 0.5em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.85em;
|
width: var(--__emoji-picker-header);
|
||||||
width: $emoji-picker-header-picture-width;
|
max-width: var(--__emoji-picker-header);
|
||||||
max-width: $emoji-picker-header-picture-width;
|
height: var(--__emoji-picker-header);
|
||||||
height: $emoji-picker-header-picture-height;
|
max-height: var(--__emoji-picker-header);
|
||||||
max-height: $emoji-picker-header-picture-height;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.svg-inline--fa {
|
||||||
|
font-size: 1.85em;
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.toggled {
|
||||||
border-bottom: 4px solid;
|
border-bottom: 0.2em solid;
|
||||||
|
|
||||||
svg {
|
|
||||||
color: $fallback--lightText;
|
|
||||||
color: var(--lightText, $fallback--lightText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,7 +129,7 @@ $emoji-picker-emoji-size: 32px;
|
||||||
|
|
||||||
.emoji {
|
.emoji {
|
||||||
&-search {
|
&-search {
|
||||||
padding: 5px;
|
padding: 0.3em;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
@ -151,6 +143,7 @@ $emoji-picker-emoji-size: 32px;
|
||||||
flex: 1 1 1px;
|
flex: 1 1 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
scrollbar-gutter: stable both-edges;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
mask:
|
mask:
|
||||||
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||||
|
@ -177,13 +170,13 @@ $emoji-picker-emoji-size: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding-left: 5px;
|
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding-left: 0.3em;
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -192,24 +185,28 @@ $emoji-picker-emoji-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
width: $emoji-picker-emoji-size;
|
width: var(--emoji-size);
|
||||||
height: $emoji-picker-emoji-size;
|
height: var(--emoji-size);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
line-height: $emoji-picker-emoji-size;
|
line-height: var(--emoji-size);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 4px;
|
margin: 0.2em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.emoji-picker-emoji.-custom {
|
.emoji-picker-emoji.-custom {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
max-width: 100%;
|
width: var(--emoji-size);
|
||||||
max-height: 100%;
|
max-width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
|
max-height: var(--emoji-size);
|
||||||
|
|
||||||
|
--_still_image-label-scale: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker-emoji.-unicode {
|
.emoji-picker-emoji.-unicode {
|
||||||
font-size: 24px;
|
font-size: 1.6em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,24 +3,32 @@
|
||||||
ref="popover"
|
ref="popover"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
popover-class="emoji-picker popover-default"
|
popover-class="emoji-picker popover-default"
|
||||||
|
:trigger-attrs="{ 'aria-hidden': true, tabindex: -1 }"
|
||||||
@show="onPopoverShown"
|
@show="onPopoverShown"
|
||||||
@close="onPopoverClosed"
|
@close="onPopoverClosed"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
|
<!--
|
||||||
|
Body scroll lock needs to be on every scrollable element on safari iOS.
|
||||||
|
Here we tell it to enable scrolling for this element.
|
||||||
|
See https://github.com/willmcpo/body-scroll-lock#vanilla-js
|
||||||
|
-->
|
||||||
<span
|
<span
|
||||||
ref="header"
|
ref="header"
|
||||||
|
v-body-scroll-lock="isInModal"
|
||||||
class="emoji-tabs"
|
class="emoji-tabs"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-for="group in filteredEmojiGroups"
|
v-for="group in filteredEmojiGroups"
|
||||||
:ref="setGroupRef('group-header-' + group.id)"
|
:ref="setGroupRef('group-header-' + group.id)"
|
||||||
:key="group.id"
|
:key="group.id"
|
||||||
class="emoji-tabs-item"
|
class="button-unstyled emoji-tabs-item"
|
||||||
:class="{
|
:class="{
|
||||||
active: activeGroupView === group.id
|
toggled: activeGroupView === group.id
|
||||||
}"
|
}"
|
||||||
:title="group.text"
|
:title="group.text"
|
||||||
|
role="button"
|
||||||
@click.prevent="highlight(group.id)"
|
@click.prevent="highlight(group.id)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
@ -44,8 +52,8 @@
|
||||||
class="additional-tabs"
|
class="additional-tabs"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="stickers-tab-icon additional-tabs-item"
|
class="button-unstyled stickers-tab-icon additional-tabs-item"
|
||||||
:class="{active: showingStickers}"
|
:class="{toggled: showingStickers}"
|
||||||
:title="$t('emoji.stickers')"
|
:title="$t('emoji.stickers')"
|
||||||
@click.prevent="toggleStickers"
|
@click.prevent="toggleStickers"
|
||||||
>
|
>
|
||||||
|
@ -69,16 +77,19 @@
|
||||||
ref="search"
|
ref="search"
|
||||||
v-model="keyword"
|
v-model="keyword"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="input form-control"
|
||||||
:placeholder="$t('emoji.search_emoji')"
|
:placeholder="$t('emoji.search_emoji')"
|
||||||
@input="$event.target.composing = false"
|
@input="$event.target.composing = false"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Enables scrolling for this element on safari iOS. See comments for header. -->
|
||||||
<DynamicScroller
|
<DynamicScroller
|
||||||
ref="emoji-groups"
|
ref="emoji-groups"
|
||||||
|
v-body-scroll-lock="isInModal"
|
||||||
class="emoji-groups"
|
class="emoji-groups"
|
||||||
:class="groupsScrolledClass"
|
:class="groupsScrolledClass"
|
||||||
:min-item-size="minItemSize"
|
:min-item-size="minItemSize"
|
||||||
|
:buffer="minItemSize"
|
||||||
:items="emojiItems"
|
:items="emojiItems"
|
||||||
:emit-update="true"
|
:emit-update="true"
|
||||||
@update="onScroll"
|
@update="onScroll"
|
||||||
|
@ -107,6 +118,7 @@
|
||||||
:key="group.id + emoji.displayText"
|
:key="group.id + emoji.displayText"
|
||||||
:title="maybeLocalizedEmojiName(emoji)"
|
:title="maybeLocalizedEmojiName(emoji)"
|
||||||
class="emoji-item"
|
class="emoji-item"
|
||||||
|
role="button"
|
||||||
@click.stop.prevent="onEmoji(emoji)"
|
@click.stop.prevent="onEmoji(emoji)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
@ -117,6 +129,7 @@
|
||||||
v-else
|
v-else
|
||||||
class="emoji-picker-emoji -custom"
|
class="emoji-picker-emoji -custom"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
:alt="maybeLocalizedEmojiName(emoji)"
|
||||||
:src="emoji.imageUrl"
|
:src="emoji.imageUrl"
|
||||||
:data-emoji-name="group.id + emoji.displayText"
|
:data-emoji-name="group.id + emoji.displayText"
|
||||||
/>
|
/>
|
||||||
|
@ -130,6 +143,17 @@
|
||||||
{{ $t('emoji.keep_open') }}
|
{{ $t('emoji.keep_open') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!hideCustomEmoji"
|
||||||
|
class="hide-custom-emoji"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
v-model="hideCustomEmojiInPicker"
|
||||||
|
@change="onShowing"
|
||||||
|
>
|
||||||
|
{{ $t('emoji.hide_custom_emoji') }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="showingStickers"
|
v-if="showingStickers"
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
import UserListPopover from '../user_list_popover/user_list_popover.vue'
|
import UserListPopover from '../user_list_popover/user_list_popover.vue'
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faPlus,
|
||||||
|
faMinus,
|
||||||
|
faCheck
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faPlus,
|
||||||
|
faMinus,
|
||||||
|
faCheck
|
||||||
|
)
|
||||||
|
|
||||||
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
||||||
|
|
||||||
|
@ -33,6 +45,9 @@ const EmojiReactions = {
|
||||||
},
|
},
|
||||||
loggedIn () {
|
loggedIn () {
|
||||||
return !!this.$store.state.users.currentUser
|
return !!this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
remoteInteractionLink () {
|
||||||
|
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -42,10 +57,10 @@ const EmojiReactions = {
|
||||||
reactedWith (emoji) {
|
reactedWith (emoji) {
|
||||||
return this.status.emoji_reactions.find(r => r.name === emoji).me
|
return this.status.emoji_reactions.find(r => r.name === emoji).me
|
||||||
},
|
},
|
||||||
fetchEmojiReactionsByIfMissing () {
|
async fetchEmojiReactionsByIfMissing () {
|
||||||
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
|
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
|
||||||
if (hasNoAccounts) {
|
if (hasNoAccounts) {
|
||||||
this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
|
return await this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reactWith (emoji) {
|
reactWith (emoji) {
|
||||||
|
@ -54,14 +69,26 @@ const EmojiReactions = {
|
||||||
unreact (emoji) {
|
unreact (emoji) {
|
||||||
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
||||||
},
|
},
|
||||||
emojiOnClick (emoji, event) {
|
async emojiOnClick (emoji, event) {
|
||||||
if (!this.loggedIn) return
|
if (!this.loggedIn) return
|
||||||
|
|
||||||
|
await this.fetchEmojiReactionsByIfMissing()
|
||||||
if (this.reactedWith(emoji)) {
|
if (this.reactedWith(emoji)) {
|
||||||
this.unreact(emoji)
|
this.unreact(emoji)
|
||||||
} else {
|
} else {
|
||||||
this.reactWith(emoji)
|
this.reactWith(emoji)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
counterTriggerAttrs (reaction) {
|
||||||
|
return {
|
||||||
|
class: [
|
||||||
|
'btn',
|
||||||
|
'button-default',
|
||||||
|
'emoji-reaction-count-button',
|
||||||
|
{ '-picked-reaction': this.reactedWith(reaction.name) }
|
||||||
|
],
|
||||||
|
'aria-label': this.$tc('status.reaction_count_label', reaction.count, { num: reaction.count })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,64 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="EmojiReactions">
|
<div class="EmojiReactions">
|
||||||
<UserListPopover
|
<span
|
||||||
v-for="(reaction) in emojiReactions"
|
v-for="(reaction) in emojiReactions"
|
||||||
:key="reaction.name"
|
:key="reaction.url || reaction.name"
|
||||||
:users="accountsForEmoji[reaction.name]"
|
class="emoji-reaction-container btn-group"
|
||||||
>
|
>
|
||||||
<button
|
<component
|
||||||
|
:is="loggedIn ? 'button' : 'a'"
|
||||||
|
v-bind="!loggedIn ? { href: remoteInteractionLink } : {}"
|
||||||
|
role="button"
|
||||||
class="emoji-reaction btn button-default"
|
class="emoji-reaction btn button-default"
|
||||||
:class="{ '-picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
:class="{ '-picked-reaction': reactedWith(reaction.name) }"
|
||||||
|
:title="reaction.url ? reaction.name : undefined"
|
||||||
|
:aria-pressed="reactedWith(reaction.name)"
|
||||||
@click="emojiOnClick(reaction.name, $event)"
|
@click="emojiOnClick(reaction.name, $event)"
|
||||||
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
|
||||||
>
|
>
|
||||||
<span class="reaction-emoji">{{ reaction.name }}</span>
|
<span
|
||||||
<span>{{ reaction.count }}</span>
|
class="reaction-emoji"
|
||||||
</button>
|
>
|
||||||
|
<img
|
||||||
|
v-if="reaction.url"
|
||||||
|
:src="reaction.url"
|
||||||
|
class="reaction-emoji-content"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="reaction-emoji reaction-emoji-content"
|
||||||
|
>{{ reaction.name }}</span>
|
||||||
|
</span>
|
||||||
|
<FALayers>
|
||||||
|
<FAIcon
|
||||||
|
v-if="reactedWith(reaction.name)"
|
||||||
|
class="active-marker"
|
||||||
|
transform="shrink-6 up-9"
|
||||||
|
icon="check"
|
||||||
|
/>
|
||||||
|
<FAIcon
|
||||||
|
v-if="!reactedWith(reaction.name)"
|
||||||
|
class="focus-marker"
|
||||||
|
transform="shrink-6 up-9"
|
||||||
|
icon="plus"
|
||||||
|
/>
|
||||||
|
<FAIcon
|
||||||
|
v-else
|
||||||
|
class="focus-marker"
|
||||||
|
transform="shrink-6 up-9"
|
||||||
|
icon="minus"
|
||||||
|
/>
|
||||||
|
</FALayers>
|
||||||
|
</component>
|
||||||
|
<UserListPopover
|
||||||
|
:users="accountsForEmoji[reaction.name]"
|
||||||
|
class="emoji-reaction-popover"
|
||||||
|
:trigger-attrs="counterTriggerAttrs(reaction)"
|
||||||
|
@show="fetchEmojiReactionsByIfMissing()"
|
||||||
|
>
|
||||||
|
<span class="emoji-reaction-counts">{{ reaction.count }}</span>
|
||||||
</UserListPopover>
|
</UserListPopover>
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
v-if="tooManyReactions"
|
v-if="tooManyReactions"
|
||||||
class="emoji-reaction-expand faint"
|
class="emoji-reaction-expand faint"
|
||||||
|
@ -28,44 +72,114 @@
|
||||||
|
|
||||||
<script src="./emoji_reactions.js"></script>
|
<script src="./emoji_reactions.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
@import "../../mixins";
|
||||||
|
|
||||||
.EmojiReactions {
|
.EmojiReactions {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.emoji-reaction {
|
--emoji-size: calc(var(--emojiSize, 1.25em) * var(--emojiReactionsScale, 1));
|
||||||
padding: 0 0.5em;
|
|
||||||
margin-right: 0.5em;
|
.emoji-reaction-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
|
||||||
|
.emoji-reaction-popover {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.emoji-reaction-count-button {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 2em;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.-picked-reaction {
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-reaction {
|
||||||
|
padding-left: 0.5em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
.reaction-emoji {
|
.reaction-emoji {
|
||||||
width: 1.25em;
|
width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
|
line-height: var(--emoji-size);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reaction-emoji-content {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
line-height: inherit;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: calc(var(--emoji-size) * 0.8);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not-clickable {
|
.svg-inline--fa {
|
||||||
cursor: default;
|
color: var(--text);
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: $fallback--buttonShadow;
|
|
||||||
box-shadow: var(--buttonShadow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-picked-reaction {
|
&.-picked-reaction {
|
||||||
border: 1px solid var(--accent, $fallback--link);
|
border: 1px solid var(--accent);
|
||||||
margin-left: -1px; // offset the border, can't use inset shadows either
|
margin-left: -1px; // offset the border, can't use inset shadows either
|
||||||
margin-right: calc(0.5em - 1px);
|
margin-right: -1px;
|
||||||
|
|
||||||
|
.svg-inline--fa {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include unfocused-style {
|
||||||
|
.focus-marker {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-marker {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include focused-style {
|
||||||
|
.svg-inline--fa {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-marker {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-marker {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
|
import genRandomSeed from '../../services/random_seed/random_seed.service.js'
|
||||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
|
import StatusBookmarkFolderMenu from '../status_bookmark_folder_menu/status_bookmark_folder_menu.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faEllipsisH,
|
faEllipsisH,
|
||||||
|
@ -35,12 +37,14 @@ const ExtraButtons = {
|
||||||
props: ['status'],
|
props: ['status'],
|
||||||
components: {
|
components: {
|
||||||
Popover,
|
Popover,
|
||||||
ConfirmModal
|
ConfirmModal,
|
||||||
|
StatusBookmarkFolderMenu
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
showingDeleteDialog: false
|
showingDeleteDialog: false,
|
||||||
|
randomSeed: genRandomSeed()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -143,6 +147,9 @@ const ExtraButtons = {
|
||||||
canBookmark () {
|
canBookmark () {
|
||||||
return !!this.currentUser
|
return !!this.currentUser
|
||||||
},
|
},
|
||||||
|
bookmarkFolders () {
|
||||||
|
return this.$store.state.instance.pleromaBookmarkFoldersAvailable
|
||||||
|
},
|
||||||
statusLink () {
|
statusLink () {
|
||||||
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
|
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
|
||||||
},
|
},
|
||||||
|
@ -152,6 +159,15 @@ const ExtraButtons = {
|
||||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||||
shouldConfirmDelete () {
|
shouldConfirmDelete () {
|
||||||
return this.$store.getters.mergedConfig.modalOnDelete
|
return this.$store.getters.mergedConfig.modalOnDelete
|
||||||
|
},
|
||||||
|
triggerAttrs () {
|
||||||
|
return {
|
||||||
|
title: this.$t('status.more_actions'),
|
||||||
|
id: `popup-trigger-${this.randomSeed}`,
|
||||||
|
'aria-controls': `popup-menu-${this.randomSeed}`,
|
||||||
|
'aria-expanded': this.expanded,
|
||||||
|
'aria-haspopup': 'menu'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<Popover
|
<Popover
|
||||||
class="ExtraButtons"
|
class="ExtraButtons"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
|
:trigger-attrs="triggerAttrs"
|
||||||
placement="top"
|
placement="top"
|
||||||
:offset="{ y: 5 }"
|
:offset="{ y: 5 }"
|
||||||
:bound-to="{ x: 'container' }"
|
:bound-to="{ x: 'container' }"
|
||||||
|
@ -10,10 +11,15 @@
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
>
|
>
|
||||||
<template #content="{close}">
|
<template #content="{close}">
|
||||||
<div class="dropdown-menu">
|
<div
|
||||||
|
:id="`popup-menu-${randomSeed}`"
|
||||||
|
class="dropdown-menu"
|
||||||
|
role="menu"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
v-if="canMute && !status.thread_muted"
|
v-if="canMute && !status.thread_muted"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="muteConversation"
|
@click.prevent="muteConversation"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
@ -23,7 +29,8 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="canMute && status.thread_muted"
|
v-if="canMute && status.thread_muted"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="unmuteConversation"
|
@click.prevent="unmuteConversation"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
@ -33,7 +40,8 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!status.pinned && canPin"
|
v-if="!status.pinned && canPin"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="pinStatus"
|
@click.prevent="pinStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -44,7 +52,8 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="status.pinned && canPin"
|
v-if="status.pinned && canPin"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="unpinStatus"
|
@click.prevent="unpinStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -56,7 +65,8 @@
|
||||||
<template v-if="canBookmark">
|
<template v-if="canBookmark">
|
||||||
<button
|
<button
|
||||||
v-if="!status.bookmarked"
|
v-if="!status.bookmarked"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="bookmarkStatus"
|
@click.prevent="bookmarkStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -67,7 +77,8 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="status.bookmarked"
|
v-if="status.bookmarked"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="unbookmarkStatus"
|
@click.prevent="unbookmarkStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -76,10 +87,15 @@
|
||||||
icon="bookmark"
|
icon="bookmark"
|
||||||
/><span>{{ $t("status.unbookmark") }}</span>
|
/><span>{{ $t("status.unbookmark") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<StatusBookmarkFolderMenu
|
||||||
|
v-if="status.bookmarked && bookmarkFolders"
|
||||||
|
:status="status"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<button
|
<button
|
||||||
v-if="ownStatus && editingAvailable"
|
v-if="ownStatus && editingAvailable"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="editStatus"
|
@click.prevent="editStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -90,7 +106,8 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="isEdited && editingAvailable"
|
v-if="isEdited && editingAvailable"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="showStatusHistory"
|
@click.prevent="showStatusHistory"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -101,7 +118,8 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="canDelete"
|
v-if="canDelete"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="deleteStatus"
|
@click.prevent="deleteStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -111,7 +129,8 @@
|
||||||
/><span>{{ $t("status.delete") }}</span>
|
/><span>{{ $t("status.delete") }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="copyLink"
|
@click.prevent="copyLink"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -122,7 +141,8 @@
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
v-if="!status.is_local"
|
v-if="!status.is_local"
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
title="Source"
|
title="Source"
|
||||||
:href="status.external_url"
|
:href="status.external_url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -133,7 +153,8 @@
|
||||||
/><span>{{ $t("status.external_source") }}</span>
|
/><span>{{ $t("status.external_source") }}</span>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
class="button-default dropdown-item dropdown-item-icon"
|
class="menu-item dropdown-item dropdown-item-icon"
|
||||||
|
role="menuitem"
|
||||||
@click.prevent="reportStatus"
|
@click.prevent="reportStatus"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
|
@ -184,7 +205,6 @@
|
||||||
<script src="./extra_buttons.js"></script>
|
<script src="./extra_buttons.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
@import "../../mixins";
|
@import "../../mixins";
|
||||||
|
|
||||||
.ExtraButtons {
|
.ExtraButtons {
|
||||||
|
@ -194,8 +214,7 @@
|
||||||
margin: -10px;
|
margin: -10px;
|
||||||
|
|
||||||
&:hover .svg-inline--fa {
|
&:hover .svg-inline--fa {
|
||||||
color: $fallback--text;
|
color: var(--text);
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
src/components/extra_notifications/extra_notifications.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faUserPlus,
|
||||||
|
faComments,
|
||||||
|
faBullhorn
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faUserPlus,
|
||||||
|
faComments,
|
||||||
|
faBullhorn
|
||||||
|
)
|
||||||
|
|
||||||
|
const ExtraNotifications = {
|
||||||
|
computed: {
|
||||||
|
shouldShowChats () {
|
||||||
|
return this.mergedConfig.showExtraNotifications && this.mergedConfig.showChatsInExtraNotifications && this.unreadChatCount
|
||||||
|
},
|
||||||
|
shouldShowAnnouncements () {
|
||||||
|
return this.mergedConfig.showExtraNotifications && this.mergedConfig.showAnnouncementsInExtraNotifications && this.unreadAnnouncementCount
|
||||||
|
},
|
||||||
|
shouldShowFollowRequests () {
|
||||||
|
return this.mergedConfig.showExtraNotifications && this.mergedConfig.showFollowRequestsInExtraNotifications && this.followRequestCount
|
||||||
|
},
|
||||||
|
hasAnythingToShow () {
|
||||||
|
return this.shouldShowChats || this.shouldShowAnnouncements || this.shouldShowFollowRequests
|
||||||
|
},
|
||||||
|
shouldShowCustomizationTip () {
|
||||||
|
return this.mergedConfig.showExtraNotificationsTip && this.hasAnythingToShow
|
||||||
|
},
|
||||||
|
currentUser () {
|
||||||
|
return this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'followRequestCount', 'mergedConfig'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openNotificationSettings () {
|
||||||
|
return this.$store.dispatch('openSettingsModalTab', 'notifications')
|
||||||
|
},
|
||||||
|
dismissConfigurationTip () {
|
||||||
|
return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtraNotifications
|
110
src/components/extra_notifications/extra_notifications.vue
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<div class="ExtraNotifications">
|
||||||
|
<div
|
||||||
|
v-if="shouldShowChats"
|
||||||
|
class="notification unseen"
|
||||||
|
>
|
||||||
|
<div class="notification-overlay" />
|
||||||
|
<router-link
|
||||||
|
class="button-unstyled -link extra-notification"
|
||||||
|
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
class="fa-scale-110 icon"
|
||||||
|
icon="comments"
|
||||||
|
/>
|
||||||
|
{{ $tc('notifications.unread_chats', unreadChatCount, { num: unreadChatCount }) }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="shouldShowAnnouncements"
|
||||||
|
class="notification unseen"
|
||||||
|
>
|
||||||
|
<div class="notification-overlay" />
|
||||||
|
<router-link
|
||||||
|
class="button-unstyled -link extra-notification"
|
||||||
|
:to="{ name: 'announcements' }"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
class="fa-scale-110 icon"
|
||||||
|
icon="bullhorn"
|
||||||
|
/>
|
||||||
|
{{ $tc('notifications.unread_announcements', unreadAnnouncementCount, { num: unreadAnnouncementCount }) }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="shouldShowFollowRequests"
|
||||||
|
class="notification unseen"
|
||||||
|
>
|
||||||
|
<div class="notification-overlay" />
|
||||||
|
<router-link
|
||||||
|
class="button-unstyled -link extra-notification"
|
||||||
|
:to="{ name: 'friend-requests' }"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
class="fa-scale-110 icon"
|
||||||
|
icon="user-plus"
|
||||||
|
/>
|
||||||
|
{{ $tc('notifications.unread_follow_requests', followRequestCount, { num: followRequestCount }) }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<i18n-t
|
||||||
|
v-if="shouldShowCustomizationTip"
|
||||||
|
tag="span"
|
||||||
|
class="notification tip extra-notification"
|
||||||
|
keypath="notifications.configuration_tip"
|
||||||
|
>
|
||||||
|
<template #theSettings>
|
||||||
|
<button
|
||||||
|
class="button-unstyled -link"
|
||||||
|
@click="openNotificationSettings"
|
||||||
|
>
|
||||||
|
{{ $t('notifications.configuration_tip_settings') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template #dismiss>
|
||||||
|
<button
|
||||||
|
class="button-unstyled -link"
|
||||||
|
@click="dismissConfigurationTip"
|
||||||
|
>
|
||||||
|
{{ $t('notifications.configuration_tip_dismiss') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./extra_notifications.js" />
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ExtraNotifications {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
border-color: var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-notification {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -38,13 +38,20 @@
|
||||||
class="button-unstyled interactive"
|
class="button-unstyled interactive"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
role="button"
|
role="button"
|
||||||
|
:title="$t('tool_tip.favorite')"
|
||||||
:href="remoteInteractionLink"
|
:href="remoteInteractionLink"
|
||||||
>
|
>
|
||||||
|
<FALayers class="fa-scale-110 fa-old-padding-layer">
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110"
|
||||||
:title="$t('tool_tip.favorite')"
|
|
||||||
:icon="['far', 'star']"
|
:icon="['far', 'star']"
|
||||||
/>
|
/>
|
||||||
|
<FAIcon
|
||||||
|
class="focus-marker"
|
||||||
|
transform="shrink-6 up-9 right-12"
|
||||||
|
icon="plus"
|
||||||
|
/>
|
||||||
|
</FALayers>
|
||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
||||||
|
@ -58,7 +65,6 @@
|
||||||
<script src="./favorite_button.js"></script>
|
<script src="./favorite_button.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
@import "../../mixins";
|
@import "../../mixins";
|
||||||
|
|
||||||
.FavoriteButton {
|
.FavoriteButton {
|
||||||
|
@ -81,8 +87,7 @@
|
||||||
|
|
||||||
&:hover .svg-inline--fa,
|
&:hover .svg-inline--fa,
|
||||||
&.-favorited .svg-inline--fa {
|
&.-favorited .svg-inline--fa {
|
||||||
color: $fallback--cOrange;
|
color: var(--cOrange);
|
||||||
color: var(--cOrange, $fallback--cOrange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include unfocused-style {
|
@include unfocused-style {
|
||||||
|
|
|
@ -42,8 +42,6 @@
|
||||||
<script src="./flash.js"></script>
|
<script src="./flash.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.Flash {
|
.Flash {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,63 +1,59 @@
|
||||||
import { set } from 'lodash'
|
|
||||||
import Select from '../select/select.vue'
|
import Select from '../select/select.vue'
|
||||||
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
import Popover from 'src/components/popover/popover.vue'
|
||||||
|
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
faExclamationTriangle,
|
||||||
|
faKeyboard,
|
||||||
|
faFont
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(
|
||||||
|
faExclamationTriangle,
|
||||||
|
faKeyboard,
|
||||||
|
faFont
|
||||||
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Select
|
Select,
|
||||||
|
Checkbox,
|
||||||
|
Popover
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
|
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
|
||||||
],
|
],
|
||||||
|
mounted () {
|
||||||
|
this.$store.dispatch('queryLocalFonts')
|
||||||
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
lValue: this.modelValue,
|
manualEntry: false,
|
||||||
availableOptions: [
|
availableOptions: [
|
||||||
this.noInherit ? '' : 'inherit',
|
this.noInherit ? '' : 'inherit',
|
||||||
'custom',
|
|
||||||
...(this.options || []),
|
|
||||||
'serif',
|
'serif',
|
||||||
|
'sans-serif',
|
||||||
'monospace',
|
'monospace',
|
||||||
'sans-serif'
|
...(this.options || [])
|
||||||
].filter(_ => _)
|
].filter(_ => _)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
methods: {
|
||||||
this.lValue = this.modelValue
|
toggleManualEntry () {
|
||||||
|
this.manualEntry = !this.manualEntry
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
present () {
|
present () {
|
||||||
return typeof this.lValue !== 'undefined'
|
return typeof this.modelValue !== 'undefined'
|
||||||
},
|
},
|
||||||
dValue () {
|
localFontsList () {
|
||||||
return this.lValue || this.fallback || {}
|
return this.$store.state.interface.localFonts
|
||||||
},
|
},
|
||||||
family: {
|
localFontsSize () {
|
||||||
get () {
|
return this.$store.state.interface.localFonts?.length
|
||||||
return this.dValue.family
|
|
||||||
},
|
|
||||||
set (v) {
|
|
||||||
set(this.lValue, 'family', v)
|
|
||||||
this.$emit('update:modelValue', this.lValue)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isCustom () {
|
|
||||||
return this.preset === 'custom'
|
|
||||||
},
|
|
||||||
preset: {
|
|
||||||
get () {
|
|
||||||
if (this.family === 'serif' ||
|
|
||||||
this.family === 'sans-serif' ||
|
|
||||||
this.family === 'monospace' ||
|
|
||||||
this.family === 'inherit') {
|
|
||||||
return this.family
|
|
||||||
} else {
|
|
||||||
return 'custom'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set (v) {
|
|
||||||
this.family = v === 'custom' ? '' : v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +1,146 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="font-control style-control"
|
class="font-control"
|
||||||
:class="{ custom: isCustom }"
|
:class="{ custom: isCustom }"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
:id="name + '-label'"
|
||||||
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
class="label"
|
class="label"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
{{ ' ' }}
|
||||||
|
<Checkbox
|
||||||
v-if="typeof fallback !== 'undefined'"
|
v-if="typeof fallback !== 'undefined'"
|
||||||
:id="name + '-o'"
|
:id="name + '-o'"
|
||||||
class="opt exlcude-disabled"
|
:modelValue="present"
|
||||||
type="checkbox"
|
|
||||||
:checked="present"
|
|
||||||
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
|
||||||
>
|
>
|
||||||
|
{{ $t('settings.style.themes3.define') }}
|
||||||
|
</Checkbox>
|
||||||
|
<p v-if="modelValue?.family">
|
||||||
<label
|
<label
|
||||||
v-if="typeof fallback !== 'undefined'"
|
v-if="manualEntry"
|
||||||
class="opt-l"
|
:id="name + '-label'"
|
||||||
:for="name + '-o'"
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
/>
|
class="label"
|
||||||
|
>
|
||||||
|
<i18n-t
|
||||||
|
keypath="settings.style.themes3.font.entry"
|
||||||
|
tag="span"
|
||||||
|
>
|
||||||
|
<template #fontFamily>
|
||||||
|
<code>font-family</code>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
v-else
|
||||||
|
:id="name + '-label'"
|
||||||
|
:for="preset === 'custom' ? name : name + '-font-switcher'"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
{{ $t('settings.style.themes3.font.select') }}
|
||||||
|
</label>
|
||||||
{{ ' ' }}
|
{{ ' ' }}
|
||||||
|
<span
|
||||||
|
v-if="manualEntry"
|
||||||
|
class="btn-group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click="toggleManualEntry"
|
||||||
|
:title="$t('settings.style.themes3.font.lookup_local_fonts')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="font"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
:id="name"
|
||||||
|
:model-value="modelValue.family"
|
||||||
|
class="input custom-font"
|
||||||
|
type="text"
|
||||||
|
@update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="btn-group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn button-default"
|
||||||
|
@click="toggleManualEntry"
|
||||||
|
:title="$t('settings.style.themes3.font.enter_manually')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="keyboard"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
<Select
|
<Select
|
||||||
:id="name + '-font-switcher'"
|
:id="name + '-local-font-switcher'"
|
||||||
v-model="preset"
|
:model-value="modelValue?.family"
|
||||||
:disabled="!present"
|
class="custom-font"
|
||||||
class="font-switcher"
|
@update:modelValue="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
|
||||||
|
>
|
||||||
|
<optgroup
|
||||||
|
:label="$t('settings.style.themes3.font.group-builtin')"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="option in availableOptions"
|
v-for="option in availableOptions"
|
||||||
:key="option"
|
:key="option"
|
||||||
:value="option"
|
:value="option"
|
||||||
|
:style="{ fontFamily: option === 'inherit' ? null : option }"
|
||||||
>
|
>
|
||||||
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
|
{{ $t('settings.style.themes3.font.builtin.' + option) }}
|
||||||
</option>
|
</option>
|
||||||
</Select>
|
</optgroup>
|
||||||
<input
|
<optgroup
|
||||||
v-if="isCustom"
|
v-if="localFontsSize > 0"
|
||||||
:id="name"
|
:label="$t('settings.style.themes3.font.group-local')"
|
||||||
v-model="family"
|
|
||||||
class="custom-font"
|
|
||||||
type="text"
|
|
||||||
>
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in localFontsList"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
:style="{ fontFamily: option }"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup
|
||||||
|
v-else
|
||||||
|
:label="$t('settings.style.themes3.font.group-local')"
|
||||||
|
>
|
||||||
|
<option disabled>
|
||||||
|
{{ $t('settings.style.themes3.font.local-unavailable1') }}
|
||||||
|
</option>
|
||||||
|
<option disabled>
|
||||||
|
{{ $t('settings.style.themes3.font.local-unavailable2') }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
</Select>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./font_control.js"></script>
|
<script src="./font_control.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.font-control {
|
.font-control {
|
||||||
input.custom-font {
|
|
||||||
min-width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.custom {
|
|
||||||
/* TODO Should make proper joiners... */
|
|
||||||
.font-switcher {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-font {
|
.custom-font {
|
||||||
border-top-left-radius: 0;
|
min-width: 20em;
|
||||||
border-bottom-left-radius: 0;
|
max-width: 20em;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invalid-tooltip {
|
||||||
|
margin: 0.5em 1em;
|
||||||
|
min-width: 10em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
40
src/components/fun_text.style.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
export default {
|
||||||
|
name: 'FunText',
|
||||||
|
selector: '/*fun-text*/',
|
||||||
|
virtual: true,
|
||||||
|
variants: {
|
||||||
|
greentext: '.greentext',
|
||||||
|
cyantext: '.cyantext'
|
||||||
|
},
|
||||||
|
states: {
|
||||||
|
faint: '.faint'
|
||||||
|
},
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
directives: {
|
||||||
|
textColor: '--text',
|
||||||
|
textAuto: 'preserve'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: ['faint'],
|
||||||
|
directives: {
|
||||||
|
textOpacity: 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'greentext',
|
||||||
|
directives: {
|
||||||
|
textColor: '--cGreen',
|
||||||
|
textAuto: 'preserve'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'cyantext',
|
||||||
|
directives: {
|
||||||
|
textColor: '--cBlue',
|
||||||
|
textAuto: 'preserve'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -87,8 +87,6 @@
|
||||||
<script src='./gallery.js'></script>
|
<script src='./gallery.js'></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.Gallery {
|
.Gallery {
|
||||||
.gallery-rows {
|
.gallery-rows {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
v-for="(notice, index) in notices"
|
v-for="(notice, index) in notices"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="alert global-notice"
|
class="alert global-notice"
|
||||||
:class="{ ['global-' + notice.level]: true }"
|
:class="{ [notice.level]: true }"
|
||||||
>
|
>
|
||||||
<div class="notice-message">
|
<div class="notice-message">
|
||||||
{{ $t(notice.messageKey, notice.messageArgs) }}
|
{{ $t(notice.messageKey, notice.messageArgs) }}
|
||||||
|
@ -25,14 +25,12 @@
|
||||||
<script src="./global_notice_list.js"></script>
|
<script src="./global_notice_list.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../variables";
|
|
||||||
|
|
||||||
.global-notice-list {
|
.global-notice-list {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: calc(var(--navbar-height) + 0.5em);
|
top: calc(var(--navbar-height) + 0.5em);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: var(--ZI_navbar_popovers);
|
z-index: var(--ZI_modals_popovers);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -52,48 +50,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-error {
|
|
||||||
background-color: var(--alertPopupError, $fallback--cRed);
|
|
||||||
color: var(--alertPopupErrorText, $fallback--text);
|
|
||||||
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: var(--alertPopupErrorText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-warning {
|
|
||||||
background-color: var(--alertPopupWarning, $fallback--cOrange);
|
|
||||||
color: var(--alertPopupWarningText, $fallback--text);
|
|
||||||
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: var(--alertPopupWarningText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-success {
|
|
||||||
background-color: var(--alertPopupSuccess, $fallback--cGreen);
|
|
||||||
color: var(--alertPopupSuccessText, $fallback--text);
|
|
||||||
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: var(--alertPopupSuccessText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-info {
|
|
||||||
background-color: var(--alertPopupNeutral, $fallback--fg);
|
|
||||||
color: var(--alertPopupNeutralText, $fallback--text);
|
|
||||||
|
|
||||||
.svg-inline--fa {
|
|
||||||
color: var(--alertPopupNeutralText, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-notice {
|
.close-notice {
|
||||||
padding-right: 0.2em;
|
padding-right: 0.2em;
|
||||||
|
|
||||||
.svg-inline--fa:hover {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
14
src/components/icon.style.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export default {
|
||||||
|
name: 'Icon',
|
||||||
|
virtual: true,
|
||||||
|
selector: '.svg-inline--fa',
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
component: 'Icon',
|
||||||
|
directives: {
|
||||||
|
textColor: '$blend(--stack, 0.5, --parent--text)',
|
||||||
|
textAuto: 'no-auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -41,7 +41,7 @@
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
type="file"
|
type="file"
|
||||||
class="image-cropper-img-input"
|
class="input image-cropper-img-input"
|
||||||
:accept="mimes"
|
:accept="mimes"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<form>
|
<form>
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
|
class="input"
|
||||||
type="file"
|
type="file"
|
||||||
@change="change"
|
@change="change"
|
||||||
>
|
>
|
||||||
|
|
89
src/components/input.style.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
const hoverGlow = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
blur: 4,
|
||||||
|
spread: 0,
|
||||||
|
color: '--text',
|
||||||
|
alpha: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Input',
|
||||||
|
selector: '.input',
|
||||||
|
states: {
|
||||||
|
hover: ':hover:not(.disabled)',
|
||||||
|
focused: ':focus-within',
|
||||||
|
disabled: '.disabled'
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
checkbox: '.-checkbox',
|
||||||
|
radio: '.-radio'
|
||||||
|
},
|
||||||
|
validInnerComponents: [
|
||||||
|
'Text',
|
||||||
|
'Icon'
|
||||||
|
],
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
component: 'Root',
|
||||||
|
directives: {
|
||||||
|
'--defaultInputBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2), $borderSide(#000000, top, 0.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'checkbox',
|
||||||
|
directives: {
|
||||||
|
roundness: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
directives: {
|
||||||
|
'--font': 'generic | inherit',
|
||||||
|
background: '--fg, -5',
|
||||||
|
roundness: 3,
|
||||||
|
shadow: [{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
blur: 2,
|
||||||
|
spread: 0,
|
||||||
|
color: '#000000',
|
||||||
|
alpha: 1
|
||||||
|
}, '--defaultInputBevel']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: ['hover'],
|
||||||
|
directives: {
|
||||||
|
shadow: [hoverGlow, '--defaultInputBevel']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: ['disabled'],
|
||||||
|
directives: {
|
||||||
|
background: '--parent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Text',
|
||||||
|
parent: {
|
||||||
|
component: 'Input',
|
||||||
|
state: ['disabled']
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
textOpacity: 0.25,
|
||||||
|
textOpacityMode: 'blend'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Icon',
|
||||||
|
parent: {
|
||||||
|
component: 'Input',
|
||||||
|
state: ['disabled']
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
textOpacity: 0.25,
|
||||||
|
textOpacityMode: 'blend'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||||
|
|
||||||
const tabModeDict = {
|
const tabModeDict = {
|
||||||
mentions: ['mention'],
|
mentions: ['mention'],
|
||||||
|
statuses: ['status'],
|
||||||
'likes+repeats': ['repeat', 'like'],
|
'likes+repeats': ['repeat', 'like'],
|
||||||
follows: ['follow'],
|
follows: ['follow'],
|
||||||
reactions: ['pleroma:emoji_reaction'],
|
reactions: ['pleroma:emoji_reaction'],
|
||||||
|
|