diff options
Diffstat (limited to 'src/components')
23 files changed, 735 insertions, 19 deletions
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 00000000..bf274d9d --- /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 00000000..9e8bef61 --- /dev/null +++ b/src/components/bookmark_folder_card/bookmark_folder_card.vue @@ -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> 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 00000000..95c01576 --- /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 00000000..b6a768d4 --- /dev/null +++ b/src/components/bookmark_folder_edit/bookmark_folder_edit.vue @@ -0,0 +1,198 @@ +<template> + <div class="panel-default panel BookmarkFolderEdit"> + <div + ref="header" + class="panel-heading folder-edit-heading" + > + <button + class="button-unstyled go-back-button" + @click="$router.back" + > + <FAIcon + size="lg" + icon="chevron-left" + /> + </button> + <div class="title"> + <i18n-t + v-if="id" + keypath="bookmark_folders.editing_folder" + > + <template #folderName> + {{ name }} + </template> + </i18n-t> + <i18n-t + v-else + keypath="bookmark_folders.creating_folder" + /> + </div> + </div> + <div class="panel-body"> + <div class="input-wrap"> + <label for="folder-edit-title">{{ $t('bookmark_folders.emoji') }}</label> + <button + class="input input-emoji" + :title="$t('bookmark_folder.emoji_pick')" + @click="showEmojiPicker" + > + <img + v-if="emojiUrlDraft" + class="iconEmoji iconEmoji-image" + :src="emojiUrlDraft" + :alt="emojiDraft" + :title="emojiDraft" + > + <span + v-else-if="emojiDraft" + class="iconEmoji" + > + <span> + {{ emojiDraft }} + </span> + </span> + </button> + <EmojiPicker + ref="picker" + class="emoji-picker-panel" + @emoji="selectEmoji" + @show="onShowPicker" + @close="onClosePicker" + /> + </div> + <div class="input-wrap"> + <label for="folder-edit-title">{{ $t('bookmark_folders.name') }}</label> + <input + id="folder-edit-title" + ref="name" + v-model="nameDraft" + class="input" + > + </div> + </div> + <div class="panel-footer"> + <span class="spacer" /> + <button + v-if="!id" + class="btn button-default footer-button" + @click="createFolder" + > + {{ $t('bookmark_folders.create') }} + </button> + <button + v-else-if="!reallyDelete" + class="btn button-default footer-button" + @click="reallyDelete = true" + > + {{ $t('bookmark_folders.delete') }} + </button> + <template v-else> + {{ $t('bookmark_folders.really_delete') }} + <button + class="btn button-default footer-button" + @click="deleteFolder" + > + {{ $t('general.yes') }} + </button> + <button + class="btn button-default footer-button" + @click="reallyDelete = false" + > + {{ $t('general.no') }} + </button> + </template> + <div + v-if="id && !reallyDelete" + > + <button + class="btn button-default follow-button" + @click="updateFolder" + > + {{ $t('bookmark_folders.update_folder') }} + </button> + </div> + </div> + </div> +</template> + +<script src="./bookmark_folder_edit.js"></script> + +<style lang="scss"> +.BookmarkFolderEdit { + --panel-body-padding: 0.5em; + + overflow: hidden; + display: flex; + flex-direction: column; + + .folder-edit-heading { + grid-template-columns: auto minmax(50%, 1fr); + } + + .panel-body { + display: flex; + gap: 0.5em; + } + + .emoji-picker-panel { + position: absolute; + z-index: 20; + margin-top: 2px; + + &.hide { + display: none; + } + } + + .input-emoji { + height: 2.5em; + width: 2.5em; + padding: 0; + + .iconEmoji { + display: inline-block; + text-align: center; + object-fit: contain; + vertical-align: middle; + height: 2.5em; + width: 2.5em; + + > span { + font-size: 1.5rem; + line-height: 2.5rem; + } + } + + img.iconEmoji { + padding: 0.25em; + box-sizing: border-box; + } + } + + .input-wrap { + display: flex; + flex-direction: column; + gap: 0.5em; + } + + .go-back-button { + text-align: center; + line-height: 1; + height: 100%; + align-self: start; + width: var(--__panel-heading-height-inner); + } + + .btn { + margin: 0 0.5em; + } + + .panel-footer { + grid-template-columns: minmax(10%, 1fr); + + .footer-button { + min-width: 9em; + } + } +} +</style> diff --git a/src/components/bookmark_folders/bookmark_folders.js b/src/components/bookmark_folders/bookmark_folders.js new file mode 100644 index 00000000..9f1f1fed --- /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 00000000..d92e95df --- /dev/null +++ b/src/components/bookmark_folders/bookmark_folders.vue @@ -0,0 +1,37 @@ +<template> + <div class="Bookmark-folders panel panel-default"> + <div class="panel-heading"> + <div class="title"> + {{ $t('nav.bookmark_folders') }} + </div> + <router-link + :to="{ name: 'bookmark-folder-new' }" + class="button-default btn new-folder-button" + > + {{ $t("bookmark_folders.new") }} + </router-link> + </div> + <div class="panel-body"> + <BookmarkFolderCard + :all-bookmarks="true" + class="list-item" + /> + <BookmarkFolderCard + v-for="folder in bookmarkFolders.slice().reverse()" + :key="folder" + :folder="folder" + class="list-item" + /> + </div> + </div> +</template> + +<script src="./bookmark_folders.js"></script> + +<style lang="scss"> +.Bookmark-folders { + .new-folder-button { + padding: 0 0.5em; + } +} +</style> 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 00000000..d5f82f46 --- /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 00000000..d603cd01 --- /dev/null +++ b/src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue @@ -0,0 +1,19 @@ +<template> + <ul> + <NavigationEntry + :item="{ + name: 'bookmarks', + routeObject: { name: 'bookmarks' }, + label: 'nav.all_bookmarks', + icon: 'bookmark' + }" + /> + <NavigationEntry + v-for="item in folders" + :key="item.id" + :item="item" + /> + </ul> +</template> + +<script src="./bookmark_folders_menu_content.js"></script> diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js index 5ac43d90..9571d630 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 = { + 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 } }, - components: { - Timeline + 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 8da6884b..cc86cf12 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" /> </template> diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js index 9ea5c877..d3d6563a 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 e2c88ceb..b3e1cb2b 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 7b38d974..c030de0c 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -87,6 +87,10 @@ icon="bookmark" /><span>{{ $t("status.unbookmark") }}</span> </button> + <StatusBookmarkFolderMenu + v-if="status.bookmarked && bookmarkFolders" + :status="status" + /> </template> <button v-if="ownStatus && editingAvailable" diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 8c9c3b11..11863e97 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,3 +1,4 @@ +import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue' import { mapState, mapGetters } from 'vuex' import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js' @@ -41,6 +42,7 @@ const NavPanel = { created () { }, components: { + BookmarkFoldersMenuContent, ListsMenuContent, NavigationEntry, NavigationPins, @@ -51,6 +53,7 @@ const NavPanel = { editMode: false, showTimelines: false, showLists: false, + showBookmarkFolders: false, timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })), rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k })) } @@ -62,6 +65,9 @@ const NavPanel = { toggleLists () { this.showLists = !this.showLists }, + toggleBookmarkFolders () { + this.showBookmarkFolders = !this.showBookmarkFolders + }, toggleEditMode () { this.editMode = !this.editMode }, @@ -90,7 +96,8 @@ const NavPanel = { pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, supportsAnnouncements: state => state.announcements.supportsAnnouncements, pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems), - collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav + collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav, + bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable }), timelinesItems () { return filterNavigation( @@ -102,7 +109,8 @@ const NavPanel = { hasAnnouncements: this.supportsAnnouncements, isFederating: this.federating, isPrivate: this.privateMode, - currentUser: this.currentUser + currentUser: this.currentUser, + supportsBookmarkFolders: this.bookmarkFolders } ) }, @@ -116,7 +124,8 @@ const NavPanel = { hasAnnouncements: this.supportsAnnouncements, isFederating: this.federating, isPrivate: this.privateMode, - currentUser: this.currentUser + currentUser: this.currentUser, + supportsBookmarkFolders: this.bookmarkFolders } ) }, diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index bf608936..a19e9ba5 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -84,6 +84,39 @@ /> </div> <NavigationEntry + v-if="currentUser && bookmarkFolders" + :show-pin="false" + :item="{ icon: 'bookmark', label: 'nav.bookmarks' }" + :aria-expanded="showBookmarkFolders ? 'true' : 'false'" + @click="toggleBookmarkFolders" + > + <router-link + :title="$t('bookmarks.manage_bookmark_folders')" + class="button-unstyled extra-button" + :to="{ name: 'bookmark-folders' }" + @click.stop + > + <FAIcon + fixed-width + icon="wrench" + /> + </router-link> + <FAIcon + class="timelines-chevron" + fixed-width + :icon="showBookmarkFolders ? 'chevron-up' : 'chevron-down'" + /> + </NavigationEntry> + <div + v-show="showBookmarkFolders" + class="timelines-background menu-item-collapsible" + :class="{ '-expanded': showBookmarkFolders }" + > + <BookmarkFoldersMenuContent + class="timelines" + /> + </div> + <NavigationEntry v-for="item in rootItems" :key="item.name" :show-pin="editMode || forceEditMode" diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js index e8e77f8f..9b8f43cb 100644 --- a/src/components/navigation/filter.js +++ b/src/components/navigation/filter.js @@ -1,4 +1,4 @@ -export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser }) => { +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 face430e..9fc264ba 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 4ea54ee3..024ee314 100644 --- a/src/components/navigation/navigation_entry.vue +++ b/src/components/navigation/navigation_entry.vue @@ -22,11 +22,25 @@ :icon="item.icon" /> </span> + <img + v-if="item.iconEmojiUrl" + class="menu-icon iconEmoji iconEmoji-image" + :src="item.iconEmojiUrl" + :alt="item.iconEmoji" + :title="item.iconEmoji" + > <span - v-if="item.iconLetter" - class="icon iconLetter fa-scale-110 menu-icon" - >{{ item.iconLetter }} + v-else-if="item.iconEmoji" + class="menu-icon iconEmoji" + > + <span> + {{ item.iconEmoji }} + </span> </span> + <span + v-else-if="item.iconLetter" + class="icon iconLetter fa-scale-110 menu-icon" + >{{ item.iconLetter }}</span> <span class="label"> {{ item.labelRaw || $t(item.label) }} </span> @@ -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; + } } </style> diff --git a/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.js b/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.js new file mode 100644 index 00000000..50bcb0ca --- /dev/null +++ b/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.js @@ -0,0 +1,38 @@ +import { library } from '@fortawesome/fontawesome-svg-core' +import { faChevronRight, faFolder } from '@fortawesome/free-solid-svg-icons' +import { mapState } from 'vuex' + +import Popover from '../popover/popover.vue' + +library.add(faChevronRight, faFolder) + +const StatusBookmarkFolderMenu = { + props: [ + 'status' + ], + data () { + return {} + }, + components: { + Popover + }, + computed: { + ...mapState({ + folders: state => state.bookmarkFolders.allFolders + }), + folderId () { + return this.status.bookmark_folder_id + } + }, + methods: { + toggleFolder (id) { + const value = id === this.folderId ? null : id + + this.$store.dispatch('bookmark', { id: this.status.id, bookmark_folder_id: value }) + .then(() => this.$emit('onSuccess')) + .catch(err => this.$emit('onError', err.error.error)) + } + } +} + +export default StatusBookmarkFolderMenu diff --git a/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.vue b/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.vue new file mode 100644 index 00000000..70baa60d --- /dev/null +++ b/src/components/status_bookmark_folder_menu/status_bookmark_folder_menu.vue @@ -0,0 +1,40 @@ +<template> + <div class="StatusBookmarkFolderMenu"> + <Popover + trigger="hover" + placement="left" + remove-padding + > + <template #content> + <div class="dropdown-menu"> + <button + v-for="folder in folders" + :key="folder.id" + class="menu-item dropdown-item" + @click="toggleFolder(folder.id)" + > + <span + class="input menu-checkbox -radio" + :class="{ 'menu-checkbox-checked': status.bookmark_folder_id == folder.id }" + /> + {{ folder.name }} + </button> + </div> + </template> + <template #trigger> + <button class="menu-item dropdown-item dropdown-item-icon -has-submenu"> + <FAIcon + fixed-width + icon="folder" + />{{ $t('bookmark_folders.select_folder') }}<FAIcon + class="chevron-icon" + size="lg" + icon="chevron-right" + /> + </button> + </template> + </Popover> + </div> +</template> + +<script src="./status_bookmark_folder_menu.js"></script> diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 59170f49..9cde9b04 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -26,6 +26,7 @@ const Timeline = { 'userId', 'listId', 'statusId', + 'bookmarkFolderId', 'tag', 'embedded', 'count', @@ -123,6 +124,7 @@ const Timeline = { userId: this.userId, listId: this.listId, statusId: this.statusId, + bookmarkFolderId: this.bookmarkFolderId, tag: this.tag }) }, @@ -186,6 +188,7 @@ const Timeline = { userId: this.userId, listId: this.listId, statusId: this.statusId, + bookmarkFolderId: this.bookmarkFolderId, tag: this.tag }).then(({ statuses }) => { if (statuses && statuses.length === 0) { diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index c4586b32..a09bbcca 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -2,6 +2,7 @@ import Popover from '../popover/popover.vue' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import { mapState } from 'vuex' import { ListsMenuContent } from '../lists_menu/lists_menu_content.vue' +import { BookmarkFoldersMenuContent } from '../bookmark_folders_menu/bookmark_folders_menu_content.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { TIMELINES } from 'src/components/navigation/navigation.js' import { filterNavigation } from 'src/components/navigation/filter.js' @@ -13,10 +14,10 @@ library.add(faChevronDown) // Route -> i18n key mapping, exported and not in the computed // because nav panel benefits from the same information. -export const timelineNames = () => { +export const timelineNames = (supportsBookmarkFolders) => { return { friends: 'nav.home_timeline', - bookmarks: 'nav.bookmarks', + bookmarks: supportsBookmarkFolders ? 'nav.all_bookmarks' : 'nav.bookmarks', dms: 'nav.dms', 'public-timeline': 'nav.public_tl', 'public-external-timeline': 'nav.twkn', @@ -28,7 +29,8 @@ const TimelineMenu = { components: { Popover, NavigationEntry, - ListsMenuContent + ListsMenuContent, + BookmarkFoldersMenuContent }, data () { return { @@ -36,7 +38,7 @@ const TimelineMenu = { } }, created () { - if (timelineNames()[this.$route.name]) { + if (timelineNames(this.bookmarkFolders)[this.$route.name]) { this.$store.dispatch('setLastTimeline', this.$route.name) } }, @@ -45,10 +47,15 @@ const TimelineMenu = { const route = this.$route.name return route === 'lists-timeline' }, + useBookmarkFoldersMenu () { + const route = this.$route.name + return this.bookmarkFolders && (route === 'bookmark-folder' || route === 'bookmarks') + }, ...mapState({ currentUser: state => state.users.currentUser, privateMode: state => state.instance.private, - federating: state => state.instance.federating + federating: state => state.instance.federating, + bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable }), timelinesList () { return filterNavigation( @@ -57,7 +64,8 @@ const TimelineMenu = { hasChats: this.pleromaChatMessagesAvailable, isFederating: this.federating, isPrivate: this.privateMode, - currentUser: this.currentUser + currentUser: this.currentUser, + supportsBookmarkFolders: this.bookmarkFolders } ) } @@ -89,7 +97,10 @@ const TimelineMenu = { if (route === 'lists-timeline') { return this.$store.getters.findListTitle(this.$route.params.id) } - const i18nkey = timelineNames()[this.$route.name] + if (route === 'bookmark-folder') { + return this.$store.getters.findBookmarkFolderName(this.$route.params.id) + } + const i18nkey = timelineNames(this.bookmarkFolders)[this.$route.name] return i18nkey ? this.$t(i18nkey) : route } } diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue index d755b9dd..bdcfb73d 100644 --- a/src/components/timeline_menu/timeline_menu.vue +++ b/src/components/timeline_menu/timeline_menu.vue @@ -15,6 +15,10 @@ :show-pin="false" class="timelines" /> + <BookmarkFoldersMenuContent + v-else-if="useBookmarkFoldersMenu" + class="timelines" + /> <ul v-else> <NavigationEntry v-for="item in timelinesList" |
