1
0
Fork 0
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:
Harvey Kandola 2018-04-13 11:01:36 +01:00
parent 6c8f23792c
commit 22b6674edb
23 changed files with 1031 additions and 694 deletions

View file

@ -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);
}
}
});

View file

@ -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");
}
}
});

View file

@ -12,5 +12,5 @@
import Component from '@ember/component';
export default Component.extend({
classNames: ['col', 'col-sm-4']
classNames: ['col', 'col-sm-5']
});

View file

@ -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: '',
});

View file

@ -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'));

View file

@ -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
//**************************************************

View file

@ -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();
}
});
});

View file

@ -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";

View 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;
}
}

View file

@ -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>&nbsp;&nbsp;
<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}}

View file

@ -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>

View file

@ -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
};

View file

@ -1,6 +1,6 @@
{
"name": "documize",
"version": "1.61.0",
"version": "1.62.0",
"description": "The Document IDE",
"private": true,
"repository": "",