diff --git a/changelog.d/remove-opn-dependency.skip b/changelog.d/remove-opn-dependency.skip new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json index 481f65e66..cb5378a86 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lint-fix": "eslint --fix src test/unit/specs test/e2e/specs" }, "dependencies": { - "@babel/runtime": "7.26.9", + "@babel/runtime": "7.26.10", "@chenfengyuan/vue-qrcode": "2.0.0", "@fortawesome/fontawesome-svg-core": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.7.2", @@ -39,13 +39,14 @@ "js-cookie": "3.0.5", "localforage": "1.10.0", "parse-link-header": "2.0.0", - "phoenix": "1.7.19", + "phoenix": "1.7.20", "pinia": "^2.0.33", "punycode.js": "2.3.1", "qrcode": "1.5.4", "querystring-es3": "0.2.1", "url": "0.11.4", "utf8": "3.0.0", + "uuid": "8.3.2", "vue": "3.5.13", "vue-i18n": "10", "vue-router": "4.5.0", @@ -53,9 +54,9 @@ "vuex": "4.1.0" }, "devDependencies": { - "@babel/core": "7.26.9", - "@babel/eslint-parser": "7.26.8", - "@babel/plugin-transform-runtime": "7.26.9", + "@babel/core": "7.26.10", + "@babel/eslint-parser": "7.26.10", + "@babel/plugin-transform-runtime": "7.26.10", "@babel/preset-env": "7.26.9", "@babel/register": "7.25.9", "@ungap/event-target": "0.2.4", @@ -71,7 +72,7 @@ "babel-plugin-lodash": "3.3.4", "chai": "4.5.0", "chalk": "5.4.1", - "chromedriver": "133.0.3", + "chromedriver": "134.0.3", "connect-history-api-fallback": "2.0.0", "cross-spawn": "7.0.6", "custom-event-polyfill": "1.0.7", @@ -89,8 +90,7 @@ "iso-639-1": "3.1.5", "lodash": "4.17.21", "msw": "2.7.3", - "nightwatch": "3.11.1", - "opn": "5.5.0", + "nightwatch": "3.12.0", "ora": "0.4.1", "playwright": "1.49.1", "postcss": "8.5.3", @@ -100,7 +100,7 @@ "selenium-server": "3.141.59", "semver": "7.7.1", "serve-static": "1.16.2", - "shelljs": "0.8.5", + "shelljs": "0.9.1", "sinon": "15.2.0", "sinon-chai": "3.7.0", "stylelint": "14.16.1", diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 10a0892f4..2085d24e3 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -1,14 +1,19 @@ import SideDrawer from '../side_drawer/side_drawer.vue' import Notifications from '../notifications/notifications.vue' import ConfirmModal from '../confirm_modal/confirm_modal.vue' +import GestureService from '../../services/gesture_service/gesture_service' +import NavigationPins from 'src/components/navigation/navigation_pins.vue' + import { unseenNotificationsFromStore, countExtraNotifications } from '../../services/notification_utils/notification_utils' -import GestureService from '../../services/gesture_service/gesture_service' -import NavigationPins from 'src/components/navigation/navigation_pins.vue' + import { mapGetters } from 'vuex' import { mapState } from 'pinia' +import { useAnnouncementsStore } from 'src/stores/announcements' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes, @@ -18,7 +23,6 @@ import { faMinus, faCheckDouble } from '@fortawesome/free-solid-svg-icons' -import { useAnnouncementsStore } from 'src/stores/announcements' library.add( faTimes, @@ -71,10 +75,9 @@ const MobileNav = { return this.$route.name === 'chat' }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), - ...mapGetters(['unreadChatCount']), - chatsPinned () { - return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats') - }, + ...mapState(useServerSideStorageStore, { + pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats') + }), shouldConfirmLogout () { return this.$store.getters.mergedConfig.modalOnLogout }, diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 9d569729f..681aaf05b 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -7,7 +7,9 @@ import { filterNavigation } from 'src/components/navigation/filter.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' + import { useAnnouncementsStore } from 'src/stores/announcements' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -76,19 +78,19 @@ const NavPanel = { this.editMode = !this.editMode }, toggleCollapse () { - this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed }) - this.$store.dispatch('pushServerSideStorage') + useServerSideStorageStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed }) + useServerSideStorageStore().pushServerSideStorage() }, isPinned (item) { return this.pinnedItems.has(item) }, togglePin (item) { if (this.isPinned(item)) { - this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) + useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item }) } else { - this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item }) + useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item }) } - this.$store.dispatch('pushServerSideStorage') + useServerSideStorageStore().pushServerSideStorage() } }, computed: { @@ -96,14 +98,16 @@ const NavPanel = { unreadAnnouncementCount: 'unreadAnnouncementCount', supportsAnnouncements: store => store.supportsAnnouncements }), + ...mapPiniaState(useServerSideStorageStore, { + collapsed: store => store.prefsStorage.simple.collapseNav, + pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems) + }), ...mapState({ currentUser: state => state.users.currentUser, followRequestCount: state => state.api.followRequests.length, privateMode: state => state.instance.private, federating: state => state.instance.federating, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, - pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems), - collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav, bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable }), timelinesItems () { diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js index dbef18fcd..11db1c9e3 100644 --- a/src/components/navigation/navigation_entry.js +++ b/src/components/navigation/navigation_entry.js @@ -3,8 +3,10 @@ import { routeTo } from 'src/components/navigation/navigation.js' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faThumbtack } from '@fortawesome/free-solid-svg-icons' -import { mapStores } from 'pinia' +import { mapStores, mapState as mapPiniaState } from 'pinia' + import { useAnnouncementsStore } from 'src/stores/announcements' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' library.add(faThumbtack) @@ -19,11 +21,11 @@ const NavigationEntry = { }, togglePin (value) { if (this.isPinned(value)) { - this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value }) + useServerSideStorageStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value }) } else { - this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value }) + useServerSideStorageStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value }) } - this.$store.dispatch('pushServerSideStorage') + useServerSideStorageStore().pushServerSideStorage() } }, computed: { @@ -35,9 +37,11 @@ const NavigationEntry = { }, ...mapStores(useAnnouncementsStore), ...mapState({ - currentUser: state => state.users.currentUser, - pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) - }) + currentUser: state => state.users.currentUser + }), + ...mapPiniaState(useServerSideStorageStore, { + pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems) + }), } } diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js index f9cdef71b..50acbbaf1 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -20,6 +20,7 @@ import { import { useListsStore } from 'src/stores/lists' import { useAnnouncementsStore } from 'src/stores/announcements' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' library.add( faUsers, @@ -54,15 +55,17 @@ const NavPanel = { supportsAnnouncements: store => store.supportsAnnouncements }), ...mapPiniaState(useBookmarkFoldersStore, { - bookmarks: getBookmarkFolderEntries + bookmarks: getBookmarkFolderEntries, + }), + ...mapPiniaState(useServerSideStorageStore, { + pinnedItems: store => new Set(store.prefsStorage.collections.pinnedNavItems) }), ...mapState({ currentUser: state => state.users.currentUser, followRequestCount: state => state.api.followRequests.length, privateMode: state => state.instance.private, federating: state => state.instance.federating, - pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, - pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems) + pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable }), pinnedList () { if (!this.currentUser) { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 87c88d8f7..4fb8e0428 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -363,12 +363,6 @@ const PostStatusForm = { } }, safeToSaveDraft () { - console.log('safe', ( - this.newStatus.status || - this.newStatus.spoilerText || - this.newStatus.files?.length || - this.newStatus.hasPoll - ) && this.saveable) return ( this.newStatus.status || this.newStatus.spoilerText || diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js index 2dc9653ea..df137157a 100644 --- a/src/components/settings_modal/helpers/setting.js +++ b/src/components/settings_modal/helpers/setting.js @@ -78,7 +78,6 @@ export default { }, computed: { draft: { - // TODO allow passing shared draft object? get () { if (this.realSource === 'admin' || this.path == null) { return get(this.$store.state.adminSettings.draft, this.canonPath) diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index fbace15df..89079635a 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -1,15 +1,20 @@ -import { filter, trim, debounce } from 'lodash' +import { throttle } from 'lodash' +import { mapState, mapActions } from 'pinia' +import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { v4 as uuidv4 } from 'uuid'; + import BooleanSetting from '../helpers/boolean_setting.vue' import ChoiceSetting from '../helpers/choice_setting.vue' import UnitSetting from '../helpers/unit_setting.vue' import IntegerSetting from '../helpers/integer_setting.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' +import Select from 'src/components/select/select.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' const FilteringTab = { data () { return { - muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n'), replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({ key: mode, value: mode, @@ -21,26 +26,97 @@ const FilteringTab = { BooleanSetting, ChoiceSetting, UnitSetting, - IntegerSetting + IntegerSetting, + Checkbox, + Select }, computed: { ...SharedComputedObject(), - muteWordsString: { - get () { - return this.muteWordsStringLocal - }, - set (value) { - this.muteWordsStringLocal = value - this.debouncedSetMuteWords(value) + ...mapState( + useServerSideStorageStore, + { + muteFilters: store => Object.entries(store.prefsStorage.simple.muteFilters), + muteFiltersObject: store => store.prefsStorage.simple.muteFilters } + ) + }, + methods: { + ...mapActions(useServerSideStorageStore, ['unsetPreference']), + pushServerSideStorage: throttle(() => useServerSideStorageStore().pushServerSideStorage(), 500), + setPreference: throttle(x => useServerSideStorageStore().setPreference(x), 500), + getDatetimeLocal (timestamp) { + const date = new Date(timestamp) + const datetime = [ + date.getFullYear(), + '-', + date.getMonth() < 9 ? ('0' + (date.getMonth() + 1)) : (date.getMonth() + 1), + '-', + date.getDate() < 10 ? ('0' + date.getDate()) : date.getDate(), + 'T', + date.getHours() < 10 ? ('0' + date.getHours()) : date.getHours(), + ':', + date.getMinutes() < 10 ? ('0' + date.getMinutes()) : date.getMinutes(), + ].join('') + return datetime }, - debouncedSetMuteWords () { - return debounce((value) => { - this.$store.dispatch('setOption', { - name: 'muteWords', - value: filter(value.split('\n'), (word) => trim(word).length > 0) - }) - }, 1000) + checkRegexValid (id) { + const filter = this.muteFiltersObject[id] + if (filter.type !== 'regexp') return true + const { value } = filter + let valid = true + try { + new RegExp(value) + } catch (e) { + valid = false + console.error('Invalid RegExp: ' + value) + } + return valid + }, + createFilter () { + const filter = { + type: 'word', + value: '', + name: 'New Filter', + enabled: true, + expires: null, + hide: false, + order: this.muteFilters.length + 2 + } + const newId = uuidv4() + + this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter }) + this.pushServerSideStorage() + }, + copyFilter (id) { + const filter = { ...this.muteFiltersObject[id] } + const newId = uuidv4() + + this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter }) + this.pushServerSideStorage() + }, + deleteFilter (id) { + this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null }) + this.pushServerSideStorage() + }, + updateFilter(id, field, value) { + const filter = { ...this.muteFiltersObject[id] } + if (field === 'expires-never') { + // filter[field] = value + if (!value) { + const offset = 1000 * 60 * 60 * 24 * 14 // 2 weeks + const date = Date.now() + offset + filter.expires = date + } else { + filter.expires = null + } + } else if (field === 'expires') { + const parsed = Date.parse(value) + filter.expires = parsed.valueOf() + } else { + filter[field] = value + } + this.setPreference({ path: 'simple.muteFilters.' + id , value: filter }) + this.pushServerSideStorage() } }, // Updating nested properties diff --git a/src/components/settings_modal/tabs/filtering_tab.scss b/src/components/settings_modal/tabs/filtering_tab.scss new file mode 100644 index 000000000..7d5ffabb6 --- /dev/null +++ b/src/components/settings_modal/tabs/filtering_tab.scss @@ -0,0 +1,61 @@ +.filtering-tab { + .muteFilterContainer { + border: 1px solid var(--border); + border-radius: var(--roundness); + + height: 33vh; + overflow-y: auto; + } + + .mute-filter { + border: 1px solid var(--border); + border-radius: var(--roundness); + margin: 0.5em; + padding: 0.5em; + + display: grid; + grid-template-columns: fit-content() 1fr fit-content(); + align-items: baseline; + grid-gap: 0.5em; + } + + .filter-name { + grid-column: 1 / span 2; + grid-row: 1; + } + + .alert, .button-default { + display: inline-block; + line-height: 2; + padding: 0 0.5em; + } + + .filter-enabled { + grid-column: 3; + grid-row: 1; + + text-align: right; + } + + .filter-field { + display: grid; + grid-template-columns: subgrid; + grid-template-rows: subgrid; + grid-column: 1 / span 3; + align-items: baseline; + + label { + grid-column: 1; + text-align: right; + } + + .filter-field-value { + grid-column: 2 / span 2; + } + } + + .filter-buttons { + grid-column: 1 / span 3; + justify-self: end; + } +} diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 32325d423..8b1291102 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -1,5 +1,5 @@