Merge remote-tracking branch 'origin/develop' into shigusegubu-vue3

* origin/develop: (41 commits)
  lint
  Apply 1 suggestion(s) to 1 file(s)
  Use feed role for notifs and timelines
  Update dependency chai to v4.3.7
  Make notification panel a list of articles
  Handle properly 2-col and 3-col desktop notifications
  Make user panel and notification <aside>
  Make timeline a list of articles
  Add labels to mobile navs
  Add labels for timeline header
  Use <main> for main content
  Add title to mobile post button
  Update dependency eslint-plugin-n to v15.5.0
  Update dependency eslint-plugin-promise to v6.1.1
  Update Node.js to v16.18.1
  Update dependency eslint-plugin-vue to v9.7.0
  Update dependency eslint-plugin-n to v15.4.0
  Update dependency eslint to v8.26.0
  Update vue monorepo
  Update dependency css-minimizer-webpack-plugin to v4.2.2
  ...
This commit is contained in:
Henry Jameson 2022-11-21 22:35:47 +02:00
commit d16d754127
43 changed files with 925 additions and 513 deletions

View file

@ -60,7 +60,7 @@
v-if="shouldShowAncestors"
class="thread-ancestors"
>
<div
<article
v-for="status in ancestorsOf(diveRoot)"
:key="status.id"
class="thread-ancestor"
@ -130,7 +130,7 @@
</i18n-t>
</div>
</div>
</div>
</article>
</div>
<thread-tree
v-for="status in showingTopLevel"
@ -168,34 +168,36 @@
v-if="isLinearView"
class="thread-body"
>
<status
v-for="status in conversation"
:key="status.id"
ref="statusComponent"
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
:show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:focused="focused(status.id)"
:in-conversation="isExpanded"
:highlight="getHighlight()"
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
class="conversation-status status-fadein panel-body"
<article>
<status
v-for="status in conversation"
:key="status.id"
ref="statusComponent"
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
:show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:focused="focused(status.id)"
:in-conversation="isExpanded"
:highlight="getHighlight()"
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
class="conversation-status status-fadein panel-body"
:toggle-thread-display="toggleThreadDisplay"
:thread-display-status="threadDisplayStatus"
:show-thread-recursively="showThreadRecursively"
:total-reply-count="totalReplyCount"
:total-reply-depth="totalReplyDepth"
:status-content-properties="statusContentProperties"
:set-status-content-property="setStatusContentProperty"
:toggle-status-content-property="toggleStatusContentProperty"
:toggle-thread-display="toggleThreadDisplay"
:thread-display-status="threadDisplayStatus"
:show-thread-recursively="showThreadRecursively"
:total-reply-count="totalReplyCount"
:total-reply-depth="totalReplyDepth"
:status-content-properties="statusContentProperties"
:set-status-content-property="setStatusContentProperty"
:toggle-status-content-property="toggleStatusContentProperty"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
/>
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
/>
</article>
</div>
</div>
</div>

View file

@ -6,9 +6,16 @@
>
<slot />
<!-- TODO: make the 'x' disappear if at the end maybe? -->
<div class="hidden-overlay" :style="overlayStyle" ref="hiddenOverlay">
<div
ref="hiddenOverlay"
class="hidden-overlay"
:style="overlayStyle"
>
<span>{{ preText }}</span>
<span class="caret" ref="hiddenOverlayCaret">x</span>
<span
ref="hiddenOverlayCaret"
class="caret"
>x</span>
<span>{{ postText }}</span>
</div>
<template v-if="enableEmojiPicker">
@ -33,9 +40,9 @@
/>
</template>
<Popover
ref="suggestorPopover"
class="autocomplete-panel"
placement="bottom"
ref="suggestorPopover"
>
<template #content>
<div

View file

@ -1,8 +1,8 @@
<template>
<Popover
ref="popover"
trigger="click"
popover-class="emoji-picker popover-default"
ref="popover"
@show="onPopoverShown"
@close="onPopoverClosed"
>
@ -66,12 +66,12 @@
>
<div class="emoji-search">
<input
ref="search"
v-model="keyword"
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
ref="search"
>
</div>
<div

View file

@ -113,8 +113,7 @@ const ExtraButtons = {
currentUser () { return this.$store.state.users.currentUser },
canDelete () {
if (!this.currentUser) { return }
const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin
return superuser || this.status.user.id === this.currentUser.id
return this.currentUser.privileges.includes('messages_delete') || this.status.user.id === this.currentUser.id
},
ownStatus () {
return this.status.user.id === this.currentUser.id

View file

@ -39,7 +39,10 @@ const FavoriteButton = {
}
},
computed: {
...mapGetters(['mergedConfig'])
...mapGetters(['mergedConfig']),
remoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
}
}

View file

@ -33,13 +33,19 @@
/>
</FALayers>
</button>
<span v-else>
<a
v-else
class="button-unstyled interactive"
target="_blank"
role="button"
:href="remoteInteractionLink"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
:title="$t('tool_tip.favorite')"
:icon="['far', 'star']"
/>
</span>
</a>
<span
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
class="action-counter"

View file

@ -15,7 +15,7 @@ const Interactions = {
return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
filterMode: tabModeDict.mentions,
canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role)
canSeeReports: this.$store.state.users.currentUser.privileges.includes('reports_manage_reports')
}
},
methods: {

View file

@ -10,6 +10,8 @@
<div class="item">
<button
class="button-unstyled mobile-nav-button"
:title="$t('nav.mobile_sidebar')"
:aria-expanaded="$refs.sideDrawer && !$refs.sideDrawer.closed"
@click.stop.prevent="toggleMobileSidebar()"
>
<FAIcon
@ -26,6 +28,7 @@
<button
v-if="currentUser"
class="button-unstyled mobile-nav-button"
:title="unseenNotificationsCount ? $t('nav.mobile_notifications_unread_active') : $t('nav.mobile_notifications')"
@click.stop.prevent="openMobileNotifications()"
>
<FAIcon
@ -39,7 +42,7 @@
</button>
</div>
</nav>
<div
<aside
v-if="currentUser"
class="mobile-notifications-drawer"
:class="{ '-closed': !notificationsOpen }"
@ -48,10 +51,11 @@
>
<div class="mobile-notifications-header">
<span class="title">{{ $t('notifications.notifications') }}</span>
<span class="spacer"/>
<span class="spacer" />
<button
v-if="notificationsAtTop"
class="button-unstyled mobile-nav-button"
:title="$t('general.scroll_to_top')"
@click.stop.prevent="scrollMobileNotificationsToTop"
>
<FALayers class="fa-scale-110 fa-old-padding-layer">
@ -64,6 +68,7 @@
</button>
<button
class="button-unstyled mobile-nav-button"
:title="$t('nav.mobile_notifications_close')"
@click.stop.prevent="closeMobileNotifications(true)"
>
<FAIcon
@ -74,11 +79,11 @@
</div>
<div
id="mobile-notifications"
class="mobile-notifications"
ref="mobileNotifications"
class="mobile-notifications"
@scroll="onScroll"
/>
</div>
</aside>
<SideDrawer
ref="sideDrawer"
:logout="logout"

View file

@ -3,6 +3,7 @@
v-if="isLoggedIn"
class="MobilePostButton button-default new-status-button"
:class="{ 'hidden': isHidden, 'always-show': isPersistent }"
:title="$t('post_status.new_status')"
@click="openPostForm"
>
<FAIcon icon="pen" />

View file

@ -41,14 +41,26 @@ const ModerationTools = {
tagsSet () {
return new Set(this.user.tags)
},
hasTagPolicy () {
return this.$store.state.instance.tagPolicyAvailable
canGrantRole () {
return this.user.is_local && !this.user.deactivated && this.$store.state.users.currentUser.role === 'admin'
},
canChangeActivationState () {
return this.privileged('users_manage_activation_state')
},
canDeleteAccount () {
return this.privileged('users_delete')
},
canUseTagPolicy () {
return this.$store.state.instance.tagPolicyAvailable && this.privileged('users_manage_tags')
}
},
methods: {
hasTag (tagName) {
return this.tagsSet.has(tagName)
},
privileged (privilege) {
return this.$store.state.users.currentUser.privileges.includes(privilege)
},
toggleTag (tag) {
const store = this.$store
if (this.tagsSet.has(tag)) {

View file

@ -10,7 +10,7 @@
>
<template #content>
<div class="dropdown-menu">
<span v-if="user.is_local">
<span v-if="canGrantRole">
<button
class="button-default dropdown-item"
@click="toggleRight(&quot;admin&quot;)"
@ -24,28 +24,31 @@
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
</button>
<div
v-if="canChangeActivationState || canDeleteAccount"
role="separator"
class="dropdown-divider"
/>
</span>
<button
v-if="canChangeActivationState"
class="button-default dropdown-item"
@click="toggleActivationStatus()"
>
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button>
<button
v-if="canDeleteAccount"
class="button-default dropdown-item"
@click="deleteUserDialog(true)"
>
{{ $t('user_card.admin_menu.delete_account') }}
</button>
<div
v-if="hasTagPolicy"
v-if="canUseTagPolicy"
role="separator"
class="dropdown-divider"
/>
<span v-if="hasTagPolicy">
<span v-if="canUseTagPolicy">
<button
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_NSFW)"

View file

@ -1,11 +1,14 @@
<template>
<Status
<article
v-if="notification.type === 'mention'"
class="Notification"
:compact="true"
:statusoid="notification.status"
/>
<div v-else>
>
<Status
class="Notification"
:compact="true"
:statusoid="notification.status"
/>
</article>
<article v-else>
<div
v-if="needMute && !unmuted"
class="Notification container -muted"
@ -226,7 +229,7 @@
</template>
</div>
</div>
</div>
</article>
</template>
<script src="./notification.js"></script>

View file

@ -3,7 +3,8 @@
:disabled="minimalMode || disableTeleport"
:to="teleportTarget"
>
<div
<component
:is="noHeading ? 'div' : 'aside'"
ref="root"
:class="{ minimal: minimalMode }"
class="Notifications"
@ -21,8 +22,8 @@
>{{ unseenCount }}</span>
</div>
<div
class="rightside-button"
v-if="showScrollTop"
class="rightside-button"
>
<button
class="button-unstyled scroll-to-top-button"
@ -49,10 +50,14 @@
</button>
<NotificationFilters class="rightside-button" />
</div>
<div class="panel-body">
<div
class="panel-body"
role="feed"
>
<div
v-for="notification in notificationsToDisplay"
:key="notification.id"
role="listitem"
class="notification"
:class="{unseen: !minimalMode && !notification.seen}"
>
@ -88,7 +93,7 @@
</div>
</div>
</div>
</div>
</component>
</teleport>
</template>

View file

@ -43,7 +43,12 @@ const Popover = {
overlayCentersSelector: String,
// Lets hover popover stay when clicking inside of it
stayOnClick: Boolean
stayOnClick: Boolean,
triggerAttrs: {
type: Object,
default: {}
}
},
inject: ['popoversZLayer'], // override popover z layer
data () {

View file

@ -7,11 +7,15 @@
ref="trigger"
class="button-unstyled popover-trigger-button"
type="button"
v-bind="triggerAttrs"
@click="onClick"
>
<slot name="trigger" />
</button>
<teleport :disabled="!teleport" to="#popovers">
<teleport
:disabled="!teleport"
to="#popovers"
>
<transition name="fade">
<div
v-if="!hidden"

View file

@ -3,6 +3,7 @@
trigger="click"
class="QuickFilterSettings"
:bound-to="{ x: 'container' }"
:trigger-attrs="{ title: $t('timeline.quick_filter_settings') }"
>
<template #content>
<div class="dropdown-menu">
@ -79,9 +80,7 @@
</div>
</template>
<template #trigger>
<button class="button-unstyled">
<FAIcon icon="filter" />
</button>
<FAIcon icon="filter" />
</template>
</Popover>
</template>

View file

@ -3,6 +3,7 @@
trigger="click"
class="QuickViewSettings"
:bound-to="{ x: 'container' }"
:trigger-attrs="{ title: $t('timeline.quick_view_settings') }"
>
<template #content>
<div class="dropdown-menu">
@ -66,9 +67,7 @@
</div>
</template>
<template #trigger>
<button class="button-unstyled">
<FAIcon icon="bars" />
</button>
<FAIcon icon="bars" />
</template>
</Popover>
</template>

View file

@ -17,6 +17,9 @@ const ReplyButton = {
computed: {
loggedIn () {
return !!this.$store.state.users.currentUser
},
remoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
}
}

View file

@ -26,13 +26,19 @@
/>
</FALayers>
</button>
<span v-else>
<a
v-else
class="button-unstyled interactive"
target="_blank"
role="button"
:href="remoteInteractionLink"
>
<FAIcon
icon="reply"
class="fa-scale-110 fa-old-padding"
:title="$t('tool_tip.reply')"
/>
</span>
</a>
<span
v-if="status.replies_count > 0"
class="action-counter"

View file

@ -36,6 +36,9 @@ const RetweetButton = {
computed: {
mergedConfig () {
return this.$store.getters.mergedConfig
},
remoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
}
}

View file

@ -40,13 +40,19 @@
:title="$t('timeline.no_retweet_hint')"
/>
</span>
<span v-else>
<a
v-else
class="button-unstyled interactive"
target="_blank"
role="button"
:href="remoteInteractionLink"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="retweet"
:title="$t('tool_tip.repeat')"
/>
</span>
</a>
<span
v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
class="no-event"

View file

@ -8,6 +8,7 @@ import {
faCircleNotch,
faSearch
} from '@fortawesome/free-solid-svg-icons'
import { uniqBy } from 'lodash'
library.add(
faCircleNotch,
@ -32,7 +33,11 @@ const Search = {
userIds: [],
statuses: [],
hashtags: [],
currenResultTab: 'statuses'
currenResultTab: 'statuses',
statusesOffset: 0,
lastStatusFetchCount: 0,
lastQuery: ''
}
},
computed: {
@ -61,26 +66,42 @@ const Search = {
this.$router.push({ name: 'search', query: { query } })
this.$refs.searchInput.focus()
},
search (query) {
search (query, searchType = null) {
if (!query) {
this.loading = false
return
}
this.loading = true
this.userIds = []
this.statuses = []
this.hashtags = []
this.$refs.searchInput.blur()
if (this.lastQuery !== query) {
this.userIds = []
this.hashtags = []
this.statuses = []
this.$store.dispatch('search', { q: query, resolve: true })
this.statusesOffset = 0
this.lastStatusFetchCount = 0
}
this.$store.dispatch('search', { q: query, resolve: true, offset: this.statusesOffset, type: searchType })
.then(data => {
this.loading = false
this.userIds = map(data.accounts, 'id')
this.statuses = data.statuses
this.hashtags = data.hashtags
const oldLength = this.statuses.length
// Always append to old results. If new results are empty, this doesn't change anything
this.userIds = this.userIds.concat(map(data.accounts, 'id'))
this.statuses = uniqBy(this.statuses.concat(data.statuses), 'id')
this.hashtags = this.hashtags.concat(data.hashtags)
this.currenResultTab = this.getActiveTab()
this.loaded = true
// Offset from whatever we already have
this.statusesOffset = this.statuses.length
// Because the amount of new statuses can actually be zero, compare to old lenght instead
this.lastStatusFetchCount = this.statuses.length - oldLength
this.lastQuery = query
})
},
resultCount (tabName) {

View file

@ -22,7 +22,7 @@
</button>
</div>
<div
v-if="loading"
v-if="loading && statusesOffset == 0"
class="text-center loading-icon"
>
<FAIcon
@ -55,12 +55,6 @@
</div>
<div class="panel-body">
<div v-if="currenResultTab === 'statuses'">
<div
v-if="visibleStatuses.length === 0 && !loading && loaded"
class="search-result-heading"
>
<h4>{{ $t('search.no_results') }}</h4>
</div>
<Status
v-for="status in visibleStatuses"
:key="status.id"
@ -71,6 +65,33 @@
:statusoid="status"
:no-heading="false"
/>
<button
v-if="!loading && loaded && lastStatusFetchCount > 0"
class="more-statuses-button button-unstyled -link -fullwidth"
@click.prevent="search(searchTerm, 'statuses')"
>
<div class="new-status-notification text-center">
{{ $t('search.load_more') }}
</div>
</button>
<div
v-else-if="loading && statusesOffset > 0"
class="text-center loading-icon"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
<div
v-if="(visibleStatuses.length === 0 || lastStatusFetchCount === 0) && !loading && loaded"
class="search-result-heading"
>
<h4>
{{ visibleStatuses.length === 0 ? $t('search.no_results') : $t('search.no_more_results') }}
</h4>
</div>
</div>
<div v-else-if="currenResultTab === 'people'">
<div
@ -208,6 +229,11 @@
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
}
.more-statuses-button {
height: 3.5em;
line-height: 3.5em;
}
</style>

View file

@ -14,7 +14,6 @@ const StaffPanel = {
computed: {
groupedStaffAccounts () {
const staffAccounts = map(this.staffAccounts, this.findUserByName).filter(_ => _)
console.log(staffAccounts)
const groupedStaffAccounts = groupBy(staffAccounts, 'role')
return [

View file

@ -1,5 +1,5 @@
<template>
<div class="thread-tree">
<article class="thread-tree">
<status
:key="status.id"
ref="statusComponent"
@ -113,7 +113,7 @@
</template>
</i18n-t>
</div>
</div>
</article>
</template>
<script src="./thread_tree.js"></script>

View file

@ -6,8 +6,8 @@
:timeline-name="timelineName"
/>
<div
class="rightside-button"
v-if="showScrollTop && !embedded"
class="rightside-button"
>
<button
class="button-unstyled scroll-to-top-button"
@ -26,8 +26,8 @@
</div>
<template v-if="mobileLayout && !embedded">
<div
class="rightside-button"
v-if="showLoadButton"
class="rightside-button"
>
<button
class="button-unstyled loadmore-button"
@ -47,6 +47,7 @@
v-else-if="!embedded"
class="loadmore-text faint veryfaint rightside-icon"
:title="$t('timeline.up_to_date')"
:aria-disabled="true"
@click.prevent
>
<FAIcon
@ -71,17 +72,25 @@
{{ $t('timeline.up_to_date') }}
</div>
</template>
<QuickFilterSettings v-if="!embedded" class="rightside-button"/>
<QuickViewSettings v-if="!embedded" class="rightside-button"/>
<QuickFilterSettings
v-if="!embedded"
class="rightside-button"
/>
<QuickViewSettings
v-if="!embedded"
class="rightside-button"
/>
</div>
<div :class="classes.body">
<div
ref="timeline"
class="timeline"
role="feed"
>
<conversation
v-for="statusId in filteredPinnedStatusIds"
:key="statusId + '-pinned'"
role="listitem"
class="status-fadein"
:status-id="statusId"
:collapsable="true"
@ -92,6 +101,7 @@
<conversation
v-for="status in filteredVisibleStatuses"
:key="status.id"
role="listitem"
class="status-fadein"
:status-id="status.id"
:collapsable="true"

View file

@ -125,6 +125,10 @@ export default {
hideFollowersCount () {
return this.isOtherUser && this.user.hide_followers_count
},
showModerationMenu () {
const privileges = this.loggedIn.privileges
return this.loggedIn.role === 'admin' || privileges.includes('users_manage_activation_state') || privileges.includes('users_delete') || privileges.includes('users_manage_tags')
},
...mapGetters(['mergedConfig'])
},
components: {

View file

@ -258,7 +258,7 @@
</button>
</div>
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
v-if="showModerationMenu"
:user="user"
/>
</div>

View file

@ -1,5 +1,5 @@
<template>
<div class="user-panel">
<aside class="user-panel">
<div
v-if="signedIn"
key="user-panel-signed"
@ -16,7 +16,7 @@
v-else
key="user-panel"
/>
</div>
</aside>
</template>
<script src="./user_panel.js"></script>