mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-18 01:28:58 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5391d8fffe | |||
| b0bfbbccdf | |||
| 9525ebfb97 | |||
| 67e9fb9f8c | |||
| a36da9f882 | |||
| c0762158d7 | |||
| 479cdbbba5 | |||
| 255a5cf53d | |||
| feeefa2926 |
@@ -1 +1 @@
|
||||
{".":"0.3.0"}
|
||||
{".":"0.4.0"}
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## [0.4.0](https://github.com/chenasraf/nextcloud-forum/compare/v0.3.0...v0.4.0) (2025-11-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **AppNavigation:** save collapse state to local storage ([a36da9f](https://github.com/chenasraf/nextcloud-forum/commit/a36da9f8822aa6b091e34d82cce8b56a86547b39))
|
||||
* **BBCodeEditor:** add attachment disclaimer ([b0bfbbc](https://github.com/chenasraf/nextcloud-forum/commit/b0bfbbccdf04bd92d374ed31e404c9fadc23f51b))
|
||||
* **BBCodeToolbar:** add emoji picker button ([255a5cf](https://github.com/chenasraf/nextcloud-forum/commit/255a5cf53dcce38c9356b30713a76e95592abe44))
|
||||
* **PostReactions:** use Nextcloud emoji picker ([feeefa2](https://github.com/chenasraf/nextcloud-forum/commit/feeefa2926589cbd0c62053f1700c9bfb6bca545))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mobile responsiveness ([c076215](https://github.com/chenasraf/nextcloud-forum/commit/c0762158d75e6eebf0ac77a512218cf7b4119a97))
|
||||
* **ProfileView:** mobile responsiveness ([67e9fb9](https://github.com/chenasraf/nextcloud-forum/commit/67e9fb9f8cdb9d1ada660b1d90e8de5aa35051de))
|
||||
* **ThreadCard:** mobile responsiveness ([9525ebf](https://github.com/chenasraf/nextcloud-forum/commit/9525ebfb9705e66281898af7fcb733ba1ae8208c))
|
||||
|
||||
## [0.3.0](https://github.com/chenasraf/nextcloud-forum/compare/v0.2.1...v0.3.0) (2025-11-18)
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ This app is in early stages of development. While functional, you may encounter
|
||||
|
||||
The forum integrates seamlessly with your Nextcloud instance, using your existing users and groups for authentication and access control.
|
||||
]]></description>
|
||||
<version>0.3.0</version>
|
||||
<version>0.4.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author mail="contact@casraf.dev" homepage="https://casraf.dev">Chen Asraf</author>
|
||||
<namespace>Forum</namespace>
|
||||
|
||||
@@ -103,6 +103,10 @@ export default defineComponent({
|
||||
padding: 1rem;
|
||||
min-height: 0;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-spacer {
|
||||
@@ -115,6 +119,7 @@ export default defineComponent({
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
margin-top: 128px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navSearch"
|
||||
:to="{ path: '/search' }"
|
||||
:active="isSearchActive"
|
||||
:active="isPathActive('/search')"
|
||||
>
|
||||
<template #icon>
|
||||
<MagnifyIcon :size="20" />
|
||||
@@ -60,7 +60,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navPreferences"
|
||||
:to="{ path: '/preferences' }"
|
||||
:active="isPreferencesActive"
|
||||
:active="isPathActive('/preferences')"
|
||||
>
|
||||
<template #icon>
|
||||
<AccountCogIcon :size="20" />
|
||||
@@ -91,7 +91,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminDashboard"
|
||||
:to="{ path: '/admin' }"
|
||||
:active="isAdminDashboardActive"
|
||||
:active="isPathActive('/admin')"
|
||||
>
|
||||
<template #icon>
|
||||
<ChartLineIcon :size="20" />
|
||||
@@ -101,7 +101,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminSettings"
|
||||
:to="{ path: '/admin/settings' }"
|
||||
:active="isAdminSettingsActive"
|
||||
:active="isPathActive('/admin/settings')"
|
||||
>
|
||||
<template #icon>
|
||||
<CogIcon :size="20" />
|
||||
@@ -111,7 +111,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminUsers"
|
||||
:to="{ path: '/admin/users' }"
|
||||
:active="isAdminUsersActive"
|
||||
:active="isPathActive('/admin/users', true)"
|
||||
>
|
||||
<template #icon>
|
||||
<AccountMultipleIcon :size="20" />
|
||||
@@ -121,7 +121,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminRoles"
|
||||
:to="{ path: '/admin/roles' }"
|
||||
:active="isAdminRolesActive"
|
||||
:active="isPathActive('/admin/roles', true)"
|
||||
>
|
||||
<template #icon>
|
||||
<ShieldAccountIcon :size="20" />
|
||||
@@ -131,7 +131,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminCategories"
|
||||
:to="{ path: '/admin/categories' }"
|
||||
:active="isAdminCategoriesActive"
|
||||
:active="isPathActive('/admin/categories', true)"
|
||||
>
|
||||
<template #icon>
|
||||
<FolderIcon :size="20" />
|
||||
@@ -141,7 +141,7 @@
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminBBCodes"
|
||||
:to="{ path: '/admin/bbcodes' }"
|
||||
:active="isAdminBBCodesActive"
|
||||
:active="isPathActive('/admin/bbcodes', true)"
|
||||
>
|
||||
<template #icon>
|
||||
<CodeBracketsIcon :size="20" />
|
||||
@@ -237,14 +237,15 @@ export default defineComponent({
|
||||
searchValue: '',
|
||||
openHeaders: {} as Record<number, boolean>,
|
||||
isAdminOpen: true,
|
||||
STORAGE_KEY: 'forum_navigation_state',
|
||||
strings: {
|
||||
searchLabel: t('forum', 'Search'),
|
||||
navHome: t('forum', 'Home'),
|
||||
navSearch: t('forum', 'Search'),
|
||||
navPreferences: t('forum', 'Preferences'),
|
||||
navPreferences: t('forum', 'User Preferences'),
|
||||
navAdmin: t('forum', 'Admin'),
|
||||
navAdminDashboard: t('forum', 'Dashboard'),
|
||||
navAdminSettings: t('forum', 'Settings'),
|
||||
navAdminSettings: t('forum', 'Forum Settings'),
|
||||
navAdminUsers: t('forum', 'Users'),
|
||||
navAdminRoles: t('forum', 'Roles'),
|
||||
navAdminCategories: t('forum', 'Categories'),
|
||||
@@ -254,53 +255,80 @@ export default defineComponent({
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSearchActive(): boolean {
|
||||
return this.$route.path === '/search'
|
||||
},
|
||||
isPreferencesActive(): boolean {
|
||||
return this.$route.path === '/preferences'
|
||||
},
|
||||
isAdminDashboardActive(): boolean {
|
||||
return this.$route.path === '/admin'
|
||||
},
|
||||
isAdminSettingsActive(): boolean {
|
||||
return this.$route.path === '/admin/settings'
|
||||
},
|
||||
isAdminUsersActive(): boolean {
|
||||
return this.$route.path.startsWith('/admin/users')
|
||||
},
|
||||
isAdminRolesActive(): boolean {
|
||||
return this.$route.path.startsWith('/admin/roles')
|
||||
},
|
||||
isAdminCategoriesActive(): boolean {
|
||||
return this.$route.path.startsWith('/admin/categories')
|
||||
},
|
||||
isAdminBBCodesActive(): boolean {
|
||||
return this.$route.path.startsWith('/admin/bbcodes')
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
// Fetch categories for sidebar
|
||||
try {
|
||||
await this.fetchCategories()
|
||||
|
||||
// Initialize all headers as open by default
|
||||
const openState: Record<number, boolean> = {}
|
||||
this.categoryHeaders.forEach((header) => {
|
||||
openState[header.id] = true
|
||||
})
|
||||
this.openHeaders = openState
|
||||
// Load saved state from local storage
|
||||
this.loadNavigationState()
|
||||
} catch (e) {
|
||||
console.error('Failed to load categories for sidebar:', e)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadNavigationState(): void {
|
||||
try {
|
||||
const savedState = localStorage.getItem(this.STORAGE_KEY)
|
||||
if (savedState) {
|
||||
const parsed = JSON.parse(savedState)
|
||||
|
||||
// Load admin section state
|
||||
if (typeof parsed.isAdminOpen === 'boolean') {
|
||||
this.isAdminOpen = parsed.isAdminOpen
|
||||
}
|
||||
|
||||
// Load category headers state
|
||||
if (parsed.openHeaders && typeof parsed.openHeaders === 'object') {
|
||||
this.openHeaders = parsed.openHeaders
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize headers that don't have saved state to open by default
|
||||
const openState: Record<number, boolean> = { ...this.openHeaders }
|
||||
this.categoryHeaders.forEach((header) => {
|
||||
if (openState[header.id] === undefined) {
|
||||
openState[header.id] = true
|
||||
}
|
||||
})
|
||||
this.openHeaders = openState
|
||||
} catch (e) {
|
||||
console.error('Failed to load navigation state from local storage:', e)
|
||||
|
||||
// Fallback: Initialize all headers as open by default
|
||||
const openState: Record<number, boolean> = {}
|
||||
this.categoryHeaders.forEach((header) => {
|
||||
openState[header.id] = true
|
||||
})
|
||||
this.openHeaders = openState
|
||||
}
|
||||
},
|
||||
|
||||
saveNavigationState(): void {
|
||||
try {
|
||||
const state = {
|
||||
isAdminOpen: this.isAdminOpen,
|
||||
openHeaders: this.openHeaders,
|
||||
}
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state))
|
||||
} catch (e) {
|
||||
console.error('Failed to save navigation state to local storage:', e)
|
||||
}
|
||||
},
|
||||
|
||||
isPathActive(path: string, usePrefix = false): boolean {
|
||||
if (usePrefix) {
|
||||
return this.$route.path.startsWith(path)
|
||||
}
|
||||
return this.$route.path === path
|
||||
},
|
||||
|
||||
toggleHeader(headerId: number): void {
|
||||
this.openHeaders = {
|
||||
...this.openHeaders,
|
||||
[headerId]: !this.openHeaders[headerId],
|
||||
}
|
||||
this.saveNavigationState()
|
||||
},
|
||||
|
||||
isHeaderOpen(headerId: number): boolean {
|
||||
@@ -309,6 +337,7 @@ export default defineComponent({
|
||||
|
||||
toggleAdmin(): void {
|
||||
this.isAdminOpen = !this.isAdminOpen
|
||||
this.saveNavigationState()
|
||||
},
|
||||
|
||||
isCategoryActive(category: Category): boolean {
|
||||
|
||||
@@ -22,7 +22,6 @@ export default defineComponent({
|
||||
.app-toolbar {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
@@ -33,15 +32,22 @@ export default defineComponent({
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
flex: 1;
|
||||
flex: 1 1 auto;
|
||||
min-width: 200px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding-left: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,18 +11,24 @@
|
||||
class="bbcode-editor-textarea"
|
||||
ref="textarea"
|
||||
/>
|
||||
<NcNoteCard v-if="hasAttachmentBBCode" type="warning" class="attachment-disclaimer">
|
||||
<span v-html="strings.attachmentDisclaimer"></span>
|
||||
</NcNoteCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from 'vue'
|
||||
import NcTextArea from '@nextcloud/vue/components/NcTextArea'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import BBCodeToolbar from './BBCodeToolbar.vue'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BBCodeEditor',
|
||||
components: {
|
||||
NcTextArea,
|
||||
NcNoteCard,
|
||||
BBCodeToolbar,
|
||||
},
|
||||
props: {
|
||||
@@ -51,8 +57,21 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
textareaElement: null as HTMLTextAreaElement | null,
|
||||
strings: {
|
||||
attachmentDisclaimer: t(
|
||||
'forum',
|
||||
"{bStart}Please note:{bEnd} Attached files will be visible to anyone in the forum, regardless of the file's sharing settings.",
|
||||
{ bStart: '<strong>', bEnd: '</strong>' },
|
||||
{ escape: false },
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasAttachmentBBCode(): boolean {
|
||||
return /\[attachment[^\]]*\]/i.test(this.modelValue)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTextareaRef()
|
||||
},
|
||||
@@ -102,4 +121,8 @@ export default defineComponent({
|
||||
height: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-disclaimer {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,12 +14,25 @@
|
||||
</template>
|
||||
</NcButton>
|
||||
|
||||
<NcEmojiPicker @select="handleEmojiSelect">
|
||||
<NcButton
|
||||
variant="tertiary"
|
||||
:aria-label="strings.emojiLabel"
|
||||
:title="strings.emojiLabel"
|
||||
class="bbcode-button"
|
||||
>
|
||||
<template #icon>
|
||||
<EmoticonIcon :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</NcEmojiPicker>
|
||||
|
||||
<div class="toolbar-spacer"></div>
|
||||
|
||||
<NcButton
|
||||
variant="tertiary"
|
||||
:aria-label="helpLabel"
|
||||
:title="helpLabel"
|
||||
:aria-label="strings.helpLabel"
|
||||
:title="strings.helpLabel"
|
||||
@click="showHelp = true"
|
||||
class="bbcode-button bbcode-help-button"
|
||||
>
|
||||
@@ -36,6 +49,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcEmojiPicker from '@nextcloud/vue/components/NcEmojiPicker'
|
||||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
import FormatBoldIcon from '@icons/FormatBold.vue'
|
||||
import FormatItalicIcon from '@icons/FormatItalic.vue'
|
||||
@@ -56,6 +70,7 @@ import FormatAlignRightIcon from '@icons/FormatAlignRight.vue'
|
||||
import EyeOffIcon from '@icons/EyeOff.vue'
|
||||
import FormatListBulletedIcon from '@icons/FormatListBulleted.vue'
|
||||
import PaperclipIcon from '@icons/Paperclip.vue'
|
||||
import EmoticonIcon from '@icons/Emoticon.vue'
|
||||
import HelpCircleIcon from '@icons/HelpCircle.vue'
|
||||
import BBCodeHelpDialog from './BBCodeHelpDialog.vue'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
@@ -76,7 +91,9 @@ export default defineComponent({
|
||||
name: 'BBCodeToolbar',
|
||||
components: {
|
||||
NcButton,
|
||||
NcEmojiPicker,
|
||||
BBCodeHelpDialog,
|
||||
EmoticonIcon,
|
||||
HelpCircleIcon,
|
||||
},
|
||||
props: {
|
||||
@@ -89,12 +106,13 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
showHelp: false,
|
||||
strings: {
|
||||
helpLabel: t('forum', 'BBCode Help'),
|
||||
emojiLabel: t('forum', 'Insert emoji'),
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
helpLabel(): string {
|
||||
return t('forum', 'BBCode Help')
|
||||
},
|
||||
bbcodeButtons(): BBCodeButton[] {
|
||||
return [
|
||||
{
|
||||
@@ -366,6 +384,34 @@ export default defineComponent({
|
||||
// Otherwise, user simply canceled - no need to log
|
||||
}
|
||||
},
|
||||
|
||||
handleEmojiSelect(emoji: string): void {
|
||||
if (!this.textareaRef) {
|
||||
return
|
||||
}
|
||||
|
||||
const textarea = this.textareaRef
|
||||
const start = textarea.selectionStart
|
||||
const end = textarea.selectionEnd
|
||||
const beforeText = textarea.value.substring(0, start)
|
||||
const afterText = textarea.value.substring(end)
|
||||
|
||||
const newText = beforeText + emoji + afterText
|
||||
const cursorPos = beforeText.length + emoji.length
|
||||
|
||||
// Emit the insert event so the parent can update the model
|
||||
this.$emit('insert', {
|
||||
text: newText,
|
||||
cursorPos,
|
||||
selectedText: '',
|
||||
})
|
||||
|
||||
// Focus the textarea after insertion
|
||||
this.$nextTick(() => {
|
||||
textarea.focus()
|
||||
textarea.setSelectionRange(cursorPos, cursorPos)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -33,6 +33,10 @@ export default defineComponent({
|
||||
.page-wrapper-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-wrapper-toolbar {
|
||||
|
||||
@@ -14,48 +14,11 @@
|
||||
</button>
|
||||
|
||||
<!-- Add custom reaction button -->
|
||||
<div class="add-reaction">
|
||||
<button
|
||||
class="add-reaction-button"
|
||||
:class="{ open: showPicker }"
|
||||
:title="strings.addReaction"
|
||||
@click="togglePicker"
|
||||
>
|
||||
<NcEmojiPicker @select="handleSelectEmoji" style="display: inline-block">
|
||||
<button class="add-reaction-button" :title="strings.addReaction">
|
||||
<span class="icon">+</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Emoji picker (teleported to body for proper fixed positioning) -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div v-if="showPicker" class="emoji-picker-overlay" @click="closePicker">
|
||||
<div class="emoji-picker-container" @click.stop>
|
||||
<button class="emoji-picker-close" :title="strings.close" @click="closePicker">
|
||||
<Close :size="20" />
|
||||
</button>
|
||||
<div class="emoji-picker-content">
|
||||
<h3>{{ strings.pickEmoji }}</h3>
|
||||
<div class="emoji-categories">
|
||||
<div v-for="group in emojiGroups" :key="group.name" class="emoji-category">
|
||||
<h4 class="category-header">{{ group.name }}</h4>
|
||||
<div class="emoji-grid">
|
||||
<button
|
||||
v-for="item in group.emojis"
|
||||
:key="item.emoji"
|
||||
class="emoji-option"
|
||||
:title="item.title"
|
||||
@click="handleSelectEmoji(item.emoji)"
|
||||
>
|
||||
{{ item.emoji }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</NcEmojiPicker>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -64,13 +27,12 @@ import { defineComponent, type PropType } from 'vue'
|
||||
import { t, n } from '@nextcloud/l10n'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { useReactions, type ReactionGroup } from '@/composables/useReactions'
|
||||
import { EMOJI_GROUPS } from '@/constants/emojis'
|
||||
import Close from '@icons/Close.vue'
|
||||
import NcEmojiPicker from '@nextcloud/vue/components/NcEmojiPicker'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PostReactions',
|
||||
components: {
|
||||
Close,
|
||||
NcEmojiPicker,
|
||||
},
|
||||
props: {
|
||||
postId: {
|
||||
@@ -91,13 +53,9 @@ export default defineComponent({
|
||||
return {
|
||||
defaultEmojis: ['👍', '❤️', '😄', '🎉', '👏'],
|
||||
reactionGroups: [...this.reactions] as ReactionGroup[],
|
||||
showPicker: false,
|
||||
strings: {
|
||||
addReaction: t('forum', 'Add reaction'),
|
||||
pickEmoji: t('forum', 'Pick an emoji'),
|
||||
close: t('forum', 'Close'),
|
||||
},
|
||||
emojiGroups: EMOJI_GROUPS,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -153,25 +111,8 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
togglePicker() {
|
||||
this.showPicker = !this.showPicker
|
||||
},
|
||||
closePicker() {
|
||||
this.showPicker = false
|
||||
},
|
||||
handleSelectEmoji(emoji: string) {
|
||||
this.handleToggleReaction(emoji)
|
||||
this.closePicker()
|
||||
},
|
||||
getEmojiTitle(emoji: string): string | null {
|
||||
// Find the emoji title from the emoji groups
|
||||
for (const group of this.emojiGroups) {
|
||||
const item = group.emojis.find((e) => e.emoji === emoji)
|
||||
if (item) {
|
||||
return item.title
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
getCount(emoji: string): number {
|
||||
const group = this.reactionGroups.find((g) => g.emoji === emoji)
|
||||
@@ -229,28 +170,27 @@ export default defineComponent({
|
||||
getReactionTooltip(emoji: string): string {
|
||||
const count = this.getCount(emoji)
|
||||
const hasReacted = this.isReacted(emoji)
|
||||
const title = this.getEmojiTitle(emoji) ?? emoji
|
||||
|
||||
if (count === 0) {
|
||||
return t('forum', 'React with {title}', { title })
|
||||
return t('forum', 'React with {emoji}', { emoji })
|
||||
}
|
||||
|
||||
if (count === 1) {
|
||||
return hasReacted
|
||||
? t('forum', 'You reacted with {title}', { title })
|
||||
: t('forum', '1 person reacted with {title}', { title })
|
||||
? t('forum', 'You reacted with {emoji}', { emoji })
|
||||
: t('forum', '1 person reacted with {emoji}', { emoji })
|
||||
}
|
||||
|
||||
return hasReacted
|
||||
? n(
|
||||
'forum',
|
||||
'You and %n other reacted with {title}',
|
||||
'You and %n others reacted with {title}',
|
||||
'You and %n other reacted with {emoji}',
|
||||
'You and %n others reacted with {emoji}',
|
||||
count - 1,
|
||||
{ title },
|
||||
{ emoji },
|
||||
)
|
||||
: n('forum', '%n person reacted with {title}', '%n people reacted with {title}', count, {
|
||||
title,
|
||||
: n('forum', '%n person reacted with {emoji}', '%n people reacted with {emoji}', count, {
|
||||
emoji,
|
||||
})
|
||||
},
|
||||
},
|
||||
@@ -324,203 +264,40 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
.add-reaction {
|
||||
position: relative;
|
||||
.add-reaction-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.add-reaction-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 10px;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
border: 1px dashed var(--color-border);
|
||||
background: transparent;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: var(--color-background-hover);
|
||||
border-color: var(--color-border-dark);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
background: var(--color-background-hover);
|
||||
border-color: var(--color-primary-element);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1;
|
||||
font-weight: bold;
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
|
||||
&:hover .icon,
|
||||
&.open .icon {
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transition animations
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
// Emoji picker overlay - not scoped because it's teleported to body
|
||||
.emoji-picker-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(2px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.emoji-picker-container {
|
||||
background: var(--color-main-background);
|
||||
justify-content: center;
|
||||
padding: 4px 10px;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
border: 1px dashed var(--color-border);
|
||||
background: transparent;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
opacity: 0.6;
|
||||
|
||||
.emoji-picker-close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-text-maxcontrast);
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s ease;
|
||||
z-index: 1;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-hover);
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: var(--color-background-hover);
|
||||
border-color: var(--color-border-dark);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.emoji-picker-content {
|
||||
padding: 20px;
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
.icon {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1;
|
||||
font-weight: bold;
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
|
||||
.emoji-categories {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--color-background-dark);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border-dark);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-text-maxcontrast);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-category {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-maxcontrast);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.emoji-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 4px;
|
||||
|
||||
.emoji-option {
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-hover);
|
||||
border-color: var(--color-border);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover .icon {
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,11 @@ export default defineComponent({
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.unread-indicator {
|
||||
@@ -212,16 +217,36 @@ export default defineComponent({
|
||||
padding: 8px;
|
||||
background: var(--color-background-hover);
|
||||
border-radius: 6px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: row;
|
||||
padding: 6px 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 1.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:deep(svg) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
color: var(--color-main-text);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
@@ -229,6 +254,10 @@ export default defineComponent({
|
||||
color: var(--color-text-maxcontrast);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,8 +268,10 @@ export default defineComponent({
|
||||
|
||||
.thread-stats {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
/**
|
||||
* Emoji groups with names and titles
|
||||
*/
|
||||
|
||||
export interface EmojiItem {
|
||||
emoji: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export interface EmojiGroup {
|
||||
name: string
|
||||
emojis: EmojiItem[]
|
||||
}
|
||||
|
||||
export const EMOJI_GROUPS: EmojiGroup[] = [
|
||||
{
|
||||
name: t('forum', 'Smileys & Emotion'),
|
||||
emojis: [
|
||||
{ emoji: '😀', title: t('forum', 'Grinning Face') },
|
||||
{ emoji: '😃', title: t('forum', 'Grinning Face with Big Eyes') },
|
||||
{ emoji: '😄', title: t('forum', 'Grinning Face with Smiling Eyes') },
|
||||
{ emoji: '😁', title: t('forum', 'Beaming Face with Smiling Eyes') },
|
||||
{ emoji: '😆', title: t('forum', 'Grinning Squinting Face') },
|
||||
{ emoji: '😅', title: t('forum', 'Grinning Face with Sweat') },
|
||||
{ emoji: '😂', title: t('forum', 'Face with Tears of Joy') },
|
||||
{ emoji: '🤣', title: t('forum', 'Rolling on the Floor Laughing') },
|
||||
{ emoji: '😊', title: t('forum', 'Smiling Face with Smiling Eyes') },
|
||||
{ emoji: '😇', title: t('forum', 'Smiling Face with Halo') },
|
||||
{ emoji: '🙂', title: t('forum', 'Slightly Smiling Face') },
|
||||
{ emoji: '🙃', title: t('forum', 'Upside-Down Face') },
|
||||
{ emoji: '😉', title: t('forum', 'Winking Face') },
|
||||
{ emoji: '😌', title: t('forum', 'Relieved Face') },
|
||||
{ emoji: '😍', title: t('forum', 'Smiling Face with Heart-Eyes') },
|
||||
{ emoji: '🥰', title: t('forum', 'Smiling Face with Hearts') },
|
||||
{ emoji: '😘', title: t('forum', 'Face Blowing a Kiss') },
|
||||
{ emoji: '😗', title: t('forum', 'Kissing Face') },
|
||||
{ emoji: '😙', title: t('forum', 'Kissing Face with Smiling Eyes') },
|
||||
{ emoji: '😚', title: t('forum', 'Kissing Face with Closed Eyes') },
|
||||
{ emoji: '😋', title: t('forum', 'Face Savoring Food') },
|
||||
{ emoji: '😛', title: t('forum', 'Face with Tongue') },
|
||||
{ emoji: '😝', title: t('forum', 'Squinting Face with Tongue') },
|
||||
{ emoji: '😜', title: t('forum', 'Winking Face with Tongue') },
|
||||
{ emoji: '🤪', title: t('forum', 'Zany Face') },
|
||||
{ emoji: '🤨', title: t('forum', 'Face with Raised Eyebrow') },
|
||||
{ emoji: '🧐', title: t('forum', 'Face with Monocle') },
|
||||
{ emoji: '🤓', title: t('forum', 'Nerd Face') },
|
||||
{ emoji: '😎', title: t('forum', 'Smiling Face with Sunglasses') },
|
||||
{ emoji: '🤩', title: t('forum', 'Star-Struck') },
|
||||
{ emoji: '🥳', title: t('forum', 'Partying Face') },
|
||||
{ emoji: '😏', title: t('forum', 'Smirking Face') },
|
||||
{ emoji: '😒', title: t('forum', 'Unamused Face') },
|
||||
{ emoji: '😞', title: t('forum', 'Disappointed Face') },
|
||||
{ emoji: '😔', title: t('forum', 'Pensive Face') },
|
||||
{ emoji: '😟', title: t('forum', 'Worried Face') },
|
||||
{ emoji: '😕', title: t('forum', 'Confused Face') },
|
||||
{ emoji: '🙁', title: t('forum', 'Slightly Frowning Face') },
|
||||
{ emoji: '😣', title: t('forum', 'Persevering Face') },
|
||||
{ emoji: '😖', title: t('forum', 'Confounded Face') },
|
||||
{ emoji: '😫', title: t('forum', 'Tired Face') },
|
||||
{ emoji: '😩', title: t('forum', 'Weary Face') },
|
||||
{ emoji: '🥺', title: t('forum', 'Pleading Face') },
|
||||
{ emoji: '😢', title: t('forum', 'Crying Face') },
|
||||
{ emoji: '😭', title: t('forum', 'Loudly Crying Face') },
|
||||
{ emoji: '😤', title: t('forum', 'Face with Steam From Nose') },
|
||||
{ emoji: '😠', title: t('forum', 'Angry Face') },
|
||||
{ emoji: '😡', title: t('forum', 'Enraged Face') },
|
||||
{ emoji: '🤬', title: t('forum', 'Face with Symbols on Mouth') },
|
||||
{ emoji: '🤯', title: t('forum', 'Exploding Head') },
|
||||
{ emoji: '😳', title: t('forum', 'Flushed Face') },
|
||||
{ emoji: '🥵', title: t('forum', 'Hot Face') },
|
||||
{ emoji: '🥶', title: t('forum', 'Cold Face') },
|
||||
{ emoji: '😱', title: t('forum', 'Face Screaming in Fear') },
|
||||
{ emoji: '😨', title: t('forum', 'Fearful Face') },
|
||||
{ emoji: '😰', title: t('forum', 'Anxious Face with Sweat') },
|
||||
{ emoji: '😥', title: t('forum', 'Sad but Relieved Face') },
|
||||
{ emoji: '😓', title: t('forum', 'Downcast Face with Sweat') },
|
||||
{ emoji: '🤗', title: t('forum', 'Smiling Face with Open Hands') },
|
||||
{ emoji: '🤔', title: t('forum', 'Thinking Face') },
|
||||
{ emoji: '🤭', title: t('forum', 'Face with Hand Over Mouth') },
|
||||
{ emoji: '🤫', title: t('forum', 'Shushing Face') },
|
||||
{ emoji: '🤥', title: t('forum', 'Lying Face') },
|
||||
{ emoji: '😶', title: t('forum', 'Face Without Mouth') },
|
||||
{ emoji: '😐', title: t('forum', 'Neutral Face') },
|
||||
{ emoji: '😑', title: t('forum', 'Expressionless Face') },
|
||||
{ emoji: '😬', title: t('forum', 'Grimacing Face') },
|
||||
{ emoji: '🙄', title: t('forum', 'Face with Rolling Eyes') },
|
||||
{ emoji: '😯', title: t('forum', 'Hushed Face') },
|
||||
{ emoji: '😦', title: t('forum', 'Frowning Face with Open Mouth') },
|
||||
{ emoji: '😧', title: t('forum', 'Anguished Face') },
|
||||
{ emoji: '😮', title: t('forum', 'Face with Open Mouth') },
|
||||
{ emoji: '😲', title: t('forum', 'Astonished Face') },
|
||||
{ emoji: '🥱', title: t('forum', 'Yawning Face') },
|
||||
{ emoji: '😴', title: t('forum', 'Sleeping Face') },
|
||||
{ emoji: '🤤', title: t('forum', 'Drooling Face') },
|
||||
{ emoji: '😪', title: t('forum', 'Sleepy Face') },
|
||||
{ emoji: '😵', title: t('forum', 'Face with Crossed-Out Eyes') },
|
||||
{ emoji: '🤐', title: t('forum', 'Zipper-Mouth Face') },
|
||||
{ emoji: '🥴', title: t('forum', 'Woozy Face') },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: t('forum', 'Gestures & Hands'),
|
||||
emojis: [
|
||||
{ emoji: '👋', title: t('forum', 'Waving Hand') },
|
||||
{ emoji: '🤚', title: t('forum', 'Raised Back of Hand') },
|
||||
{ emoji: '🖐', title: t('forum', 'Hand with Fingers Splayed') },
|
||||
{ emoji: '✋', title: t('forum', 'Raised Hand') },
|
||||
{ emoji: '🖖', title: t('forum', 'Vulcan Salute') },
|
||||
{ emoji: '👌', title: t('forum', 'OK Hand') },
|
||||
{ emoji: '🤌', title: t('forum', 'Pinched Fingers') },
|
||||
{ emoji: '🤏', title: t('forum', 'Pinching Hand') },
|
||||
{ emoji: '✌️', title: t('forum', 'Victory Hand') },
|
||||
{ emoji: '🤞', title: t('forum', 'Crossed Fingers') },
|
||||
{ emoji: '🤟', title: t('forum', 'Love-You Gesture') },
|
||||
{ emoji: '🤘', title: t('forum', 'Sign of the Horns') },
|
||||
{ emoji: '🤙', title: t('forum', 'Call Me Hand') },
|
||||
{ emoji: '👈', title: t('forum', 'Backhand Index Pointing Left') },
|
||||
{ emoji: '👉', title: t('forum', 'Backhand Index Pointing Right') },
|
||||
{ emoji: '👆', title: t('forum', 'Backhand Index Pointing Up') },
|
||||
{ emoji: '🖕', title: t('forum', 'Middle Finger') },
|
||||
{ emoji: '👇', title: t('forum', 'Backhand Index Pointing Down') },
|
||||
{ emoji: '☝️', title: t('forum', 'Index Pointing Up') },
|
||||
{ emoji: '👍', title: t('forum', 'Thumbs Up') },
|
||||
{ emoji: '👎', title: t('forum', 'Thumbs Down') },
|
||||
{ emoji: '✊', title: t('forum', 'Raised Fist') },
|
||||
{ emoji: '👊', title: t('forum', 'Oncoming Fist') },
|
||||
{ emoji: '🤛', title: t('forum', 'Left-Facing Fist') },
|
||||
{ emoji: '🤜', title: t('forum', 'Right-Facing Fist') },
|
||||
{ emoji: '👏', title: t('forum', 'Clapping Hands') },
|
||||
{ emoji: '🙌', title: t('forum', 'Raising Hands') },
|
||||
{ emoji: '👐', title: t('forum', 'Open Hands') },
|
||||
{ emoji: '🤲', title: t('forum', 'Palms Up Together') },
|
||||
{ emoji: '🤝', title: t('forum', 'Handshake') },
|
||||
{ emoji: '🙏', title: t('forum', 'Folded Hands') },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: t('forum', 'Hearts & Love'),
|
||||
emojis: [
|
||||
{ emoji: '❤️', title: t('forum', 'Red Heart') },
|
||||
{ emoji: '💛', title: t('forum', 'Yellow Heart') },
|
||||
{ emoji: '💙', title: t('forum', 'Blue Heart') },
|
||||
{ emoji: '💜', title: t('forum', 'Purple Heart') },
|
||||
{ emoji: '🧡', title: t('forum', 'Orange Heart') },
|
||||
{ emoji: '💚', title: t('forum', 'Green Heart') },
|
||||
{ emoji: '🖤', title: t('forum', 'Black Heart') },
|
||||
{ emoji: '🤍', title: t('forum', 'White Heart') },
|
||||
{ emoji: '🤎', title: t('forum', 'Brown Heart') },
|
||||
{ emoji: '💔', title: t('forum', 'Broken Heart') },
|
||||
{ emoji: '❣️', title: t('forum', 'Heart Exclamation') },
|
||||
{ emoji: '💕', title: t('forum', 'Two Hearts') },
|
||||
{ emoji: '💞', title: t('forum', 'Revolving Hearts') },
|
||||
{ emoji: '💓', title: t('forum', 'Beating Heart') },
|
||||
{ emoji: '💗', title: t('forum', 'Growing Heart') },
|
||||
{ emoji: '💖', title: t('forum', 'Sparkling Heart') },
|
||||
{ emoji: '💘', title: t('forum', 'Heart with Arrow') },
|
||||
{ emoji: '💝', title: t('forum', 'Heart with Ribbon') },
|
||||
{ emoji: '💟', title: t('forum', 'Heart Decoration') },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: t('forum', 'Symbols'),
|
||||
emojis: [
|
||||
{ emoji: '🎉', title: t('forum', 'Party Popper') },
|
||||
{ emoji: '🎊', title: t('forum', 'Confetti Ball') },
|
||||
{ emoji: '🎈', title: t('forum', 'Balloon') },
|
||||
{ emoji: '🎁', title: t('forum', 'Wrapped Gift') },
|
||||
{ emoji: '🏆', title: t('forum', 'Trophy') },
|
||||
{ emoji: '🥇', title: t('forum', '1st Place Medal') },
|
||||
{ emoji: '🥈', title: t('forum', '2nd Place Medal') },
|
||||
{ emoji: '🥉', title: t('forum', '3rd Place Medal') },
|
||||
{ emoji: '⭐', title: t('forum', 'Star') },
|
||||
{ emoji: '🌟', title: t('forum', 'Glowing Star') },
|
||||
{ emoji: '✨', title: t('forum', 'Sparkles') },
|
||||
{ emoji: '💫', title: t('forum', 'Dizzy') },
|
||||
{ emoji: '🔥', title: t('forum', 'Fire') },
|
||||
{ emoji: '💯', title: t('forum', 'Hundred Points') },
|
||||
{ emoji: '✅', title: t('forum', 'Check Mark Button') },
|
||||
{ emoji: '❌', title: t('forum', 'Cross Mark') },
|
||||
{ emoji: '⚠️', title: t('forum', 'Warning') },
|
||||
{ emoji: '❗', title: t('forum', 'Exclamation Mark') },
|
||||
{ emoji: '❓', title: t('forum', 'Question Mark') },
|
||||
{ emoji: '💬', title: t('forum', 'Speech Balloon') },
|
||||
{ emoji: '💭', title: t('forum', 'Thought Balloon') },
|
||||
{ emoji: '👀', title: t('forum', 'Eyes') },
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -363,11 +363,19 @@ export default defineComponent({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.profile-view {
|
||||
@media (max-width: 768px) {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.ml-8 {
|
||||
@@ -380,6 +388,10 @@ export default defineComponent({
|
||||
|
||||
.mt-24 {
|
||||
margin-top: 24px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
@@ -394,16 +406,38 @@ export default defineComponent({
|
||||
background: var(--color-main-background);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
@media (max-width: 768px) {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
@@ -412,6 +446,17 @@ export default defineComponent({
|
||||
gap: 8px;
|
||||
color: var(--color-text-maxcontrast);
|
||||
font-size: 14px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
@@ -425,6 +470,10 @@ export default defineComponent({
|
||||
|
||||
.meta-divider {
|
||||
color: var(--color-text-maxcontrast);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-tabs {
|
||||
@@ -444,6 +493,12 @@ export default defineComponent({
|
||||
color: var(--color-text-maxcontrast);
|
||||
transition: all 0.2s;
|
||||
border-radius: 0;
|
||||
flex: 1;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-light);
|
||||
@@ -481,6 +536,10 @@ export default defineComponent({
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-hover);
|
||||
border-color: var(--color-primary-element);
|
||||
@@ -494,12 +553,24 @@ export default defineComponent({
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-maxcontrast);
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-thread {
|
||||
strong {
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.post-content {
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.3.0
|
||||
0.4.0
|
||||
|
||||
Reference in New Issue
Block a user