diff --git a/changelog.d/better-shadow-control.fix b/changelog.d/better-shadow-control.fix new file mode 100644 index 000000000..585ef6d26 --- /dev/null +++ b/changelog.d/better-shadow-control.fix @@ -0,0 +1 @@ +Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name. diff --git a/changelog.d/bookmark-folders.add b/changelog.d/bookmark-folders.add new file mode 100644 index 000000000..f22966602 --- /dev/null +++ b/changelog.d/bookmark-folders.add @@ -0,0 +1 @@ +Support bookmark folders diff --git a/changelog.d/splashscreen.add b/changelog.d/splashscreen.add new file mode 100644 index 000000000..f1f56551a --- /dev/null +++ b/changelog.d/splashscreen.add @@ -0,0 +1 @@ +Splash screen + loading indicator to make process of identifying initialization issues and load performance diff --git a/index.html b/index.html index 6d9c4ce5c..461f5acc1 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,138 @@ + + - + -
+
+ + +
+ +
+ + + +
+
+
+
diff --git a/package.json b/package.json index 61db87d20..403a2b231 100644 --- a/package.json +++ b/package.json @@ -132,5 +132,6 @@ "engines": { "node": ">= 16.0.0", "npm": ">= 3.0.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/App.js b/src/App.js index b7eb2f72e..e87108dd9 100644 --- a/src/App.js +++ b/src/App.js @@ -44,16 +44,29 @@ 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 + }, classes () { return [ { @@ -130,6 +143,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') } } } diff --git a/src/App.scss b/src/App.scss index 9d1ce77a6..f52ba06b9 100644 --- a/src/App.scss +++ b/src/App.scss @@ -914,3 +914,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; + } + } + } +} diff --git a/src/assets/pleromatan_apology.png b/src/assets/pleromatan_apology.png deleted file mode 100644 index 36ad7aeb8..000000000 Binary files a/src/assets/pleromatan_apology.png and /dev/null differ diff --git a/src/assets/pleromatan_apology.png b/src/assets/pleromatan_apology.png new file mode 120000 index 000000000..a7f6191f6 --- /dev/null +++ b/src/assets/pleromatan_apology.png @@ -0,0 +1 @@ +../../static/pleromatan_apology.png \ No newline at end of file diff --git a/src/assets/pleromatan_apology_fox.png b/src/assets/pleromatan_apology_fox.png deleted file mode 100644 index 17f87694c..000000000 Binary files a/src/assets/pleromatan_apology_fox.png and /dev/null differ diff --git a/src/assets/pleromatan_apology_fox.png b/src/assets/pleromatan_apology_fox.png new file mode 120000 index 000000000..b3db4af3f --- /dev/null +++ b/src/assets/pleromatan_apology_fox.png @@ -0,0 +1 @@ +../../static/pleromatan_apology_fox.png \ No newline at end of file diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 6cad05f62..9a6964d15 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -253,6 +253,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') }) @@ -327,11 +328,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,19 +346,26 @@ const afterStoreSetup = async ({ store, i18n }) => { const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin store.dispatch('setInstanceOption', { name: 'server', value: server }) + document.querySelector('#status').textContent = i18n.global.t('splash.settings') await setConfig({ store }) - await store.dispatch('setTheme') + document.querySelector('#status').textContent = i18n.global.t('splash.theme') + try { + await store.dispatch('setTheme').catch((e) => { console.error('Error setting theme', e) }) + } catch (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 + document.querySelector('#status').textContent = i18n.global.t('splash.instance') await Promise.all([ checkOAuthToken({ store }), getInstancePanel({ store }), getNodeInfo({ store }), getInstanceConfig({ store }) - ]) + ]).catch(e => Promise.reject(e)) // Start fetching things that don't need to block the UI store.dispatch('fetchMutes') @@ -395,9 +399,9 @@ const afterStoreSetup = async ({ store, i18n }) => { // remove after vue 3.3 app.config.unwrapInjectedRef = true + document.querySelector('#status').textContent = i18n.global.t('splash.almost') app.mount('#app') - return app } diff --git a/src/boot/routes.js b/src/boot/routes.js index 31e3dbb07..f87b2ec85 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -26,6 +26,8 @@ import ListsEdit from 'components/lists_edit/lists_edit.vue' import NavPanel from 'src/components/nav_panel/nav_panel.vue' import AnnouncementsPage from 'components/announcements_page/announcements_page.vue' import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue' +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) => { @@ -86,7 +88,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) { diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.js b/src/components/bookmark_folder_card/bookmark_folder_card.js new file mode 100644 index 000000000..bf274d9d7 --- /dev/null +++ b/src/components/bookmark_folder_card/bookmark_folder_card.js @@ -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 diff --git a/src/components/bookmark_folder_card/bookmark_folder_card.vue b/src/components/bookmark_folder_card/bookmark_folder_card.vue new file mode 100644 index 000000000..9e8bef618 --- /dev/null +++ b/src/components/bookmark_folder_card/bookmark_folder_card.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.js b/src/components/bookmark_folder_edit/bookmark_folder_edit.js new file mode 100644 index 000000000..95c015764 --- /dev/null +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.js @@ -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 diff --git a/src/components/bookmark_folder_edit/bookmark_folder_edit.vue b/src/components/bookmark_folder_edit/bookmark_folder_edit.vue new file mode 100644 index 000000000..b6a768d41 --- /dev/null +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js new file mode 100644 index 000000000..9f1f1fed0 --- /dev/null +++ b/src/components/bookmark_folders/bookmark_folders.js @@ -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 diff --git a/src/components/bookmark_folders/bookmark_folders.vue b/src/components/bookmark_folders/bookmark_folders.vue new file mode 100644 index 000000000..d92e95df0 --- /dev/null +++ b/src/components/bookmark_folders/bookmark_folders.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js new file mode 100644 index 000000000..d5f82f466 --- /dev/null +++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.js @@ -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 diff --git a/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue new file mode 100644 index 000000000..d603cd010 --- /dev/null +++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js index 5ac43d90c..9571d630f 100644 --- a/src/components/bookmark_timeline/bookmark_timeline.js +++ b/src/components/bookmark_timeline/bookmark_timeline.js @@ -1,16 +1,31 @@ import Timeline from '../timeline/timeline.vue' const Bookmarks = { - computed: { - timeline () { - return this.$store.state.statuses.timelines.bookmarks - } + created () { + this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null }) }, components: { Timeline }, + computed: { + folderId () { + return this.$route.params.id + }, + timeline () { + return this.$store.state.statuses.timelines.bookmarks + } + }, + watch: { + folderId () { + this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) + this.$store.dispatch('stopFetchingTimeline', 'bookmarks') + this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null }) + } + }, unmounted () { this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) + this.$store.dispatch('stopFetchingTimeline', 'bookmarks') } } diff --git a/src/components/bookmark_timeline/bookmark_timeline.vue b/src/components/bookmark_timeline/bookmark_timeline.vue index 8da6884b8..cc86cf12e 100644 --- a/src/components/bookmark_timeline/bookmark_timeline.vue +++ b/src/components/bookmark_timeline/bookmark_timeline.vue @@ -3,6 +3,7 @@ :title="$t('nav.bookmarks')" :timeline="timeline" :timeline-name="'bookmarks'" + :bookmark-folder-id="folderId" /> diff --git a/src/components/button.style.js b/src/components/button.style.js index 1bee8f8e7..1423d5c78 100644 --- a/src/components/button.style.js +++ b/src/components/button.style.js @@ -96,6 +96,17 @@ export default { textOpacity: 0.25, textOpacityMode: 'blend' } + }, + { + component: 'Icon', + parent: { + component: 'Button', + state: ['disabled'] + }, + directives: { + textOpacity: 0.25, + textOpacityMode: 'blend' + } } ] } diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue index 6261bf3a2..8ffcc63b4 100644 --- a/src/components/checkbox/checkbox.vue +++ b/src/components/checkbox/checkbox.vue @@ -3,6 +3,13 @@ class="checkbox" :class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }" > + + + @@ -93,14 +102,9 @@ export default { box-sizing: border-box; } - &.disabled { - .checkbox-indicator::before, - .label { - opacity: 0.5; - } - - .label { - color: var(--text); + .disabled { + .checkbox-indicator::before { + background-color: var(--background); } } @@ -121,8 +125,14 @@ export default { } } - & > span { - margin-left: 0.5em; + & > .label { + &.-after { + margin-left: 0.5em; + } + + &.-before { + margin-right: 0.5em; + } } } diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss index b0fc879f7..19c88a690 100644 --- a/src/components/color_input/color_input.scss +++ b/src/components/color_input/color_input.scss @@ -1,12 +1,15 @@ .color-input { display: inline-flex; + .label { + flex: 1 1 auto; + } + &-field.input { display: inline-flex; flex: 0 0 0; max-width: 9em; align-items: stretch; - padding: 0.2em 8px; input { color: var(--text); @@ -25,6 +28,7 @@ .nativeColor { cursor: pointer; flex: 0 0 auto; + padding: 0; input { appearance: none; @@ -41,10 +45,10 @@ .invalidIndicator, .transparentIndicator { flex: 0 0 2em; - margin: 0 0.5em; + margin: 0.2em 0.5em; min-width: 2em; align-self: stretch; - min-height: 1.5em; + min-height: 1.1em; border-radius: var(--roundness); } @@ -81,9 +85,17 @@ border-bottom-right-radius: var(--roundness); } } - } - .label { - flex: 1 1 auto; + &.disabled, + &:disabled { + .nativeColor input, + .computedIndicator, + .validIndicator, + .invalidIndicator, + .transparentIndicator { + /* stylelint-disable-next-line declaration-no-important */ + opacity: 0.25 !important; + } + } } } diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue index 66ee9d53a..b6e84629f 100644 --- a/src/components/color_input/color_input.vue +++ b/src/components/color_input/color_input.vue @@ -6,6 +6,7 @@ @@ -14,16 +15,20 @@ :model-value="present" :disabled="disabled" class="opt" - @update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)" + @update:modelValue="update(typeof modelValue === 'undefined' ? fallback : undefined)" /> -
+
@@ -60,6 +66,7 @@ diff --git a/src/components/component_preview/component_preview.vue b/src/components/component_preview/component_preview.vue new file mode 100644 index 000000000..3b2cf63b9 --- /dev/null +++ b/src/components/component_preview/component_preview.vue @@ -0,0 +1,212 @@ + + + + diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 9ea5c877a..d3d6563ab 100644 --- a/src/components/emoji_picker/emoji_picker.js +++ b/src/components/emoji_picker/emoji_picker.js @@ -180,7 +180,7 @@ const EmojiPicker = { if (!this.keepOpen) { this.$refs.popover.hidePopover() } - this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen }) + this.$emit('emoji', { insertion: value, insertionUrl: emoji.imageUrl, keepOpen: this.keepOpen }) }, onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) { const target = this.$refs['emoji-groups'].$el diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index e2c88cebb..b3e1cb2bb 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -1,6 +1,7 @@ import Popover from '../popover/popover.vue' import genRandomSeed from '../../services/random_seed/random_seed.service.js' import ConfirmModal from '../confirm_modal/confirm_modal.vue' +import StatusBookmarkFolderMenu from '../status_bookmark_folder_menu/status_bookmark_folder_menu.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faEllipsisH, @@ -36,7 +37,8 @@ const ExtraButtons = { props: ['status'], components: { Popover, - ConfirmModal + ConfirmModal, + StatusBookmarkFolderMenu }, data () { return { @@ -145,6 +147,9 @@ const ExtraButtons = { canBookmark () { return !!this.currentUser }, + bookmarkFolders () { + return this.$store.state.instance.pleromaBookmarkFoldersAvailable + }, statusLink () { return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}` }, diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index 7b38d9740..c030de0c8 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -87,6 +87,10 @@ icon="bookmark" />{{ $t("status.unbookmark") }} +
+ + + + + + + { +export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser, supportsBookmarkFolders }) => { return list.filter(({ criteria, anon, anonRoute }) => { const set = new Set(criteria || []) if (!isFederating && set.has('federating')) return false @@ -7,6 +7,7 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false if (!hasChats && set.has('chats')) return false if (!hasAnnouncements && set.has('announcements')) return false + if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false return true }) } @@ -17,3 +18,12 @@ export const getListEntries = state => state.lists.allLists.map(list => ({ labelRaw: list.title, iconLetter: list.title[0] })) + +export const getBookmarkFolderEntries = state => state.bookmarkFolders.allFolders.map(folder => ({ + name: 'bookmark-folder-' + folder.id, + routeObject: { name: 'bookmark-folder', params: { id: folder.id } }, + labelRaw: folder.name, + iconEmoji: folder.emoji, + iconEmojiUrl: folder.emoji_url, + iconLetter: folder.name[0] +})) diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js index face430ed..9fc264baa 100644 --- a/src/components/navigation/navigation.js +++ b/src/components/navigation/navigation.js @@ -32,7 +32,8 @@ export const TIMELINES = { bookmarks: { route: 'bookmarks', icon: 'bookmark', - label: 'nav.bookmarks' + label: 'nav.bookmarks', + criteria: ['!supportsBookmarkFolders'] }, favorites: { routeObject: { name: 'user-profile', query: { tab: 'favorites' } }, diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue index 4ea54ee3c..024ee3148 100644 --- a/src/components/navigation/navigation_entry.vue +++ b/src/components/navigation/navigation_entry.vue @@ -22,11 +22,25 @@ :icon="item.icon" /> + {{ item.iconLetter }} + v-else-if="item.iconEmoji" + class="menu-icon iconEmoji" + > + + {{ item.iconEmoji }} + + {{ item.iconLetter }} {{ item.labelRaw || $t(item.label) }} @@ -110,5 +124,23 @@ .badge { margin: 0 var(--__horizontal-gap); } + + .iconEmoji { + display: inline-block; + text-align: center; + object-fit: contain; + vertical-align: middle; + height: var(--__line-height); + width: var(--__line-height); + + > span { + font-size: 1.5rem; + } + } + + img.iconEmoji { + padding: 0.25rem; + box-sizing: border-box; + } } diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue index a45bdd920..5a80b100d 100644 --- a/src/components/opacity_input/opacity_input.vue +++ b/src/components/opacity_input/opacity_input.vue @@ -6,6 +6,7 @@ @@ -22,6 +23,7 @@ type="number" :value="modelValue || fallback" :disabled="!present || disabled" + :class="{ disabled: !present || disabled }" max="1" min="0" step=".05" diff --git a/src/components/select/select.vue b/src/components/select/select.vue index 328321265..0fb6fcc0e 100644 --- a/src/components/select/select.vue +++ b/src/components/select/select.vue @@ -6,13 +6,14 @@ {{ ' ' }} @@ -39,6 +40,38 @@ label.Select { z-index: 1; height: 2em; line-height: 16px; + + &[multiple], + &[size] { + height: 100%; + padding: 0.2em; + + option { + background-color: transparent; + + &.-active { + color: var(--selectionText); + background-color: var(--selectionBackground); + } + } + } + } + + &.disabled, + &:disabled { + background-color: var(--background); + opacity: 1; /* override browser */ + color: var(--faint); + + select { + &[multiple], + &[size] { + option.-active { + color: var(--faint); + background: transparent; + } + } + } } .select-down-icon { diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js index 25836559f..64de28bc3 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js @@ -314,7 +314,18 @@ export default { }, set (val) { if (val) { - this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _)) + this.shadowsLocal[this.shadowSelected] = (this.currentShadowFallback || []) + .map(s => ({ + name: null, + x: 0, + y: 0, + blur: 0, + spread: 0, + inset: false, + color: '#000000', + alpha: 1, + ...s + })) } else { delete this.shadowsLocal[this.shadowSelected] } diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss index 84933fb8e..e86e61dab 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss @@ -25,7 +25,9 @@ margin-bottom: 5px; .label { + margin-right: 1em; flex: 1; + line-height: 2; } .opt { @@ -48,15 +50,14 @@ &[type="range"] { flex: 1; - min-width: 3em; - align-self: flex-start; + min-width: 2em; + align-self: center; + margin: 0 0.5em; } - } - &.disabled { - input, - select { - opacity: 0.5; + &[type="checkbox"] + i { + height: 1.1em; + align-self: center; } } } diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue index 4498c1434..00a55832f 100644 --- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue +++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue @@ -123,10 +123,13 @@
- - - - + + + +
- - {{ ' ' }} - -