Merge branch 'develop' into 'tusooa/save-draft'
# Conflicts: # src/boot/routes.js # src/i18n/en.json # src/main.js # src/modules/config.js # src/modules/instance.js
7
.browserslistrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
>0.2%
|
||||
not op_mini all
|
||||
Safari > 15
|
||||
Firefox >= 115
|
||||
Firefox ESR
|
||||
Android > 4
|
||||
not dead
|
|
@ -45,6 +45,7 @@ test:
|
|||
stage: test
|
||||
tags:
|
||||
- amd64
|
||||
- himem
|
||||
variables:
|
||||
APT_CACHE_DIR: apt-cache
|
||||
script:
|
||||
|
@ -58,6 +59,7 @@ build:
|
|||
stage: build
|
||||
tags:
|
||||
- amd64
|
||||
- himem
|
||||
script:
|
||||
- yarn
|
||||
- npm run build
|
||||
|
|
68
CHANGELOG.md
|
@ -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/).
|
||||
|
||||
## 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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Make Pleroma FE to also view apng (Animated PNG) attachment.
|
|
@ -1 +0,0 @@
|
|||
Added emoji pack management to the admin panel
|
|
@ -1 +0,0 @@
|
|||
Reorganized Settings modal to move out visual stuff into Appearance tab
|
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/checkbox.fix
Normal file
|
@ -0,0 +1 @@
|
|||
checkbox vertical alignment has been fixed
|
|
@ -1 +0,0 @@
|
|||
stop using that one runner for intensive tasks
|
1
changelog.d/colorfuncs.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix some of the color manipulation functions
|
|
@ -1 +0,0 @@
|
|||
Create a link to the URL of the scrobble when it's present
|
1
changelog.d/custom.add
Normal file
|
@ -0,0 +1 @@
|
|||
Added support for fetching /{resource}.custom.ext to allow adding instance-specific themes without altering sourcetree
|
1
changelog.d/date-absolute.add
Normal file
|
@ -0,0 +1 @@
|
|||
Support displaying time in absolute format
|
1
changelog.d/deprecate-subscribe.change
Normal file
|
@ -0,0 +1 @@
|
|||
Use /api/v1/accounts/:id/follow for account subscriptions instead of the deprecated routes
|
|
@ -1 +0,0 @@
|
|||
Fix native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
|
|
@ -1 +0,0 @@
|
|||
Ability to change size of emoji
|
1
changelog.d/emoji-size.fix
Normal file
|
@ -0,0 +1 @@
|
|||
fix emoji inconsistencies in notifications, fix some emoji not scaling with interface
|
|
@ -1 +0,0 @@
|
|||
Support showing extra notifications in the notifications column
|
|
@ -1 +0,0 @@
|
|||
Bug with firefox and redmond themes
|
|
@ -1 +0,0 @@
|
|||
fixed themes for spw and kazvmoew
|
|
@ -1 +0,0 @@
|
|||
fix post appearance tab bugs part I
|
|
@ -1 +0,0 @@
|
|||
Focusing into a tab clears all current desktop notifications
|
|
@ -1 +0,0 @@
|
|||
Support group actors
|
|
@ -1 +0,0 @@
|
|||
Allow hiding custom emojis in picker.
|
1
changelog.d/misc-markup.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix small markup inconsistencies
|
|
@ -1 +0,0 @@
|
|||
Fixed error that appeared on mobile Chrome(ium) (and derivatives) when native notifications are allowed
|
|
@ -1 +0,0 @@
|
|||
Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
|
|
@ -1 +0,0 @@
|
|||
Fixed being unable to set notification visibility for reports and follow requests
|
1
changelog.d/multiple-status-mute-reasons.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix whitespaces for multiple status mute reasons, display bot status reason
|
|
@ -1 +0,0 @@
|
|||
Added ability to mute sensitive posts (ported from eintei)
|
|
@ -1 +0,0 @@
|
|||
Added option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
|
|
@ -1 +0,0 @@
|
|||
Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
|
|
@ -1 +0,0 @@
|
|||
Ensure selection text color has enough contrast
|
1
changelog.d/non-anonymous-polls.add
Normal file
|
@ -0,0 +1 @@
|
|||
Inform users that Smithereen public polls are public
|
|
@ -1 +0,0 @@
|
|||
The expiry date indication won't be shown if the poll never expires
|
|
@ -1 +0,0 @@
|
|||
Added option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
|
|
@ -1 +0,0 @@
|
|||
Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
|
|
@ -1 +0,0 @@
|
|||
Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
|
|
@ -1 +0,0 @@
|
|||
Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
|
1
changelog.d/oauth-app-name.change
Normal file
|
@ -0,0 +1 @@
|
|||
Simplify the OAuth client_name to 'PleromaFE'
|
1
changelog.d/panel-stack.fix
Normal file
|
@ -0,0 +1 @@
|
|||
proper sticky header for conversations on user page
|
0
changelog.d/piss-fix.skip
Normal file
0
changelog.d/piss-serialization.skip
Normal file
|
@ -1 +0,0 @@
|
|||
Add poll end notifications to fetched types.
|
|
@ -1 +0,0 @@
|
|||
skip
|
|
@ -1 +0,0 @@
|
|||
Fix profile mentions causing a 422 error
|
|
@ -1 +0,0 @@
|
|||
Display public favorites on user profiles
|
1
changelog.d/quote-buttons.fix
Normal file
|
@ -0,0 +1 @@
|
|||
reply-or-quote buttons now take less space
|
|
@ -1 +0,0 @@
|
|||
Display quotes count on posts and add quotes list page
|
|
@ -1 +0,0 @@
|
|||
Show a dedicated registration notice page when further action is required after registering
|
|
@ -1 +0,0 @@
|
|||
Option to only show scrobbles that are recent enough
|
|
@ -1 +0,0 @@
|
|||
Notifications are now shown through a serviceworker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
|
1
changelog.d/show-bookmarks-on-mobile.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Bookmarks visible again on mobile
|
|
@ -1 +0,0 @@
|
|||
Shows the most recent scrobble under each post when available
|
0
changelog.d/splashfix.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
|
|
@ -1 +0,0 @@
|
|||
Display loading and error indicator for conversation page
|
|
@ -1 +0,0 @@
|
|||
Support `status` notification type
|
1
changelog.d/tabs.change
Normal file
|
@ -0,0 +1 @@
|
|||
Tabs now have indentation for better visibility of which tab is currently active
|
|
@ -1 +0,0 @@
|
|||
Theme selector with visual previews of the theme
|
|
@ -1 +0,0 @@
|
|||
Add caching system for themes3
|
|
@ -1 +0,0 @@
|
|||
fix color inputs and some in-development themes3 issues
|
1
changelog.d/themes3.add
Normal file
|
@ -0,0 +1 @@
|
|||
UI for making v3 themes and palettes, support for bundling v3 themes
|
|
@ -1 +0,0 @@
|
|||
Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system.
|
|
@ -1 +0,0 @@
|
|||
Fix Themes v3 on Safari not working
|
|
@ -1 +0,0 @@
|
|||
Ability to resize UI (and certain components) scale independent of browser/text scale
|
|
@ -1 +0,0 @@
|
|||
unread notifications should now properly catch up (eventually) in polling mode
|
1
changelog.d/user-link.add
Normal file
|
@ -0,0 +1 @@
|
|||
Make UserLink wrappable
|
|
@ -1 +0,0 @@
|
|||
Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
|
|
@ -1 +0,0 @@
|
|||
Video posters on Safari
|
|
@ -1 +0,0 @@
|
|||
nothing
|
|
@ -1 +0,0 @@
|
|||
Added option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
|
1
changelog.d/weird-absolute-time-format.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Show only month and day instead of weird "day, hour" format. While at it, fixed typo "defualt" in a comment.
|
155
index.html
|
@ -3,14 +3,163 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<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-->
|
||||
</head>
|
||||
<body class="hidden">
|
||||
<body style="margin: 0; padding: 0">
|
||||
<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>
|
||||
<code id="statusError"></code>
|
||||
<pre id="statusStack"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app" class="hidden"></div>
|
||||
<div id="modal"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<div id="popovers" />
|
||||
|
|
14
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "pleroma_fe",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.1",
|
||||
"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>",
|
||||
"private": false,
|
||||
|
@ -24,7 +24,7 @@
|
|||
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.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.2024.8.21",
|
||||
"@vuelidate/core": "2.0.3",
|
||||
"@vuelidate/validators": "2.0.4",
|
||||
"body-scroll-lock": "3.1.5",
|
||||
|
@ -35,6 +35,7 @@
|
|||
"hash-sum": "^2.0.0",
|
||||
"js-cookie": "3.0.5",
|
||||
"localforage": "1.10.0",
|
||||
"pako": "^2.1.0",
|
||||
"parse-link-header": "2.0.0",
|
||||
"phoenix": "1.7.7",
|
||||
"punycode.js": "2.3.0",
|
||||
|
@ -58,7 +59,7 @@
|
|||
"@intlify/vue-i18n-loader": "5.0.1",
|
||||
"@ungap/event-target": "0.2.4",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||
"@vue/babel-plugin-jsx": "1.2.1",
|
||||
"@vue/babel-plugin-jsx": "1.2.2",
|
||||
"@vue/compiler-sfc": "3.2.45",
|
||||
"@vue/test-utils": "2.2.8",
|
||||
"autoprefixer": "10.4.19",
|
||||
|
@ -88,9 +89,9 @@
|
|||
"http-proxy-middleware": "2.0.6",
|
||||
"iso-639-1": "2.1.15",
|
||||
"json-loader": "0.5.7",
|
||||
"karma": "6.4.2",
|
||||
"karma": "6.4.4",
|
||||
"karma-coverage": "2.2.0",
|
||||
"karma-firefox-launcher": "2.1.2",
|
||||
"karma-firefox-launcher": "2.1.3",
|
||||
"karma-mocha": "2.0.1",
|
||||
"karma-mocha-reporter": "2.2.5",
|
||||
"karma-sinon-chai": "2.0.2",
|
||||
|
@ -132,5 +133,6 @@
|
|||
"engines": {
|
||||
"node": ">= 16.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
|
25
src/App.js
|
@ -44,16 +44,32 @@ export default {
|
|||
data: () => ({
|
||||
mobileActivePanel: 'timeline'
|
||||
}),
|
||||
watch: {
|
||||
themeApplied (value) {
|
||||
this.removeSplash()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// Load the locale from the storage
|
||||
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
||||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||
window.addEventListener('resize', this.updateMobileState)
|
||||
},
|
||||
mounted () {
|
||||
if (this.$store.state.interface.themeApplied) {
|
||||
this.removeSplash()
|
||||
}
|
||||
},
|
||||
unmounted () {
|
||||
window.removeEventListener('resize', this.updateMobileState)
|
||||
},
|
||||
computed: {
|
||||
themeApplied () {
|
||||
return this.$store.state.interface.themeApplied
|
||||
},
|
||||
layoutModalClass () {
|
||||
return '-' + this.layoutType
|
||||
},
|
||||
classes () {
|
||||
return [
|
||||
{
|
||||
|
@ -130,6 +146,15 @@ export default {
|
|||
updateMobileState () {
|
||||
this.$store.dispatch('setLayoutWidth', windowWidth())
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
166
src/App.scss
|
@ -920,3 +920,169 @@ option {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<PostStatusModal />
|
||||
<EditStatusModal v-if="editingAvailable" />
|
||||
<StatusHistoryModal v-if="editingAvailable" />
|
||||
<SettingsModal />
|
||||
<SettingsModal :class="layoutModalClass" />
|
||||
<UpdateNotification />
|
||||
<GlobalNoticeList />
|
||||
</div>
|
||||
|
|
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 |
|
@ -122,6 +122,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
|||
store.dispatch('setInstanceOption', { name, value: config[name] })
|
||||
}
|
||||
|
||||
copyInstanceOption('theme')
|
||||
copyInstanceOption('style')
|
||||
copyInstanceOption('palette')
|
||||
copyInstanceOption('nsfwCensorImage')
|
||||
copyInstanceOption('background')
|
||||
copyInstanceOption('hidePostStats')
|
||||
|
@ -240,7 +243,7 @@ const resolveStaffAccounts = ({ store, accounts }) => {
|
|||
|
||||
const getNodeInfo = async ({ store }) => {
|
||||
try {
|
||||
const res = await preloadFetch('/nodeinfo/2.0.json')
|
||||
const res = await preloadFetch('/nodeinfo/2.1.json')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const metadata = data.metadata
|
||||
|
@ -252,6 +255,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
||||
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: 'pollsAvailable', value: features.includes('polls') })
|
||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
||||
|
@ -276,6 +280,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
|
||||
const software = data.software
|
||||
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' })
|
||||
|
||||
const priv = metadata.private
|
||||
|
@ -326,11 +331,7 @@ const setConfig = async ({ store }) => {
|
|||
|
||||
const checkOAuthToken = async ({ store }) => {
|
||||
if (store.getters.getUserToken()) {
|
||||
try {
|
||||
await store.dispatch('loginUser', store.getters.getUserToken())
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
return store.dispatch('loginUser', store.getters.getUserToken())
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
@ -349,9 +350,14 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
||||
|
||||
await setConfig({ store })
|
||||
await store.dispatch('setTheme')
|
||||
try {
|
||||
await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) })
|
||||
} catch (e) {
|
||||
window.splashError(e)
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
applyConfig(store.state.config)
|
||||
applyConfig(store.state.config, i18n.global)
|
||||
|
||||
// Now we can try getting the server settings and logging in
|
||||
// Most of these are preloaded into the index.html so blocking is minimized
|
||||
|
@ -360,7 +366,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
getInstancePanel({ store }),
|
||||
getNodeInfo({ store }),
|
||||
getInstanceConfig({ store })
|
||||
])
|
||||
]).catch(e => Promise.reject(e))
|
||||
|
||||
await store.dispatch('loadDrafts')
|
||||
|
||||
|
@ -387,6 +393,13 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
app.use(store)
|
||||
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(VBodyScrollLock)
|
||||
app.use(VueVirtualScroller)
|
||||
|
@ -398,7 +411,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
app.config.unwrapInjectedRef = true
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
|||
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
|
||||
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
|
||||
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) => {
|
||||
const validateAuthenticatedRoute = (to, from, next) => {
|
||||
|
@ -88,7 +90,11 @@ export default (store) => {
|
|||
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
|
||||
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
|
||||
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
|
||||
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }
|
||||
{ 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) {
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
<i18n-t
|
||||
keypath="user_card.block_confirm"
|
||||
tag="span"
|
||||
scope="global"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
|
@ -107,6 +108,7 @@
|
|||
<i18n-t
|
||||
keypath="user_card.remove_follower_confirm"
|
||||
tag="span"
|
||||
scope="global"
|
||||
>
|
||||
<template #user>
|
||||
<span
|
||||
|
|
|
@ -14,6 +14,10 @@ export default {
|
|||
warning: '.warning',
|
||||
success: '.success'
|
||||
},
|
||||
editor: {
|
||||
border: 1,
|
||||
aspect: '3 / 1'
|
||||
},
|
||||
defaultRules: [
|
||||
{
|
||||
directives: {
|
||||
|
@ -27,7 +31,9 @@ export default {
|
|||
component: 'Alert'
|
||||
},
|
||||
component: 'Border',
|
||||
textColor: '--parent'
|
||||
directives: {
|
||||
textColor: '--parent'
|
||||
}
|
||||
},
|
||||
{
|
||||
variant: 'error',
|
||||
|
|
|
@ -34,8 +34,9 @@
|
|||
id="announcement-all-day"
|
||||
v-model="announcement.allDay"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
|
||||
>
|
||||
{{ $t('announcements.all_day_prompt') }}
|
||||
</Checkbox>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="panel panel-default announcements-page">
|
||||
<div class="panel-heading">
|
||||
<span>
|
||||
<h1 class="title">
|
||||
{{ $t('announcements.page_header') }}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<section
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export default {
|
||||
name: 'Attachment',
|
||||
selector: '.Attachment',
|
||||
notEditable: true,
|
||||
validInnerComponents: [
|
||||
'Border',
|
||||
'ButtonUnstyled',
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
flex: 1 0;
|
||||
margin: 0;
|
||||
|
||||
--emoji-size: 14px;
|
||||
--emoji-size: 1em;
|
||||
|
||||
&-collapsed-content {
|
||||
margin-left: 0.7em;
|
||||
|
|
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
|
200
src/components/bookmark_folder_edit/bookmark_folder_edit.vue
Normal 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>
|
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">
|
||||
<h1 class="title">
|
||||
{{ $t('nav.bookmark_folders') }}
|
||||
</h1>
|
||||
<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
|