diff --git a/changelog.d/bookmark-folders.add b/changelog.d/bookmark-folders.add new file mode 100644 index 00000000..f2296660 --- /dev/null +++ b/changelog.d/bookmark-folders.add @@ -0,0 +1 @@ +Support bookmark folders diff --git a/package.json b/package.json index 61db87d2..403a2b23 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/boot/after_store.js b/src/boot/after_store.js index 3ad58c89..20a1a591 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') }) diff --git a/src/boot/routes.js b/src/boot/routes.js index 31e3dbb0..f87b2ec8 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 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + 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 = { - 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 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" /> 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" />{{ $t("status.unbookmark") }} +