mirror of
https://github.com/documize/community.git
synced 2025-08-02 20:15:26 +02:00
Content liking
New per space option that allows users to like/dislike content. The prompt is configurable per space, e.g. "Was this useful?". Enterprise edition gets new Votes report providing insights into most/least liked content.
This commit is contained in:
parent
6c8f23792c
commit
22b6674edb
23 changed files with 1031 additions and 694 deletions
|
@ -40,6 +40,7 @@ export default Component.extend(TooltipMixin, {
|
|||
return this.get('blocks.length') > 0;
|
||||
}),
|
||||
mousetrap: null,
|
||||
voteThanks: false,
|
||||
|
||||
didRender() {
|
||||
this._super(...arguments);
|
||||
|
@ -77,7 +78,7 @@ export default Component.extend(TooltipMixin, {
|
|||
let mousetrap = this.get('mousetrap');
|
||||
if (is.not.null(mousetrap)) {
|
||||
mousetrap.unbind('esc');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
contentLinkHandler() {
|
||||
|
@ -229,7 +230,7 @@ export default Component.extend(TooltipMixin, {
|
|||
// let cb2 = this.get('onSavePage');
|
||||
// cb2(page, meta);
|
||||
this.attrs.onSavePage(page, meta); // eslint-disable-line ember/no-attrs-in-components
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -351,6 +352,11 @@ export default Component.extend(TooltipMixin, {
|
|||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
onVote(vote) {
|
||||
this.get('documentService').vote(this.get('document.id'), vote);
|
||||
this.set('voteThanks', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
import $ from 'jquery';
|
||||
import { computed } from '@ember/object';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { A } from '@ember/array';
|
||||
import { inject as service } from '@ember/service';
|
||||
import constants from '../../utils/constants';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
import ModalMixin from '../../mixins/modal';
|
||||
import AuthMixin from '../../mixins/auth';
|
||||
|
@ -48,6 +50,11 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
|
||||
dropzone: null,
|
||||
|
||||
spaceTypeOptions: A([]),
|
||||
spaceType: constants.FolderType.Private,
|
||||
likes: '',
|
||||
allowLikes: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.importedDocuments = [];
|
||||
|
@ -77,6 +84,16 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
if (this.get('inviteMessage').length === 0) {
|
||||
this.set('inviteMessage', this.getDefaultInvitationMessage());
|
||||
}
|
||||
|
||||
let spaceTypeOptions = A([]);
|
||||
spaceTypeOptions.pushObject({id: constants.FolderType.Private, label: 'Private - viewable only by me'});
|
||||
spaceTypeOptions.pushObject({id: constants.FolderType.Protected, label: 'Protected - access is restricted to selected users'});
|
||||
spaceTypeOptions.pushObject({id: constants.FolderType.Public, label: 'Public - can be seen by everyone'});
|
||||
this.set('spaceTypeOptions', spaceTypeOptions);
|
||||
this.set('spaceType', spaceTypeOptions.findBy('id', folder.get('folderType')));
|
||||
|
||||
this.set('likes', folder.get('likes'));
|
||||
this.set('allowLikes', folder.get('allowLikes'));
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
|
@ -357,6 +374,31 @@ export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
|
|||
|
||||
let slug = stringUtil.makeSlug(template.get('title'));
|
||||
this.get('router').transitionTo('document', this.get('space.id'), this.get('space.slug'), id, slug);
|
||||
},
|
||||
|
||||
onSetSpaceType(t) {
|
||||
this.set('spaceType', t);
|
||||
},
|
||||
|
||||
onSetLikes(l) {
|
||||
this.set('allowLikes', l);
|
||||
schedule('afterRender', () => {
|
||||
if (l) $('#space-likes-prompt').focus();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
onSpaceSettings() {
|
||||
let space = this.get('space');
|
||||
space.set('folderType', this.get('spaceType.id'));
|
||||
|
||||
let allowLikes = this.get('allowLikes');
|
||||
space.set('likes', allowLikes ? this.get('likes') : '');
|
||||
|
||||
this.get('spaceService').save(space).then(() => {
|
||||
});
|
||||
|
||||
this.modalClose("#space-settings-modal");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['col', 'col-sm-4']
|
||||
classNames: ['col', 'col-sm-5']
|
||||
});
|
||||
|
|
|
@ -14,6 +14,6 @@ import Component from '@ember/component';
|
|||
|
||||
export default Component.extend({
|
||||
appMeta: service(),
|
||||
classNames: ['col', 'col-sm-8'],
|
||||
classNames: ['col', 'col-sm-7'],
|
||||
selectItem: '',
|
||||
});
|
||||
|
|
|
@ -20,6 +20,11 @@ export default Model.extend({
|
|||
orgId: attr('string'),
|
||||
userId: attr('string'),
|
||||
folderType: attr('number', { defaultValue: 2 }),
|
||||
likes: attr('string'),
|
||||
|
||||
allowLikes: computed('likes', function () {
|
||||
return is.not.empty(this.get('likes')) && is.not.undefined(this.get('likes'));
|
||||
}),
|
||||
|
||||
slug: computed('name', function () {
|
||||
return stringUtil.makeSlug(this.get('name'));
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
|
||||
import { set } from '@ember/object';
|
||||
import { A } from '@ember/array';
|
||||
import stringUtil from '../utils/string';
|
||||
import ArrayProxy from '@ember/array/proxy';
|
||||
import Service, { inject as service } from '@ember/service';
|
||||
|
||||
export default Service.extend({
|
||||
sessionService: service('session'),
|
||||
storageSvc: service('localStorage'),
|
||||
folderService: service('folder'),
|
||||
ajax: service(),
|
||||
store: service(),
|
||||
|
@ -318,6 +320,41 @@ export default Service.extend({
|
|||
});
|
||||
},
|
||||
|
||||
//**************************************************
|
||||
// Voting / Liking
|
||||
//**************************************************
|
||||
|
||||
// Vote records content vote from user.
|
||||
// Anonymous users can vote to and are assigned temp id that is stored
|
||||
// client-side in browser local storage.
|
||||
vote(documentId, vote) {
|
||||
let userId = '';
|
||||
|
||||
if (this.get('sessionService.authenticated')) {
|
||||
userId = this.get('sessionService.user.id');
|
||||
} else {
|
||||
let id = this.get('storageSvc').getSessionItem('anonId');
|
||||
|
||||
if (is.not.null(id) && is.not.undefined(id) && id.length === 16) {
|
||||
userId = id;
|
||||
} else {
|
||||
userId = stringUtil.anonUserId();
|
||||
}
|
||||
|
||||
this.get('storageSvc').storeSessionItem('anonId', userId);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
userId: userId,
|
||||
vote: vote
|
||||
};
|
||||
|
||||
return this.get('ajax').post(`public/document/${documentId}/vote`, {
|
||||
data: JSON.stringify(payload),
|
||||
contentType: 'json'
|
||||
});
|
||||
},
|
||||
|
||||
//**************************************************
|
||||
// Fetch bulk data
|
||||
//**************************************************
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
|
@ -27,4 +27,4 @@ export default Service.extend({
|
|||
clearAll() {
|
||||
localStorage.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,4 +7,5 @@
|
|||
@import "view-attachment.scss";
|
||||
@import "view-activity.scss";
|
||||
@import "view-revision.scss";
|
||||
@import "wysiwyg.scss";
|
||||
@import "vote-likes.scss";
|
||||
@import "wysiwyg.scss";
|
||||
|
|
25
gui/app/styles/view/document/vote-likes.scss
Normal file
25
gui/app/styles/view/document/vote-likes.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
.vote-box {
|
||||
margin: 50px 0;
|
||||
padding: 30px 50px;
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
border: 1px dotted $color-border;
|
||||
background: $color-off-white;
|
||||
@include border-radius(3px);
|
||||
|
||||
> .prompt {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: $color-dark;
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
margin: 30px 0 0 0;
|
||||
}
|
||||
|
||||
> .ack {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: $color-green;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
<div class="section-divider" />
|
||||
{{/if}}
|
||||
|
||||
{{document/document-page document=document folder=folder page=item.page meta=item.meta pending=item.pending
|
||||
{{document/document-page document=document folder=folder page=item.page meta=item.meta pending=item.pending
|
||||
permissions=permissions toEdit=toEdit roles=roles blocks=blocks
|
||||
onSavePage=(action 'onSavePage') onSavePageAsBlock=(action 'onSavePageAsBlock')
|
||||
onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage') refresh=(action refresh)}}
|
||||
|
@ -23,6 +23,24 @@
|
|||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if folder.allowLikes}}
|
||||
<div class=" d-flex justify-content-center">
|
||||
<div class="vote-box">
|
||||
{{#unless voteThanks}}
|
||||
<div class="prompt">
|
||||
{{folder.likes}}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button type="button" class="btn btn-outline-success font-weight-bold" {{action 'onVote' 1}}>Yes, thanks!</button>
|
||||
<button type="button" class="btn btn-outline-secondary font-weight-bold" {{action 'onVote' 2}}>Not really</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="ack">Thanks for the feedback!</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless hasPages}}
|
||||
|
|
|
@ -105,6 +105,47 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (or permissions.spaceOwner permissions.spaceManage)}}
|
||||
<div id="space-settings-button" class="button-icon-gray align-middle" data-toggle="tooltip" data-placement="top" title="Settings">
|
||||
<i class="material-icons" data-toggle="modal" data-target="#space-settings-modal" data-backdrop="static">settings</i>
|
||||
</div>
|
||||
<div class="button-icon-gap" />
|
||||
<div id="space-settings-modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">Space Settings</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label>Space Type</label>
|
||||
{{ui-select id="group-dropdown" content=spaceTypeOptions optionValuePath="id" optionLabelPath="label" selection=spaceType action=(action 'onSetSpaceType')}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Content Liking</label>
|
||||
{{#if allowLikes}}
|
||||
{{input type='text' id="space-likes-prompt" class="form-control" placeholder="Did this help you?" value=likes}}
|
||||
<small class="form-text text-muted">Specify the prompt, e.g. Did this help you? Was this helpful? Did you find what you needed?</small>
|
||||
<div class="mt-4">
|
||||
<button type="button" class="btn btn-secondary" onclick={{action 'onSetLikes' false}}>Do not allow users to like content</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-success" onclick={{action 'onSetLikes' true}}>Allow users to like content</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-success" onclick={{action 'onSpaceSettings'}}>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if pinState.isPinned}}
|
||||
<div id="space-pin-button" class="button-icon-gold align-middle" data-toggle="tooltip" data-placement="top" title="Remove favorite" {{action 'onUnpin'}}>
|
||||
<i class="material-icons">star</i>
|
||||
|
|
|
@ -36,9 +36,14 @@ function endsWith(str, suffix) {
|
|||
return str.indexOf(suffix, str.length - suffix.length) !== -1;
|
||||
}
|
||||
|
||||
function anonUserId() {
|
||||
return 'anon_' + makeId(11);
|
||||
}
|
||||
|
||||
export default {
|
||||
makeSlug,
|
||||
makeId,
|
||||
endsWith
|
||||
endsWith,
|
||||
anonUserId
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "documize",
|
||||
"version": "1.61.0",
|
||||
"version": "1.62.0",
|
||||
"description": "The Document IDE",
|
||||
"private": true,
|
||||
"repository": "",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue