aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry Jameson <me@hjkos.com>2022-08-23 21:52:17 +0300
committerHenry Jameson <me@hjkos.com>2022-08-23 21:52:17 +0300
commitcd7380efe74b791c042c2d7af307dadbc3acd99f (patch)
tree54aaf9eb79cc805ed409ddf364dad1733f957c38
parentbd7356376ec9137da674f146a89c17ed62f56bc3 (diff)
parent361aea13998bfba512a81422d3d0ca5b13ee60ee (diff)
Merge remote-tracking branch 'origin/develop' into navigation-update
* origin/develop: Update dependency opn to v5 fix notices being under the navbar, also change offset to use variable fix modals not having proper z index reduce indexes to be below 9999 so that develop error messages appear above Do not allow to find by name in findUser() Use lookup endpoint to obtain users by nickname Use $ for id UserProfile routes Allow opening profile: multiChoiceProprties record, anonymous access Allow opening profile when clicking an avatar inside of user popover
-rw-r--r--package.json2
-rw-r--r--src/App.scss17
-rw-r--r--src/boot/routes.js5
-rw-r--r--src/components/global_notice_list/global_notice_list.vue4
-rw-r--r--src/components/settings_modal/tabs/general_tab.js5
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue10
-rw-r--r--src/components/user_popover/user_popover.js4
-rw-r--r--src/components/user_popover/user_popover.vue2
-rw-r--r--src/components/user_profile/user_profile.js15
-rw-r--r--src/i18n/en.json5
-rw-r--r--src/modules/config.js5
-rw-r--r--src/modules/users.js27
-rw-r--r--src/services/api/api.service.js21
-rw-r--r--test/unit/specs/components/user_profile.spec.js5
-rw-r--r--test/unit/specs/modules/users.spec.js44
-rw-r--r--yarn.lock26
16 files changed, 139 insertions, 58 deletions
diff --git a/package.json b/package.json
index a8cbaa3f..dfc9c4b1 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,7 @@
"mini-css-extract-plugin": "2.6.1",
"mocha": "10.0.0",
"nightwatch": "2.3.3",
- "opn": "4.0.2",
+ "opn": "5.5.0",
"ora": "0.4.1",
"postcss": "8.4.16",
"postcss-loader": "7.0.1",
diff --git a/src/App.scss b/src/App.scss
index 665d52ff..9c08c6b2 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -5,12 +5,12 @@
--navbar-height: 3.5rem;
--post-line-height: 1.4;
// Z-Index stuff
- --ZI_media_modal: 90000;
- --ZI_modals_popovers: 85000;
- --ZI_modals: 80000;
- --ZI_navbar_popovers: 75000;
- --ZI_navbar: 70000;
- --ZI_popovers: 60000;
+ --ZI_media_modal: 9000;
+ --ZI_modals_popovers: 8500;
+ --ZI_modals: 8000;
+ --ZI_navbar_popovers: 7500;
+ --ZI_navbar: 7000;
+ --ZI_popovers: 6000;
}
html {
@@ -158,6 +158,11 @@ nav {
grid-area: sidebar;
}
+#modal {
+ position: absolute;
+ z-index: var(--ZI_modals);
+}
+
.column.-scrollable {
top: var(--navbar-height);
position: sticky;
diff --git a/src/boot/routes.js b/src/boot/routes.js
index 6b9c8db2..63dd1297 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -62,7 +62,7 @@ export default (store) => {
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
},
- { name: 'external-user-profile', path: '/users/:id', component: UserProfile },
+ { name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
@@ -76,7 +76,8 @@ export default (store) => {
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
- { name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile },
+ { name: 'user-profile', path: '/users/:name', component: UserProfile },
+ { name: 'legacy-user-profile', path: '/:name', component: UserProfile },
{ name: 'lists', path: '/lists', component: Lists },
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
index 09904761..d828b819 100644
--- a/src/components/global_notice_list/global_notice_list.vue
+++ b/src/components/global_notice_list/global_notice_list.vue
@@ -29,10 +29,10 @@
.global-notice-list {
position: fixed;
- top: 50px;
+ top: calc(var(--navbar-height) + 0.5em);
width: 100%;
pointer-events: none;
- z-index: var(--ZI_popovers);
+ z-index: var(--ZI_navbar_popovers);
display: flex;
flex-direction: column;
align-items: center;
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index a22b9b03..ea24d6ad 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -44,6 +44,11 @@ const GeneralTab = {
value: mode,
label: this.$t(`settings.third_column_mode_${mode}`)
})),
+ userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.user_popover_avatar_action_${mode}`)
+ })),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 815a32fa..8561647b 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -60,12 +60,14 @@
</BooleanSetting>
</li>
<li>
- <BooleanSetting
- path="userPopoverZoom"
+ <ChoiceSetting
+ id="userPopoverAvatarAction"
+ path="userPopoverAvatarAction"
+ :options="userPopoverAvatarActionOptions"
expert="1"
>
- {{ $t('settings.user_popover_avatar_zoom') }}
- </BooleanSetting>
+ {{ $t('settings.user_popover_avatar_action') }}
+ </ChoiceSetting>
</li>
<li>
<BooleanSetting
diff --git a/src/components/user_popover/user_popover.js b/src/components/user_popover/user_popover.js
index 69b25383..3b12aa1e 100644
--- a/src/components/user_popover/user_popover.js
+++ b/src/components/user_popover/user_popover.js
@@ -11,8 +11,8 @@ const UserPopover = {
Popover: defineAsyncComponent(() => import('../popover/popover.vue'))
},
computed: {
- userPopoverZoom () {
- return this.$store.getters.mergedConfig.userPopoverZoom
+ userPopoverAvatarAction () {
+ return this.$store.getters.mergedConfig.userPopoverAvatarAction
},
userPopoverOverlay () {
return this.$store.getters.mergedConfig.userPopoverOverlay
diff --git a/src/components/user_popover/user_popover.vue b/src/components/user_popover/user_popover.vue
index 4e999672..53d51fc4 100644
--- a/src/components/user_popover/user_popover.vue
+++ b/src/components/user_popover/user_popover.vue
@@ -14,7 +14,7 @@
class="user-popover"
:user-id="userId"
:hide-bio="true"
- :avatar-action="userPopoverZoom ? 'zoom' : close"
+ :avatar-action="userPopoverAvatarAction == 'close' ? close : userPopoverAvatarAction"
:on-close="close"
/>
</template>
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index f779b823..08adaeab 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -45,7 +45,7 @@ const UserProfile = {
},
created () {
const routeParams = this.$route.params
- this.load(routeParams.name || routeParams.id)
+ this.load({ name: routeParams.name, id: routeParams.id })
this.tab = get(this.$route, 'query.tab', defaultTabKey)
},
unmounted () {
@@ -106,12 +106,17 @@ const UserProfile = {
this.userId = null
this.error = false
+ const maybeId = userNameOrId.id
+ const maybeName = userNameOrId.name
+
// Check if user data is already loaded in store
- const user = this.$store.getters.findUser(userNameOrId)
+ const user = maybeId ? this.$store.getters.findUser(maybeId) : this.$store.getters.findUserByName(maybeName)
if (user) {
loadById(user.id)
} else {
- this.$store.dispatch('fetchUser', userNameOrId)
+ (maybeId
+ ? this.$store.dispatch('fetchUser', maybeId)
+ : this.$store.dispatch('fetchUserByName', maybeName))
.then(({ id }) => loadById(id))
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
@@ -150,12 +155,12 @@ const UserProfile = {
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
- this.switchUser(newVal)
+ this.switchUser({ id: newVal })
}
},
'$route.params.name': function (newVal) {
if (newVal) {
- this.switchUser(newVal)
+ this.switchUser({ name: newVal })
}
},
'$route.query': function (newVal) {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index da3da26b..b7839a12 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -584,7 +584,10 @@
"mention_link_show_avatar_quick": "Show user avatar next to mentions",
"mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)",
"mention_link_bolden_you": "Highlight mention of you when you are mentioned",
- "user_popover_avatar_zoom": "Clicking on user avatar in popover zooms it instead of closing the popover",
+ "user_popover_avatar_action": "Popover avatar click action",
+ "user_popover_avatar_action_zoom": "Zoom the avatar",
+ "user_popover_avatar_action_close": "Close the popover",
+ "user_popover_avatar_action_open": "Open profile",
"user_popover_avatar_overlay": "Show user popover over user avatar",
"fun": "Fun",
"greentext": "Meme arrows",
diff --git a/src/modules/config.js b/src/modules/config.js
index c9082895..eeaac917 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -17,7 +17,8 @@ export const multiChoiceProperties = [
'subjectLineBehavior',
'conversationDisplay', // tree | linear
'conversationOtherRepliesButton', // below | inside
- 'mentionLinkDisplay' // short | full_for_remote | full
+ 'mentionLinkDisplay', // short | full_for_remote | full
+ 'userPopoverAvatarAction' // close | zoom | open
]
export const defaultState = {
@@ -82,7 +83,7 @@ export const defaultState = {
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
- userPopoverZoom: false,
+ userPopoverAvatarAction: 'close',
userPopoverOverlay: true,
sidebarColumnWidth: '25rem',
contentColumnWidth: '45rem',
diff --git a/src/modules/users.js b/src/modules/users.js
index 59e8b391..de28766a 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -16,9 +16,6 @@ export const mergeOrAdd = (arr, obj, item) => {
// This is a new item, prepare it
arr.push(item)
obj[item.id] = item
- if (item.screen_name && !item.screen_name.includes('@')) {
- obj[item.screen_name.toLowerCase()] = item
- }
return { item, new: true }
}
}
@@ -162,7 +159,11 @@ export const mutations = {
if (user.relationship) {
state.relationships[user.relationship.id] = user.relationship
}
- mergeOrAdd(state.users, state.usersObject, user)
+ const res = mergeOrAdd(state.users, state.usersObject, user)
+ const item = res.item
+ if (res.new && item.screen_name && !item.screen_name.includes('@')) {
+ state.usersByNameObject[item.screen_name.toLowerCase()] = item
+ }
})
},
updateUserRelationship (state, relationships) {
@@ -242,12 +243,10 @@ export const mutations = {
export const getters = {
findUser: state => query => {
- const result = state.usersObject[query]
- // In case it's a screen_name, we can try searching case-insensitive
- if (!result && typeof query === 'string') {
- return state.usersObject[query.toLowerCase()]
- }
- return result
+ return state.usersObject[query]
+ },
+ findUserByName: state => query => {
+ return state.usersByNameObject[query.toLowerCase()]
},
findUserByUrl: state => query => {
return state.users
@@ -266,6 +265,7 @@ export const defaultState = {
currentUser: false,
users: [],
usersObject: {},
+ usersByNameObject: {},
signUpPending: false,
signUpErrors: [],
relationships: {}
@@ -288,6 +288,13 @@ const users = {
return user
})
},
+ fetchUserByName (store, name) {
+ return store.rootState.api.backendInteractor.fetchUserByName({ name })
+ .then((user) => {
+ store.commit('addNewUsers', [user])
+ return user
+ })
+ },
fetchUserRelationship (store, id) {
if (store.state.currentUser) {
store.rootState.api.backendInteractor.fetchUserRelationship({ id })
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index df899827..dd85b281 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -50,6 +50,7 @@ const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
const MASTODON_STATUS_URL = id => `/api/v1/statuses/${id}`
const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
const MASTODON_USER_URL = '/api/v1/accounts'
+const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
const MASTODON_USER_IN_LISTS = id => `/api/v1/accounts/${id}/lists`
@@ -326,6 +327,25 @@ const fetchUser = ({ id, credentials }) => {
.then((data) => parseUser(data))
}
+const fetchUserByName = ({ name, credentials }) => {
+ return promisedRequest({
+ url: MASTODON_USER_LOOKUP_URL,
+ credentials,
+ params: { acct: name }
+ })
+ .then(data => data.id)
+ .catch(error => {
+ if (error && error.statusCode === 404) {
+ // Either the backend does not support lookup endpoint,
+ // or there is no user with such name. Fallback and treat name as id.
+ return name
+ } else {
+ throw error
+ }
+ })
+ .then(id => fetchUser({ id, credentials }))
+}
+
const fetchUserRelationship = ({ id, credentials }) => {
const url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`
return fetch(url, { headers: authHeaders(credentials) })
@@ -1489,6 +1509,7 @@ const apiService = {
blockUser,
unblockUser,
fetchUser,
+ fetchUserByName,
fetchUserRelationship,
favorite,
unfavorite,
diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js
index 0fbab722..dc0b938a 100644
--- a/test/unit/specs/components/user_profile.spec.js
+++ b/test/unit/specs/components/user_profile.spec.js
@@ -15,6 +15,7 @@ const actions = {
const testGetters = {
findUser: state => getters.findUser(state.users),
+ findUserByName: state => getters.findUserByName(state.users),
relationship: state => getters.relationship(state.users),
mergedConfig: state => ({
colors: '',
@@ -95,6 +96,7 @@ const externalProfileStore = createStore({
credentials: ''
},
usersObject: { 100: extUser },
+ usersByNameObject: {},
users: [extUser],
relationships: {}
}
@@ -163,7 +165,8 @@ const localProfileStore = createStore({
currentUser: {
credentials: ''
},
- usersObject: { 100: localUser, testuser: localUser },
+ usersObject: { 100: localUser },
+ usersByNameObject: { testuser: localUser },
users: [localUser],
relationships: {}
}
diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js
index dfa5684d..3073f507 100644
--- a/test/unit/specs/modules/users.spec.js
+++ b/test/unit/specs/modules/users.spec.js
@@ -57,24 +57,27 @@ describe('The users module', () => {
})
describe('findUser', () => {
- it('returns user with matching screen_name', () => {
+ it('does not return user with matching screen_name', () => {
const user = { screen_name: 'Guy', id: '1' }
const state = {
usersObject: {
- 1: user,
+ 1: user
+ },
+ usersByNameObject: {
guy: user
}
}
const name = 'Guy'
- const expected = { screen_name: 'Guy', id: '1' }
- expect(getters.findUser(state)(name)).to.eql(expected)
+ expect(getters.findUser(state)(name)).to.eql(undefined)
})
it('returns user with matching id', () => {
const user = { screen_name: 'Guy', id: '1' }
const state = {
usersObject: {
- 1: user,
+ 1: user
+ },
+ usersByNameObject: {
guy: user
}
}
@@ -83,4 +86,35 @@ describe('The users module', () => {
expect(getters.findUser(state)(id)).to.eql(expected)
})
})
+
+ describe('findUserByName', () => {
+ it('returns user with matching screen_name', () => {
+ const user = { screen_name: 'Guy', id: '1' }
+ const state = {
+ usersObject: {
+ 1: user
+ },
+ usersByNameObject: {
+ guy: user
+ }
+ }
+ const name = 'Guy'
+ const expected = { screen_name: 'Guy', id: '1' }
+ expect(getters.findUserByName(state)(name)).to.eql(expected)
+ })
+
+ it('does not return user with matching id', () => {
+ const user = { screen_name: 'Guy', id: '1' }
+ const state = {
+ usersObject: {
+ 1: user
+ },
+ usersByNameObject: {
+ guy: user
+ }
+ }
+ const id = '1'
+ expect(getters.findUserByName(state)(id)).to.eql(undefined)
+ })
+ })
})
diff --git a/yarn.lock b/yarn.lock
index 15b139a7..d8215d9d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5188,6 +5188,11 @@ is-weakref@^1.0.1:
dependencies:
call-bind "^1.0.2"
+is-wsl@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+ integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==
+
is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
@@ -6551,13 +6556,12 @@ open@^8.4.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
-opn@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
- integrity sha1-erwi5kTf9jsKltWrfyeQwPAavJU=
+opn@5.5.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
+ integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
dependencies:
- object-assign "^4.0.1"
- pinkie-promise "^2.0.0"
+ is-wsl "^1.1.0"
optimist@^0.6.1:
version "0.6.1"
@@ -6835,16 +6839,6 @@ pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
-pinkie-promise@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
- dependencies:
- pinkie "^2.0.0"
-
-pinkie@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
-
pirates@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"