Merge remote-tracking branch 'origin/develop' into renovate/karma-webpack-5.x

This commit is contained in:
Henry Jameson 2025-01-20 03:01:54 +02:00
commit 0885f5cac2
426 changed files with 19315 additions and 6684 deletions

7
.browserslistrc Normal file
View file

@ -0,0 +1,7 @@
>0.2%
not op_mini all
Safari > 15
Firefox >= 115
Firefox ESR
Android > 4
not dead

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ selenium-debug.log
.idea/ .idea/
config/local.json config/local.json
static/emoji.json static/emoji.json
logs/

View file

@ -38,11 +38,14 @@ lint:
stage: lint stage: lint
script: script:
- yarn - yarn
- npm run lint - yarn lint
- npm run stylelint - yarn stylelint
test: test:
stage: test stage: test
tags:
- amd64
- himem
variables: variables:
APT_CACHE_DIR: apt-cache APT_CACHE_DIR: apt-cache
script: script:
@ -54,9 +57,12 @@ test:
build: build:
stage: build stage: build
tags:
- amd64
- himem
script: script:
- yarn - yarn
- npm run build - yarn build
artifacts: artifacts:
paths: paths:
- dist/ - dist/

View file

@ -3,6 +3,74 @@ 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 ## 2.6.1
### Fixed ### Fixed
- fix admin dashboard not having any feedback on frontend installation - fix admin dashboard not having any feedback on frontend installation

View file

@ -11,11 +11,6 @@ var versionRequirements = [
name: 'node', name: 'node',
currentVersion: semver.clean(process.version), currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node versionRequirement: packageConfig.engines.node
},
{
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
} }
] ]

View file

@ -1 +0,0 @@
Make Pleroma FE to also view apng (Animated PNG) attachment.

View file

@ -1 +0,0 @@
Added emoji pack management to the admin panel

View file

0
changelog.d/batch2.skip Normal file
View file

View file

@ -0,0 +1 @@
Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name.

View file

@ -0,0 +1 @@
Support bookmark folders

View 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/checkbox.fix Normal file
View file

@ -0,0 +1 @@
checkbox vertical alignment has been fixed

View file

@ -0,0 +1 @@
Some new default color schemes

View file

@ -0,0 +1 @@
Fix some of the color manipulation functions

View file

@ -1 +0,0 @@
Create a link to the URL of the scrobble when it's present

1
changelog.d/custom.add Normal file
View file

@ -0,0 +1 @@
Added support for fetching /{resource}.custom.ext to allow adding instance-specific themes without altering sourcetree

View file

@ -0,0 +1 @@
Support displaying time in absolute format

View file

View file

@ -0,0 +1 @@
Use /api/v1/accounts/:id/follow for account subscriptions instead of the deprecated routes

View file

@ -1 +0,0 @@
Fix native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.

View file

1
changelog.d/drafts.add Normal file
View file

@ -0,0 +1 @@
Add draft management system

View file

@ -0,0 +1 @@
fixed occasional overflows in emoji picker and made header scrollable

View file

@ -0,0 +1 @@
fix emoji inconsistencies in notifications, fix some emoji not scaling with interface

View file

@ -1 +0,0 @@
Support showing extra notifications in the notifications column

View file

@ -1 +0,0 @@
Focusing into a tab clears all current desktop notifications

View file

@ -1 +0,0 @@
Support group actors

View file

@ -1 +0,0 @@
Allow hiding custom emojis in picker.

View file

@ -0,0 +1 @@
Fix small markup inconsistencies

View file

@ -1 +0,0 @@
Fixed error that appeared on mobile Chrome(ium) (and derivatives) when native notifications are allowed

View file

@ -1 +0,0 @@
Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.

View file

@ -0,0 +1 @@
modal layout for mobile has new layout to make it easy to use

1
changelog.d/modals.fix Normal file
View file

@ -0,0 +1 @@
fixed modals buttons overflow

View file

@ -1 +0,0 @@
Fixed being unable to set notification visibility for reports and follow requests

View file

@ -0,0 +1 @@
Fix whitespaces for multiple status mute reasons, display bot status reason

View file

@ -0,0 +1 @@
Added missing EN translation key for status.muted_user

1
changelog.d/mutes.change Normal file
View file

@ -0,0 +1 @@
better display of mute reason on posts

View file

@ -1 +0,0 @@
Added option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.

View file

@ -1 +0,0 @@
Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)

View file

View file

@ -0,0 +1 @@
Inform users that Smithereen public polls are public

View file

@ -1 +0,0 @@
The expiry date indication won't be shown if the poll never expires

View file

@ -1 +0,0 @@
Added option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)

View file

@ -1 +0,0 @@
Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.

View file

@ -1 +0,0 @@
Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.

View file

@ -0,0 +1 @@
Simplify the OAuth client_name to 'PleromaFE'

View file

@ -0,0 +1 @@
proper sticky header for conversations on user page

View file

View file

View file

@ -1 +0,0 @@
Display public favorites on user profiles

View file

@ -0,0 +1 @@
reply-or-quote buttons now take less space

View file

@ -1 +0,0 @@
Show a dedicated registration notice page when further action is required after registering

View file

@ -1 +0,0 @@
Notifications are now shown through a serviceworker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.

View file

@ -0,0 +1 @@
Bookmarks visible again on mobile

View file

@ -1 +0,0 @@
Shows the most recent scrobble under each post when available

View file

View file

@ -0,0 +1 @@
Splash screen + loading indicator to make process of identifying initialization issues and load performance

View file

@ -0,0 +1 @@
Authenticate and subscribe to streaming after connection

1
changelog.d/tabs.change Normal file
View file

@ -0,0 +1 @@
Tabs now have indentation for better visibility of which tab is currently active

1
changelog.d/themes3.add Normal file
View file

@ -0,0 +1 @@
UI for making v3 themes and palettes, support for bundling v3 themes

View file

@ -1 +0,0 @@
unread notifications should now properly catch up (eventually) in polling mode

View file

@ -0,0 +1 @@
Resize most kinds of images on upload.

View file

@ -0,0 +1 @@
Make UserLink wrappable

View file

@ -1 +0,0 @@
Video posters on Safari

View file

@ -1 +0,0 @@
nothing

1
changelog.d/vue.change Normal file
View file

@ -0,0 +1 @@
Upgraded Vue to version 3.5

View file

View file

@ -1 +0,0 @@
Added option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)

View file

@ -0,0 +1 @@
Show only month and day instead of weird "day, hour" format. While at it, fixed typo "defualt" in a comment.

View file

@ -3,12 +3,163 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<link rel="icon" type="image/png" href="/favicon.png"> <!-- 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";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
}
.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;
}
#statusError {
display: none;
margin-top: 1em;
font-size: calc(1vw + 1vh + 1vmin);
line-height: 2;
width: 100%;
text-align: center;
}
#statusStack {
display: none;
margin-top: 1em;
font-size: calc((1vw + 1vh + 1vmin) / 2.5);
width: calc(100vw - 5em);
padding: 1em;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
line-height: 2;
}
@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--> <!--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">(。&gt;&lt;)</span>
</div>
<code id="statusError"></code>
<pre id="statusStack"></pre>
</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" />

View file

@ -1,6 +1,6 @@
{ {
"name": "pleroma_fe", "name": "pleroma_fe",
"version": "2.6.1", "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,
@ -10,13 +10,13 @@
"unit": "karma start test/unit/karma.conf.js --single-run", "unit": "karma start test/unit/karma.conf.js --single-run",
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false", "unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js", "e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e", "test": "yarn run unit && yarn run e2e",
"stylelint": "npx stylelint '**/*.scss' '**/*.vue'", "stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs", "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.21.5", "@babel/runtime": "7.26.0",
"@chenfengyuan/vue-qrcode": "2.0.0", "@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "6.4.0", "@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-regular-svg-icons": "6.4.0",
@ -24,55 +24,56 @@
"@fortawesome/vue-fontawesome": "3.0.3", "@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.2024.3.17", "@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@vuelidate/core": "2.0.3", "@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.4", "@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.13", "cropperjs": "1.6.2",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"hash-sum": "^2.0.0",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"localforage": "1.10.0", "localforage": "1.10.0",
"pako": "^2.1.0",
"parse-link-header": "2.0.0", "parse-link-header": "2.0.0",
"phoenix": "1.7.7", "phoenix": "1.7.18",
"punycode.js": "2.3.0", "punycode.js": "2.3.1",
"qrcode": "1.5.3", "qrcode": "1.5.4",
"querystring-es3": "0.2.1", "querystring-es3": "0.2.1",
"url": "0.11.0", "url": "0.11.4",
"utf8": "3.0.0", "utf8": "3.0.0",
"vue": "3.2.45", "vue": "3.5.13",
"vue-i18n": "9.2.2", "vue-i18n": "10",
"vue-router": "4.1.6", "vue-router": "4.5.0",
"vue-template-compiler": "2.7.14",
"vue-virtual-scroller": "^2.0.0-beta.7", "vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0" "vuex": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.21.8", "@babel/core": "7.26.0",
"@babel/eslint-parser": "7.21.8", "@babel/eslint-parser": "7.26.5",
"@babel/plugin-transform-runtime": "7.21.4", "@babel/plugin-transform-runtime": "7.25.9",
"@babel/preset-env": "7.21.5", "@babel/preset-env": "7.26.0",
"@babel/register": "7.21.0", "@babel/register": "7.25.9",
"@intlify/vue-i18n-loader": "5.0.1", "@intlify/vue-i18n-loader": "5.0.1",
"@ungap/event-target": "0.2.4", "@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.2.1", "@vue/babel-plugin-jsx": "1.2.5",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.5.13",
"@vue/test-utils": "2.2.8", "@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.19", "autoprefixer": "10.4.20",
"babel-loader": "9.1.3", "babel-loader": "9.2.1",
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
"chai": "4.3.7", "chai": "4.5.0",
"chalk": "1.1.3", "chalk": "1.1.3",
"chromedriver": "108.0.0", "chromedriver": "108.0.0",
"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.6",
"css-loader": "6.10.0", "css-loader": "6.11.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.33.0", "eslint": "8.57.1",
"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",
@ -81,15 +82,15 @@
"eslint-plugin-vue": "9.9.0", "eslint-plugin-vue": "9.9.0",
"eslint-webpack-plugin": "3.2.0", "eslint-webpack-plugin": "3.2.0",
"eventsource-polyfill": "0.9.6", "eventsource-polyfill": "0.9.6",
"express": "4.18.2", "express": "4.21.2",
"function-bind": "1.1.1", "function-bind": "1.1.2",
"html-webpack-plugin": "5.5.1", "html-webpack-plugin": "5.5.1",
"http-proxy-middleware": "2.0.6", "http-proxy-middleware": "2.0.7",
"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.2", "karma": "6.4.4",
"karma-coverage": "2.2.0", "karma-coverage": "2.2.1",
"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",
@ -108,7 +109,7 @@
"postcss-scss": "^4.0.6", "postcss-scss": "^4.0.6",
"sass": "1.60.0", "sass": "1.60.0",
"sass-loader": "13.2.2", "sass-loader": "13.2.2",
"selenium-server": "2.53.1", "selenium-server": "3.141.59",
"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",
@ -129,7 +130,7 @@
"webpack-merge": "0.20.0" "webpack-merge": "0.20.0"
}, },
"engines": { "engines": {
"node": ">= 16.0.0", "node": ">= 16.0.0"
"npm": ">= 3.0.0" },
} "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

0
preview.style.js Normal file
View file

View file

@ -44,16 +44,36 @@ export default {
data: () => ({ data: () => ({
mobileActivePanel: 'timeline' mobileActivePanel: 'timeline'
}), }),
watch: {
themeApplied (value) {
this.removeSplash()
},
layoutType (value) {
document.getElementById('modal').classList = ['-' + this.layoutType]
}
},
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)
document.getElementById('modal').classList = ['-' + this.layoutType]
},
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
},
layoutModalClass () {
return '-' + this.layoutType
},
classes () { classes () {
return [ return [
{ {
@ -130,6 +150,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')
} }
} }
} }

View file

@ -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,123 @@ 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, .menu-item {
i { line-height: var(--__line-height);
color: $fallback--text; font-family: inherit;
color: var(--btnDisabledText, $fallback--text); font-weight: 400;
} font-size: 100%;
cursor: pointer;
a,
button:not(.button-default) {
color: var(--text);
font-size: 100%;
} }
&.toggled { &.disabled {
color: $fallback--text; cursor: not-allowed;
color: var(--btnToggledText, $fallback--text); }
background-color: $fallback--fg; }
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, .menu-item,
i { .list-item {
color: $fallback--text; display: block;
color: var(--btnToggledText, $fallback--text); box-sizing: border-box;
} border: none;
outline: none;
text-align: initial;
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%;
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;
} }
&.danger { &.-active,
// TODO: add better color variable &:hover {
color: $fallback--text; border-top-width: 1px;
color: var(--alertErrorPanelText, $fallback--text); border-bottom-width: 1px;
background-color: $fallback--alertError; }
background-color: var(--alertError, $fallback--alertError);
&.-active + &,
&:hover + & {
border-top-width: 0;
}
&: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-family: inherit;
line-height: unset;
}
&: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 +496,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 +520,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 +537,6 @@ textarea,
&[disabled="disabled"], &[disabled="disabled"],
&.disabled { &.disabled {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5;
} }
&[type="range"] { &[type="range"] {
@ -543,9 +551,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 +567,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;
@ -581,8 +587,9 @@ textarea,
&[type="checkbox"] { &[type="checkbox"] {
&: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 {
@ -600,13 +607,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;
@ -622,17 +625,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 {
@ -653,7 +665,7 @@ option {
li { li {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--inputRadius); border-radius: var(--roundness);
padding: 0.5em; padding: 0.5em;
margin: 0.25em; margin: 0.25em;
} }
@ -669,22 +681,23 @@ option {
display: inline-flex; display: inline-flex;
vertical-align: middle; vertical-align: middle;
button, > *,
.button-dropdown { > * .button-default {
--_roundness-left: 0;
--_roundness-right: 0;
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
}
&:not(:last-child), > *:first-child,
&:not(:last-child) .button-default { > *:first-child .button-default {
border-top-right-radius: 0; --_roundness-left: var(--roundness);
border-bottom-right-radius: 0; }
}
&:not(:first-child), > *:last-child,
&:not(:first-child) .button-default { > *:last-child .button-default {
border-top-left-radius: 0; --_roundness-right: var(--roundness);
border-bottom-left-radius: 0;
}
} }
} }
@ -714,74 +727,64 @@ 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;
position: absolute;
}
&.-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;
}
&.-neutral {
background-color: var(--badgeNeutral);
color: white; color: white;
color: var(--badgeNotificationText, white); color: var(--badgeNeutralText, white);
} }
} }
.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 {
@ -802,6 +805,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 {
@ -816,6 +823,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;
@ -914,3 +926,174 @@ option {
padding: 0; padding: 0;
position: absolute; 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;
}
}
}
}

View file

@ -1,5 +1,6 @@
<template> <template>
<div <div
v-show="$store.state.interface.themeApplied"
id="app-loaded" id="app-loaded"
:style="bgStyle" :style="bgStyle"
> >
@ -69,7 +70,7 @@
<PostStatusModal /> <PostStatusModal />
<EditStatusModal v-if="editingAvailable" /> <EditStatusModal v-if="editingAvailable" />
<StatusHistoryModal v-if="editingAvailable" /> <StatusHistoryModal v-if="editingAvailable" />
<SettingsModal /> <SettingsModal :class="layoutModalClass" />
<UpdateNotification /> <UpdateNotification />
<GlobalNoticeList /> <GlobalNoticeList />
</div> </div>

View file

@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 35 B

View file

@ -0,0 +1 @@
../../static/pleromatan_apology.png

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 35 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 KiB

After

Width:  |  Height:  |  Size: 39 B

View file

@ -0,0 +1 @@
../../static/pleromatan_apology_fox.png

Before

Width:  |  Height:  |  Size: 521 KiB

After

Width:  |  Height:  |  Size: 39 B

View file

@ -13,8 +13,7 @@ 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' import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
@ -123,6 +122,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
store.dispatch('setInstanceOption', { name, value: config[name] }) store.dispatch('setInstanceOption', { name, value: config[name] })
} }
copyInstanceOption('theme')
copyInstanceOption('style')
copyInstanceOption('palette')
copyInstanceOption('nsfwCensorImage') copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background') copyInstanceOption('background')
copyInstanceOption('hidePostStats') copyInstanceOption('hidePostStats')
@ -160,8 +162,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 }) => {
@ -243,7 +243,7 @@ const resolveStaffAccounts = ({ store, accounts }) => {
const getNodeInfo = async ({ store }) => { const getNodeInfo = async ({ store }) => {
try { try {
const res = await preloadFetch('/nodeinfo/2.0.json') const res = await preloadFetch('/nodeinfo/2.1.json')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
const metadata = data.metadata const metadata = data.metadata
@ -255,6 +255,7 @@ const getNodeInfo = async ({ store }) => {
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: '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') })
@ -279,6 +280,7 @@ const getNodeInfo = async ({ store }) => {
const software = data.software const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' }) store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private const priv = metadata.private
@ -328,17 +330,10 @@ const setConfig = async ({ store }) => {
} }
const checkOAuthToken = async ({ store }) => { const checkOAuthToken = async ({ store }) => {
// eslint-disable-next-line no-async-promise-executor if (store.getters.getUserToken()) {
return new Promise(async (resolve, reject) => { return store.dispatch('loginUser', store.getters.getUserToken())
if (store.getters.getUserToken()) { }
try { return Promise.resolve()
await store.dispatch('loginUser', store.getters.getUserToken())
} catch (e) {
console.error(e)
}
}
resolve()
})
} }
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ store, i18n }) => {
@ -355,24 +350,14 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setInstanceOption', { name: 'server', value: server }) store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store }) await setConfig({ store })
try {
const { customTheme, customThemeSource } = store.state.config await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) })
const { theme } = store.state.instance } catch (e) {
const customThemePresent = customThemeSource || customTheme window.splashError(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
@ -381,7 +366,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
getInstancePanel({ store }), getInstancePanel({ store }),
getNodeInfo({ store }), getNodeInfo({ store }),
getInstanceConfig({ store }) getInstanceConfig({ store })
]) ]).catch(e => Promise.reject(e))
await store.dispatch('loadDrafts')
// 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')
@ -406,6 +393,13 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.use(store) app.use(store)
app.use(i18n) app.use(i18n)
// Little thing to get out of invalid theme state
window.resetThemes = () => {
store.dispatch('resetThemeV3')
store.dispatch('resetThemeV3Palette')
store.dispatch('resetThemeV2')
}
app.use(vClickOutside) app.use(vClickOutside)
app.use(VBodyScrollLock) app.use(VBodyScrollLock)
app.use(VueVirtualScroller) app.use(VueVirtualScroller)
@ -417,7 +411,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
app.config.unwrapInjectedRef = true app.config.unwrapInjectedRef = true
app.mount('#app') app.mount('#app')
return app return app
} }

View file

@ -25,6 +25,10 @@ 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 Drafts from 'components/drafts/drafts.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 +55,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([^/@]+)',
@ -78,13 +83,18 @@ export default (store) => {
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About }, { name: 'about', path: '/about', component: About },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage }, { name: 'announcements', path: '/announcements', component: AnnouncementsPage },
{ name: 'drafts', path: '/drafts', component: Drafts },
{ name: 'user-profile', path: '/users/:name', component: UserProfile }, { name: 'user-profile', path: '/users/:name', component: UserProfile },
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile }, { name: 'legacy-user-profile', path: '/:name', component: UserProfile },
{ name: 'lists', path: '/lists', component: Lists }, { name: 'lists', path: '/lists', component: Lists },
{ 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) {

View file

@ -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') }}
@ -86,6 +86,7 @@
<i18n-t <i18n-t
keypath="user_card.block_confirm" keypath="user_card.block_confirm"
tag="span" tag="span"
scope="global"
> >
<template #user> <template #user>
<span <span
@ -107,6 +108,7 @@
<i18n-t <i18n-t
keypath="user_card.remove_follower_confirm" keypath="user_card.remove_follower_confirm"
tag="span" tag="span"
scope="global"
> >
<template #user> <template #user>
<span <span
@ -122,19 +124,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>

View file

@ -0,0 +1,57 @@
export default {
name: 'Alert',
selector: '.alert',
validInnerComponents: [
'Text',
'Icon',
'Link',
'Border',
'ButtonUnstyled'
],
variants: {
normal: '.neutral',
error: '.error',
warning: '.warning',
success: '.success'
},
editor: {
border: 1,
aspect: '3 / 1'
},
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'
}
}
]
}

View file

@ -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 {

View file

@ -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"
> >
@ -32,8 +34,9 @@
id="announcement-all-day" id="announcement-all-day"
v-model="announcement.allDay" v-model="announcement.allDay"
:disabled="disabled" :disabled="disabled"
/> >
<label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label> {{ $t('announcements.all_day_prompt') }}
</Checkbox>
</span> </span>
</div> </div>
</template> </template>

View file

@ -1,9 +1,9 @@
<template> <template>
<div class="panel panel-default announcements-page"> <div class="panel panel-default announcements-page">
<div class="panel-heading"> <div class="panel-heading">
<span> <h1 class="title">
{{ $t('announcements.page_header') }} {{ $t('announcements.page_header') }}
</span> </h1>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<section <section
@ -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 {

View file

@ -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;

View file

@ -0,0 +1,25 @@
export default {
name: 'Attachment',
selector: '.Attachment',
notEditable: true,
validInnerComponents: [
'Border',
'ButtonUnstyled',
'Input'
],
defaultRules: [
{
directives: {
roundness: 3
}
},
{
component: 'ButtonUnstyled',
parent: { component: 'Attachment' },
directives: {
background: '#FFFFFF',
opacity: 0.5
}
}
]
}

View file

@ -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=""
> >

View file

@ -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;
} }

View file

@ -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;
} }

View 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'
}
}
]
}

View file

@ -47,9 +47,8 @@
display: flex; display: flex;
flex: 1 0; flex: 1 0;
margin: 0; margin: 0;
padding: 0.6em 1em;
--emoji-size: 14px; --emoji-size: 1em;
&-collapsed-content { &-collapsed-content {
margin-left: 0.7em; margin-left: 0.7em;

View 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

View 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>

View 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

View file

@ -0,0 +1,200 @@
<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>
<h1 class="title">
<i18n-t
v-if="id"
keypath="bookmark_folders.editing_folder"
scope="global"
>
<template #folderName>
{{ name }}
</template>
</i18n-t>
<i18n-t
v-else
keypath="bookmark_folders.creating_folder"
scope="global"
/>
</h1>
</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>

View 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

Some files were not shown because too many files have changed in this diff Show more