refactor(webapp): ds-table to plain html (#9251)

This commit is contained in:
Ulf Gebhardt 2026-02-20 20:29:47 +01:00 committed by GitHub
parent 951a24f100
commit cdf2d12e69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 813 additions and 659 deletions

View File

@ -25,7 +25,7 @@ Feature: Admin tag overview
Scenario: See an overview of tags
When I navigate to page "/admin/hashtags"
Then I can see the following table:
| No. | Hashtags | Users | Posts |
| No. | Name | Users | Posts |
| 1 | #Democracy | 3 | 3 |
| 2 | #Nature | 2 | 2 |
| 3 | #Ecology | 1 | 1 |

View File

@ -14,7 +14,7 @@ Phase 3: OsButton ██████████ 100% (133/133 Buttons) ✅
Phase 4: Tier 1 ██████████ 100% (OsButton, OsIcon, OsSpinner, OsCard) ✅
Phase 4: Tier A → HTML ██████████ 100% (10 ds-* Wrapper → Plain HTML) ✅
Phase 4: Tier B ██████░░░░ 60% (ds-chip→OsBadge✅, ds-tag→OsBadge✅, ds-grid✅, ds-number⬜, ds-radio⬜)
Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, OsMenu, OsSelect, OsTable)
Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅) | Tier 2-3 ausstehend (OsModal, OsInput, OsMenu, OsSelect)
```
### Statistiken
@ -24,10 +24,10 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
| Styleguide Komponenten | 38 (23 in Webapp genutzt) |
| **Gesamt** | **177** |
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge (5) |
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem (12) |
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table (13) |
| ✅ → OsBadge | Chip (20 Nutzungen → OsBadge), Tag (3 → OsBadge shape="square") |
| ⬜ → Plain HTML | Number, Radio (2) — Tier B Rest |
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select, Table (6) — Tier 2-4 |
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select (5) — Tier 2-3 |
| ⬜ Offen | Form (18 Dateien — HTML oder OsForm?) |
| ⬜ Nicht in Webapp | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
@ -60,9 +60,9 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
| 6 | Number | ⬜ Tier B | 5 Dateien → Plain HTML `<div class="ds-number">` |
| 7 | Placeholder | ✅ → HTML | Tier A: `<div class="ds-placeholder">` |
| 8 | Spinner | ✅ UI-Library | → OsSpinner (LoadingSpinner gelöscht) |
| 9 | Table | ⬜ Tier 4 | 7 Dateien → OsTable |
| 10 | TableCol | ⬜ Tier 4 | Intern von Table genutzt |
| 11 | TableHeadCol | ⬜ Tier 4 | Intern von Table genutzt |
| 9 | Table | ✅ → HTML | 7 Dateien → Plain HTML `<table>` + CSS-Klassen (kein OsTable nötig) |
| 10 | TableCol | ✅ → HTML | Ersetzt durch native `<td class="ds-table-col">` |
| 11 | TableHeadCol | ✅ → HTML | Ersetzt durch native `<th class="ds-table-head-col">` |
| 12 | Tag | ✅ UI-Library | → OsBadge shape="square" (3 Nutzungen in 3 Dateien) |
### Data Input

View File

@ -81,10 +81,10 @@ Phase 0: ██████████ 100% (6/6 Aufgaben) ✅
Phase 1: ██████████ 100% (6/6 Aufgaben) ✅
Phase 2: ██████████ 100% (26/26 Aufgaben) ✅
Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett
Phase 4: ██████░░░░ 54% (14/26 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅ | Tier B (rest), Tier 2-4 ausstehend
Phase 4: ██████░░░░ 59% (16/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅ | Tier B (rest), Tier 2-3 ausstehend
Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)
───────────────────────────────────────
Gesamt: ████████░░ 80% (76/95 Aufgaben)
Gesamt: ████████░░ 81% (78/96 Aufgaben)
```
### Katalogisierung (Details in KATALOG.md)
@ -205,11 +205,24 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
## Aktueller Stand
**Letzte Aktualisierung:** 2026-02-20 (Session 30)
**Letzte Aktualisierung:** 2026-02-20 (Session 31)
**Aktuelle Phase:** Phase 4 - Tier 1 ✅, Tier A ✅, OsBadge ✅ (ds-chip + ds-tag → UI-Library) | Tier B, Tier 2-4 ausstehend
**Aktuelle Phase:** Phase 4 - Tier 1 ✅, Tier A ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅ | Tier B (rest), Tier 2-3 ausstehend
**Zuletzt abgeschlossen (Session 30 - OsBadge Code-Review Fixes):**
**Zuletzt abgeschlossen (Session 31 - ds-table → Plain HTML):**
- [x] ds-table (7 Nutzungen) → native `<table>` + CSS-Klassen (kein OsTable nötig)
- [x] Table-CSS in `_ds-compat.scss`: .ds-table-wrap, .ds-table, .ds-table-col, .ds-table-head-col, .ds-table-bordered, .ds-table-condensed, Alignment-Klassen
- [x] `pages/admin/hashtags.vue`: 4 Spalten (index, id-Link, taggedCountUnique, taggedCount)
- [x] `pages/admin/categories.vue`: 3 Spalten (icon, name, postCount)
- [x] `pages/admin/users/index.vue`: 9-10 Spalten (conditional badges), komplexeste Tabelle
- [x] `pages/settings/blocked-users.vue`: 4 Spalten, unblockUser() auf direktes User-Objekt umgestellt
- [x] `pages/settings/muted-users.vue`: 4 Spalten, unmuteUser() auf direktes User-Objekt umgestellt
- [x] `components/Group/GroupMember.vue`: 5 Spalten (avatar, name, slug, roleInGroup, edit)
- [x] `components/features/FiledReportsTable/FiledReportsTable.vue`: 4 Spalten mit colgroup widths
- [x] `fields()` / `tableFields()` Computed Properties aus allen 7 Dateien entfernt (Labels direkt in `<th>`)
- [x] Alle 16 Tests bestanden (3 Test-Suites: admin/users Snapshots aktualisiert, FiledReportsTable ✅, ReportsTable ✅)
**Zuvor abgeschlossen (Session 30 - OsBadge Code-Review Fixes):**
- [x] `--color-default-contrast` zu `requiredCssVariables` in tailwind.preset.ts hinzugefügt
- [x] Doppelte `--color-default`-Deklaration in ocelot-ui-variables.scss entfernt (softest → softer konsolidiert)
- [x] Redundante Ternär-Ausdrücke entfernt: GroupTeaser.vue + groups/_slug.vue (`group ? group.about : ''``group.about`)
@ -248,9 +261,9 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
- [x] Test-Fix: Empty.spec.js `attributes().margin``classes().toContain('ds-my-xxx-small')`
- [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend
**Verbleibende ds-* Komponenten (9 Typen):**
**Verbleibende ds-* Komponenten (8 Typen):**
- Tier B Rest (→ Plain HTML): ds-number (5), ds-radio (1)
- Tier C (→ UI-Library): ds-input (23), ds-form (18), ds-modal (7), ds-menu/ds-menu-item (17), ds-table (7), ds-select (3)
- Tier C (→ UI-Library): ds-input (23), ds-form (18), ds-modal (7), ds-menu/ds-menu-item (17), ds-select (3)
**Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):**
- [x] Cypress: `.os-card .title``.os-card > .title` (Kind-Kombinator statt Nachfahren)
@ -644,7 +657,7 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ
- [x] ds-chip (5 Dateien) → OsBadge (UI-Library) ✅
- [x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library) ✅
- [ ] ds-number (5 Dateien) → `<div class="ds-number">`
- [ ] ds-grid / ds-grid-item (10 Dateien) → CSS Grid
- [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid
- [ ] ds-radio (1 Datei) → native `<input type="radio">`
**Tier 2: Layout & Feedback (UI-Library)**
@ -659,7 +672,7 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ
**Tier 4: Spezial-Komponenten**
- [ ] OsSelect (3 Dateien)
- [ ] OsTable (7 Dateien)
- [x] ds-table (7 Dateien) → Plain HTML `<table>` + CSS-Klassen ✅ (kein OsTable nötig)
- [ ] ds-form → Plain HTML `<form>` oder OsForm (18 Dateien)
**Infrastruktur**
@ -1788,6 +1801,11 @@ Bei der Migration werden:
| 2026-02-20 | **Type Safety** | BadgeVariant Typ exportiert, PlaygroundArgs typisiert, redundante Ternäre entfernt |
| 2026-02-20 | **Layout-Konsistenz** | GroupForm float:right → Flexbox align-self:flex-end, Inline-Style → Tailwind in Stories |
| 2026-02-20 | **ARIA Live Regions** | role="status"/aria-live="polite" auf 11 Form-Badges (WCAG 4.1.3), live Prop → Standard-Attribute |
| 2026-02-20 | **ds-grid → CSS Grid** | 10 Dateien migriert: ds-grid/ds-grid-item → native CSS Grid + Klassen |
| 2026-02-20 | **ds-table → Plain HTML (Session 31)** | 7 Dateien: `<ds-table>` → native `<table>` + CSS-Klassen (.ds-table, .ds-table-col, etc.) |
| 2026-02-20 | **Table-CSS** | _ds-compat.scss erweitert: .ds-table-wrap, .ds-table, .ds-table-col, .ds-table-head-col, bordered, condensed, alignment |
| 2026-02-20 | **fields() entfernt** | Computed Properties `fields()`/`tableFields()` aus 7 Dateien entfernt — Labels direkt in `<th>` |
| 2026-02-20 | **Scope-Objekte entfernt** | `scope.row` Zugriffe → direkte Iteration-Variable (user, tag, member, report) |
---
@ -1806,10 +1824,10 @@ Bei der Migration werden:
| Status | Komponenten |
|--------|------------|
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge (5) |
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem (10) — Tier A |
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table (13) — Tier A/B |
| ✅ → UI-Library | Chip, Tag → OsBadge (2) — Tier B |
| ⬜ → Plain HTML | Number, Grid, GridItem, Radio (4) — Tier B |
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select, Table (6) — Tier 2-4 |
| ⬜ → Plain HTML | Number, Radio (2) — Tier B |
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select (5) — Tier 2-3 |
| ⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
| ⬜ Offen | Form (18 Dateien — HTML `<form>` oder OsForm?) |

View File

@ -68,3 +68,29 @@
.ds-flex-gap-small { gap: $space-small; }
.ds-flex-gap-base { gap: $space-base; }
.ds-flex-gap-large { gap: $space-large; }
// ds-table Ersatz
.ds-table-wrap { width: 100%; overflow: auto; }
.ds-table { width: 100%; border-collapse: collapse; }
.ds-table-col {
vertical-align: middle;
padding: $space-small $space-xx-small;
&:last-child { padding-right: 0; }
}
.ds-table-head-col {
border-bottom: $border-size-base solid $border-color-softer;
padding: $space-small $space-xx-small;
text-align: left;
font-weight: $font-weight-bold;
}
.ds-table-bordered {
.ds-table-col, .ds-table-head-col { border-bottom: $border-size-base dotted $border-color-softer; }
tr:last-child .ds-table-col { border-bottom: none; }
}
.ds-table-condensed {
.ds-table-col, .ds-table-head-col { padding-top: $space-x-small; padding-bottom: $space-x-small; }
}
.ds-table-col, .ds-table-head-col {
&.ds-table-col-right, &.ds-table-head-col-right { text-align: right; }
&.ds-table-col-center, &.ds-table-head-col-center { text-align: center; }
}

View File

@ -2,74 +2,96 @@
<div class="group-member">
<h2 class="title">{{ $t('group.membersListTitle') }}</h2>
<div class="ds-mb-small"></div>
<ds-table :fields="tableFields" :data="groupMembers" condensed>
<template #avatar="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.user.id, slug: scope.row.user.slug },
}"
>
<profile-avatar :profile="scope.row.user" size="small" />
</nuxt-link>
</template>
<template #name="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.user.id, slug: scope.row.user.slug },
}"
>
<p class="ds-text">
<b>{{ scope.row.user.name | truncate(20) }}</b>
</p>
</nuxt-link>
</template>
<template #slug="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.user.id, slug: scope.row.user.slug },
}"
>
<p class="ds-text">
<b>{{ `@${scope.row.user.slug}` | truncate(20) }}</b>
</p>
</nuxt-link>
</template>
<template #roleInGroup="scope">
<select
v-if="scope.row.membership.role !== 'owner'"
:options="['pending', 'usual', 'admin', 'owner']"
:value="`${scope.row.membership.role}`"
@change="changeMemberRole(scope.row.user.id, $event)"
>
<option v-for="role in ['pending', 'usual', 'admin', 'owner']" :key="role" :value="role">
{{ $t(`group.roles.${role}`) }}
</option>
</select>
<os-badge v-else variant="primary">
{{ $t(`group.roles.${scope.row.membership.role}`) }}
</os-badge>
</template>
<template #edit="scope">
<os-button
v-if="scope.row.membership.role !== 'owner'"
appearance="outline"
variant="primary"
size="sm"
@click="
isOpen = true
userId = scope.row.user.id
"
>
<template #icon>
<os-icon :icon="icons.userTimes" />
</template>
{{ $t('group.removeMemberButton') }}
</os-button>
</template>
</ds-table>
<div class="ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<thead>
<tr>
<th scope="col" class="ds-table-head-col">
{{ $t('group.membersAdministrationList.avatar') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('group.membersAdministrationList.name') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('group.membersAdministrationList.slug') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('group.membersAdministrationList.roleInGroup') }}
</th>
<th class="ds-table-head-col" aria-hidden="true"></th>
</tr>
</thead>
<tbody>
<tr v-for="member in groupMembers" :key="member.user.id">
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: member.user.id, slug: member.user.slug },
}"
>
<profile-avatar :profile="member.user" size="small" />
</nuxt-link>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: member.user.id, slug: member.user.slug },
}"
>
<p class="ds-text">
<b>{{ member.user.name | truncate(20) }}</b>
</p>
</nuxt-link>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: member.user.id, slug: member.user.slug },
}"
>
<p class="ds-text">
<b>{{ `@${member.user.slug}` | truncate(20) }}</b>
</p>
</nuxt-link>
</td>
<td class="ds-table-col">
<select
v-if="member.membership.role !== 'owner'"
:value="`${member.membership.role}`"
@change="changeMemberRole(member.user.id, $event)"
>
<option v-for="role in groupRoles" :key="role" :value="role">
{{ $t(`group.roles.${role}`) }}
</option>
</select>
<os-badge v-else variant="primary">
{{ $t(`group.roles.${member.membership.role}`) }}
</os-badge>
</td>
<td class="ds-table-col">
<os-button
v-if="member.membership.role !== 'owner'"
appearance="outline"
variant="primary"
size="sm"
@click="
isOpen = true
userId = member.user.id
"
>
<template #icon>
<os-icon :icon="icons.userTimes" />
</template>
{{ $t('group.removeMemberButton') }}
</os-button>
</td>
</tr>
</tbody>
</table>
</div>
<ds-modal
v-if="isOpen"
v-model="isOpen"
@ -88,6 +110,8 @@ import { iconRegistry } from '~/utils/iconRegistry'
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
const GROUP_ROLES = ['pending', 'usual', 'admin', 'owner']
export default {
name: 'GroupMember',
components: {
@ -104,10 +128,12 @@ export default {
groupMembers: {
type: Array,
required: false,
default: () => [],
},
},
created() {
this.icons = iconRegistry
this.groupRoles = GROUP_ROLES
},
data() {
return {
@ -119,32 +145,6 @@ export default {
userId: null,
}
},
computed: {
tableFields() {
return {
avatar: {
label: this.$t('group.membersAdministrationList.avatar'),
align: 'left',
},
name: {
label: this.$t('group.membersAdministrationList.name'),
align: 'left',
},
slug: {
label: this.$t('group.membersAdministrationList.slug'),
align: 'left',
},
roleInGroup: {
label: this.$t('group.membersAdministrationList.roleInGroup'),
align: 'left',
},
edit: {
label: '',
align: 'left',
},
}
},
},
methods: {
async changeMemberRole(id, event) {
const newRole = event.target.value

View File

@ -1,31 +1,53 @@
<template>
<ds-table
class="nested-table"
v-if="filed && filed.length"
:data="filed"
:fields="fields"
condensed
>
<template #submitter="scope">
<user-teaser
:user="scope.row.submitter"
:showAvatar="false"
:showPopover="false"
data-test="filing-user"
/>
</template>
<template #reportedOn="scope">
<p class="ds-text ds-text-size-small">
<date-time :date-time="scope.row.createdAt" data-test="filed-date" />
</p>
</template>
<template #reasonCategory="scope">
{{ $t('report.reason.category.options.' + scope.row.reasonCategory) }}
</template>
<template #reasonDescription="scope">
{{ scope.row.reasonDescription.length ? scope.row.reasonDescription : '—' }}
</template>
</ds-table>
<div v-if="filed && filed.length" class="nested-table ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="30%" />
<col width="35%" />
</colgroup>
<thead>
<tr>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.submitter') }}</th>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.reportedOn') }}</th>
<th scope="col" class="ds-table-head-col">
{{ $t('moderation.reports.reasonCategory') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('moderation.reports.reasonDescription') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="report in filed" :key="report.id">
<td class="ds-table-col">
<user-teaser
:user="report.submitter"
:showAvatar="false"
:showPopover="false"
data-test="filing-user"
/>
</td>
<td class="ds-table-col">
<p class="ds-text ds-text-size-small">
<date-time :date-time="report.createdAt" data-test="filed-date" />
</p>
</td>
<td class="ds-table-col">
{{
report.reasonCategory
? $t('report.reason.category.options.' + report.reasonCategory)
: '—'
}}
</td>
<td class="ds-table-col">
{{ report.reasonDescription || '—' }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import UserTeaser from '~/components/UserTeaser/UserTeaser'
@ -39,28 +61,6 @@ export default {
props: {
filed: { type: Array, default: () => [] },
},
computed: {
fields() {
return {
submitter: {
label: this.$t('moderation.reports.submitter'),
width: '15%',
},
reportedOn: {
label: this.$t('moderation.reports.reportedOn'),
width: '20%',
},
reasonCategory: {
label: this.$t('moderation.reports.reasonCategory'),
width: '30%',
},
reasonDescription: {
label: this.$t('moderation.reports.reasonDescription'),
width: '35%',
},
}
},
},
}
</script>

View File

@ -2,8 +2,6 @@
<table
v-if="reports && reports.length"
class="ds-table ds-table-condensed ds-table-bordered reports-table"
cellspacing="0"
cellpadding="0"
>
<colgroup>
<col width="4%" />
@ -14,13 +12,13 @@
<col width="12%" />
</colgroup>
<thead class="ds-table-col ds-table-head-col">
<tr valign="top">
<th class="ds-table-head-col"></th>
<th class="ds-table-head-col">{{ $t('moderation.reports.submitter') }}</th>
<th class="ds-table-head-col">{{ $t('moderation.reports.content') }}</th>
<th class="ds-table-head-col">{{ $t('moderation.reports.author') }}</th>
<th class="ds-table-head-col">{{ $t('moderation.reports.status') }}</th>
<th class="ds-table-head-col">{{ $t('moderation.reports.decision') }}</th>
<tr>
<th class="ds-table-head-col" aria-hidden="true"></th>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.submitter') }}</th>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.content') }}</th>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.author') }}</th>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.status') }}</th>
<th scope="col" class="ds-table-head-col">{{ $t('moderation.reports.decision') }}</th>
</tr>
</thead>
<template v-for="report in reports">

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Name",
"empty": "Keine Themen gefunden.",
"name": "Themen",
"postCount": "Beiträge"
},
@ -108,11 +109,14 @@
"table": {
"columns": {
"badges": "Auszeichnungen",
"comments": "Kommentare",
"contributions": "Beiträge",
"createdAt": "Erstellt am",
"email": "E-Mail",
"name": "Name",
"number": "Nr.",
"role": "Rolle",
"shouted": "Geteilt",
"slug": "Alias"
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Name",
"empty": "No topics found.",
"name": "Topics",
"postCount": "Posts"
},
@ -108,11 +109,14 @@
"table": {
"columns": {
"badges": "Badges",
"comments": "Comments",
"contributions": "Contributions",
"createdAt": "Created at",
"email": "E-mail",
"name": "Name",
"number": "No.",
"role": "Role",
"shouted": "Shouted",
"slug": "Slug"
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Nombre",
"empty": "No se encontraron categorías.",
"name": "Categorías",
"postCount": "Contribuciones"
},
@ -107,12 +108,15 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"badges": "Insignias",
"comments": "Comentarios",
"contributions": "Contribuciones",
"createdAt": "Creado el",
"email": "Correo electrónico",
"name": "Nombre",
"number": "No.",
"number": "N.º",
"role": "Rol",
"shouted": "Compartido",
"slug": "Alias"
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Nom",
"empty": "Aucune catégorie trouvée.",
"name": "Catégories",
"postCount": "Postes"
},
@ -107,13 +108,16 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"createdAt": "Créé à",
"email": "Mail",
"badges": "Badges",
"comments": "Commentaires",
"contributions": "Contributions",
"createdAt": "Créé le",
"email": "E-mail",
"name": "Nom",
"number": "Num.",
"number": "N°",
"role": "Rôle",
"slug": "Slug"
"shouted": "Partagé",
"slug": "Identifiant"
}
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Nome",
"empty": "Nessuna categoria trovata.",
"name": "Categorie",
"postCount": "Messaggi"
},
@ -107,13 +108,16 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"createdAt": null,
"email": null,
"name": null,
"number": null,
"role": null,
"slug": null
"badges": "Badge",
"comments": "Commenti",
"contributions": "Contributi",
"createdAt": "Creato il",
"email": "E-mail",
"name": "Nome",
"number": "N.",
"role": "Ruolo",
"shouted": "Condiviso",
"slug": "Alias"
}
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Naam",
"empty": "Geen categorieën gevonden.",
"name": "Categorieën",
"postCount": "Berichten"
},
@ -107,13 +108,16 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"createdAt": null,
"email": null,
"name": null,
"number": null,
"role": null,
"slug": null
"badges": "Badges",
"comments": "Reacties",
"contributions": "Bijdragen",
"createdAt": "Aangemaakt op",
"email": "E-mail",
"name": "Naam",
"number": "Nr.",
"role": "Rol",
"shouted": "Gedeeld",
"slug": "Alias"
}
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Nazwa",
"empty": "Nie znaleziono kategorii.",
"name": "Kategorie",
"postCount": "Stanowiska"
},
@ -107,13 +108,16 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"createdAt": null,
"email": null,
"name": null,
"number": null,
"role": null,
"slug": null
"badges": "Odznaki",
"comments": "Komentarze",
"contributions": "Wpisy",
"createdAt": "Utworzono",
"email": "E-mail",
"name": "Nazwa",
"number": "Nr",
"role": "Rola",
"shouted": "Udostępniono",
"slug": "Alias"
}
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Nome",
"empty": "Nenhuma categoria encontrada.",
"name": "Categorias",
"postCount": "Postagens"
},
@ -107,12 +108,15 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"badges": "Medalhas",
"comments": "Comentários",
"contributions": "Contribuições",
"createdAt": "Criado em",
"email": "E-mail",
"name": "Nome",
"number": "N.º",
"role": "Função",
"shouted": "Partilhado",
"slug": "Slug"
}
}

View File

@ -39,6 +39,7 @@
},
"categories": {
"categoryName": "Имя",
"empty": "Категории не найдены.",
"name": "Категории",
"postCount": "Посты"
},
@ -107,12 +108,15 @@
"roleChanged": null,
"table": {
"columns": {
"badges": null,
"badges": "Награды",
"comments": "Комментарии",
"contributions": "Публикации",
"createdAt": "Дата создания",
"email": "Эл. почта",
"name": "Имя",
"number": "№",
"role": "Роль",
"shouted": "Поделились",
"slug": "Алиас"
}
}

View File

@ -9,24 +9,53 @@ describe('categories.vue', () => {
beforeEach(() => {
mocks = {
$t: jest.fn(),
$t: jest.fn((key) => key),
}
})
describe('mount', () => {
const Wrapper = () => {
return mount(Categories, {
mocks,
localVue,
})
}
const Wrapper = (data) => {
return mount(Categories, {
mocks,
localVue,
data: () => data,
})
}
describe('when categories exist', () => {
beforeEach(() => {
wrapper = Wrapper()
wrapper = Wrapper({
Category: [
{ id: '1', name: 'Environment', slug: 'environment', icon: 'tree', postCount: 12 },
{ id: '2', name: 'Democracy', slug: 'democracy', icon: 'balance-scale', postCount: 5 },
],
})
})
it('renders', () => {
expect(wrapper.classes('os-card')).toBe(true)
it('renders the table', () => {
expect(wrapper.find('table').exists()).toBe(true)
})
it('renders a row for each category', () => {
expect(wrapper.findAll('tbody tr')).toHaveLength(2)
})
it('does not show the empty placeholder', () => {
expect(wrapper.find('.ds-placeholder').exists()).toBe(false)
})
})
describe('when categories are empty', () => {
beforeEach(() => {
wrapper = Wrapper({ Category: [] })
})
it('does not render the table', () => {
expect(wrapper.find('table').exists()).toBe(false)
})
it('shows the empty placeholder', () => {
expect(wrapper.find('.ds-placeholder').exists()).toBe(true)
expect(mocks.$t).toHaveBeenCalledWith('admin.categories.empty')
})
})
})

View File

@ -1,12 +1,37 @@
<template>
<os-card>
<h2 class="title">{{ $t('admin.categories.name') }}</h2>
<ds-table :data="Category" :fields="fields" condensed>
<template #icon="scope">
<os-icon :icon="resolveIcon(scope.row.icon)" />
</template>
</ds-table>
</os-card>
<div>
<os-card v-if="Category && Category.length">
<h2 class="title">{{ $t('admin.categories.name') }}</h2>
<div class="ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<thead>
<tr>
<th class="ds-table-head-col" aria-hidden="true"></th>
<th scope="col" class="ds-table-head-col">
{{ $t('admin.categories.categoryName') }}
</th>
<th scope="col" class="ds-table-head-col ds-table-head-col-right">
{{ $t('admin.categories.postCount') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="category in Category" :key="category.id">
<td class="ds-table-col">
<os-icon :icon="resolveIcon(category.icon)" />
</td>
<td class="ds-table-col">{{ category.name }}</td>
<td class="ds-table-col ds-table-col-right">{{ category.postCount }}</td>
</tr>
</tbody>
</table>
</div>
</os-card>
<os-card v-else>
<h2 class="title">{{ $t('admin.categories.name') }}</h2>
<div class="ds-placeholder">{{ $t('admin.categories.empty') }}</div>
</os-card>
</div>
</template>
<script>
@ -21,18 +46,6 @@ export default {
Category: [],
}
},
computed: {
fields() {
return {
icon: ' ',
name: this.$t('admin.categories.categoryName'),
postCount: {
label: this.$t('admin.categories.postCount'),
align: 'right',
},
}
},
},
methods: {
resolveIcon(iconName) {
return resolveIcon(iconName)

View File

@ -1,14 +1,34 @@
<template>
<os-card>
<h2 class="title">{{ $t('admin.hashtags.name') }}</h2>
<ds-table :data="Tag" :fields="fields" condensed>
<template #index="scope">{{ scope.index + 1 }}.</template>
<template #id="scope">
<nuxt-link :to="{ path: '/', query: { hashtag: encodeURI(scope.row.id) } }">
<b>#{{ scope.row.id | truncateStr(20) }}</b>
</nuxt-link>
</template>
</ds-table>
<div class="ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<thead>
<tr>
<th scope="col" class="ds-table-head-col">{{ $t('admin.hashtags.number') }}</th>
<th scope="col" class="ds-table-head-col">{{ $t('admin.hashtags.nameOfHashtag') }}</th>
<th scope="col" class="ds-table-head-col ds-table-head-col-right">
{{ $t('admin.hashtags.tagCountUnique') }}
</th>
<th scope="col" class="ds-table-head-col ds-table-head-col-right">
{{ $t('admin.hashtags.tagCount') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(tag, index) in Tag" :key="tag.id">
<td class="ds-table-col">{{ index + 1 }}.</td>
<td class="ds-table-col">
<nuxt-link :to="{ path: '/', query: { hashtag: encodeURI(tag.id) } }">
<b>#{{ tag.id | truncateStr(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col ds-table-col-right">{{ tag.taggedCountUnique }}</td>
<td class="ds-table-col ds-table-col-right">{{ tag.taggedCount }}</td>
</tr>
</tbody>
</table>
</div>
</os-card>
</template>
@ -23,22 +43,6 @@ export default {
Tag: [],
}
},
computed: {
fields() {
return {
index: this.$t('admin.hashtags.number'),
id: this.$t('admin.hashtags.name'),
taggedCountUnique: {
label: this.$t('admin.hashtags.tagCountUnique'),
align: 'right',
},
taggedCount: {
label: this.$t('admin.hashtags.tagCount'),
align: 'right',
},
}
},
},
apollo: {
Tag: {
query: gql`

View File

@ -125,106 +125,98 @@ exports[`Users given badges are disabled renders 1`] = `
class="ds-table-wrap"
>
<table
cellpadding="0"
cellspacing="0"
class="ds-table ds-table-condensed ds-table-bordered"
>
<colgroup>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
</colgroup>
<thead>
<tr>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.number
admin.users.table.columns.number
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.name
admin.users.table.columns.name
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.email
admin.users.table.columns.email
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.slug
admin.users.table.columns.slug
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.createdAt
admin.users.table.columns.createdAt
</th>
<th
aria-label="admin.users.table.columns.contributions"
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🖉
</th>
<th
aria-label="admin.users.table.columns.comments"
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🗨
</th>
<th
aria-label="admin.users.table.columns.shouted"
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🖉
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
>
🗨
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
>
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
>
admin.users.table.columns.role
admin.users.table.columns.role
</th>
<!---->
</tr>
</thead>
<tbody>
<tr>
<td
@ -232,6 +224,7 @@ exports[`Users given badges are disabled renders 1`] = `
>
NaN.
</td>
<td
class="ds-table-col"
>
@ -243,6 +236,7 @@ exports[`Users given badges are disabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
@ -254,6 +248,7 @@ exports[`Users given badges are disabled renders 1`] = `
</b>
</a>
</td>
<td
class="ds-table-col"
>
@ -265,39 +260,40 @@ exports[`Users given badges are disabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
<select />
<!---->
</td>
<!---->
</tr>
<tr>
<td
@ -305,6 +301,7 @@ exports[`Users given badges are disabled renders 1`] = `
>
NaN.
</td>
<td
class="ds-table-col"
>
@ -316,6 +313,7 @@ exports[`Users given badges are disabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
@ -327,6 +325,7 @@ exports[`Users given badges are disabled renders 1`] = `
</b>
</a>
</td>
<td
class="ds-table-col"
>
@ -338,39 +337,40 @@ exports[`Users given badges are disabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
<select />
<!---->
</td>
<!---->
</tr>
</tbody>
</table>
@ -576,116 +576,105 @@ exports[`Users given badges are enabled renders 1`] = `
class="ds-table-wrap"
>
<table
cellpadding="0"
cellspacing="0"
class="ds-table ds-table-condensed ds-table-bordered"
>
<colgroup>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
<col
width=""
/>
</colgroup>
<thead>
<tr>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.number
admin.users.table.columns.number
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.name
admin.users.table.columns.name
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.email
admin.users.table.columns.email
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.slug
admin.users.table.columns.slug
</th>
<th
class="ds-table-head-col"
scope="col"
>
admin.users.table.columns.createdAt
admin.users.table.columns.createdAt
</th>
<th
aria-label="admin.users.table.columns.contributions"
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🖉
</th>
<th
aria-label="admin.users.table.columns.comments"
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🗨
</th>
<th
aria-label="admin.users.table.columns.shouted"
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🖉
admin.users.table.columns.role
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
scope="col"
>
🗨
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
>
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
>
admin.users.table.columns.role
</th>
<th
class="ds-table-head-col ds-table-head-col-right"
>
admin.users.table.columns.badges
admin.users.table.columns.badges
</th>
</tr>
</thead>
<tbody>
<tr>
<td
@ -693,6 +682,7 @@ exports[`Users given badges are enabled renders 1`] = `
>
NaN.
</td>
<td
class="ds-table-col"
>
@ -704,6 +694,7 @@ exports[`Users given badges are enabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
@ -715,6 +706,7 @@ exports[`Users given badges are enabled renders 1`] = `
</b>
</a>
</td>
<td
class="ds-table-col"
>
@ -726,39 +718,39 @@ exports[`Users given badges are enabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
<select />
<!---->
</td>
<td
class="ds-table-col ds-table-col-right"
>
@ -800,6 +792,7 @@ exports[`Users given badges are enabled renders 1`] = `
>
NaN.
</td>
<td
class="ds-table-col"
>
@ -811,6 +804,7 @@ exports[`Users given badges are enabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
@ -822,6 +816,7 @@ exports[`Users given badges are enabled renders 1`] = `
</b>
</a>
</td>
<td
class="ds-table-col"
>
@ -833,39 +828,39 @@ exports[`Users given badges are enabled renders 1`] = `
</b>
</nuxt-link-stub>
</td>
<td
class="ds-table-col"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
</td>
<td
class="ds-table-col ds-table-col-right"
>
<select />
<!---->
</td>
<td
class="ds-table-col ds-table-col-right"
>

View File

@ -27,67 +27,125 @@
</ds-form>
</os-card>
<os-card v-if="User && User.length">
<ds-table :data="User" :fields="fields" condensed>
<template #index="scope">{{ scope.row.index + 1 }}.</template>
<template #name="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<b>{{ scope.row.name | truncate(20) }}</b>
</nuxt-link>
</template>
<template #email="scope">
<a :href="`mailto:${scope.row.email}`">
<b>{{ scope.row.email }}</b>
</a>
</template>
<template #slug="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<b>{{ scope.row.slug | truncate(20) }}</b>
</nuxt-link>
</template>
<template #createdAt="scope">
{{ scope.row.createdAt | dateTime }}
</template>
<template #role="scope">
<template v-if="userRoles">
<select
v-if="scope.row.id !== currentUser.id"
:value="`${scope.row.role}`"
v-on:change="changeUserRole(scope.row.id, $event)"
>
<option v-for="value in userRoles" :key="value">
{{ value }}
</option>
</select>
<p class="ds-text" v-else>{{ scope.row.role }}</p>
</template>
</template>
<template #badges="scope">
<os-button
as="nuxt-link"
:to="{
name: 'admin-users-id',
params: { id: scope.row.id },
}"
variant="primary"
appearance="filled"
circle
:aria-label="$t('actions.edit')"
>
<template #icon><os-icon :icon="icons.pencil" /></template>
</os-button>
</template>
</ds-table>
<div class="ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<thead>
<tr>
<th scope="col" class="ds-table-head-col">
{{ $t('admin.users.table.columns.number') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('admin.users.table.columns.name') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('admin.users.table.columns.email') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('admin.users.table.columns.slug') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('admin.users.table.columns.createdAt') }}
</th>
<th
scope="col"
class="ds-table-head-col ds-table-head-col-right"
:aria-label="$t('admin.users.table.columns.contributions')"
>
🖉
</th>
<th
scope="col"
class="ds-table-head-col ds-table-head-col-right"
:aria-label="$t('admin.users.table.columns.comments')"
>
🗨
</th>
<th
scope="col"
class="ds-table-head-col ds-table-head-col-right"
:aria-label="$t('admin.users.table.columns.shouted')"
>
</th>
<th scope="col" class="ds-table-head-col ds-table-head-col-right">
{{ $t('admin.users.table.columns.role') }}
</th>
<th
v-if="$env.BADGES_ENABLED"
scope="col"
class="ds-table-head-col ds-table-head-col-right"
>
{{ $t('admin.users.table.columns.badges') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="user in User" :key="user.id">
<td class="ds-table-col">{{ user.index + 1 }}.</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<b>{{ user.name | truncate(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col">
<a :href="`mailto:${user.email}`">
<b>{{ user.email }}</b>
</a>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<b>{{ user.slug | truncate(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col">
{{ user.createdAt | dateTime }}
</td>
<td class="ds-table-col ds-table-col-right">{{ user.contributionsCount }}</td>
<td class="ds-table-col ds-table-col-right">{{ user.commentedCount }}</td>
<td class="ds-table-col ds-table-col-right">{{ user.shoutedCount }}</td>
<td class="ds-table-col ds-table-col-right">
<template v-if="userRoles.length">
<select
v-if="user.id !== currentUser.id"
:value="user.role"
@change="changeUserRole(user.id, $event)"
>
<option v-for="value in userRoles" :key="value" :value="value">
{{ value }}
</option>
</select>
<p class="ds-text" v-else>{{ user.role }}</p>
</template>
</td>
<td v-if="$env.BADGES_ENABLED" class="ds-table-col ds-table-col-right">
<os-button
as="nuxt-link"
:to="{
name: 'admin-users-id',
params: { id: user.id },
}"
variant="primary"
appearance="filled"
circle
:aria-label="$t('actions.edit')"
>
<template #icon><os-icon :icon="icons.pencil" /></template>
</os-button>
</td>
</tr>
</tbody>
</table>
</div>
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
</os-card>
<os-card v-else>
@ -140,40 +198,6 @@ export default {
...mapGetters({
currentUser: 'auth/user',
}),
fields() {
const fields = {
index: this.$t('admin.users.table.columns.number'),
name: this.$t('admin.users.table.columns.name'),
email: this.$t('admin.users.table.columns.email'),
slug: this.$t('admin.users.table.columns.slug'),
createdAt: this.$t('admin.users.table.columns.createdAt'),
contributionsCount: {
label: '🖉',
align: 'right',
},
commentedCount: {
label: '🗨',
align: 'right',
},
shoutedCount: {
label: '❤',
align: 'right',
},
role: {
label: this.$t('admin.users.table.columns.role'),
align: 'right',
},
}
if (this.$env.BADGES_ENABLED) {
fields.badges = {
label: this.$t('admin.users.table.columns.badges'),
align: 'right',
}
}
return fields
},
},
apollo: {
User: {
@ -245,8 +269,4 @@ export default {
.admin-users > .os-card:first-child {
margin-bottom: $space-small;
}
.ds-table-col {
vertical-align: middle;
}
</style>

View File

@ -20,53 +20,72 @@
</os-card>
</div>
<os-card v-if="blockedUsers && blockedUsers.length">
<ds-table :data="blockedUsers" :fields="fields" condensed>
<template #avatar="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<profile-avatar :profile="scope.row" size="small" />
</nuxt-link>
</template>
<template #name="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<b>{{ scope.row.name | truncate(20) }}</b>
</nuxt-link>
</template>
<template #slug="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<b>{{ scope.row.slug | truncate(20) }}</b>
</nuxt-link>
</template>
<template #unblockUser="scope">
<os-button
data-test="unblock-btn"
variant="primary"
appearance="outline"
circle
size="sm"
:loading="unblockingUserId === scope.row.id"
:aria-label="$t('settings.blocked-users.columns.unblock')"
@click="unblockUser(scope)"
>
<template #icon><os-icon :icon="icons.userPlus" /></template>
</os-button>
</template>
</ds-table>
<div class="ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<thead>
<tr>
<th class="ds-table-head-col" aria-hidden="true"></th>
<th scope="col" class="ds-table-head-col">
{{ $t('settings.blocked-users.columns.name') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('settings.blocked-users.columns.slug') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('settings.blocked-users.columns.unblock') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="user in blockedUsers" :key="user.id">
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<profile-avatar :profile="user" size="small" />
</nuxt-link>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<b>{{ user.name | truncate(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<b>{{ user.slug | truncate(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col">
<os-button
data-test="unblock-btn"
variant="primary"
appearance="outline"
circle
size="sm"
:loading="unblockingUserId === user.id"
:aria-label="$t('settings.blocked-users.columns.unblock')"
@click="unblockUser(user)"
>
<template #icon><os-icon :icon="icons.userPlus" /></template>
</os-button>
</td>
</tr>
</tbody>
</table>
</div>
</os-card>
<os-card v-else>
<div class="ds-mb-large">
@ -105,29 +124,19 @@ export default {
unblockingUserId: null,
}
},
computed: {
fields() {
return {
avatar: '',
name: this.$t('settings.blocked-users.columns.name'),
slug: this.$t('settings.blocked-users.columns.slug'),
unblockUser: this.$t('settings.blocked-users.columns.unblock'),
}
},
},
apollo: {
blockedUsers: { query: blockedUsers, fetchPolicy: 'cache-and-network' },
},
methods: {
async unblockUser(user) {
this.unblockingUserId = user.row.id
this.unblockingUserId = user.id
try {
await this.$apollo.mutate({
mutation: unblockUser(),
variables: { id: user.row.id },
variables: { id: user.id },
})
this.$apollo.queries.blockedUsers.refetch()
const { name } = user.row
const { name } = user
this.$toast.success(this.$t('settings.blocked-users.unblocked', { name }))
} catch (error) {
this.$toast.error(error.message)
@ -138,9 +147,3 @@ export default {
},
}
</script>
<style lang="scss">
.ds-table-col {
vertical-align: middle;
}
</style>

View File

@ -17,52 +17,72 @@
</os-card>
</div>
<os-card v-if="mutedUsers && mutedUsers.length">
<ds-table :data="mutedUsers" :fields="fields" condensed>
<template #avatar="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<profile-avatar :profile="scope.row" size="small" />
</nuxt-link>
</template>
<template #name="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<b>{{ scope.row.name | truncate(20) }}</b>
</nuxt-link>
</template>
<template #slug="scope">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: scope.row.id, slug: scope.row.slug },
}"
>
<b>{{ scope.row.slug | truncate(20) }}</b>
</nuxt-link>
</template>
<template #unmuteUser="scope">
<os-button
data-test="unmute-btn"
variant="primary"
appearance="outline"
circle
size="sm"
:aria-label="$t('settings.muted-users.columns.unmute')"
@click="unmuteUser(scope)"
>
<template #icon><os-icon :icon="icons.userPlus" /></template>
</os-button>
</template>
</ds-table>
<div class="ds-table-wrap">
<table class="ds-table ds-table-condensed ds-table-bordered">
<thead>
<tr>
<th class="ds-table-head-col" aria-hidden="true"></th>
<th scope="col" class="ds-table-head-col">
{{ $t('settings.muted-users.columns.name') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('settings.muted-users.columns.slug') }}
</th>
<th scope="col" class="ds-table-head-col">
{{ $t('settings.muted-users.columns.unmute') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="user in mutedUsers" :key="user.id">
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<profile-avatar :profile="user" size="small" />
</nuxt-link>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<b>{{ user.name | truncate(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col">
<nuxt-link
:to="{
name: 'profile-id-slug',
params: { id: user.id, slug: user.slug },
}"
>
<b>{{ user.slug | truncate(20) }}</b>
</nuxt-link>
</td>
<td class="ds-table-col">
<os-button
data-test="unmute-btn"
variant="primary"
appearance="outline"
circle
size="sm"
:loading="unmutingUserId === user.id"
:aria-label="$t('settings.muted-users.columns.unmute')"
@click="unmuteUser(user)"
>
<template #icon><os-icon :icon="icons.userPlus" /></template>
</os-button>
</td>
</tr>
</tbody>
</table>
</div>
</os-card>
<os-card v-else>
<div class="ds-mb-large">
@ -100,37 +120,29 @@ export default {
data() {
return {
mutedUsers: [],
unmutingUserId: null,
}
},
computed: {
fields() {
return {
avatar: '',
name: this.$t('settings.muted-users.columns.name'),
slug: this.$t('settings.muted-users.columns.slug'),
unmuteUser: this.$t('settings.muted-users.columns.unmute'),
}
},
},
apollo: {
mutedUsers: { query: mutedUsers, fetchPolicy: 'cache-and-network' },
},
methods: {
async unmuteUser(user) {
await this.$apollo.mutate({
mutation: unmuteUser(),
variables: { id: user.row.id },
})
this.$apollo.queries.mutedUsers.refetch()
const { name } = user.row
this.$toast.success(this.$t('settings.muted-users.unmuted', { name }))
this.unmutingUserId = user.id
try {
await this.$apollo.mutate({
mutation: unmuteUser(),
variables: { id: user.id },
})
this.$apollo.queries.mutedUsers.refetch()
const { name } = user
this.$toast.success(this.$t('settings.muted-users.unmuted', { name }))
} catch (error) {
this.$toast.error(error.message)
} finally {
this.unmutingUserId = null
}
},
},
}
</script>
<style lang="scss">
.ds-table-col {
vertical-align: middle;
}
</style>