diff --git a/frontend/controller/actions/group.js b/frontend/controller/actions/group.js index be78db844f..10e180cc56 100644 --- a/frontend/controller/actions/group.js +++ b/frontend/controller/actions/group.js @@ -755,7 +755,9 @@ export default (sbp('sbp/selectors/register', { const rootState = sbp('state/vuex/state') const contractState = rootState[groupID] if (memberID && rootState.contracts[groupID]?.type === 'gi.contracts/group' && contractState?.profiles?.[memberID]?.status === PROFILE_STATUS.ACTIVE) { - console.warn(`autoBanSenderOfMessage: autobanning ${memberID} from ${groupID}`) + const rootGetters = sbp('state/vuex/getters') + const username = rootGetters.usernameFromID(memberID) + console.warn(`autoBanSenderOfMessage: autobanning ${memberID} (username ${username}) from ${groupID}`) // find existing proposal if it exists let [proposalHash, proposal]: [string, ?Object] = Object.entries(contractState.proposals) .find(([hash, prop]: [string, Object]) => ( diff --git a/frontend/model/contracts/chatroom.js b/frontend/model/contracts/chatroom.js index 33078eecd1..e229ccb1e5 100644 --- a/frontend/model/contracts/chatroom.js +++ b/frontend/model/contracts/chatroom.js @@ -4,7 +4,7 @@ import { L, Vue } from '@common/common.js' import sbp from '@sbp/sbp' -import { objectOf, optional, string, arrayOf } from '~/frontend/model/contracts/misc/flowTyper.js' +import { objectOf, optional, string, arrayOf, actionRequireInnerSignature } from '~/frontend/model/contracts/misc/flowTyper.js' import { findForeignKeysByContractID, findKeyIdByName } from '~/shared/domains/chelonia/utils.js' import { CHATROOM_ACTIONS_PER_PAGE, @@ -18,7 +18,7 @@ import { MESSAGE_TYPES, POLL_STATUS } from './shared/constants.js' -import { createMessage, findMessageIdx, leaveChatRoom, makeMentionFromUserID, actionRequireInnerSignature } from './shared/functions.js' +import { createMessage, findMessageIdx, leaveChatRoom, makeMentionFromUserID } from './shared/functions.js' import { cloneDeep, merge } from './shared/giLodash.js' import { makeNotification } from './shared/nativeNotification.js' import { chatRoomAttributesType, messageType } from './shared/types.js' diff --git a/frontend/model/contracts/group.js b/frontend/model/contracts/group.js index 40fd17f102..4d94d0bc96 100644 --- a/frontend/model/contracts/group.js +++ b/frontend/model/contracts/group.js @@ -14,13 +14,13 @@ import { CHATROOM_GENERAL_NAME, CHATROOM_PRIVACY_LEVEL, CHATROOM_TYPES } from './shared/constants.js' import { paymentStatusType, paymentType, PAYMENT_COMPLETED } from './shared/payments/index.js' -import { createPaymentInfo, paymentHashesFromPaymentPeriod, actionRequireInnerSignature } from './shared/functions.js' +import { createPaymentInfo, paymentHashesFromPaymentPeriod } from './shared/functions.js' import { cloneDeep, deepEqualJSONType, omit, merge } from './shared/giLodash.js' import { addTimeToDate, dateToPeriodStamp, dateFromPeriodStamp, isPeriodStamp, comparePeriodStamps, dateIsWithinPeriod, DAYS_MILLIS, periodStampsForDate, plusOnePeriodLength } from './shared/time.js' import { unadjustedDistribution, adjustedDistribution } from './shared/distribution/distribution.js' import currencies from './shared/currencies.js' import { inviteType, groupChatRoomAttributesType } from './shared/types.js' -import { arrayOf, objectOf, objectMaybeOf, optional, string, number, boolean, object, unionOf, tupleOf } from '~/frontend/model/contracts/misc/flowTyper.js' +import { arrayOf, objectOf, objectMaybeOf, optional, string, number, boolean, object, unionOf, tupleOf, actionRequireInnerSignature } from '~/frontend/model/contracts/misc/flowTyper.js' import { findKeyIdByName, findForeignKeysByContractID } from '~/shared/domains/chelonia/utils.js' import { REMOVE_NOTIFICATION } from '~/frontend/model/notifications/mutationKeys.js' @@ -1846,7 +1846,11 @@ sbp('chelonia/defineContract', { return } - // TODO: Use this later + // TODO: Use this later. This is an attempt at removing the need for JOINING_GROUP. + // The following detects whether we're in the process of joining, and if we + // are, it doesn't remove the contract and calls /join to complete the + // joining process. It'll likely be useful later for removing JOINING_GROUP + // and handling re-joininig within the group contract itself. /* if (isNaN(0) && member === identityContractID) { // It looks like we were removed. Now, before removing the contract we diff --git a/frontend/model/contracts/manifests.json b/frontend/model/contracts/manifests.json index 0d482e5e94..8fd47c2cf7 100644 --- a/frontend/model/contracts/manifests.json +++ b/frontend/model/contracts/manifests.json @@ -1,7 +1,7 @@ { "manifests": { - "gi.contracts/chatroom": "z9brRu3VFH6CXQpXRjLtV8GzCSajcHJvXgE6aYTYCcMefQJta7dC", - "gi.contracts/group": "z9brRu3VLNocAURgHNfNT5tKNesEAmWki8ykHizeQcob49qMjGZ1", + "gi.contracts/chatroom": "z9brRu3VRwA9WQ6nhRbJz8KvZ3W6a43vhXrx1iES6yRsExsXzTpG", + "gi.contracts/group": "z9brRu3VNeHviRAc7DLPBQqyagTrpUHoXF9YdMFr1gtrgYGMzfSB", "gi.contracts/identity": "z9brRu3VSVRddCxKg3YRMP8niKH7gwNq5TexdjvBEDfoLRv97DBh" } } diff --git a/frontend/model/contracts/misc/flowTyper.js b/frontend/model/contracts/misc/flowTyper.js index 2897e14130..a848cd0490 100644 --- a/frontend/model/contracts/misc/flowTyper.js +++ b/frontend/model/contracts/misc/flowTyper.js @@ -401,4 +401,12 @@ function unionOf_ (...typeFuncs) { } // $FlowFixMe // const unionOf: UnionT = (unionOf_) -export const unionOf = unionOf_ \ No newline at end of file +export const unionOf = unionOf_ + +export const actionRequireInnerSignature = (next: Function): Function => (data, props) => { + const innerSigningContractID = props.message.innerSigningContractID + if (!innerSigningContractID || innerSigningContractID === props.contractID) { + throw new Error('Missing inner signature') + } + return next(data, props) +} \ No newline at end of file diff --git a/frontend/model/contracts/shared/functions.js b/frontend/model/contracts/shared/functions.js index 6264a0e3a2..c83388efbf 100644 --- a/frontend/model/contracts/shared/functions.js +++ b/frontend/model/contracts/shared/functions.js @@ -150,11 +150,3 @@ export function makeMentionFromUserID (userID: string): { all: '@all' } } - -export const actionRequireInnerSignature = (next: Function): Function => (data, props) => { - const innerSigningContractID = props.message.innerSigningContractID - if (!innerSigningContractID || innerSigningContractID === props.contractID) { - throw new Error('Missing inner signature') - } - return next(data, props) -} diff --git a/frontend/views/components/UsersSelector.vue b/frontend/views/components/UsersSelector.vue index 7528b06785..dafe34c3ac 100644 --- a/frontend/views/components/UsersSelector.vue +++ b/frontend/views/components/UsersSelector.vue @@ -12,7 +12,7 @@ form.c-search-form(@submit.prevent='') name='search' ) .profile-wrapper( - v-for='(contractID, index) in users' + v-for='(contractID, index) in userIDs' :key='index' ) .profile @@ -31,7 +31,7 @@ form.c-search-form(@submit.prevent='') @keyup='onHandleKeyUp' ) - .buttons.is-end.c-button-container(v-if='users.length') + .buttons.is-end.c-button-container(v-if='userIDs.length') button-submit.is-success.c-create-btn(@click='submitHandler') i18n Create @@ -52,7 +52,7 @@ export default ({ type: String, required: true }, - users: { + userIDs: { type: Array, default: [] }, diff --git a/frontend/views/containers/chatroom/MessageBase.vue b/frontend/views/containers/chatroom/MessageBase.vue index 458f3119d8..eeff17da5b 100644 --- a/frontend/views/containers/chatroom/MessageBase.vue +++ b/frontend/views/containers/chatroom/MessageBase.vue @@ -204,6 +204,11 @@ export default ({ const possibleMentions = Object.keys(this.chatRoomMembers).map(u => makeMentionFromUserID(u).me).filter(v => !!v) return text + // We try to find all the mentions and render them as mentions instead + // of regular text. The `(?<=\\s|^)` part ensures that a mention is + // preceded by a space or is the start of a line and the `(?=[^\\w\\d]|$)` + // ensures that it's followed by an end-of-line or a character that's not + // a letter or a number (so `Hi @user!` works). .split(new RegExp(`(?<=\\s|^)(${allMention}|${possibleMentions.join('|')})(?=[^\\w\\d]|$)`)) .map(t => { if (t === allMention) { diff --git a/frontend/views/containers/chatroom/NewDirectMessageModal.vue b/frontend/views/containers/chatroom/NewDirectMessageModal.vue index 54c4b70887..1a23e7d9f6 100644 --- a/frontend/views/containers/chatroom/NewDirectMessageModal.vue +++ b/frontend/views/containers/chatroom/NewDirectMessageModal.vue @@ -11,7 +11,7 @@ modal-base-template.has-background( .card.c-card users-selector( :label='L("Search")' - :users='selections' + :userIDs='selections' :autofocus='true' @change='onChangeKeyword' @remove='onRemoveSelection' diff --git a/frontend/views/containers/dashboard/GroupMembersActivity.vue b/frontend/views/containers/dashboard/GroupMembersActivity.vue index 14832b8a99..1a6585dbf0 100644 --- a/frontend/views/containers/dashboard/GroupMembersActivity.vue +++ b/frontend/views/containers/dashboard/GroupMembersActivity.vue @@ -91,15 +91,15 @@ export default ({ ]), onTimePayments () { return Object.entries(this.groupStreaks.onTimePayments || {}) - .filter(([username, streak]) => streak >= STREAK_ON_TIME_PAYMENTS) + .filter(([, streak]) => streak >= STREAK_ON_TIME_PAYMENTS) .sort((a, b) => b[1] - a[1]) - .map(([username, streak]) => L('{user} - {count} month streak', { user: this.userDisplayNameFromID(username), count: streak })) + .map(([memberID, streak]) => L('{user} - {count} month streak', { user: this.userDisplayNameFromID(memberID), count: streak })) }, missedPayments () { return Object.entries(this.groupStreaks.missedPayments || {}) - .filter(([username, streak]) => streak >= STREAK_MISSED_PAYMENTS) - .map(([username, streak]) => { - const Largs = { user: this.userDisplayNameFromID(username), streak } + .filter(([, streak]) => streak >= STREAK_MISSED_PAYMENTS) + .map(([memberID, streak]) => { + const Largs = { user: this.userDisplayNameFromID(memberID), streak } return streak >= 2 ? L('{user} missed {streak} payments', Largs) @@ -110,19 +110,19 @@ export default ({ const now = new Date().toISOString() return Object.entries(this.groupProfiles) - .filter(([username, profile]) => compareISOTimestamps(now, profile.lastLoggedIn) >= STREAK_NOT_LOGGED_IN_DAYS * DAYS_MILLIS) - .map(([username]) => this.userDisplayNameFromID(username)) + .filter(([, profile]) => compareISOTimestamps(now, profile.lastLoggedIn) >= STREAK_NOT_LOGGED_IN_DAYS * DAYS_MILLIS) + .map(([memberID]) => this.userDisplayNameFromID(memberID)) }, noIncomeDetails () { // group members that haven't entered their income details yet return Object.entries(this.groupProfiles) - .filter(([username, profile]) => !profile.incomeDetailsType) - .map(([username]) => this.userDisplayNameFromID(username)) + .filter(([, profile]) => !profile.incomeDetailsType) + .map(([memberID]) => this.userDisplayNameFromID(memberID)) }, noVotes () { return Object.entries(this.groupStreaks.noVotes || {}) - .filter(([username, streak]) => streak >= STREAK_MISSED_PROPSAL_VOTE) - .map(([username, streak]) => { - const Largs = { user: this.userDisplayNameFromID(username), streak } + .filter(([, streak]) => streak >= STREAK_MISSED_PROPSAL_VOTE) + .map(([memberID, streak]) => { + const Largs = { user: this.userDisplayNameFromID(memberID), streak } return streak >= 2 ? L('{user} missed {streak} votes', Largs) diff --git a/frontend/views/containers/payments/PaymentDetail.vue b/frontend/views/containers/payments/PaymentDetail.vue index 631550dae9..c08fa3bc90 100644 --- a/frontend/views/containers/payments/PaymentDetail.vue +++ b/frontend/views/containers/payments/PaymentDetail.vue @@ -96,7 +96,7 @@ export default ({ }, subtitleCopy () { const toMemberID = this.payment.data.toMemberID - const arg = (username) => ({ name: this.userDisplayNameFromID(username) }) + const arg = (memberID) => ({ name: this.userDisplayNameFromID(memberID) }) return toMemberID === this.ourIdentityContractId ? L('Sent by {name}', arg(this.fromMemberID)) : L('Sent to {name}', arg(toMemberID)) } }, diff --git a/frontend/views/pages/DesignSystem.vue b/frontend/views/pages/DesignSystem.vue index c7974a42b0..26e3d2b231 100644 --- a/frontend/views/pages/DesignSystem.vue +++ b/frontend/views/pages/DesignSystem.vue @@ -1050,7 +1050,7 @@ page( pre | users-selector( | label='Search for users' - | :users='["contractID 1", "contractID 2", "contractID 3"]' + | :userIDs='["contractID 1", "contractID 2", "contractID 3"]' | defaultValue='contractID 1' | :autofocus='true' | @change='onChange' @@ -1061,7 +1061,7 @@ page( td users-selector( label='Search for users' - :users='form.searchUsers' + :userIDs='form.searchUserIDs' defaultValue='contractID 1' ) tr @@ -1534,7 +1534,7 @@ export default ({ }, form: { searchValue: '', - searchUsers: [], + searchUserIDs: [], selectPayment: 'choose', copyableInput: '', sliderValue: 25