diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-11-13 09:42:26 +0100 |
---|---|---|
committer | skjnldsv <skjnldsv@protonmail.com> | 2024-11-14 10:25:02 +0100 |
commit | b15fdfd40e4bf96b53f7afcf0e4dce359158cf1c (patch) | |
tree | 0c5cfe660bcdb04338661687b3733ae3cbc88ced /core | |
parent | dfa7e7edea66c37a7b33965ad9e93648d44243b0 (diff) | |
download | nextcloud-server-b15fdfd40e4bf96b53f7afcf0e4dce359158cf1c.tar.gz nextcloud-server-b15fdfd40e4bf96b53f7afcf0e4dce359158cf1c.zip |
chore(profile): move profile app from core to apps
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Diffstat (limited to 'core')
-rw-r--r-- | core/Controller/ProfilePageController.php | 115 | ||||
-rw-r--r-- | core/shipped.json | 2 | ||||
-rw-r--r-- | core/src/profile.ts | 27 | ||||
-rw-r--r-- | core/src/profile/ProfileSections.js | 25 | ||||
-rw-r--r-- | core/src/views/Profile.vue | 489 | ||||
-rw-r--r-- | core/templates/404-profile.php | 30 | ||||
-rw-r--r-- | core/templates/profile.php | 7 |
7 files changed, 2 insertions, 693 deletions
diff --git a/core/Controller/ProfilePageController.php b/core/Controller/ProfilePageController.php deleted file mode 100644 index 7463173e906..00000000000 --- a/core/Controller/ProfilePageController.php +++ /dev/null @@ -1,115 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace OC\Core\Controller; - -use OC\Profile\ProfileManager; -use OCP\AppFramework\Controller; -use OCP\AppFramework\Http\Attribute\AnonRateLimit; -use OCP\AppFramework\Http\Attribute\BruteForceProtection; -use OCP\AppFramework\Http\Attribute\FrontpageRoute; -use OCP\AppFramework\Http\Attribute\NoCSRFRequired; -use OCP\AppFramework\Http\Attribute\OpenAPI; -use OCP\AppFramework\Http\Attribute\PublicPage; -use OCP\AppFramework\Http\Attribute\UserRateLimit; -use OCP\AppFramework\Http\TemplateResponse; -use OCP\AppFramework\Services\IInitialState; -use OCP\EventDispatcher\IEventDispatcher; -use OCP\INavigationManager; -use OCP\IRequest; -use OCP\IUserManager; -use OCP\IUserSession; -use OCP\Profile\BeforeTemplateRenderedEvent; -use OCP\Share\IManager as IShareManager; -use OCP\UserStatus\IManager as IUserStatusManager; - -#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] -class ProfilePageController extends Controller { - public function __construct( - string $appName, - IRequest $request, - private IInitialState $initialStateService, - private ProfileManager $profileManager, - private IShareManager $shareManager, - private IUserManager $userManager, - private IUserSession $userSession, - private IUserStatusManager $userStatusManager, - private INavigationManager $navigationManager, - private IEventDispatcher $eventDispatcher, - ) { - parent::__construct($appName, $request); - } - - #[PublicPage] - #[NoCSRFRequired] - #[FrontpageRoute(verb: 'GET', url: '/u/{targetUserId}')] - #[BruteForceProtection(action: 'user')] - #[UserRateLimit(limit: 30, period: 120)] - #[AnonRateLimit(limit: 30, period: 120)] - public function index(string $targetUserId): TemplateResponse { - $profileNotFoundTemplate = new TemplateResponse( - 'core', - '404-profile', - [], - TemplateResponse::RENDER_AS_GUEST, - ); - - $targetUser = $this->userManager->get($targetUserId); - if ($targetUser === null) { - $profileNotFoundTemplate->throttle(); - return $profileNotFoundTemplate; - } - if (!$targetUser->isEnabled()) { - return $profileNotFoundTemplate; - } - $visitingUser = $this->userSession->getUser(); - - if (!$this->profileManager->isProfileEnabled($targetUser)) { - return $profileNotFoundTemplate; - } - - // Run user enumeration checks only if viewing another user's profile - if ($targetUser !== $visitingUser) { - if (!$this->shareManager->currentUserCanEnumerateTargetUser($visitingUser, $targetUser)) { - return $profileNotFoundTemplate; - } - } - - if ($visitingUser !== null) { - $userStatuses = $this->userStatusManager->getUserStatuses([$targetUserId]); - $status = $userStatuses[$targetUserId] ?? null; - if ($status !== null) { - $this->initialStateService->provideInitialState('status', [ - 'icon' => $status->getIcon(), - 'message' => $status->getMessage(), - ]); - } - } - - $this->initialStateService->provideInitialState( - 'profileParameters', - $this->profileManager->getProfileFields($targetUser, $visitingUser), - ); - - if ($targetUser === $visitingUser) { - $this->navigationManager->setActiveEntry('profile'); - } - - $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($targetUserId)); - - \OCP\Util::addScript('core', 'profile'); - - return new TemplateResponse( - 'core', - 'profile', - [], - $this->userSession->isLoggedIn() ? TemplateResponse::RENDER_AS_USER : TemplateResponse::RENDER_AS_PUBLIC, - ); - } -} diff --git a/core/shipped.json b/core/shipped.json index 6eb6a9879c2..3583ce68546 100644 --- a/core/shipped.json +++ b/core/shipped.json @@ -30,6 +30,7 @@ "password_policy", "photos", "privacy", + "profile", "provisioning_api", "recommendations", "related_resources", @@ -79,6 +80,7 @@ "password_policy", "photos", "privacy", + "profile", "provisioning_api", "recommendations", "related_resources", diff --git a/core/src/profile.ts b/core/src/profile.ts deleted file mode 100644 index 454562edb05..00000000000 --- a/core/src/profile.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { getCSPNonce } from '@nextcloud/auth' -import Vue from 'vue' - -import Profile from './views/Profile.vue' -import ProfileSections from './profile/ProfileSections.js' - -__webpack_nonce__ = getCSPNonce() - -if (!window.OCA) { - window.OCA = {} -} - -if (!window.OCA.Core) { - window.OCA.Core = {} -} -Object.assign(window.OCA.Core, { ProfileSections: new ProfileSections() }) - -const View = Vue.extend(Profile) - -window.addEventListener('DOMContentLoaded', () => { - new View().$mount('#content') -}) diff --git a/core/src/profile/ProfileSections.js b/core/src/profile/ProfileSections.js deleted file mode 100644 index 9c6ca08e33f..00000000000 --- a/core/src/profile/ProfileSections.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -export default class ProfileSections { - - _sections - - constructor() { - this._sections = [] - } - - /** - * @param {registerSectionCallback} section To be called to mount the section to the profile page - */ - registerSection(section) { - this._sections.push(section) - } - - getSections() { - return this._sections - } - -} diff --git a/core/src/views/Profile.vue b/core/src/views/Profile.vue deleted file mode 100644 index 661e9a7fa0b..00000000000 --- a/core/src/views/Profile.vue +++ /dev/null @@ -1,489 +0,0 @@ -<!-- - - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - - SPDX-License-Identifier: AGPL-3.0-or-later ---> - -<template> - <NcContent app-name="profile"> - <NcAppContent> - <div class="profile__header"> - <div class="profile__header__container"> - <div class="profile__header__container__placeholder" /> - <div class="profile__header__container__displayname"> - <h2>{{ displayname || userId }}</h2> - <span v-if="pronouns">·</span> - <span v-if="pronouns" class="profile__header__container__pronouns">{{ pronouns }}</span> - <NcButton v-if="isCurrentUser" - type="primary" - :href="settingsUrl"> - <template #icon> - <PencilIcon :size="20" /> - </template> - {{ t('core', 'Edit Profile') }} - </NcButton> - </div> - <NcButton v-if="status.icon || status.message" - :disabled="!isCurrentUser" - :type="isCurrentUser ? 'tertiary' : 'tertiary-no-background'" - @click="openStatusModal"> - {{ status.icon }} {{ status.message }} - </NcButton> - </div> - </div> - - <div class="profile__wrapper"> - <div class="profile__content"> - <div class="profile__sidebar"> - <NcAvatar class="avatar" - :class="{ interactive: isCurrentUser }" - :user="userId" - :size="180" - :show-user-status="true" - :show-user-status-compact="false" - :disable-menu="true" - :disable-tooltip="true" - :is-no-user="!isUserAvatarVisible" - @click.native.prevent.stop="openStatusModal" /> - - <div class="user-actions"> - <!-- When a tel: URL is opened with target="_blank", a blank new tab is opened which is inconsistent with the handling of other URLs so we set target="_self" for the phone action --> - <NcButton v-if="primaryAction" - type="primary" - class="user-actions__primary" - :href="primaryAction.target" - :icon="primaryAction.icon" - :target="primaryAction.id === 'phone' ? '_self' :'_blank'"> - <template #icon> - <!-- Fix for https://github.com/nextcloud-libraries/nextcloud-vue/issues/2315 --> - <img :src="primaryAction.icon" alt="" class="user-actions__primary__icon"> - </template> - {{ primaryAction.title }} - </NcButton> - <NcActions class="user-actions__other" :inline="4"> - <NcActionLink v-for="action in otherActions" - :key="action.id" - :close-after-click="true" - :href="action.target" - :target="action.id === 'phone' ? '_self' :'_blank'"> - <template #icon> - <!-- Fix for https://github.com/nextcloud-libraries/nextcloud-vue/issues/2315 --> - <img :src="action.icon" alt="" class="user-actions__other__icon"> - </template> - {{ action.title }} - </NcActionLink> - </NcActions> - </div> - </div> - - <div class="profile__blocks"> - <div v-if="organisation || role || address" class="profile__blocks-details"> - <div v-if="organisation || role" class="detail"> - <p>{{ organisation }} <span v-if="organisation && role">•</span> {{ role }}</p> - </div> - <div v-if="address" class="detail"> - <p> - <MapMarkerIcon class="map-icon" - :size="16" /> - {{ address }} - </p> - </div> - </div> - <template v-if="headline || biography || sections.length > 0"> - <h3 v-if="headline" class="profile__blocks-headline"> - {{ headline }} - </h3> - <p v-if="biography" class="profile__blocks-biography"> - {{ biography }} - </p> - - <!-- additional entries, use it with cautious --> - <div v-for="(section, index) in sections" - :ref="'section-' + index" - :key="index" - class="profile__additionalContent"> - <component :is="section($refs['section-'+index], userId)" :user-id="userId" /> - </div> - </template> - <NcEmptyContent v-else - class="profile__blocks-empty-info" - :name="emptyProfileMessage" - :description="t('core', 'The headline and about sections will show up here')"> - <template #icon> - <AccountIcon :size="60" /> - </template> - </NcEmptyContent> - </div> - </div> - </div> - </NcAppContent> - </NcContent> -</template> - -<script lang="ts"> -import { getCurrentUser } from '@nextcloud/auth' -import { showError } from '@nextcloud/dialogs' -import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import { loadState } from '@nextcloud/initial-state' -import { translate as t } from '@nextcloud/l10n' -import { generateUrl } from '@nextcloud/router' -import { defineComponent } from 'vue' - -import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' -import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js' -import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js' -import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' -import NcContent from '@nextcloud/vue/dist/Components/NcContent.js' -import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' -import AccountIcon from 'vue-material-design-icons/Account.vue' -import MapMarkerIcon from 'vue-material-design-icons/MapMarker.vue' -import PencilIcon from 'vue-material-design-icons/Pencil.vue' - -interface IProfileAction { - target: string - icon: string - id: string - title: string -} - -interface IStatus { - icon: string, - message: string, - userId: string, -} - -export default defineComponent({ - name: 'Profile', - - components: { - AccountIcon, - MapMarkerIcon, - NcActionLink, - NcActions, - NcAppContent, - NcAvatar, - NcButton, - NcContent, - NcEmptyContent, - PencilIcon, - }, - - data() { - const profileParameters = loadState('core', 'profileParameters', { - userId: null as string|null, - displayname: null as string|null, - address: null as string|null, - organisation: null as string|null, - role: null as string|null, - headline: null as string|null, - biography: null as string|null, - actions: [] as IProfileAction[], - isUserAvatarVisible: false, - pronouns: null as string|null, - }) - - return { - ...profileParameters, - status: loadState<Partial<IStatus>>('core', 'status', {}), - sections: window.OCA.Core.ProfileSections.getSections(), - } - }, - - computed: { - isCurrentUser() { - return getCurrentUser()?.uid === this.userId - }, - - allActions() { - return this.actions - }, - - primaryAction() { - if (this.allActions.length) { - return this.allActions[0] - } - return null - }, - - otherActions() { - console.warn(this.allActions) - if (this.allActions.length > 1) { - return this.allActions.slice(1) - } - return [] - }, - - settingsUrl() { - return generateUrl('/settings/user') - }, - - emptyProfileMessage() { - return this.isCurrentUser - ? t('core', 'You have not added any info yet') - : t('core', '{user} has not added any info yet', { user: (this.displayname || this.userId!) }) - }, - }, - - mounted() { - // Set the user's displayname or userId in the page title and preserve the default title of "Nextcloud" at the end - document.title = `${this.displayname || this.userId} - ${document.title}` - subscribe('user_status:status.updated', this.handleStatusUpdate) - }, - - beforeDestroy() { - unsubscribe('user_status:status.updated', this.handleStatusUpdate) - }, - - methods: { - t, - - handleStatusUpdate(status: IStatus) { - if (this.isCurrentUser && status.userId === this.userId) { - this.status = status - } - }, - - openStatusModal() { - const statusMenuItem = document.querySelector<HTMLButtonElement>('.user-status-menu-item') - // Changing the user status is only enabled if you are the current user - if (this.isCurrentUser) { - if (statusMenuItem) { - statusMenuItem.click() - } else { - showError(t('core', 'Error opening the user status modal, try hard refreshing the page')) - } - } - }, - }, -}) -</script> - -<style lang="scss" scoped> -$profile-max-width: 1024px; -$content-max-width: 640px; - -:deep(#app-content-vue) { - background-color: unset; -} - -.profile { - width: 100%; - overflow-y: auto; - - &__header { - display: flex; - position: sticky; - height: 190px; - top: -40px; - background-color: var(--color-main-background-blur); - backdrop-filter: var(--filter-background-blur); - -webkit-backdrop-filter: var(--filter-background-blur); - - &__container { - align-self: flex-end; - width: 100%; - max-width: $profile-max-width; - margin: 8px auto; - row-gap: 8px; - display: grid; - grid-template-rows: max-content max-content; - grid-template-columns: 240px 1fr; - justify-content: center; - - &__placeholder { - grid-row: 1 / 3; - } - - &__displayname { - padding-inline: 16px; // same as the status text button, see NcButton - width: $content-max-width; - height: 45px; - margin-block: 125px 0; - display: flex; - align-items: center; - gap: 18px; - - h2 { - font-size: 30px; - margin: 0; - } - - span { - font-size: 20px; - } - } - } - } - - &__sidebar { - position: sticky; - top: 0; - align-self: flex-start; - padding-top: 20px; - min-width: 220px; - margin-block: -150px 0; - margin-inline: 0 20px; - - // Specificity hack is needed to override Avatar component styles - :deep(.avatar.avatardiv) { - text-align: center; - margin: auto; - display: block; - padding: 8px; - - &.interactive { - .avatardiv__user-status { - // Show that the status is interactive - cursor: pointer; - } - } - - .avatardiv__user-status { - inset-inline-end: 14px; - bottom: 14px; - width: 34px; - height: 34px; - background-size: 28px; - border: none; - // Styles when custom status icon and status text are set - background-color: var(--color-main-background); - line-height: 34px; - font-size: 20px; - } - } - } - - &__wrapper { - background-color: var(--color-main-background); - min-height: 100%; - } - - &__content { - max-width: $profile-max-width; - margin: 0 auto; - display: flex; - width: 100%; - } - - &__blocks { - margin: 18px 0 80px 0; - display: grid; - gap: 16px 0; - width: $content-max-width; - - p, h3 { - cursor: text; - overflow-wrap: anywhere; - } - - &-details { - display: flex; - flex-direction: column; - gap: 2px 0; - - .detail { - display: inline-block; - color: var(--color-text-maxcontrast); - - p .map-icon { - display: inline-block; - vertical-align: middle; - } - } - } - - &-headline { - margin-inline: 0; - margin-block: 10px 0; - font-weight: bold; - font-size: 20px; - } - - &-biography { - white-space: pre-line; - } - } -} - -@media only screen and (max-width: 1024px) { - .profile { - &__header { - height: 250px; - position: unset; - - &__container { - grid-template-columns: unset; - margin-bottom: 110px; - - &__displayname { - margin: 80px 20px 0px 0px!important; - width: unset; - text-align: center; - padding-inline: 12px; - } - - &__edit-button { - width: fit-content; - display: block; - margin: 60px auto; - } - - &__status-text { - margin: 4px auto; - } - } - } - - &__content { - display: block; - - .avatar { - // Overlap avatar to top header - margin-top: -110px !important; - } - } - - &__blocks { - width: unset; - max-width: 600px; - margin: 0 auto; - padding: 20px 50px 50px 50px; - } - - &__sidebar { - margin: unset; - position: unset; - } - } -} - -.user-actions { - display: flex; - flex-direction: column; - gap: 8px 0; - margin-top: 20px; - - &__primary { - margin: 0 auto; - - &__icon { - filter: var(--primary-invert-if-dark); - } - } - - &__other { - display: flex; - justify-content: center; - gap: 0 4px; - - &__icon { - height: 20px; - width: 20px; - object-fit: contain; - filter: var(--background-invert-if-dark); - align-self: center; - margin: 12px; // so we get 44px x 44px - } - } -} -</style> diff --git a/core/templates/404-profile.php b/core/templates/404-profile.php deleted file mode 100644 index 3f2d8731347..00000000000 --- a/core/templates/404-profile.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -/** - * SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -/** @var array $_ */ -/** @var \OCP\IL10N $l */ -/** @var \OCP\Defaults $theme */ -// @codeCoverageIgnoreStart -if (!isset($_)) { //standalone page is not supported anymore - redirect to / - require_once '../../lib/base.php'; - - $urlGenerator = \OC::$server->getURLGenerator(); - header('Location: ' . $urlGenerator->getAbsoluteURL('/')); - exit; -} -// @codeCoverageIgnoreEnd -?> -<?php if (isset($_['content'])) : ?> - <?php print_unescaped($_['content']) ?> -<?php else : ?> - <div class="body-login-container update"> - <div class="icon-big icon-error"></div> - <h2><?php p($l->t('Profile not found')); ?></h2> - <p class="infogroup"><?php p($l->t('The profile does not exist.')); ?></p> - <p><a class="button primary" href="<?php p(\OC::$server->getURLGenerator()->linkTo('', 'index.php')) ?>"> - <?php p($l->t('Back to %s', [$theme->getName()])); ?> - </a></p> - </div> -<?php endif; ?> diff --git a/core/templates/profile.php b/core/templates/profile.php deleted file mode 100644 index 460bfcc4221..00000000000 --- a/core/templates/profile.php +++ /dev/null @@ -1,7 +0,0 @@ -<?php -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -?> -<div id="content"></div> |