зеркало из https://github.com/nextcloud/server.git
Remake profile picture saving with Vue
Signed-off-by: Christopher Ng <chrng8@gmail.com>
This commit is contained in:
Родитель
f167fe0ceb
Коммит
f44d2586b1
|
@ -93,69 +93,6 @@ input#openid, input#webdav {
|
|||
background-image: var(--icon-password-dark);
|
||||
}
|
||||
|
||||
#avatarform .avatardiv {
|
||||
margin: 10px auto;
|
||||
}
|
||||
#avatarform .warning {
|
||||
width: 100%;
|
||||
}
|
||||
#avatarform .jcrop-keymgr {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#displayavatar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#uploadavatarbutton, #selectavatar, #removeavatar {
|
||||
padding: 21px;
|
||||
}
|
||||
|
||||
#selectavatar, #removeavatar {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jcrop-holder {
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
#cropper {
|
||||
float: left;
|
||||
z-index: 500;
|
||||
/* float cropper above settings page to prevent unexpected flowing from dynamically sized element */
|
||||
position: fixed;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
top: 45px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 45px);
|
||||
}
|
||||
#cropper .inner-container {
|
||||
z-index: 2001;
|
||||
/* above the top bar if needed */
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: 0 0 10px var(--color-box-shadow);
|
||||
padding: 15px;
|
||||
}
|
||||
#cropper .inner-container .jcrop-holder,
|
||||
#cropper .inner-container .jcrop-holder img,
|
||||
#cropper .inner-container img.jcrop-preview {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
#cropper .inner-container .button {
|
||||
margin-top: 15px;
|
||||
}
|
||||
#cropper .inner-container .primary {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#personal-settings-avatar-container {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -36,76 +36,6 @@ input {
|
|||
@include icon-color('password', 'settings', variables.$color-black);
|
||||
}
|
||||
|
||||
#avatarform {
|
||||
.avatardiv {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.warning {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jcrop-keymgr {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
#displayavatar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#uploadavatarbutton, #selectavatar, #removeavatar {
|
||||
padding: 21px;
|
||||
}
|
||||
#selectavatar, #removeavatar {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jcrop-holder {
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
#cropper {
|
||||
float: left;
|
||||
z-index: 500;
|
||||
/* float cropper above settings page to prevent unexpected flowing from dynamically sized element */
|
||||
position: fixed;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
top: 45px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 45px);
|
||||
|
||||
.inner-container {
|
||||
z-index: 2001;
|
||||
/* above the top bar if needed */
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border-radius: var(--border-radius-large);
|
||||
box-shadow: 0 0 10px var(--color-box-shadow);
|
||||
padding: 15px;
|
||||
|
||||
.jcrop-holder,
|
||||
.jcrop-holder img,
|
||||
img.jcrop-preview {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.primary {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#personal-settings-avatar-container {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
|
|
@ -41,107 +41,6 @@ jQuery.fn.keyUpDelayedOrEnter = function (callback, allowEmptyValue) {
|
|||
});
|
||||
};
|
||||
|
||||
function updateAvatar (hidedefault) {
|
||||
var $headerdiv = $('#header .avatardiv'),
|
||||
$displaydiv = $('#displayavatar .avatardiv'),
|
||||
user = OC.getCurrentUser();
|
||||
|
||||
//Bump avatar avatarversion
|
||||
oc_userconfig.avatar.version = -(Math.floor(Math.random() * 1000));
|
||||
|
||||
if (hidedefault) {
|
||||
$headerdiv.hide();
|
||||
$('#header .avatardiv').removeClass('avatardiv-shown');
|
||||
} else {
|
||||
$headerdiv.css({'background-color': ''});
|
||||
$headerdiv.avatar(user.uid, 32, true, false, undefined, user.displayName);
|
||||
$('#header .avatardiv').addClass('avatardiv-shown');
|
||||
}
|
||||
$displaydiv.css({'background-color': ''});
|
||||
$displaydiv.avatar(user.uid, 145, true, null, function() {
|
||||
$displaydiv.removeClass('loading');
|
||||
$('#displayavatar img').show();
|
||||
if($('#displayavatar img').length === 0 || oc_userconfig.avatar.generated) {
|
||||
$('#removeavatar').removeClass('inlineblock').addClass('hidden');
|
||||
} else {
|
||||
$('#removeavatar').removeClass('hidden').addClass('inlineblock');
|
||||
}
|
||||
}, user.displayName);
|
||||
$('#uploadavatar').prop('disabled', false);
|
||||
}
|
||||
|
||||
function showAvatarCropper () {
|
||||
var $cropper = $('#cropper');
|
||||
var $cropperImage = $('<img/>');
|
||||
$cropperImage.css('opacity', 0); // prevent showing the unresized image
|
||||
$cropper.children('.inner-container').prepend($cropperImage);
|
||||
|
||||
$cropperImage.attr('src',
|
||||
OC.generateUrl('/avatar/tmp') + '?requesttoken=' + encodeURIComponent(OC.requestToken) + '#' + Math.floor(Math.random() * 1000));
|
||||
|
||||
$cropperImage.load(function () {
|
||||
var img = $cropperImage.get()[0];
|
||||
var selectSize = Math.min(img.width, img.height);
|
||||
var offsetX = (img.width - selectSize) / 2;
|
||||
var offsetY = (img.height - selectSize) / 2;
|
||||
$cropperImage.Jcrop({
|
||||
onChange: saveCoords,
|
||||
onSelect: saveCoords,
|
||||
aspectRatio: 1,
|
||||
boxHeight: Math.min(500, $('#app-content').height() -100),
|
||||
boxWidth: Math.min(500, $('#app-content').width()),
|
||||
setSelect: [offsetX, offsetY, selectSize, selectSize]
|
||||
}, function() {
|
||||
$cropper.show();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sendCropData () {
|
||||
cleanCropper();
|
||||
|
||||
var cropperData = $('#cropper').data();
|
||||
var data = {
|
||||
x: cropperData.x,
|
||||
y: cropperData.y,
|
||||
w: cropperData.w,
|
||||
h: cropperData.h
|
||||
};
|
||||
$.post(OC.generateUrl('/avatar/cropped'), {crop: data}, avatarResponseHandler);
|
||||
}
|
||||
|
||||
function saveCoords (c) {
|
||||
$('#cropper').data(c);
|
||||
}
|
||||
|
||||
function cleanCropper () {
|
||||
var $cropper = $('#cropper');
|
||||
$('#displayavatar').show();
|
||||
$cropper.hide();
|
||||
$('.jcrop-holder').remove();
|
||||
$('#cropper img').removeData('Jcrop').removeAttr('style').removeAttr('src');
|
||||
$('#cropper img').remove();
|
||||
}
|
||||
|
||||
function avatarResponseHandler (data) {
|
||||
if (typeof data === 'string') {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
var $warning = $('#avatarform .warning');
|
||||
$warning.hide();
|
||||
if (data.status === "success") {
|
||||
$('#displayavatar .avatardiv').removeClass('icon-loading');
|
||||
oc_userconfig.avatar.generated = false;
|
||||
updateAvatar();
|
||||
} else if (data.data === "notsquare") {
|
||||
cleanCropper();
|
||||
showAvatarCropper();
|
||||
} else {
|
||||
$warning.show();
|
||||
$warning.text(data.data.message);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
if($('#pass2').length) {
|
||||
$('#pass2').showPassword().keyup();
|
||||
|
@ -208,9 +107,6 @@ window.addEventListener('DOMContentLoaded', function () {
|
|||
showPublishedScope: !!settingsEl.data('lookup-server-upload-enabled'),
|
||||
});
|
||||
|
||||
userSettings.on("sync", function() {
|
||||
updateAvatar(false);
|
||||
});
|
||||
federationSettingsView.render();
|
||||
|
||||
var updateLanguage = function () {
|
||||
|
@ -264,125 +160,6 @@ window.addEventListener('DOMContentLoaded', function () {
|
|||
});
|
||||
};
|
||||
$("#localeinput").change(updateLocale);
|
||||
|
||||
var uploadparms = {
|
||||
pasteZone: null,
|
||||
done: function (e, data) {
|
||||
var response = data;
|
||||
if (typeof data.result === 'string') {
|
||||
response = JSON.parse(data.result);
|
||||
} else if (data.result && data.result.length) {
|
||||
// fetch response from iframe
|
||||
response = JSON.parse(data.result[0].body.innerText);
|
||||
} else {
|
||||
response = data.result;
|
||||
}
|
||||
avatarResponseHandler(response);
|
||||
},
|
||||
submit: function(e, data) {
|
||||
$('#displayavatar img').hide();
|
||||
$('#displayavatar .avatardiv').addClass('icon-loading');
|
||||
$('#uploadavatar').prop('disabled', true)
|
||||
data.formData = _.extend(data.formData || {}, {
|
||||
requesttoken: OC.requestToken
|
||||
});
|
||||
},
|
||||
fail: function (e, data) {
|
||||
$('#displayavatar .avatardiv').removeClass('icon-loading');
|
||||
$('#uploadavatar').prop('disabled', false)
|
||||
var msg = data.jqXHR.statusText + ' (' + data.jqXHR.status + ')';
|
||||
if (!_.isUndefined(data.jqXHR.responseJSON) &&
|
||||
!_.isUndefined(data.jqXHR.responseJSON.data) &&
|
||||
!_.isUndefined(data.jqXHR.responseJSON.data.message)
|
||||
) {
|
||||
msg = data.jqXHR.responseJSON.data.message;
|
||||
}
|
||||
avatarResponseHandler({
|
||||
data: {
|
||||
message: msg
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$('#uploadavatar').fileupload(uploadparms);
|
||||
|
||||
// Trigger upload action also with keyboard navigation on enter
|
||||
$('#uploadavatarbutton').on('keyup', function(event) {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
$('#uploadavatar').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
$('#selectavatar').click(function (event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
OC.dialogs.filepicker(
|
||||
t('settings', "Select a profile picture"),
|
||||
function (path) {
|
||||
$('#displayavatar img').hide();
|
||||
$('#displayavatar .avatardiv').addClass('icon-loading');
|
||||
$('#uploadavatar').prop('disabled', true);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: OC.generateUrl('/avatar/'),
|
||||
data: { path: path }
|
||||
}).done(avatarResponseHandler)
|
||||
.fail(function(jqXHR) {
|
||||
var msg = jqXHR.statusText + ' (' + jqXHR.status + ')';
|
||||
if (!_.isUndefined(jqXHR.responseJSON) &&
|
||||
!_.isUndefined(jqXHR.responseJSON.data) &&
|
||||
!_.isUndefined(jqXHR.responseJSON.data.message)
|
||||
) {
|
||||
msg = jqXHR.responseJSON.data.message;
|
||||
}
|
||||
avatarResponseHandler({
|
||||
data: {
|
||||
message: msg
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
false,
|
||||
["image/png", "image/jpeg"]
|
||||
);
|
||||
});
|
||||
|
||||
$('#removeavatar').click(function (event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: OC.generateUrl('/avatar/'),
|
||||
success: function () {
|
||||
oc_userconfig.avatar.generated = true;
|
||||
updateAvatar(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#abortcropperbutton').click(function () {
|
||||
$('#displayavatar .avatardiv').removeClass('icon-loading');
|
||||
$('#displayavatar img').show();
|
||||
$('#uploadavatar').prop('disabled', false);
|
||||
cleanCropper();
|
||||
});
|
||||
|
||||
$('#sendcropperbutton').click(function () {
|
||||
sendCropData();
|
||||
});
|
||||
|
||||
// Load the big avatar
|
||||
var user = OC.getCurrentUser();
|
||||
$('#avatarform .avatardiv').avatar(user.uid, 145, true, null, function() {
|
||||
if($('#displayavatar img').length === 0 || oc_userconfig.avatar.generated) {
|
||||
$('#removeavatar').removeClass('inlineblock').addClass('hidden');
|
||||
} else {
|
||||
$('#removeavatar').removeClass('hidden').addClass('inlineblock');
|
||||
}
|
||||
}, user.displayName);
|
||||
});
|
||||
|
||||
window.setInterval(function() {
|
||||
|
@ -390,5 +167,3 @@ window.setInterval(function() {
|
|||
$('#localeexample-date').text(moment().format('L'))
|
||||
$('#localeexample-fdow').text(t('settings', 'Week starts on {fdow}', { fdow: dayNames[firstDay] }))
|
||||
}, 1000)
|
||||
|
||||
OC.Settings.updateAvatar = updateAvatar;
|
||||
|
|
|
@ -143,10 +143,8 @@ class PersonalInfo implements ISettings {
|
|||
'usage' => \OC_Helper::humanFileSize($storageInfo['used']),
|
||||
'usage_relative' => round($storageInfo['relative']),
|
||||
'quota' => $storageInfo['quota'],
|
||||
'avatarChangeSupported' => $user->canChangeAvatar(),
|
||||
'federationEnabled' => $federationEnabled,
|
||||
'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
|
||||
'avatarScope' => $account->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(),
|
||||
'groups' => $this->getGroups($user),
|
||||
'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
|
||||
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
|
||||
|
@ -154,6 +152,7 @@ class PersonalInfo implements ISettings {
|
|||
|
||||
$personalInfoParameters = [
|
||||
'userId' => $uid,
|
||||
'avatar' => $this->getProperty($account, IAccountManager::PROPERTY_AVATAR),
|
||||
'displayName' => $this->getProperty($account, IAccountManager::PROPERTY_DISPLAYNAME),
|
||||
'emailMap' => $this->getEmailMap($account),
|
||||
'phone' => $this->getProperty($account, IAccountManager::PROPERTY_PHONE),
|
||||
|
@ -170,6 +169,7 @@ class PersonalInfo implements ISettings {
|
|||
];
|
||||
|
||||
$accountParameters = [
|
||||
'avatarChangeSupported' => $user->canChangeAvatar(),
|
||||
'displayNameChangeSupported' => $user->canChangeDisplayName(),
|
||||
'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
<!--
|
||||
- @copyright 2022 Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<HeaderBar :input-id="avatarChangeSupported ? inputId : null"
|
||||
:readable="avatar.readable"
|
||||
:scope.sync="avatar.scope" />
|
||||
|
||||
<div v-if="!showCropper" class="avatar__container">
|
||||
<div class="avatar__preview">
|
||||
<NcAvatar v-if="!loading"
|
||||
:user="userId"
|
||||
:aria-label="t('settings', 'Your profile picture')"
|
||||
:disabled-menu="true"
|
||||
:disabled-tooltip="true"
|
||||
:show-user-status="false"
|
||||
:size="180"
|
||||
:key="version" />
|
||||
<div v-else class="icon-loading" />
|
||||
</div>
|
||||
<template v-if="avatarChangeSupported">
|
||||
<div class="avatar__buttons">
|
||||
<NcButton :aria-label="t('settings', 'Upload profile picture')"
|
||||
@click="activateLocalFilePicker">
|
||||
<template #icon>
|
||||
<Upload :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton :aria-label="t('settings', 'Choose profile picture from files')"
|
||||
@click="openFilePicker">
|
||||
<template #icon>
|
||||
<Folder :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton v-if="!isGenerated"
|
||||
:aria-label="t('settings', 'Remove profile picture')"
|
||||
@click="removeAvatar">
|
||||
<template #icon>
|
||||
<Delete :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
<span>{{ t('settings', 'png or jpg, max. 20 MB') }}</span>
|
||||
<input ref="input"
|
||||
:id="inputId"
|
||||
type="file"
|
||||
:accept="validMimeTypes.join(',')"
|
||||
@change="onChange">
|
||||
</template>
|
||||
<span v-else>
|
||||
{{ t('settings', 'Picture provided by original account') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Use v-show to ensure early cropper ref availability -->
|
||||
<div v-show="showCropper" class="avatar__container">
|
||||
<VueCropper ref="cropper"
|
||||
class="avatar__cropper"
|
||||
v-bind="cropperOptions" />
|
||||
<div class="avatar__cropper-buttons">
|
||||
<NcButton @click="cancel">
|
||||
{{ t('settings', 'Cancel') }}
|
||||
</NcButton>
|
||||
<NcButton type="primary"
|
||||
@click="saveAvatar">
|
||||
{{ t('settings', 'Set as profile picture') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
<span>{{ t('settings', 'Please note that it can take up to 24 hours for your profile picture to be updated everywhere.') }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '@nextcloud/axios'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
|
||||
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
|
||||
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton'
|
||||
import VueCropper from 'vue-cropperjs'
|
||||
// eslint-disable-next-line node/no-extraneous-import
|
||||
import 'cropperjs/dist/cropper.css'
|
||||
|
||||
import Upload from 'vue-material-design-icons/Upload'
|
||||
import Folder from 'vue-material-design-icons/Folder'
|
||||
import Delete from 'vue-material-design-icons/Delete'
|
||||
|
||||
import HeaderBar from './shared/HeaderBar.vue'
|
||||
import { NAME_READABLE_ENUM } from '../../constants/AccountPropertyConstants.js'
|
||||
|
||||
const { avatar } = loadState('settings', 'personalInfoParameters', {})
|
||||
const { avatarChangeSupported } = loadState('settings', 'accountParameters', {})
|
||||
|
||||
const VALID_MIME_TYPES = ['image/png', 'image/jpeg']
|
||||
|
||||
const picker = getFilePickerBuilder(t('settings', 'Choose your profile picture'))
|
||||
.setMultiSelect(false)
|
||||
.setMimeTypeFilter(VALID_MIME_TYPES)
|
||||
.setModal(true)
|
||||
.setType(1)
|
||||
.allowDirectories(false)
|
||||
.build()
|
||||
|
||||
export default {
|
||||
name: 'AvatarSection',
|
||||
|
||||
components: {
|
||||
Delete,
|
||||
Folder,
|
||||
HeaderBar,
|
||||
NcAvatar,
|
||||
NcButton,
|
||||
Upload,
|
||||
VueCropper,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
avatar: { ...avatar, readable: NAME_READABLE_ENUM[avatar.name] },
|
||||
avatarChangeSupported,
|
||||
showCropper: false,
|
||||
loading: false,
|
||||
userId: getCurrentUser().uid,
|
||||
displayName: getCurrentUser().displayName,
|
||||
version: oc_userconfig.avatar.version,
|
||||
isGenerated: oc_userconfig.avatar.generated,
|
||||
validMimeTypes: VALID_MIME_TYPES,
|
||||
cropperOptions: {
|
||||
aspectRatio: 1 / 1,
|
||||
viewMode: 1,
|
||||
guides: false,
|
||||
center: false,
|
||||
highlight: false,
|
||||
autoCropArea: 1,
|
||||
minContainerWidth: 300,
|
||||
minContainerHeight: 300,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
subscribe('settings:display-name:updated', this.handleDisplayNameUpdate)
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
unsubscribe('settings:display-name:updated', this.handleDisplayNameUpdate)
|
||||
},
|
||||
|
||||
computed: {
|
||||
inputId() {
|
||||
return `account-property-${this.avatar.name}`
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
activateLocalFilePicker() {
|
||||
// Set to null so that selecting the same file will trigger the change event
|
||||
this.$refs.input.value = null
|
||||
this.$refs.input.click()
|
||||
},
|
||||
|
||||
onChange(e) {
|
||||
this.loading = true
|
||||
const file = e.target.files[0]
|
||||
if (!this.validMimeTypes.includes(file.type)) {
|
||||
showError(t('settings', 'Please select a valid png or jpg file'))
|
||||
this.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
this.$refs.cropper.replace(e.target.result)
|
||||
this.showCropper = true
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
},
|
||||
|
||||
async openFilePicker() {
|
||||
const path = await picker.pick()
|
||||
this.loading = true
|
||||
try {
|
||||
const { data } = await axios.post(generateUrl('/avatar'), { path })
|
||||
if (data.status === 'success') {
|
||||
this.handleAvatarUpdate(false)
|
||||
} else if (data.data === 'notsquare') {
|
||||
const tempAvatar = generateUrl('/avatar/tmp') + '?requesttoken=' + encodeURIComponent(OC.requestToken) + '#' + Math.floor(Math.random() * 1000)
|
||||
this.$refs.cropper.replace(tempAvatar)
|
||||
this.showCropper = true
|
||||
} else {
|
||||
showError(data.data.message)
|
||||
this.cancel()
|
||||
}
|
||||
} catch (e) {
|
||||
showError(t('settings', 'Error setting profile picture'))
|
||||
this.cancel()
|
||||
}
|
||||
},
|
||||
|
||||
saveAvatar() {
|
||||
this.showCropper = false
|
||||
this.loading = true
|
||||
|
||||
this.$refs.cropper.getCroppedCanvas().toBlob(async (blob) => {
|
||||
if (blob === null) {
|
||||
showError(t('settings', 'Error cropping profile picture'))
|
||||
this.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('files[]', blob)
|
||||
try {
|
||||
await axios.post(generateUrl('/avatar'), formData)
|
||||
this.handleAvatarUpdate(false)
|
||||
} catch (e) {
|
||||
showError(t('settings', 'Error saving profile picture'))
|
||||
this.handleAvatarUpdate(this.isGenerated)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async removeAvatar() {
|
||||
this.loading = true
|
||||
try {
|
||||
await axios.delete(generateUrl('/avatar'))
|
||||
this.handleAvatarUpdate(true)
|
||||
} catch (e) {
|
||||
showError(t('settings', 'Error removing profile picture'))
|
||||
this.handleAvatarUpdate(this.isGenerated)
|
||||
}
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.showCropper = false
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
handleAvatarUpdate(isGenerated) {
|
||||
// Update the avatar version so that avatar update handlers refresh correctly
|
||||
this.version = oc_userconfig.avatar.version = Date.now()
|
||||
this.isGenerated = oc_userconfig.avatar.generated = isGenerated
|
||||
this.loading = false
|
||||
emit('settings:avatar:updated', oc_userconfig.avatar.version)
|
||||
/**
|
||||
* FIXME refresh all other avatars on the page when updated,
|
||||
* the NcAvatar component itself should listen to the
|
||||
* global events and optionally live refresh with a prop toggle
|
||||
* https://github.com/nextcloud/nextcloud-vue/issues/2975
|
||||
*/
|
||||
},
|
||||
|
||||
handleDisplayNameUpdate() {
|
||||
this.version = oc_userconfig.avatar.version
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar {
|
||||
&__container {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px 0;
|
||||
width: 300px;
|
||||
|
||||
span {
|
||||
color: var(--color-text-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
&__preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
gap: 0 10px;
|
||||
}
|
||||
|
||||
&__cropper {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
|
||||
&-buttons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&::v-deep .cropper-view-box {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -59,6 +59,10 @@ export default {
|
|||
},
|
||||
|
||||
onSave(value) {
|
||||
if (oc_userconfig.avatar.generated) {
|
||||
// Update the avatar version so that avatar update handlers refresh correctly
|
||||
oc_userconfig.avatar.version = Date.now()
|
||||
}
|
||||
emit('settings:display-name:updated', value)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export const ACCOUNT_PROPERTY_ENUM = Object.freeze({
|
|||
/** Enum of account properties to human readable account property names */
|
||||
export const ACCOUNT_PROPERTY_READABLE_ENUM = Object.freeze({
|
||||
ADDRESS: t('settings', 'Location'),
|
||||
AVATAR: t('settings', 'Avatar'),
|
||||
AVATAR: t('settings', 'Profile picture'),
|
||||
BIOGRAPHY: t('settings', 'About'),
|
||||
DISPLAYNAME: t('settings', 'Full name'),
|
||||
EMAIL_COLLECTION: t('settings', 'Additional email'),
|
||||
|
|
|
@ -26,6 +26,7 @@ import { loadState } from '@nextcloud/initial-state'
|
|||
import { translate as t } from '@nextcloud/l10n'
|
||||
import '@nextcloud/dialogs/styles/toast.scss'
|
||||
|
||||
import AvatarSection from './components/PersonalInfo/AvatarSection.vue'
|
||||
import DisplayNameSection from './components/PersonalInfo/DisplayNameSection.vue'
|
||||
import EmailSection from './components/PersonalInfo/EmailSection/EmailSection.vue'
|
||||
import PhoneSection from './components/PersonalInfo/PhoneSection.vue'
|
||||
|
@ -50,6 +51,7 @@ Vue.mixin({
|
|||
},
|
||||
})
|
||||
|
||||
const AvatarView = Vue.extend(AvatarSection)
|
||||
const DisplayNameView = Vue.extend(DisplayNameSection)
|
||||
const EmailView = Vue.extend(EmailSection)
|
||||
const PhoneView = Vue.extend(PhoneSection)
|
||||
|
@ -58,6 +60,7 @@ const WebsiteView = Vue.extend(WebsiteSection)
|
|||
const TwitterView = Vue.extend(TwitterSection)
|
||||
const LanguageView = Vue.extend(LanguageSection)
|
||||
|
||||
new AvatarView().$mount('#vue-avatar-section')
|
||||
new DisplayNameView().$mount('#vue-displayname-section')
|
||||
new EmailView().$mount('#vue-email-section')
|
||||
new PhoneView().$mount('#vue-phone-section')
|
||||
|
|
|
@ -47,42 +47,7 @@ script('settings', [
|
|||
data-lookup-server-upload-enabled="<?php p($_['lookupServerUploadEnabled'] ? 'true' : 'false') ?>">
|
||||
<h2 class="hidden-visually"><?php p($l->t('Personal info')); ?></h2>
|
||||
<div id="personal-settings-avatar-container" class="personal-settings-container">
|
||||
<div>
|
||||
<form id="avatarform" class="section" method="post" action="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.avatar.postAvatar')); ?>">
|
||||
<h3>
|
||||
<?php p($l->t('Profile picture')); ?>
|
||||
<a href="#" class="federation-menu" aria-label="<?php p($l->t('Change privacy level of profile picture')); ?>">
|
||||
<span class="icon-federation-menu icon-password">
|
||||
<span class="icon-triangle-s"></span>
|
||||
</span>
|
||||
</a>
|
||||
</h3>
|
||||
<div id="displayavatar">
|
||||
<div class="avatardiv"></div>
|
||||
<div class="warning hidden"></div>
|
||||
<?php if ($_['avatarChangeSupported']) : ?>
|
||||
<label for="uploadavatar" class="inlineblock button icon-upload svg" id="uploadavatarbutton" title="<?php p($l->t('Upload new')); ?>" tabindex="0"></label>
|
||||
<button class="inlineblock button icon-folder svg" id="selectavatar" title="<?php p($l->t('Select from Files')); ?>"></button>
|
||||
<button class="hidden button icon-delete svg" id="removeavatar" title="<?php p($l->t('Remove image')); ?>"></button>
|
||||
<input type="file" name="files[]" id="uploadavatar" class="hiddenuploadfield" accept="image/*">
|
||||
<p><em><?php p($l->t('png or jpg, max. 20 MB')); ?></em></p>
|
||||
<?php else : ?>
|
||||
<?php p($l->t('Picture provided by original account')); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div id="cropper" class="hidden">
|
||||
<div class="inner-container">
|
||||
<p style="width: 300px; margin-top: 0.5rem"><?php p($l->t('Please note that it can take up to 24 hours for the avatar to get updated everywhere.')); ?></p>
|
||||
<div class="inlineblock button" id="abortcropperbutton"><?php p($l->t('Cancel')); ?></div>
|
||||
<div class="inlineblock button primary" id="sendcropperbutton"><?php p($l->t('Choose as profile picture')); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="icon-checkmark hidden"></span>
|
||||
<span class="icon-error hidden"></span>
|
||||
<input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>">
|
||||
</form>
|
||||
</div>
|
||||
<div id="vue-avatar-section"></div>
|
||||
<div class="personal-settings-setting-box personal-settings-group-box section">
|
||||
<h3><?php p($l->t('Details')); ?></h3>
|
||||
<div id="groups" class="personal-info icon-user">
|
||||
|
|
До Ширина: | Высота: | Размер: 2.4 KiB После Ширина: | Высота: | Размер: 2.4 KiB |
|
@ -21,20 +21,20 @@ Feature: avatar
|
|||
|
||||
|
||||
|
||||
Scenario: get temporary user avatar before cropping it
|
||||
Scenario: get temporary non-square user avatar before cropping it
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/green-square-256.png"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
When logged in user gets temporary avatar
|
||||
Then The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
# "last avatar" also includes the last temporary avatar
|
||||
And last avatar is a square of size 256
|
||||
And last avatar is a single "#00FF00" color
|
||||
And last avatar is not a square
|
||||
And last avatar is not a single color
|
||||
|
||||
Scenario: get user avatar before cropping it
|
||||
Scenario: get non-square user avatar before cropping it
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/green-square-256.png"
|
||||
# Avatar needs to be cropped to finish setting it even if it is squared
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
# Avatar needs to be cropped to finish setting it
|
||||
When user "user0" gets avatar for user "user0"
|
||||
Then The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
|
@ -42,11 +42,43 @@ Feature: avatar
|
|||
And last avatar is a square of size 512
|
||||
And last avatar is not a single color
|
||||
|
||||
|
||||
|
||||
Scenario: set user avatar from file
|
||||
Scenario: set square user avatar from file
|
||||
Given Logging in using web as "user0"
|
||||
When logged in user posts temporary avatar from file "data/coloured-pattern.png"
|
||||
When logged in user posts temporary avatar from file "data/green-square-256.png"
|
||||
And user "user0" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
# Last avatar size is 512 by default when getting avatar without size parameter
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#00FF00" color
|
||||
And user "anonymous" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#00FF00" color
|
||||
|
||||
Scenario: set square user avatar from internal path
|
||||
Given user "user0" uploads file "data/green-square-256.png" to "/internal-green-square-256.png"
|
||||
And Logging in using web as "user0"
|
||||
When logged in user posts temporary avatar from internal path "internal-green-square-256.png"
|
||||
And user "user0" gets avatar for user "user0" with size "64"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 64
|
||||
And last avatar is a single "#00FF00" color
|
||||
And user "anonymous" gets avatar for user "user0" with size "64"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 64
|
||||
And last avatar is a single "#00FF00" color
|
||||
|
||||
Scenario: set non-square user avatar from file
|
||||
Given Logging in using web as "user0"
|
||||
When logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
|
@ -66,10 +98,10 @@ Feature: avatar
|
|||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#FF0000" color
|
||||
|
||||
Scenario: set user avatar from internal path
|
||||
Given user "user0" uploads file "data/coloured-pattern.png" to "/internal-coloured-pattern.png"
|
||||
Scenario: set non-square user avatar from internal path
|
||||
Given user "user0" uploads file "data/coloured-pattern-non-square.png" to "/internal-coloured-pattern-non-square.png"
|
||||
And Logging in using web as "user0"
|
||||
When logged in user posts temporary avatar from internal path "internal-coloured-pattern.png"
|
||||
When logged in user posts temporary avatar from internal path "internal-coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 704 |
|
||||
| y | 320 |
|
||||
|
@ -91,7 +123,7 @@ Feature: avatar
|
|||
|
||||
Scenario: cropped user avatar needs to be squared
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern.png"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
When logged in user crops temporary avatar with 400
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
|
@ -102,7 +134,7 @@ Feature: avatar
|
|||
|
||||
Scenario: delete user avatar
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern.png"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
|
@ -138,7 +170,7 @@ Feature: avatar
|
|||
|
||||
Scenario: get user avatar with a larger size than the original one
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern.png"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
|
@ -153,7 +185,7 @@ Feature: avatar
|
|||
|
||||
Scenario: get user avatar with a smaller size than the original one
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern.png"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
|
|
|
@ -174,10 +174,19 @@ trait Avatar {
|
|||
public function lastAvatarIsASquareOfSize(string $size) {
|
||||
[$width, $height] = getimagesizefromstring($this->lastAvatar);
|
||||
|
||||
Assert::assertEquals($width, $height, 'Avatar is not a square');
|
||||
Assert::assertEquals($width, $height, 'Expected avatar to be a square');
|
||||
Assert::assertEquals($size, $width);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then last avatar is not a square
|
||||
*/
|
||||
public function lastAvatarIsNotASquare() {
|
||||
[$width, $height] = getimagesizefromstring($this->lastAvatar);
|
||||
|
||||
Assert::assertNotEquals($width, $height, 'Expected avatar to not be a square');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then last avatar is not a single color
|
||||
*/
|
||||
|
|
|
@ -215,6 +215,19 @@ class AvatarController extends Controller {
|
|||
);
|
||||
}
|
||||
|
||||
if ($image->width() === $image->height()) {
|
||||
try {
|
||||
$avatar = $this->avatarManager->getAvatar($this->userId);
|
||||
$avatar->set($image);
|
||||
// Clean up
|
||||
$this->cache->remove('tmpAvatar');
|
||||
return new JSONResponse(['status' => 'success']);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'core']);
|
||||
return new JSONResponse(['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache->set('tmpAvatar', $image->data(), 7200);
|
||||
return new JSONResponse(
|
||||
['data' => 'notsquare'],
|
||||
|
|
|
@ -42,8 +42,6 @@ import './Polyfill/tooltip'
|
|||
import ClipboardJS from 'clipboard'
|
||||
import { dav } from 'davclient.js'
|
||||
import Handlebars from 'handlebars'
|
||||
import '@nextcloud/jcrop/js/jquery.Jcrop'
|
||||
import '@nextcloud/jcrop/css/jquery.Jcrop.css'
|
||||
import md5 from 'blueimp-md5'
|
||||
import moment from 'moment'
|
||||
import 'select2'
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -16,6 +16,16 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Cropper.js v1.5.12
|
||||
* https://fengyuanchen.github.io/cropperjs
|
||||
*
|
||||
* Copyright 2015-present Chen Fengyuan
|
||||
* Released under the MIT license
|
||||
*
|
||||
* Date: 2021-06-12T08:00:17.411Z
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Determine if an object is a Buffer
|
||||
*
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -12,13 +12,12 @@
|
|||
"@chenfengyuan/vue-qrcode": "^1.0.2",
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
"@nextcloud/axios": "^1.10.0",
|
||||
"@nextcloud/calendar-availability-vue": "^0.5.0-beta.1",
|
||||
"@nextcloud/calendar-availability-vue": "^0.5.0-beta.2",
|
||||
"@nextcloud/capabilities": "^1.0.4",
|
||||
"@nextcloud/dialogs": "^3.1.4",
|
||||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.1",
|
||||
"@nextcloud/jcrop": "^0.10.0",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/logger": "^2.1.0",
|
||||
"@nextcloud/moment": "^1.2.0",
|
||||
|
@ -71,6 +70,7 @@
|
|||
"vue": "^2.7.10",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-cropperjs": "^4.2.0",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-localstorage": "^0.6.2",
|
||||
"vue-material-design-icons": "^5.0.0",
|
||||
|
@ -3137,19 +3137,6 @@
|
|||
"core-js": "^3.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@nextcloud/jcrop": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/jcrop/-/jcrop-0.10.0.tgz",
|
||||
"integrity": "sha512-8grlksc0gI739aBbTMVtP0wbwH5V8qiAgY+qsr+7dyTIshiDJHmhwvnUT9aOLNrLMuvvqAf4/prCLh/Xa/4Xfg==",
|
||||
"deprecated": "This software is not maintained anymore",
|
||||
"dependencies": {
|
||||
"jquery": "~3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nextcloud/l10n": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.4.1.tgz",
|
||||
|
@ -12101,6 +12088,11 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cropperjs": {
|
||||
"version": "1.5.12",
|
||||
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.12.tgz",
|
||||
"integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
|
@ -30871,6 +30863,14 @@
|
|||
"tinycolor2": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-cropperjs": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-4.2.0.tgz",
|
||||
"integrity": "sha512-dvwCBtjGMiznkNIK2GFd1SQm1x+wmtWg4g4t+NrJSPj/fpHnubXxAUOIvY7lMFeR2lawRLsigCaGZrcXCzuTKA==",
|
||||
"dependencies": {
|
||||
"cropperjs": "^1.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-eslint-parser": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz",
|
||||
|
@ -34378,14 +34378,6 @@
|
|||
"core-js": "^3.6.4"
|
||||
}
|
||||
},
|
||||
"@nextcloud/jcrop": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/jcrop/-/jcrop-0.10.0.tgz",
|
||||
"integrity": "sha512-8grlksc0gI739aBbTMVtP0wbwH5V8qiAgY+qsr+7dyTIshiDJHmhwvnUT9aOLNrLMuvvqAf4/prCLh/Xa/4Xfg==",
|
||||
"requires": {
|
||||
"jquery": "~3"
|
||||
}
|
||||
},
|
||||
"@nextcloud/l10n": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.4.1.tgz",
|
||||
|
@ -41659,6 +41651,11 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"cropperjs": {
|
||||
"version": "1.5.12",
|
||||
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.12.tgz",
|
||||
"integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
|
||||
},
|
||||
"cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
|
@ -56282,6 +56279,14 @@
|
|||
"tinycolor2": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"vue-cropperjs": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-4.2.0.tgz",
|
||||
"integrity": "sha512-dvwCBtjGMiznkNIK2GFd1SQm1x+wmtWg4g4t+NrJSPj/fpHnubXxAUOIvY7lMFeR2lawRLsigCaGZrcXCzuTKA==",
|
||||
"requires": {
|
||||
"cropperjs": "^1.5.6"
|
||||
}
|
||||
},
|
||||
"vue-eslint-parser": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz",
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
"@nextcloud/event-bus": "^2.1.1",
|
||||
"@nextcloud/files": "^2.1.0",
|
||||
"@nextcloud/initial-state": "^1.2.1",
|
||||
"@nextcloud/jcrop": "^0.10.0",
|
||||
"@nextcloud/l10n": "^1.4.1",
|
||||
"@nextcloud/logger": "^2.1.0",
|
||||
"@nextcloud/moment": "^1.2.0",
|
||||
|
@ -91,6 +90,7 @@
|
|||
"vue": "^2.7.10",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-cropperjs": "^4.2.0",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-localstorage": "^0.6.2",
|
||||
"vue-material-design-icons": "^5.0.0",
|
||||
|
|
Загрузка…
Ссылка в новой задаче