diff --git a/.github/workflows/test-webapp.yml b/.github/workflows/test-webapp.yml index 9ca3023cc..c1aee47cf 100644 --- a/.github/workflows/test-webapp.yml +++ b/.github/workflows/test-webapp.yml @@ -23,7 +23,7 @@ jobs: prepare: name: Prepare - if: needs.files-changed.outputs.webapp + if: needs.files-changed.outputs.webapp == 'true' needs: files-changed runs-on: ubuntu-latest steps: @@ -37,7 +37,7 @@ jobs: build_test_webapp: name: Docker Build Test - Webapp - if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp + if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true' needs: [files-changed, prepare] runs-on: ubuntu-latest steps: @@ -57,7 +57,7 @@ jobs: lint_webapp: name: Lint Webapp - if: needs.files-changed.outputs.webapp + if: needs.files-changed.outputs.webapp == 'true' needs: files-changed runs-on: ubuntu-latest steps: @@ -69,7 +69,7 @@ jobs: unit_test_webapp: name: Unit Tests - Webapp - if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp + if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true' needs: [files-changed, build_test_webapp] runs-on: ubuntu-latest permissions: diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index d27c64e57..03c3d4456 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -92,6 +92,21 @@ describe('Room', () => { }) }) + describe('user id is self', () => { + it('throws error', async () => { + await expect( + mutate({ + mutation: createRoomMutation(), + variables: { + userId: 'chatting-user', + }, + }), + ).resolves.toMatchObject({ + errors: [{ message: 'Cannot create a room with self' }], + }) + }) + }) + describe('user id exists', () => { it('returns the id of the room', async () => { const result = await mutate({ diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index 02309e172..d5015a03b 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -32,6 +32,9 @@ export default { const { user: { id: currentUserId }, } = context + if (userId === currentUserId) { + throw new Error('Cannot create a room with self') + } const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { const createRoomCypher = ` diff --git a/backend/src/schema/types/type/Location.gql b/backend/src/schema/types/type/Location.gql index fad24cc26..9cb5c970a 100644 --- a/backend/src/schema/types/type/Location.gql +++ b/backend/src/schema/types/type/Location.gql @@ -25,4 +25,3 @@ type LocationMapBox { type Query { queryLocations(place: String!, lang: String!): [LocationMapBox]! } - diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 0a7277515..7e6d1d0e7 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -84,8 +84,8 @@ input _PostFilter { group: _GroupFilter postsInMyGroups: Boolean postType_in: [PostType] - eventStart_gte: String - eventEnd_gte: String + eventStart_gte: String + eventEnd_gte: String } enum _PostOrdering { diff --git a/cypress/cypress.config.js b/cypress/cypress.config.js index 2d2cefc47..b169c0ce0 100644 --- a/cypress/cypress.config.js +++ b/cypress/cypress.config.js @@ -42,10 +42,7 @@ module.exports = defineConfig({ baseUrl: "http://localhost:3000", specPattern: "cypress/e2e/**/*.feature", supportFile: "cypress/support/e2e.js", - retries: { - runMode: 2, - openMode: 0, - }, + retries: 0, video: false, setupNodeEvents, }, diff --git a/deployment/TODO-next-update.md b/deployment/TODO-next-update.md index 8630275b7..8e30d1f47 100644 --- a/deployment/TODO-next-update.md +++ b/deployment/TODO-next-update.md @@ -2,6 +2,10 @@ When you overtake this deploy and rebrand repo to your network you have to recognize the following changes and doings: +## Version >= 2.7.0 with 'ocelotDockerVersionTag' 2.7.0-470 + +- You have to rename all `.js` files to `.ts` in `branding/constants` + ## Version >= 2.4.0 with 'ocelotDockerVersionTag' 2.4.0-298 - You have to set `SHOW_CONTENT_FILTER_HEADER_MENU` and `SHOW_CONTENT_FILTER_MASONRY_GRID` in `branding/constants/filter.js` originally in main code file `webapp/constants/filter.js` to your preferred value. diff --git a/deployment/configurations/stage.ocelot.social b/deployment/configurations/stage.ocelot.social index 350237c62..fdc2e52fa 160000 --- a/deployment/configurations/stage.ocelot.social +++ b/deployment/configurations/stage.ocelot.social @@ -1 +1 @@ -Subproject commit 350237c62dcff1a5c34f1e8d718f89b05ce3d33f +Subproject commit fdc2e52fa444b300e1c4736600bc0e9ae3314222 diff --git a/webapp/assets/_new/icons/svgs/chat-bubble.svg b/webapp/assets/_new/icons/svgs/chat-bubble.svg new file mode 100644 index 000000000..377b52f2f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/chat-bubble.svg @@ -0,0 +1,4 @@ + +chat-bubble + + \ No newline at end of file diff --git a/webapp/assets/_new/styles/export.scss b/webapp/assets/_new/styles/export.scss new file mode 100644 index 000000000..88b42bfc9 --- /dev/null +++ b/webapp/assets/_new/styles/export.scss @@ -0,0 +1,30 @@ + +:export { + colorPrimary: $color-primary; + colorPrimaryActive: $color-primary-active; + colorPrimaryLight: $color-primary-light; + + borderColorSoft: $border-color-soft; + + borderRadiusBase: $border-radius-base; + + textColorBase: $text-color-base; + textColorSoft: $text-color-soft; + textColorInverse: $text-color-inverse; + + boxShadowBase: $box-shadow-base; + + backgroundColorBase: $background-color-base; + backgroundColorSoft: $background-color-soft; + backgroundColorSoftest: $background-color-softest; + backgroundColorPrimary: $background-color-primary; + + colorNeutral30: $color-neutral-30; + + chatMessageColor: $chat-message-color; + + chatMessageBgMe: $chat-message-bg-me; + chatMessageBgOthers: $chat-message-bg-others; + + chatNewMessageColor: $chat-new-message-color; + } \ No newline at end of file diff --git a/webapp/assets/_new/styles/tokens.scss b/webapp/assets/_new/styles/tokens.scss index 22e0214ff..e001ffa84 100644 --- a/webapp/assets/_new/styles/tokens.scss +++ b/webapp/assets/_new/styles/tokens.scss @@ -406,4 +406,14 @@ $color-toast-green: $color-success; $color-ribbon-event: $background-color-third; $color-ribbon-event-active: $background-color-third-active; $color-ribbon-article: $background-color-secondary; -$color-ribbon-article-active: $background-color-secondary-active; \ No newline at end of file +$color-ribbon-article-active: $background-color-secondary-active; + +/** + * @tokens Chat Color + */ + +$chat-message-bg-me: $color-primary-light; +$chat-message-color: $text-color-base; +$chat-message-bg-others: $color-neutral-80; +$chat-sidemenu-bg: $color-secondary-active; +$chat-new-message-color: $color-secondary-active; diff --git a/webapp/components/Chat/Chat.vue b/webapp/components/Chat/Chat.vue index 2b9514bf3..cca6c4319 100644 --- a/webapp/components/Chat/Chat.vue +++ b/webapp/components/Chat/Chat.vue @@ -15,13 +15,42 @@ :rooms-loaded="true" show-files="false" show-audio="false" + :styles="JSON.stringify(computedChatStyle)" :show-footer="true" @send-message="sendMessage($event.detail[0])" @fetch-messages="fetchMessages($event.detail[0])" :responsive-breakpoint="responsiveBreakpoint" :single-room="singleRoom" @show-demo-options="showDemoOptions = $event" - /> + > +
+
+ +
+
+ +
+
+
+ {{ getInitialsName(selectedRoom.roomName) }} +
+
+ +
+
+
+ {{ getInitialsName(room.roomName) }} +
+
+
@@ -29,6 +58,7 @@ - diff --git a/webapp/components/HeaderMenu/HeaderMenu.vue b/webapp/components/HeaderMenu/HeaderMenu.vue index bef73186b..f4d48220e 100644 --- a/webapp/components/HeaderMenu/HeaderMenu.vue +++ b/webapp/components/HeaderMenu/HeaderMenu.vue @@ -74,6 +74,10 @@ @@ -55,7 +59,6 @@ export default { .chat-modul { background-color: rgb(233, 228, 228); - height: 667px; width: 355px; position: fixed; bottom: 45px; diff --git a/webapp/locales/de.json b/webapp/locales/de.json index febb8898c..e1137deb7 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -77,6 +77,24 @@ } } }, + "chat": { + "cancelSelectMessage": "Abbrechen", + "conversationStarted": "Unterhaltung startete am:", + "isOnline": "online", + "isTyping": "tippt...", + "lastSeen": "zuletzt gesehen ", + "messageDeleted": "Diese Nachricht wuerde gelöscht", + "messagesEmpty": "Keine Nachrichten", + "newMessages": "Neue Nachrichten", + "roomEmpty": "Keinen Raum selektiert", + "roomsEmpty": "Keine Räume", + "search": "Suche", + "typeMessage": "Nachricht schreiben", + "userProfileButton": { + "label": "Chat", + "tooltip": "Chatte mit „{name}“" + } + }, "client-only": { "loading": "Lade …" }, @@ -552,6 +570,9 @@ }, "groups": "Gruppen", "myProfile": "Mein Profil" + }, + "chat": { + "tooltip": "Meine Chats" } }, "index": { @@ -580,7 +601,7 @@ "moreInfo": "Was ist {APPLICATION_NAME}?", "moreInfoHint": "zur Präsentationsseite", "no-account": "Du hast noch kein Nutzerkonto?", - "no-cookie": "Es kann kein Cookie angelegt werden. Du must Cookies akzeptieren.", + "no-cookie": "Es kann kein Cookie angelegt werden. Du musst Cookies akzeptieren.", "password": "Dein Passwort", "register": "Nutzerkonto erstellen", "success": "Du bist eingeloggt!" @@ -595,9 +616,16 @@ "button": { "tooltip": "Landkarte anzeigen" }, - "markerTypes": { + "legend": { + "event": "Veranstaltung", "group": "Gruppe", - "theUser": "deine Position", + "theUser": "Meine Position", + "user": "Nutzer" + }, + "markerTypes": { + "event": "Veranstaltung", + "group": "Gruppe", + "theUser": "meine Position", "user": "Nutzer" }, "pageTitle": "Landkarte", @@ -691,10 +719,6 @@ "unread": "Ungelesen" }, "group": "Beschreibung", - "headerMenuButton": { - "chat": "Meine Chat", - "tooltip": "Meine Benachrichtigungen" - }, "markAllAsRead": "Markiere alle als gelesen", "pageLink": "Alle Benachrichtigungen", "post": "Beitrag oder Gruppe", @@ -710,11 +734,6 @@ "title": "Benachrichtigungen", "user": "Nutzer" }, - "position": { - "group": "Gruppe", - "my": "Meine Position", - "user": "Nutzer" - }, "post": { "comment": { "reply": "Antworten", @@ -759,9 +778,9 @@ "viewEvent": { "eventEnd": "Ende", "eventIsOnline": "Online", - "eventLocationName": "Stadt", + "eventLocationName": "Stadt - z.B. Musterstraße 1, 12345 Musterstadt", "eventStart": "Beginn", - "eventVenue": "Veranstaltungsort", + "eventVenue": "Veranstaltungsort - z.B. Hinterhof, 1. OG, ...", "title": "Veranstaltung" }, "viewPost": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index c0fb5c2f6..2e1e361ab 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -77,6 +77,24 @@ } } }, + "chat": { + "cancelSelectMessage": "Cancel", + "conversationStarted": "Conversation started on:", + "isOnline": "is online", + "isTyping": "is writing...", + "lastSeen": "last seen ", + "messageDeleted": "This message was deleted", + "messagesEmpty": "No messages", + "newMessages": "New Messages", + "roomEmpty": "No room selected", + "roomsEmpty": "No rooms", + "search": "Search", + "typeMessage": "Type message", + "userProfileButton": { + "label": "Chat", + "tooltip": "Chat with “{name}”" + } + }, "client-only": { "loading": "Loading …" }, @@ -552,6 +570,9 @@ }, "groups": "Groups", "myProfile": "My profile" + }, + "chat": { + "tooltip": "My chats" } }, "index": { @@ -595,9 +616,16 @@ "button": { "tooltip": "Show map" }, + "legend": { + "event": "Event", + "group": "Group", + "theUser": "My position", + "user": "User" + }, "markerTypes": { + "event": "event", "group": "group", - "theUser": "your position", + "theUser": "my position", "user": "user" }, "pageTitle": "Map", @@ -691,10 +719,6 @@ "unread": "Unread" }, "group": "Description", - "headerMenuButton": { - "chat": "My Chat", - "tooltip": "My notifications" - }, "markAllAsRead": "Mark all as read", "pageLink": "All notifications", "post": "Post or Group", @@ -710,11 +734,6 @@ "title": "Notifications", "user": "User" }, - "position": { - "group": "Group", - "my": "My position", - "user": "User" - }, "post": { "comment": { "reply": "Reply", @@ -759,9 +778,9 @@ "viewEvent": { "eventEnd": "End", "eventIsOnline": "Online", - "eventLocationName": "City", + "eventLocationName": "City - e.g. Example street 1, 12345 City", "eventStart": "Start", - "eventVenue": "Venue", + "eventVenue": "Venue - e.g. Backyard, 1st Floor, ...", "title": "Event" }, "viewPost": { diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index 66a23c191..c9b4db317 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -105,6 +105,7 @@ export default { styleguideStyles, '~assets/_new/styles/tokens.scss', '~assets/styles/imports/_branding.scss', + '~assets/_new/styles/export.scss', ], }, diff --git a/webapp/pages/map.vue b/webapp/pages/map.vue index 1a34cbda1..2bf1817f7 100644 --- a/webapp/pages/map.vue +++ b/webapp/pages/map.vue @@ -5,16 +5,15 @@ {{ $t('map.pageTitle') }}
- my position - {{ $t('position.my') }} - user - {{ $t('position.user') }} - group - {{ $t('position.group') }} + + + {{ $t('map.legend.' + type.id) }} +    +
@@ -66,6 +65,7 @@ import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css' import { mapGetters } from 'vuex' import { profileUserQuery, mapUserQuery } from '~/graphql/User' import { groupQuery } from '~/graphql/groups' +import { filterPosts } from '~/graphql/PostQuery.js' import mobile from '~/mixins/mobile' import Empty from '~/components/Empty/Empty' import MapStylesButtons from '~/components/Map/MapStylesButtons' @@ -95,19 +95,40 @@ export default { currentUserCoordinates: null, users: null, groups: null, + posts: null, markers: { - icons: [ + types: [ { - id: 'marker-blue', - name: 'mapbox-marker-icon-20px-blue.png', + id: 'theUser', + icon: { + id: 'marker-orange', + legendName: 'mapbox-marker-icon-orange.svg', + mapName: 'mapbox-marker-icon-20px-orange.png', + }, }, { - id: 'marker-orange', - name: 'mapbox-marker-icon-20px-orange.png', + id: 'user', + icon: { + id: 'marker-green', + legendName: 'mapbox-marker-icon-green.svg', + mapName: 'mapbox-marker-icon-20px-green.png', + }, }, { - id: 'marker-green', - name: 'mapbox-marker-icon-20px-green.png', + id: 'group', + icon: { + id: 'marker-red', + legendName: 'mapbox-marker-icon-red.svg', + mapName: 'mapbox-marker-icon-20px-red.png', + }, + }, + { + id: 'event', + icon: { + id: 'marker-purple', + legendName: 'mapbox-marker-icon-purple.svg', + mapName: 'mapbox-marker-icon-20px-purple.png', + }, }, ], isImagesLoaded: false, @@ -137,7 +158,8 @@ export default { this.markers.isImagesLoaded && this.currentUser && this.users && - this.groups + this.groups && + this.posts ) }, styles() { @@ -236,17 +258,27 @@ export default { // Copy coordinates array. const coordinates = e.features[0].geometry.coordinates.slice() - const markerTypeLabel = - e.features[0].properties.type === 'group' - ? this.$t('map.markerTypes.group') - : e.features[0].properties.type === 'user' - ? this.$t('map.markerTypes.user') - : this.$t('map.markerTypes.theUser') - const markerProfileLinkTitle = - (e.features[0].properties.type === 'group' ? '&' : '@') + e.features[0].properties.slug - const markerProfileLink = - (e.features[0].properties.type === 'group' ? '/group' : '/profile') + - `/${e.features[0].properties.id}/${e.features[0].properties.slug}` + const markerTypeLabel = this.$t(`map.markerTypes.${e.features[0].properties.type}`) + const markerProfile = { + theUser: { + linkTitle: '@' + e.features[0].properties.slug, + link: `/profile/${e.features[0].properties.id}/${e.features[0].properties.slug}`, + }, + user: { + linkTitle: '@' + e.features[0].properties.slug, + link: `/profile/${e.features[0].properties.id}/${e.features[0].properties.slug}`, + }, + group: { + linkTitle: '&' + e.features[0].properties.slug, + link: `/group/${e.features[0].properties.id}/${e.features[0].properties.slug}`, + }, + event: { + linkTitle: e.features[0].properties.slug, + link: `/post/${e.features[0].properties.id}/${e.features[0].properties.slug}`, + }, + } + const markerProfileLinkTitle = markerProfile[e.features[0].properties.type].linkTitle + const markerProfileLink = markerProfile[e.features[0].properties.type].link let description = `
@@ -258,11 +290,11 @@ export default {
` description += - e.features[0].properties.about && e.features[0].properties.about.length > 0 + e.features[0].properties.description && e.features[0].properties.description.length > 0 ? `
- ${e.features[0].properties.about} + ${e.features[0].properties.description}
` : '' @@ -305,15 +337,18 @@ export default { }, loadMarkersIconsAndAddMarkers() { Promise.all( - this.markers.icons.map( + this.markers.types.map( (marker) => new Promise((resolve, reject) => { // our images have to be in the 'static/img/*' folder otherwise they are not reachable via URL - this.map.loadImage('img/mapbox/marker-icons/' + marker.name, (error, image) => { - if (error) throw error - this.map.addImage(marker.id, image) - resolve() - }) + this.map.loadImage( + 'img/mapbox/marker-icons/' + marker.icon.mapName, + (error, image) => { + if (error) throw error + this.map.addImage(marker.icon.id, image) + resolve() + }, + ) }), ), ).then(() => { @@ -337,7 +372,7 @@ export default { id: user.id, slug: user.slug, name: user.name, - about: user.about ? user.about : undefined, + description: user.about ? user.about : undefined, }, geometry: { type: 'Point', @@ -346,27 +381,6 @@ export default { }) } }) - // add markers for "groups" - this.groups.forEach((group) => { - if (group.location) { - this.markers.geoJSON.push({ - type: 'Feature', - properties: { - type: 'group', - iconName: 'marker-blue', - iconRotate: 0.0, - id: group.id, - slug: group.slug, - name: group.name, - about: group.about ? group.about : undefined, - }, - geometry: { - type: 'Point', - coordinates: this.getCoordinates(group.location), - }, - }) - } - }) // add marker for "currentUser" if (this.currentUserCoordinates) { this.markers.geoJSON.push({ @@ -378,7 +392,7 @@ export default { id: this.currentUser.id, slug: this.currentUser.slug, name: this.currentUser.name, - about: this.currentUser.about ? this.currentUser.about : undefined, + description: this.currentUser.about ? this.currentUser.about : undefined, }, geometry: { type: 'Point', @@ -386,6 +400,48 @@ export default { }, }) } + // add markers for "groups" + this.groups.forEach((group) => { + if (group.location) { + this.markers.geoJSON.push({ + type: 'Feature', + properties: { + type: 'group', + iconName: 'marker-red', + iconRotate: 0.0, + id: group.id, + slug: group.slug, + name: group.name, + description: group.about ? group.about : undefined, + }, + geometry: { + type: 'Point', + coordinates: this.getCoordinates(group.location), + }, + }) + } + }) + // add markers for "posts", post type "Event" with location coordinates + this.posts.forEach((post) => { + if (post.postType.includes('Event') && post.eventLocation) { + this.markers.geoJSON.push({ + type: 'Feature', + properties: { + type: 'event', + iconName: 'marker-purple', + iconRotate: 0.0, + id: post.id, + slug: post.slug, + name: post.title, + description: post.contentExcerpt, + }, + geometry: { + type: 'Point', + coordinates: this.getCoordinates(post.eventLocation), + }, + }) + } + }) this.markers.isGeoJSON = true } @@ -483,6 +539,24 @@ export default { }, fetchPolicy: 'cache-and-network', }, + Post: { + query() { + return filterPosts(this.$i18n) + }, + variables() { + return { + filter: { + postType_in: ['Event'], + eventStart_gte: new Date(), + // would be good to just query for events with defined "eventLocation". couldn't get it working + }, + } + }, + update({ Post }) { + this.posts = Post + }, + fetchPolicy: 'cache-and-network', + }, }, } diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index 62d2f1be7..ce97cd53a 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -80,13 +80,14 @@ @update="updateFollow" /> - + {{ $t('chat.userProfileButton.label') }}