mirror of
https://github.com/documize/community.git
synced 2025-07-21 14:19:43 +02:00
Merge pull request #89 from documize/pdf-print
Print view enhancements, bug fixes
This commit is contained in:
commit
e97b992fa9
21 changed files with 1070 additions and 800 deletions
|
@ -49,7 +49,11 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
|
|
||||||
this.set('document.name', this.get('docName'));
|
this.set('document.name', this.get('docName'));
|
||||||
this.set('document.excerpt', this.get('docExcerpt'));
|
this.set('document.excerpt', this.get('docExcerpt'));
|
||||||
|
|
||||||
this.showNotification('Saved');
|
this.showNotification('Saved');
|
||||||
|
this.get('browser').setTitle(this.get('document.name'));
|
||||||
|
this.get('browser').setMetaDescription(this.get('document.excerpt'));
|
||||||
|
|
||||||
this.get('documentService').save(this.get('document'));
|
this.get('documentService').save(this.get('document'));
|
||||||
|
|
||||||
this.set('editMode', false);
|
this.set('editMode', false);
|
||||||
|
|
|
@ -13,64 +13,12 @@ import Ember from 'ember';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
documentService: Ember.inject.service('document'),
|
documentService: Ember.inject.service('document'),
|
||||||
appMeta: Ember.inject.service(),
|
|
||||||
sortedItems: [],
|
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
||||||
this.get('documentService').getMeta(this.get('document.id')).then((activity) => {
|
this.get('documentService').getActivity(this.get('document.id')).then((activity) => {
|
||||||
this.set('activity', activity);
|
this.set('activity', activity);
|
||||||
|
|
||||||
let editors = this.get('activity.editors');
|
|
||||||
let viewers = this.get('activity.viewers');
|
|
||||||
let pages = this.get('pages');
|
|
||||||
let sorted = [];
|
|
||||||
|
|
||||||
if (is.null(editors)) {
|
|
||||||
editors = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is.null(viewers)) {
|
|
||||||
viewers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
viewers.forEach((item) => {
|
|
||||||
Ember.set(item, 'changeLabel', "viewed");
|
|
||||||
Ember.set(item, "viewed", true);
|
|
||||||
sorted.pushObject({ date: item.created, item: item });
|
|
||||||
});
|
|
||||||
|
|
||||||
editors.forEach(function (item) {
|
|
||||||
Ember.set(item, "added", item.action === "add-page");
|
|
||||||
Ember.set(item, "changed", item.action === "update-page");
|
|
||||||
Ember.set(item, "deleted", item.action === "remove-page");
|
|
||||||
|
|
||||||
let page = pages.findBy('id', item.pageId);
|
|
||||||
let title = "";
|
|
||||||
|
|
||||||
if (item.deleted || is.undefined(page)) {
|
|
||||||
title = "removed section";
|
|
||||||
} else {
|
|
||||||
if (item.added) {
|
|
||||||
title = "added " + page.get('title');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.changed) {
|
|
||||||
title = "changed " + page.get('title');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ember.set(item, 'changeLabel', title);
|
|
||||||
|
|
||||||
let exists = sorted.findBy('item.pageId', item.pageId);
|
|
||||||
|
|
||||||
if (is.undefined(exists)) {
|
|
||||||
sorted.pushObject({ date: item.created, item: item });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set('sortedItems', _.sortBy(sorted, 'date').reverse());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -123,7 +123,7 @@ export default Ember.Component.extend(NotifierMixin, {
|
||||||
this.audit.record('used-saved-template');
|
this.audit.record('used-saved-template');
|
||||||
this.send("showNotification", "Creating");
|
this.send("showNotification", "Creating");
|
||||||
|
|
||||||
this.get('templateService').importSavedTemplate(this.folder.get('id'), template.id).then((document) => {
|
this.get('templateService').importSavedTemplate(this.folder.get('id'), template.id, this.get('newDocumentName')).then((document) => {
|
||||||
this.get('router').transitionTo('document', this.get('folder.id'), this.get('folder.slug'), document.get('id'), document.get('slug'));
|
this.get('router').transitionTo('document', this.get('folder.id'), this.get('folder.slug'), document.get('id'), document.get('slug'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
103
app/app/models/document-activity.js
Normal file
103
app/app/models/document-activity.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||||
|
//
|
||||||
|
// 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>.
|
||||||
|
//
|
||||||
|
// https://documize.com
|
||||||
|
|
||||||
|
import Model from 'ember-data/model';
|
||||||
|
import attr from 'ember-data/attr';
|
||||||
|
import Ember from 'ember';
|
||||||
|
import constants from '../utils/constants';
|
||||||
|
|
||||||
|
export default Model.extend({
|
||||||
|
orgId: attr('string'),
|
||||||
|
folderId: attr('string'),
|
||||||
|
documentId: attr('string'),
|
||||||
|
userId: attr('string'),
|
||||||
|
firstname: attr('string'),
|
||||||
|
lastname: attr('string'),
|
||||||
|
activityType: attr('number'),
|
||||||
|
created: attr(),
|
||||||
|
|
||||||
|
activityLabel: Ember.computed('activityType', function() {
|
||||||
|
let label = '';
|
||||||
|
|
||||||
|
switch (this.get('activityType')) {
|
||||||
|
case constants.UserActivityType.Created:
|
||||||
|
label = 'Added';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Read:
|
||||||
|
label = 'Viewed';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Edited:
|
||||||
|
label = 'Edited';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Deleted:
|
||||||
|
label = 'Deleted';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Archived:
|
||||||
|
label = 'Archived';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Approved:
|
||||||
|
label = 'Approved';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Reverted:
|
||||||
|
label = 'Reverted';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.PublishedTemplate:
|
||||||
|
label = 'Published Template';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.PublishedBlock:
|
||||||
|
label = 'Published Block';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}),
|
||||||
|
|
||||||
|
activityColor: Ember.computed('activityType', function() {
|
||||||
|
let color = '';
|
||||||
|
|
||||||
|
switch (this.get('activityType')) {
|
||||||
|
case constants.UserActivityType.Created:
|
||||||
|
color = 'color-blue';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Read:
|
||||||
|
color = 'color-black';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Edited:
|
||||||
|
color = 'color-green';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Deleted:
|
||||||
|
color = 'color-red';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Archived:
|
||||||
|
color = 'color-gray';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Approved:
|
||||||
|
color = 'color-green';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.Reverted:
|
||||||
|
color = 'color-red';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.PublishedTemplate:
|
||||||
|
color = 'color-blue';
|
||||||
|
break;
|
||||||
|
case constants.UserActivityType.PublishedBlock:
|
||||||
|
color = 'color-blue';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
|
@ -23,6 +23,7 @@ export default Ember.Service.extend({
|
||||||
ajax: service(),
|
ajax: service(),
|
||||||
localStorage: service(),
|
localStorage: service(),
|
||||||
kcAuth: service(),
|
kcAuth: service(),
|
||||||
|
apiHost: `${config.apiHost}`,
|
||||||
endpoint: `${config.apiHost}/${config.apiNamespace}`,
|
endpoint: `${config.apiHost}/${config.apiNamespace}`,
|
||||||
orgId: '',
|
orgId: '',
|
||||||
title: '',
|
title: '',
|
||||||
|
|
|
@ -208,12 +208,19 @@ export default Ember.Service.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// document meta referes to number of views, edits, approvals, etc.
|
// document meta referes to number of views, edits, approvals, etc.
|
||||||
getMeta(documentId) {
|
getActivity(documentId) {
|
||||||
return this.get('ajax').request(`documents/${documentId}/meta`, {
|
return this.get('ajax').request(`documents/${documentId}/activity`, {
|
||||||
method: "GET"
|
method: "GET"
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
return response;
|
let data = [];
|
||||||
|
data = response.map((obj) => {
|
||||||
|
let data = this.get('store').normalize('documentActivity', obj);
|
||||||
|
return this.get('store').push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
return [];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,13 @@ export default Ember.Service.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
importSavedTemplate: function (folderId, templateId) {
|
importSavedTemplate: function (folderId, templateId, docName) {
|
||||||
let url = `templates/${templateId}/folder/${folderId}?type=saved`;
|
let url = `templates/${templateId}/folder/${folderId}?type=saved`;
|
||||||
|
|
||||||
return this.get('ajax').post(url).then((doc) => {
|
return this.get('ajax').request(url, {
|
||||||
|
method: 'POST',
|
||||||
|
data: docName
|
||||||
|
}).then((doc) => {
|
||||||
let data = this.get('store').normalize('document', doc);
|
let data = this.get('store').normalize('document', doc);
|
||||||
return this.get('store').push(data);
|
return this.get('store').push(data);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,22 +9,57 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
@media print {
|
@media print {
|
||||||
.non-printable,
|
@page {
|
||||||
.zone-navigation,
|
size: 8.5in 11in;
|
||||||
.zone-sidebar,
|
margin: 20mm;
|
||||||
#sidebar-wrapper,
|
|
||||||
.document-heading,
|
@top-right {
|
||||||
.edit-document-heading,
|
font-size: 12px;
|
||||||
#sidebar-toggle,
|
content: string(doctitle);
|
||||||
.back-to-space,
|
color: $color-off-black;
|
||||||
.start-section,
|
|
||||||
.new-section-wizard {
|
|
||||||
display: none !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-content-wrapper, #wrapper {
|
@bottom-left {
|
||||||
padding: 0 !important;
|
font-size: 12px;
|
||||||
|
content: 'Exported from Documize';
|
||||||
|
color: $color-off-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bottom-right {
|
||||||
|
content: counter(page);
|
||||||
|
font-size: 12px;
|
||||||
|
color: $color-off-black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background-color: transparent !important;
|
||||||
|
max-width: none !important;
|
||||||
|
float: none !important;
|
||||||
|
position: relative !important;
|
||||||
|
height: initial !important;
|
||||||
|
min-height: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.non-printable,
|
||||||
|
.zone-navigation,
|
||||||
|
#sidebar-wrapper,
|
||||||
|
.sidebar-wrapper,
|
||||||
|
.sidebar-common,
|
||||||
|
.sidebar-toolbar,
|
||||||
|
.sidebar-panel,
|
||||||
|
.edit-document-heading,
|
||||||
|
.back-to-space,
|
||||||
|
.start-section,
|
||||||
|
.start-button,
|
||||||
|
.is-a-tab,
|
||||||
|
.new-section-wizard {
|
||||||
|
float: none !important;
|
||||||
|
display: none !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 0 !important;
|
||||||
|
z-index: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.non-printable-message,
|
.non-printable-message,
|
||||||
|
@ -32,9 +67,86 @@
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-a-tab, .is-a-page {
|
#wrapper {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
box-shadow: none !important;
|
position: relative !important;
|
||||||
|
box-shadow: initial !important;
|
||||||
|
border-radius: initial !important;
|
||||||
|
float: none !important;
|
||||||
|
overflow-x: initial !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
position: initial !important;
|
||||||
|
box-shadow: initial !important;
|
||||||
|
border-radius: initial !important;
|
||||||
|
width: initial !important;
|
||||||
|
max-width: initial !important;
|
||||||
|
float: initial !important;
|
||||||
|
overflow-x: initial !important;
|
||||||
|
|
||||||
|
#page-content-wrapper {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
position: initial !important;
|
||||||
|
box-shadow: initial !important;
|
||||||
|
border-radius: initial !important;
|
||||||
|
width: initial !important;
|
||||||
|
max-width: initial !important;
|
||||||
|
float: initial !important;
|
||||||
|
overflow-x: initial !important;
|
||||||
|
|
||||||
|
#zone-document-content {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
position: initial !important;
|
||||||
|
box-shadow: initial !important;
|
||||||
|
border-radius: initial !important;
|
||||||
|
width: initial !important;
|
||||||
|
max-width: initial !important;
|
||||||
|
float: initial !important;
|
||||||
|
overflow-x: initial !important;
|
||||||
|
|
||||||
|
.document-heading {
|
||||||
|
.doc-title {
|
||||||
|
margin: 0 0 10px 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-view {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
position: initial !important;
|
||||||
|
box-shadow: initial !important;
|
||||||
|
border-radius: initial !important;
|
||||||
|
width: initial !important;
|
||||||
|
max-width: initial !important;
|
||||||
|
float: initial !important;
|
||||||
|
overflow-x: initial !important;
|
||||||
|
|
||||||
|
.is-a-page /*, .is-a-tab */ {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 30px 0 20px 0 !important;
|
||||||
|
position: initial !important;
|
||||||
|
box-shadow: initial !important;
|
||||||
|
border-radius: initial !important;
|
||||||
|
width: initial !important;
|
||||||
|
max-width: initial !important;
|
||||||
|
float: initial !important;
|
||||||
|
overflow-x: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wysiwyg {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 25px;
|
||||||
|
color: $color-black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ $sidebar-width: 400px;
|
||||||
-moz-transition: all 0.5s ease;
|
-moz-transition: all 0.5s ease;
|
||||||
-o-transition: all 0.5s ease;
|
-o-transition: all 0.5s ease;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
background-color: $color-off-white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar-wrapper {
|
#sidebar-wrapper {
|
||||||
|
@ -20,7 +19,6 @@ $sidebar-width: 400px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: -$sidebar-width;
|
margin-left: -$sidebar-width;
|
||||||
border-right: 1px solid $color-stroke;
|
border-right: 1px solid $color-stroke;
|
||||||
background: $color-off-white;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
-webkit-transition: all 0.5s ease;
|
-webkit-transition: all 0.5s ease;
|
||||||
-moz-transition: all 0.5s ease;
|
-moz-transition: all 0.5s ease;
|
||||||
|
@ -28,10 +26,6 @@ $sidebar-width: 400px;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper.toggled #sidebar-wrapper {
|
|
||||||
width: $sidebar-width;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
@ -42,7 +36,8 @@ $sidebar-width: 400px;
|
||||||
|
|
||||||
#page-content-wrapper {
|
#page-content-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
position: relative;
|
||||||
|
// position: absolute;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
|
@ -54,42 +49,19 @@ $sidebar-width: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper.toggled #page-content-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
margin-right: -$sidebar-width;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(min-width:768px) {
|
@media(min-width:768px) {
|
||||||
#wrapper {
|
#wrapper {
|
||||||
padding-left: $sidebar-width;
|
padding-left: $sidebar-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper.toggled {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar-wrapper {
|
#sidebar-wrapper {
|
||||||
width: $sidebar-width;
|
width: $sidebar-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper.toggled #sidebar-wrapper {
|
// #page-content-wrapper {
|
||||||
width: 0;
|
// padding: 30px;
|
||||||
|
// position: relative;
|
||||||
.sidebar-toolbar {
|
// }
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#page-content-wrapper {
|
|
||||||
padding: 30px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wrapper.toggled #page-content-wrapper {
|
|
||||||
position: relative;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-toolbar {
|
.sidebar-toolbar {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div id="page-{{ page.id }}" class="is-a-tab wysiwyg {{if expanded 'tab-max' 'tab-min'}}" data-id="{{ page.id }}" data-type="{{ page.contentType }}">
|
<div id="page-{{ page.id }}" class="is-a-tab wysiwyg non-printable {{if expanded 'tab-max' 'tab-min'}}" data-id="{{ page.id }}" data-type="{{ page.contentType }}">
|
||||||
{{document/tab-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor
|
{{document/tab-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor
|
||||||
onExpand=(action 'onExpand') onSavePageAsBlock=(action 'onSavePageAsBlock') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage')}}
|
onExpand=(action 'onExpand') onSavePageAsBlock=(action 'onSavePageAsBlock') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onDeletePage')}}
|
||||||
{{#if expanded}}
|
{{#if expanded}}
|
||||||
|
|
|
@ -2,26 +2,13 @@
|
||||||
<div class="title">Activity</div>
|
<div class="title">Activity</div>
|
||||||
<div class="document-sidebar-view-activity">
|
<div class="document-sidebar-view-activity">
|
||||||
<ul class="items">
|
<ul class="items">
|
||||||
{{#each sortedItems as |e|}}
|
{{#each activity as |a|}}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<div class="avatar-box">
|
<div class="avatar-box">
|
||||||
<div class="avatar">{{user-initials e.item.firstname e.item.lastname}}</div>
|
<div class="avatar">{{user-initials a.firstname a.lastname}}</div>
|
||||||
</div>
|
|
||||||
<div class="name">{{e.item.firstname}} {{e.item.lastname}}, {{time-ago e.date}}</div>
|
|
||||||
<div class="detail">
|
|
||||||
{{#if e.item.deleted}}
|
|
||||||
<div class="deleted">{{e.item.changeLabel}}</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if e.item.changed}}
|
|
||||||
<div class="changed">{{e.item.changeLabel}}</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if e.item.added}}
|
|
||||||
<div class="added">{{e.item.changeLabel}}</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if e.item.viewed}}
|
|
||||||
<div class="viewed">{{e.item.changeLabel}}</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="name">{{a.firstname}} {{a.lastname}}</div>
|
||||||
|
<div class="detail {{a.activityColor}}">{{a.activityLabel}}, {{time-ago a.created}}</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -26,5 +26,18 @@ export default {
|
||||||
Feedback: 2,
|
Feedback: 2,
|
||||||
Contribute: 3,
|
Contribute: 3,
|
||||||
Approve: 4
|
Approve: 4
|
||||||
|
},
|
||||||
|
|
||||||
|
UserActivityType: {
|
||||||
|
Created: 1,
|
||||||
|
Read: 2,
|
||||||
|
Edited: 3,
|
||||||
|
Deleted: 4,
|
||||||
|
Archived: 5,
|
||||||
|
Approved: 6,
|
||||||
|
Reverted: 7,
|
||||||
|
PublishedTemplate: 8,
|
||||||
|
PublishedBlock: 9,
|
||||||
|
Feedback: 10
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
20
app/vendor/bootstrap.css
vendored
20
app/vendor/bootstrap.css
vendored
|
@ -227,16 +227,16 @@ textarea {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
a {
|
/*a {
|
||||||
/*color: #337ab7;*/
|
color: #337ab7;
|
||||||
/*text-decoration: none;*/
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
/*a:hover,
|
a:hover,
|
||||||
a:focus {*/
|
a:focus {
|
||||||
/*color: #23527c;*/
|
color: #23527c;
|
||||||
/*text-decoration: underline;*/
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
/*a:focus {
|
a:focus {
|
||||||
outline: thin dotted;
|
outline: thin dotted;
|
||||||
outline: 5px auto -webkit-focus-ring-color;
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
|
@ -1155,7 +1155,7 @@ hr {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.visible-print {
|
/*.visible-print {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@media print {
|
@media print {
|
||||||
|
@ -1201,4 +1201,4 @@ hr {
|
||||||
.hidden-print {
|
.hidden-print {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
|
@ -227,6 +227,93 @@ func Authorize(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidToken finds and validates authentication token.
|
||||||
|
func ValidToken(r *http.Request) (context request.Context, valid bool) {
|
||||||
|
valid = false
|
||||||
|
|
||||||
|
token := findJWT(r)
|
||||||
|
hasToken := len(token) > 1
|
||||||
|
context, _, tokenErr := decodeJWT(token)
|
||||||
|
|
||||||
|
var org = entity.Organization{}
|
||||||
|
var err = errors.New("")
|
||||||
|
p := request.GetPersister(r)
|
||||||
|
|
||||||
|
// We always grab the org record regardless of token status.
|
||||||
|
// Why? If bad token we might be OK to alow anonymous access
|
||||||
|
// depending upon the domain in question.
|
||||||
|
if len(context.OrgID) == 0 {
|
||||||
|
org, err = p.GetOrganizationByDomain(request.GetRequestSubdomain(r))
|
||||||
|
} else {
|
||||||
|
org, err = p.GetOrganization(context.OrgID)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Subdomain = org.Domain
|
||||||
|
|
||||||
|
// Inability to find org record spells the end of this request.
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have bad auth token and the domain does not allow anon access
|
||||||
|
if !org.AllowAnonymousAccess && tokenErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := request.GetSubdomainFromHost(r)
|
||||||
|
domain2 := request.GetRequestSubdomain(r)
|
||||||
|
if org.Domain != domain && org.Domain != domain2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have bad auth token and the domain allows anon access
|
||||||
|
// then we generate guest context.
|
||||||
|
if org.AllowAnonymousAccess {
|
||||||
|
// So you have a bad token
|
||||||
|
if hasToken {
|
||||||
|
if tokenErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just grant anon user guest access
|
||||||
|
context.UserID = "0"
|
||||||
|
context.OrgID = org.RefID
|
||||||
|
context.Authenticated = false
|
||||||
|
context.Guest = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh context and persister
|
||||||
|
request.SetContext(r, context)
|
||||||
|
p = request.GetPersister(r)
|
||||||
|
|
||||||
|
context.AllowAnonymousAccess = org.AllowAnonymousAccess
|
||||||
|
context.OrgName = org.Title
|
||||||
|
context.Administrator = false
|
||||||
|
context.Editor = false
|
||||||
|
context.Global = false
|
||||||
|
|
||||||
|
// Fetch user permissions for this org
|
||||||
|
if context.Authenticated {
|
||||||
|
user, err := getSecuredUser(p, org.RefID, context.UserID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Administrator = user.Admin
|
||||||
|
context.Editor = user.Editor
|
||||||
|
context.Global = user.Global
|
||||||
|
}
|
||||||
|
|
||||||
|
request.SetContext(r, context)
|
||||||
|
p = request.GetPersister(r)
|
||||||
|
|
||||||
|
valid = context.Authenticated || org.AllowAnonymousAccess
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Certain assets/URL do not require authentication.
|
// Certain assets/URL do not require authentication.
|
||||||
// Just stops the log files being clogged up with failed auth errors.
|
// Just stops the log files being clogged up with failed auth errors.
|
||||||
func preAuthorizeStaticAssets(r *http.Request) bool {
|
func preAuthorizeStaticAssets(r *http.Request) bool {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/documize/community/core/log"
|
"github.com/documize/community/core/log"
|
||||||
"github.com/documize/community/core/utility"
|
"github.com/documize/community/core/utility"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/api/util"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -126,39 +127,25 @@ func GetDocument(w http.ResponseWriter, r *http.Request) {
|
||||||
writeSuccessBytes(w, json)
|
writeSuccessBytes(w, json)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDocumentMeta is an endpoint returning the metadata for a document.
|
// GetDocumentActivity is an endpoint returning the activity logs for specified document.
|
||||||
func GetDocumentMeta(w http.ResponseWriter, r *http.Request) {
|
func GetDocumentActivity(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "GetDocumentMeta"
|
method := "GetDocumentActivity"
|
||||||
p := request.GetPersister(r)
|
p := request.GetPersister(r)
|
||||||
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
id := params["documentID"]
|
|
||||||
|
|
||||||
|
id := params["documentID"]
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
writeMissingDataError(w, method, "documentID")
|
writeMissingDataError(w, method, "documentID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := p.GetDocumentMeta(id)
|
a, err := p.GetDocumentActivity(id)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
writeNotFoundError(w, method, id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
writeGeneralSQLError(w, method, err)
|
writeGeneralSQLError(w, method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json, err := json.Marshal(meta)
|
util.WriteJSON(w, a)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
writeJSONMarshalError(w, method, "document", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessBytes(w, json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDocumentLinks is an endpoint returning the links for a document.
|
// GetDocumentLinks is an endpoint returning the links for a document.
|
||||||
|
|
|
@ -16,6 +16,7 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/documize/community/core/api/entity"
|
"github.com/documize/community/core/api/entity"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PageSequenceRequestModel details a page ID and its sequence within the document.
|
// PageSequenceRequestModel details a page ID and its sequence within the document.
|
||||||
|
@ -66,3 +67,16 @@ type PageModel struct {
|
||||||
Page entity.Page `json:"page"`
|
Page entity.Page `json:"page"`
|
||||||
Meta entity.PageMeta `json:"meta"`
|
Meta entity.PageMeta `json:"meta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DocumentActivity represents an activity taken against a document.
|
||||||
|
type DocumentActivity struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
OrgID string `json:"orgId"`
|
||||||
|
LabelID string `json:"folderId"`
|
||||||
|
DocumentID string `json:"documentId"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
Firstname string `json:"firstname"`
|
||||||
|
Lastname string `json:"lastname"`
|
||||||
|
ActivityType int `json:"activityType"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
}
|
||||||
|
|
|
@ -155,7 +155,7 @@ func init() {
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, GetDocument))
|
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, GetDocument))
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, UpdateDocument))
|
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, UpdateDocument))
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, DeleteDocument))
|
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, DeleteDocument))
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/meta", []string{"GET", "OPTIONS"}, nil, GetDocumentMeta))
|
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/activity", []string{"GET", "OPTIONS"}, nil, GetDocumentActivity))
|
||||||
|
|
||||||
// Document Page
|
// Document Page
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, ChangeDocumentPageLevel))
|
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, ChangeDocumentPageLevel))
|
||||||
|
|
|
@ -33,9 +33,9 @@ var port, certFile, keyFile, forcePort2SSL string
|
||||||
var Product core.ProdInfo
|
var Product core.ProdInfo
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Product.Major = "0"
|
Product.Major = "1"
|
||||||
Product.Minor = "44"
|
Product.Minor = "0"
|
||||||
Product.Patch = "1"
|
Product.Patch = "0"
|
||||||
Product.Version = fmt.Sprintf("%s.%s.%s", Product.Major, Product.Minor, Product.Patch)
|
Product.Version = fmt.Sprintf("%s.%s.%s", Product.Major, Product.Minor, Product.Patch)
|
||||||
Product.Edition = "Community"
|
Product.Edition = "Community"
|
||||||
Product.Title = fmt.Sprintf("%s Edition", Product.Edition)
|
Product.Title = fmt.Sprintf("%s Edition", Product.Edition)
|
||||||
|
@ -175,7 +175,6 @@ func cors(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func metrics(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
func metrics(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
|
||||||
w.Header().Add("X-Documize-Version", Product.Version)
|
w.Header().Add("X-Documize-Version", Product.Version)
|
||||||
w.Header().Add("Cache-Control", "no-cache")
|
w.Header().Add("Cache-Control", "no-cache")
|
||||||
|
|
||||||
|
|
|
@ -315,27 +315,32 @@ func StartDocumentFromStockTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
func StartDocumentFromSavedTemplate(w http.ResponseWriter, r *http.Request) {
|
func StartDocumentFromSavedTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "StartDocumentFromSavedTemplate"
|
method := "StartDocumentFromSavedTemplate"
|
||||||
p := request.GetPersister(r)
|
p := request.GetPersister(r)
|
||||||
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
folderID := params["folderID"]
|
|
||||||
|
|
||||||
|
folderID := params["folderID"]
|
||||||
if len(folderID) == 0 {
|
if len(folderID) == 0 {
|
||||||
writeMissingDataError(w, method, "folderID")
|
writeMissingDataError(w, method, "folderID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templateID := params["templateID"]
|
templateID := params["templateID"]
|
||||||
|
|
||||||
// We are OK with zero valued template ID because it signals 'give me empty document'
|
|
||||||
if len(templateID) == 0 {
|
if len(templateID) == 0 {
|
||||||
writeMissingDataError(w, method, "templateID")
|
writeMissingDataError(w, method, "templateID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer utility.Close(r.Body)
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writeBadRequestError(w, method, "Bad payload")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
docTitle := string(body)
|
||||||
|
|
||||||
// Define an empty document just in case user wanted one.
|
// Define an empty document just in case user wanted one.
|
||||||
var err error
|
|
||||||
var d = entity.Document{}
|
var d = entity.Document{}
|
||||||
d.Title = "New Document"
|
d.Title = docTitle
|
||||||
d.Location = fmt.Sprintf("template-%s", templateID)
|
d.Location = fmt.Sprintf("template-%s", templateID)
|
||||||
d.Excerpt = "A new document"
|
d.Excerpt = "A new document"
|
||||||
d.Slug = utility.MakeSlug(d.Title)
|
d.Slug = utility.MakeSlug(d.Title)
|
||||||
|
@ -381,6 +386,7 @@ func StartDocumentFromSavedTemplate(w http.ResponseWriter, r *http.Request) {
|
||||||
d.Template = false
|
d.Template = false
|
||||||
d.LabelID = folderID
|
d.LabelID = folderID
|
||||||
d.UserID = p.Context.UserID
|
d.UserID = p.Context.UserID
|
||||||
|
d.Title = docTitle
|
||||||
|
|
||||||
err = p.AddDocument(d)
|
err = p.AddDocument(d)
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,11 @@
|
||||||
package request
|
package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/api/endpoint/models"
|
||||||
"github.com/documize/community/core/api/entity"
|
"github.com/documize/community/core/api/entity"
|
||||||
"github.com/documize/community/core/log"
|
"github.com/documize/community/core/log"
|
||||||
"github.com/documize/community/core/utility"
|
"github.com/documize/community/core/utility"
|
||||||
|
@ -42,3 +45,27 @@ func (p *Persister) RecordUserActivity(activity entity.UserActivity) (err error)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDocumentActivity returns the metadata for a specified document.
|
||||||
|
func (p *Persister) GetDocumentActivity(id string) (a []models.DocumentActivity, err error) {
|
||||||
|
s := `SELECT a.id, a.created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.sourceid as documentid, a.activitytype,
|
||||||
|
IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname
|
||||||
|
FROM useractivity a
|
||||||
|
LEFT JOIN user u ON a.userid=u.refid
|
||||||
|
WHERE a.orgid=? AND a.sourceid=? AND a.sourcetype=2
|
||||||
|
AND a.userid != '0' AND a.userid != ''
|
||||||
|
ORDER BY a.created DESC`
|
||||||
|
|
||||||
|
err = Db.Select(&a, s, p.Context.OrgID, id)
|
||||||
|
|
||||||
|
if len(a) == 0 {
|
||||||
|
a = []models.DocumentActivity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
log.Error(fmt.Sprintf("Unable to execute GetDocumentActivity %s", id), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue