mirror of
https://github.com/documize/community.git
synced 2025-07-19 13:19:43 +02:00
pin spaces and documents to sidebar
This commit is contained in:
parent
e0d2dd47df
commit
8cc798990a
27 changed files with 2943 additions and 1589 deletions
|
@ -8,7 +8,7 @@ The mission is to bring software dev inspired features (refactoring, testing, li
|
||||||
|
|
||||||
## Latest version
|
## Latest version
|
||||||
|
|
||||||
v0.33.0
|
v0.34.0
|
||||||
|
|
||||||
## OS Support
|
## OS Support
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"Tooltip",
|
"Tooltip",
|
||||||
"Drop",
|
"Drop",
|
||||||
"Dropzone",
|
"Dropzone",
|
||||||
"dragula",
|
"Sortable",
|
||||||
"datetimepicker",
|
"datetimepicker",
|
||||||
"Waypoint"
|
"Waypoint"
|
||||||
],
|
],
|
||||||
|
|
|
@ -24,6 +24,12 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
name: "",
|
name: "",
|
||||||
description: ""
|
description: ""
|
||||||
},
|
},
|
||||||
|
pinned: Ember.inject.service(),
|
||||||
|
pinState : {
|
||||||
|
isPinned: false,
|
||||||
|
pinId: '',
|
||||||
|
newName: '',
|
||||||
|
},
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this.set('saveTemplate.name', this.get('document.name'));
|
this.set('saveTemplate.name', this.get('document.name'));
|
||||||
|
@ -32,6 +38,10 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
let doc = this.get('document');
|
let doc = this.get('document');
|
||||||
|
|
||||||
this.set('layoutLabel', doc.get('layout') === 'doc' ? 'Wiki style' : 'Document style');
|
this.set('layoutLabel', doc.get('layout') === 'doc' ? 'Wiki style' : 'Document style');
|
||||||
|
|
||||||
|
this.set('pinState.pinId', this.get('pinned').isDocumentPinned(doc.get('id')));
|
||||||
|
this.set('pinState.isPinned', this.get('pinState.pinId') !== '');
|
||||||
|
this.set('pinState.newName', doc.get('name').substring(0,3).toUpperCase());
|
||||||
},
|
},
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
|
@ -106,5 +116,34 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
this.attrs.onSaveMeta(doc);
|
this.attrs.onSaveMeta(doc);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unpin() {
|
||||||
|
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
|
||||||
|
this.set('pinState.isPinned', false);
|
||||||
|
this.set('pinState.pinId', '');
|
||||||
|
this.eventBus.publish('pinChange');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
pin() {
|
||||||
|
let pin = {
|
||||||
|
pin: this.get('pinState.newName'),
|
||||||
|
documentId: this.get('document.id'),
|
||||||
|
folderId: this.get('folder.id')
|
||||||
|
};
|
||||||
|
|
||||||
|
if (is.empty(pin.pin)) {
|
||||||
|
$("#pin-document-name").addClass("error").focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get('pinned').pinItem(pin).then((pin) => {
|
||||||
|
this.set('pinState.isPinned', true);
|
||||||
|
this.set('pinState.pinId', pin.get('id'));
|
||||||
|
this.eventBus.publish('pinChange');
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,13 +21,18 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
folderService: Ember.inject.service('folder'),
|
folderService: Ember.inject.service('folder'),
|
||||||
session: Ember.inject.service(),
|
session: Ember.inject.service(),
|
||||||
appMeta: Ember.inject.service(),
|
appMeta: Ember.inject.service(),
|
||||||
|
|
||||||
showToolbar: false,
|
showToolbar: false,
|
||||||
folder: {},
|
folder: {},
|
||||||
busy: false,
|
busy: false,
|
||||||
isFolderOwner: computed.equal('folder.userId', 'session.user.id'),
|
isFolderOwner: computed.equal('folder.userId', 'session.user.id'),
|
||||||
moveFolderId: "",
|
moveFolderId: "",
|
||||||
drop: null,
|
drop: null,
|
||||||
|
pinned: Ember.inject.service(),
|
||||||
|
pinState : {
|
||||||
|
isPinned: false,
|
||||||
|
pinId: '',
|
||||||
|
newName: '',
|
||||||
|
},
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this.set('isFolderOwner', this.get('folder.userId') === this.get("session.user.id"));
|
this.set('isFolderOwner', this.get('folder.userId') === this.get("session.user.id"));
|
||||||
|
@ -40,6 +45,11 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.set('movedFolderOptions', targets);
|
this.set('movedFolderOptions', targets);
|
||||||
|
|
||||||
|
let folder = this.get('folder');
|
||||||
|
this.set('pinState.pinId', this.get('pinned').isSpacePinned(folder.get('id')));
|
||||||
|
this.set('pinState.isPinned', this.get('pinState.pinId') !== '');
|
||||||
|
this.set('pinState.newName', folder.get('name').substring(0,3).toUpperCase());
|
||||||
},
|
},
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
|
@ -87,6 +97,35 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||||
this.set("moveFolderId", "");
|
this.set("moveFolderId", "");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
unpin() {
|
||||||
|
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
|
||||||
|
this.set('pinState.isPinned', false);
|
||||||
|
this.set('pinState.pinId', '');
|
||||||
|
this.eventBus.publish('pinChange');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
pin() {
|
||||||
|
let pin = {
|
||||||
|
pin: this.get('pinState.newName'),
|
||||||
|
documentId: '',
|
||||||
|
folderId: this.get('folder.id')
|
||||||
|
};
|
||||||
|
|
||||||
|
if (is.empty(pin.pin)) {
|
||||||
|
$("#pin-space-name").addClass("error").focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get('pinned').pinItem(pin).then((pin) => {
|
||||||
|
this.set('pinState.isPinned', true);
|
||||||
|
this.set('pinState.pinId', pin.get('id'));
|
||||||
|
this.eventBus.publish('pinChange');
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,15 +19,18 @@ const {
|
||||||
|
|
||||||
export default Ember.Component.extend(TooltipMixin, {
|
export default Ember.Component.extend(TooltipMixin, {
|
||||||
folderService: service('folder'),
|
folderService: service('folder'),
|
||||||
folder: null,
|
|
||||||
appMeta: service(),
|
appMeta: service(),
|
||||||
session: service(),
|
session: service(),
|
||||||
|
store: service(),
|
||||||
|
folder: null,
|
||||||
view: {
|
view: {
|
||||||
folder: false,
|
folder: false,
|
||||||
search: false,
|
search: false,
|
||||||
settings: false,
|
settings: false,
|
||||||
profile: false
|
profile: false
|
||||||
},
|
},
|
||||||
|
pinned: service(),
|
||||||
|
pins: [],
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -38,6 +41,8 @@ export default Ember.Component.extend(TooltipMixin, {
|
||||||
account.active = account.orgId === this.get("appMeta.orgId");
|
account.active = account.orgId === this.get("appMeta.orgId");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.set('pins', this.get('pinned').get('pins'));
|
||||||
},
|
},
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
|
@ -52,6 +57,31 @@ export default Ember.Component.extend(TooltipMixin, {
|
||||||
this.set('view.search', (route === 'search') ? true : false);
|
this.set('view.search', (route === 'search') ? true : false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
// Size the pinned items zone
|
||||||
|
if (this.get("session.authenticated")) {
|
||||||
|
this.eventBus.subscribe('resized', this, 'sizePinnedZone');
|
||||||
|
this.eventBus.subscribe('pinChange', this, 'setupPins');
|
||||||
|
this.sizePinnedZone();
|
||||||
|
this.setupPins();
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
var sortable = Sortable.create(document.getElementById('pinned-zone'), {
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function () {
|
||||||
|
self.get('pinned').updateSequence(this.toArray()).then((pins) => {
|
||||||
|
self.set('pins', pins);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('sortable', sortable);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
if (this.get('session.isAdmin')) {
|
if (this.get('session.isAdmin')) {
|
||||||
this.addTooltip(document.getElementById("workspace-settings"));
|
this.addTooltip(document.getElementById("workspace-settings"));
|
||||||
|
@ -63,10 +93,53 @@ export default Ember.Component.extend(TooltipMixin, {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setupPins() {
|
||||||
|
this.get('pinned').getUserPins().then((pins) => {
|
||||||
|
this.set('pins', pins);
|
||||||
|
|
||||||
|
pins.forEach((pin) => {
|
||||||
|
this.addTooltip(document.getElementById(`pin-${pin.id}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// set height for pinned zone so ti scrolls on spill
|
||||||
|
sizePinnedZone() {
|
||||||
|
let topofBottomZone = parseInt($('#bottom-zone').css("top").replace("px", ""));
|
||||||
|
let heightOfTopZone = parseInt($('#top-zone').css("height").replace("px", ""));
|
||||||
|
let size = topofBottomZone - heightOfTopZone - 40;
|
||||||
|
$('#pinned-zone').css('height', size + "px");
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
let sortable = this.get('sortable');
|
||||||
|
|
||||||
|
if (!_.isUndefined(sortable)) {
|
||||||
|
sortable.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.destroyTooltips();
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
switchAccount(domain) {
|
switchAccount(domain) {
|
||||||
this.audit.record('switched-account');
|
this.audit.record('switched-account');
|
||||||
window.location.href = netUtil.getAppUrl(domain);
|
window.location.href = netUtil.getAppUrl(domain);
|
||||||
|
},
|
||||||
|
|
||||||
|
jumpToPin(pin) {
|
||||||
|
let folderId = pin.get('folderId');
|
||||||
|
let documentId = pin.get('documentId');
|
||||||
|
|
||||||
|
if (_.isEmpty(documentId)) {
|
||||||
|
// jump to space
|
||||||
|
let folder = this.get('store').peekRecord('folder', folderId);
|
||||||
|
this.get('router').transitionTo('folder', folderId, folder.get('slug'));
|
||||||
|
} else {
|
||||||
|
// jump to doc
|
||||||
|
let folder = this.get('store').peekRecord('folder', folderId);
|
||||||
|
this.get('router').transitionTo('document', folderId, folder.get('slug'), documentId, 'document');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
24
app/app/models/pin.js
Normal file
24
app/app/models/pin.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
export default Model.extend({
|
||||||
|
orgId: attr('string'),
|
||||||
|
userId: attr('string'),
|
||||||
|
folderId: attr('string'),
|
||||||
|
documentId: attr('string'),
|
||||||
|
sequence: attr('number', { defaultValue: 99 }),
|
||||||
|
pin: attr('string'),
|
||||||
|
created: attr(),
|
||||||
|
revised: attr()
|
||||||
|
});
|
|
@ -21,6 +21,7 @@ const {
|
||||||
export default Ember.Route.extend(ApplicationRouteMixin, TooltipMixin, {
|
export default Ember.Route.extend(ApplicationRouteMixin, TooltipMixin, {
|
||||||
appMeta: service(),
|
appMeta: service(),
|
||||||
session: service(),
|
session: service(),
|
||||||
|
pinned: service(),
|
||||||
|
|
||||||
beforeModel(transition) {
|
beforeModel(transition) {
|
||||||
return this.get('appMeta').boot(transition.targetName).then(data => {
|
return this.get('appMeta').boot(transition.targetName).then(data => {
|
||||||
|
|
130
app/app/services/pinned.js
Normal file
130
app/app/services/pinned.js
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// 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 Ember from 'ember';
|
||||||
|
|
||||||
|
const {
|
||||||
|
inject: { service }
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
|
export default Ember.Service.extend({
|
||||||
|
session: service('session'),
|
||||||
|
ajax: service(),
|
||||||
|
appMeta: service(),
|
||||||
|
store: service(),
|
||||||
|
pins: [],
|
||||||
|
|
||||||
|
getUserPins() {
|
||||||
|
let userId = this.get('session.user.id');
|
||||||
|
|
||||||
|
return this.get('ajax').request(`pin/${userId}`, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then((response) => {
|
||||||
|
if (is.not.array(response)) {
|
||||||
|
response = [];
|
||||||
|
}
|
||||||
|
let pins = Ember.ArrayProxy.create({
|
||||||
|
content: Ember.A([])
|
||||||
|
});
|
||||||
|
|
||||||
|
pins = response.map((pin) => {
|
||||||
|
let data = this.get('store').normalize('pin', pin);
|
||||||
|
return this.get('store').push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('pins', pins);
|
||||||
|
|
||||||
|
return pins;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Pin an item.
|
||||||
|
pinItem(data) {
|
||||||
|
let userId = this.get('session.user.id');
|
||||||
|
|
||||||
|
if(this.get('session.authenticated')) {
|
||||||
|
return this.get('ajax').request(`pin/${userId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: JSON.stringify(data)
|
||||||
|
}).then((response) => {
|
||||||
|
let data = this.get('store').normalize('pin', response);
|
||||||
|
return this.get('store').push(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Unpin an item.
|
||||||
|
unpinItem(pinId) {
|
||||||
|
let userId = this.get('session.user.id');
|
||||||
|
|
||||||
|
if(this.get('session.authenticated')) {
|
||||||
|
return this.get('ajax').request(`pin/${userId}/${pinId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// updateSequence persists order after use drag-drop sorting.
|
||||||
|
updateSequence(data) {
|
||||||
|
let userId = this.get('session.user.id');
|
||||||
|
|
||||||
|
if(this.get('session.authenticated')) {
|
||||||
|
return this.get('ajax').request(`pin/${userId}/sequence`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: JSON.stringify(data)
|
||||||
|
}).then((response) => {
|
||||||
|
if (is.not.array(response)) {
|
||||||
|
response = [];
|
||||||
|
}
|
||||||
|
let pins = Ember.ArrayProxy.create({
|
||||||
|
content: Ember.A([])
|
||||||
|
});
|
||||||
|
|
||||||
|
pins = response.map((pin) => {
|
||||||
|
let data = this.get('store').normalize('pin', pin);
|
||||||
|
return this.get('store').push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('pins', pins);
|
||||||
|
|
||||||
|
return pins;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isDocumentPinned(documentId) {
|
||||||
|
let userId = this.get('session.user.id');
|
||||||
|
let pins = this.get('pins');
|
||||||
|
let pinId = '';
|
||||||
|
|
||||||
|
pins.forEach((pin) => {
|
||||||
|
if (pin.get('userId') === userId && pin.get('documentId') === documentId) {
|
||||||
|
pinId = pin.get('id');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pinId;
|
||||||
|
},
|
||||||
|
|
||||||
|
isSpacePinned(spaceId) {
|
||||||
|
let userId = this.get('session.user.id');
|
||||||
|
let pins = this.get('pins');
|
||||||
|
let pinId = '';
|
||||||
|
|
||||||
|
pins.forEach((pin) => {
|
||||||
|
if (pin.get('userId') === userId && pin.get('documentId') === '' && pin.get('folderId') === spaceId) {
|
||||||
|
pinId = pin.get('id');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pinId;
|
||||||
|
}
|
||||||
|
});
|
|
@ -83,6 +83,10 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $color-link;
|
color: $color-link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .pinned {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,50 @@
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pinned-zone {
|
||||||
|
position: relative;
|
||||||
|
top: 220px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
> .pin {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 20px 0 20px 9px;
|
||||||
|
padding: 11px 3px;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-transform: uppercase;
|
||||||
|
@include ease-in();
|
||||||
|
@include border-radius(3px);
|
||||||
|
font-family: $font-semibold;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
background-color: $color-off-white;
|
||||||
|
color: $color-primary;
|
||||||
|
|
||||||
|
> .key {
|
||||||
|
width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-link;
|
||||||
|
color: $color-off-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .sortable-ghost {
|
||||||
|
background-color: $color-gray;
|
||||||
|
color: $color-off-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.zone-sidebar {
|
.zone-sidebar {
|
||||||
|
|
|
@ -29,9 +29,16 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="options">
|
<ul class="options">
|
||||||
|
{{#if session.authenticated}}
|
||||||
|
{{#if pinState.isPinned}}
|
||||||
|
<li class="option" {{action 'unpin'}}><i class="material-icons pinned">star</i></li>
|
||||||
|
{{else}}
|
||||||
|
<li class="option" id="pin-document-button"><i class="material-icons">star_border</i></li>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
{{#if isEditor}}
|
{{#if isEditor}}
|
||||||
<li class="option" id="set-meta-button"><i class="material-icons">settings</i></li>
|
|
||||||
<li class="option" id="save-template-button"><i class="material-icons">content_copy</i></li>
|
<li class="option" id="save-template-button"><i class="material-icons">content_copy</i></li>
|
||||||
|
<li class="option" id="set-meta-button"><i class="material-icons">settings</i></li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<li class="option" id="document-toolbar-menu"><i class="material-icons">more_horiz</i></li>
|
<li class="option" id="document-toolbar-menu"><i class="material-icons">more_horiz</i></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -59,6 +66,20 @@
|
||||||
{{/dropdown-dialog}}
|
{{/dropdown-dialog}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if session.authenticated}}
|
||||||
|
{{#unless pinState.isPinned}}
|
||||||
|
{{#dropdown-dialog target="pin-document-button" position="bottom right" button="Pin" color="flat-green" onAction=(action 'pin') focusOn="pin-document-name" }}
|
||||||
|
<div>
|
||||||
|
<div class="input-control">
|
||||||
|
<label>Pin Document</label>
|
||||||
|
<div class="tip">A 3 or 4 character name</div>
|
||||||
|
{{input type='text' id="pin-document-name" value=pinState.newName}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/dropdown-dialog}}
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if isEditor}}
|
{{#if isEditor}}
|
||||||
{{#dropdown-dialog target="save-template-button" position="bottom right" button="Save as Template" color="flat-green" onAction=(action 'saveTemplate') focusOn="new-template-name" }}
|
{{#dropdown-dialog target="save-template-button" position="bottom right" button="Save as Template" color="flat-green" onAction=(action 'saveTemplate') focusOn="new-template-name" }}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -28,6 +28,20 @@
|
||||||
</ul>
|
</ul>
|
||||||
{{/dropdown-dialog}}
|
{{/dropdown-dialog}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{#if session.authenticated}}
|
||||||
|
{{#if pinState.isPinned}}
|
||||||
|
<div class="round-button-mono" {{action 'unpin'}}>
|
||||||
|
<i class="material-icons color-primary">star</i>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="round-button-mono" id="pin-space-button">
|
||||||
|
<i class="material-icons color-gray">star_border</i>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if isFolderOwner}}
|
||||||
|
<div class="button-gap"></div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
{{#if isFolderOwner}}
|
{{#if isFolderOwner}}
|
||||||
{{#link-to 'settings' folder.id folder.slug (query-params tab="tabShare")}}
|
{{#link-to 'settings' folder.id folder.slug (query-params tab="tabShare")}}
|
||||||
<div class="round-button-mono" id="folder-share-button" data-tooltip="Share" data-tooltip-position="top center">
|
<div class="round-button-mono" id="folder-share-button" data-tooltip="Share" data-tooltip-position="top center">
|
||||||
|
@ -41,6 +55,17 @@
|
||||||
</div>
|
</div>
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if session.authenticated}}
|
||||||
|
{{#unless pinState.isPinned}}
|
||||||
|
{{#dropdown-dialog target="pin-space-button" position="bottom right" button="Pin" color="flat-green" onAction=(action 'pin') focusOn="pin-space-name" }}
|
||||||
|
<div class="input-control">
|
||||||
|
<label>Pin Space</label>
|
||||||
|
<div class="tip">A 3 or 4 character name</div>
|
||||||
|
{{input type='text' id="pin-space-name" value=pinState.newName}}
|
||||||
|
</div>
|
||||||
|
{{/dropdown-dialog}}
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,61 +1,67 @@
|
||||||
<div id="zone-navigation" class="zone-navigation">
|
<div id="zone-navigation" class="zone-navigation">
|
||||||
<ul class="top-zone">
|
<ul id="top-zone" class="top-zone">
|
||||||
{{#if session.authenticated}}
|
{{#if session.authenticated}}
|
||||||
<li>
|
<li>
|
||||||
<div id="accounts-button" class="filled-tool">
|
<div id="accounts-button" class="filled-tool">
|
||||||
<i class="material-icons">apps</i>
|
<i class="material-icons">apps</i>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{else}}
|
{{else}}
|
||||||
<li>
|
<li>
|
||||||
{{#link-to 'folders' class='title'}}
|
{{#link-to 'folders' class='title'}}
|
||||||
<div class="filled-tool" title={{appMeta.title}}>
|
<div class="filled-tool" title={{appMeta.title}}>
|
||||||
<i class="material-icons">apps</i>
|
<i class="material-icons">apps</i>
|
||||||
</div>
|
</div>
|
||||||
{{/link-to}}
|
|
||||||
</li>
|
|
||||||
{{/if}}
|
|
||||||
<li class="{{if view.folder 'selected'}}">
|
|
||||||
{{#link-to 'folders'}}
|
|
||||||
<i class="material-icons icon-tool">home</i>
|
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
</li>
|
</li>
|
||||||
<li class="{{if view.search 'selected'}}">
|
{{/if}}
|
||||||
{{#link-to 'search'}}
|
<li class="{{if view.folder 'selected'}}">
|
||||||
<i class="material-icons icon-tool">search</i>
|
{{#link-to 'folders'}}
|
||||||
{{/link-to}}
|
<i class="material-icons icon-tool">home</i>
|
||||||
</li>
|
{{/link-to}}
|
||||||
</ul>
|
</li>
|
||||||
|
<li class="{{if view.search 'selected'}}">
|
||||||
|
{{#link-to 'search'}}
|
||||||
|
<i class="material-icons icon-tool">search</i>
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<ul class="bottom-zone">
|
<ul id="pinned-zone" class="pinned-zone">
|
||||||
{{#if session.session.content.authenticated.user.admin}}
|
{{#each pins as |pin|}}
|
||||||
<li id="workspace-settings" class="{{if view.settings 'selected'}}" data-tooltip="Settings" data-tooltip-position="right center">
|
<li {{action 'jumpToPin' pin}} data-id={{pin.id}} id="pin-{{pin.id}}" data-tooltip="{{pin.pin}}" data-tooltip-position="right middle" class="pin"><span class="key">{{pin.pin}}</span></li>
|
||||||
{{#link-to 'customize.general'}}
|
{{/each}}
|
||||||
<i class="material-icons icon-tool">tune</i>
|
</ul>
|
||||||
{{/link-to}}
|
|
||||||
</li>
|
<ul id="bottom-zone" class="bottom-zone">
|
||||||
{{/if}}
|
{{#if session.session.content.authenticated.user.admin}}
|
||||||
{{#if session.authenticated}}
|
<li id="workspace-settings" class="{{if view.settings 'selected'}}" data-tooltip="Settings" data-tooltip-position="right center">
|
||||||
<li id="workspace-logout" data-tooltip="Logout" data-tooltip-position="right center">
|
{{#link-to 'customize.general'}}
|
||||||
{{#link-to 'auth.logout'}}
|
<i class="material-icons icon-tool">tune</i>
|
||||||
<i class="material-icons icon-tool">exit_to_app</i>
|
{{/link-to}}
|
||||||
{{/link-to}}
|
</li>
|
||||||
</li>
|
{{/if}}
|
||||||
<li class="{{if view.profile 'selected'}}">
|
{{#if session.authenticated}}
|
||||||
{{#link-to 'profile'}}
|
<li id="workspace-logout" data-tooltip="Logout" data-tooltip-position="right center">
|
||||||
<div class="filled-tool">
|
{{#link-to 'auth.logout'}}
|
||||||
<i class="initials">{{session.user.initials}}</i>
|
<i class="material-icons icon-tool">exit_to_app</i>
|
||||||
</div>
|
{{/link-to}}
|
||||||
{{/link-to}}
|
</li>
|
||||||
</li>
|
<li class="{{if view.profile 'selected'}}">
|
||||||
{{else}}
|
{{#link-to 'profile'}}
|
||||||
<li id="workspace-login" data-tooltip="Login" data-tooltip-position="right center">
|
<div class="filled-tool">
|
||||||
{{#link-to 'auth.login'}}
|
<i class="initials">{{session.user.initials}}</i>
|
||||||
<i class="material-icons icon-tool">lock_open</i>
|
</div>
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{else}}
|
||||||
</ul>
|
<li id="workspace-login" data-tooltip="Login" data-tooltip-position="right center">
|
||||||
|
{{#link-to 'auth.login'}}
|
||||||
|
<i class="material-icons icon-tool">lock_open</i>
|
||||||
|
{{/link-to}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
{{#if session.authenticated}}
|
{{#if session.authenticated}}
|
||||||
{{#dropdown-menu target="accounts-button" position="bottom left" open="click" }}
|
{{#dropdown-menu target="accounts-button" position="bottom left" open="click" }}
|
||||||
|
|
|
@ -53,7 +53,7 @@ module.exports = function (defaults) {
|
||||||
app.import('vendor/drop.js');
|
app.import('vendor/drop.js');
|
||||||
app.import('vendor/tooltip.js');
|
app.import('vendor/tooltip.js');
|
||||||
app.import('vendor/markdown-it.min.js');
|
app.import('vendor/markdown-it.min.js');
|
||||||
app.import('vendor/dragula.js');
|
app.import('vendor/sortable.js');
|
||||||
app.import('vendor/datetimepicker.min.js');
|
app.import('vendor/datetimepicker.min.js');
|
||||||
app.import('vendor/hoverIntent.js');
|
app.import('vendor/hoverIntent.js');
|
||||||
app.import('vendor/waypoints.js');
|
app.import('vendor/waypoints.js');
|
||||||
|
|
907
app/vendor/dragula.js
vendored
907
app/vendor/dragula.js
vendored
|
@ -1,907 +0,0 @@
|
||||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.dragula = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var cache = {};
|
|
||||||
var start = '(?:^|\\s)';
|
|
||||||
var end = '(?:\\s|$)';
|
|
||||||
|
|
||||||
function lookupClass (className) {
|
|
||||||
var cached = cache[className];
|
|
||||||
if (cached) {
|
|
||||||
cached.lastIndex = 0;
|
|
||||||
} else {
|
|
||||||
cache[className] = cached = new RegExp(start + className + end, 'g');
|
|
||||||
}
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addClass (el, className) {
|
|
||||||
var current = el.className;
|
|
||||||
if (!current.length) {
|
|
||||||
el.className = className;
|
|
||||||
} else if (!lookupClass(className).test(current)) {
|
|
||||||
el.className += ' ' + className;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rmClass (el, className) {
|
|
||||||
el.className = el.className.replace(lookupClass(className), ' ').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
add: addClass,
|
|
||||||
rm: rmClass
|
|
||||||
};
|
|
||||||
|
|
||||||
},{}],2:[function(require,module,exports){
|
|
||||||
(function (global){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var emitter = require('contra/emitter');
|
|
||||||
var crossvent = require('crossvent');
|
|
||||||
var classes = require('./classes');
|
|
||||||
var doc = document;
|
|
||||||
var documentElement = doc.documentElement;
|
|
||||||
|
|
||||||
function dragula (initialContainers, options) {
|
|
||||||
var len = arguments.length;
|
|
||||||
if (len === 1 && Array.isArray(initialContainers) === false) {
|
|
||||||
options = initialContainers;
|
|
||||||
initialContainers = [];
|
|
||||||
}
|
|
||||||
var _mirror; // mirror image
|
|
||||||
var _source; // source container
|
|
||||||
var _item; // item being dragged
|
|
||||||
var _offsetX; // reference x
|
|
||||||
var _offsetY; // reference y
|
|
||||||
var _moveX; // reference move x
|
|
||||||
var _moveY; // reference move y
|
|
||||||
var _initialSibling; // reference sibling when grabbed
|
|
||||||
var _currentSibling; // reference sibling now
|
|
||||||
var _copy; // item used for copying
|
|
||||||
var _renderTimer; // timer for setTimeout renderMirrorImage
|
|
||||||
var _lastDropTarget = null; // last container item was over
|
|
||||||
var _grabbed; // holds mousedown context until first mousemove
|
|
||||||
|
|
||||||
var o = options || {};
|
|
||||||
if (o.moves === void 0) { o.moves = always; }
|
|
||||||
if (o.accepts === void 0) { o.accepts = always; }
|
|
||||||
if (o.invalid === void 0) { o.invalid = invalidTarget; }
|
|
||||||
if (o.containers === void 0) { o.containers = initialContainers || []; }
|
|
||||||
if (o.isContainer === void 0) { o.isContainer = never; }
|
|
||||||
if (o.copy === void 0) { o.copy = false; }
|
|
||||||
if (o.copySortSource === void 0) { o.copySortSource = false; }
|
|
||||||
if (o.revertOnSpill === void 0) { o.revertOnSpill = false; }
|
|
||||||
if (o.removeOnSpill === void 0) { o.removeOnSpill = false; }
|
|
||||||
if (o.direction === void 0) { o.direction = 'vertical'; }
|
|
||||||
if (o.ignoreInputTextSelection === void 0) { o.ignoreInputTextSelection = true; }
|
|
||||||
if (o.mirrorContainer === void 0) { o.mirrorContainer = doc.body; }
|
|
||||||
|
|
||||||
var drake = emitter({
|
|
||||||
containers: o.containers,
|
|
||||||
start: manualStart,
|
|
||||||
end: end,
|
|
||||||
cancel: cancel,
|
|
||||||
remove: remove,
|
|
||||||
destroy: destroy,
|
|
||||||
canMove: canMove,
|
|
||||||
dragging: false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (o.removeOnSpill === true) {
|
|
||||||
drake.on('over', spillOver).on('out', spillOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
events();
|
|
||||||
|
|
||||||
return drake;
|
|
||||||
|
|
||||||
function isContainer (el) {
|
|
||||||
return drake.containers.indexOf(el) !== -1 || o.isContainer(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
function events (remove) {
|
|
||||||
var op = remove ? 'remove' : 'add';
|
|
||||||
touchy(documentElement, op, 'mousedown', grab);
|
|
||||||
touchy(documentElement, op, 'mouseup', release);
|
|
||||||
}
|
|
||||||
|
|
||||||
function eventualMovements (remove) {
|
|
||||||
var op = remove ? 'remove' : 'add';
|
|
||||||
touchy(documentElement, op, 'mousemove', startBecauseMouseMoved);
|
|
||||||
}
|
|
||||||
|
|
||||||
function movements (remove) {
|
|
||||||
var op = remove ? 'remove' : 'add';
|
|
||||||
crossvent[op](documentElement, 'selectstart', preventGrabbed); // IE8
|
|
||||||
crossvent[op](documentElement, 'click', preventGrabbed);
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroy () {
|
|
||||||
events(true);
|
|
||||||
release({});
|
|
||||||
}
|
|
||||||
|
|
||||||
function preventGrabbed (e) {
|
|
||||||
if (_grabbed) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function grab (e) {
|
|
||||||
_moveX = e.clientX;
|
|
||||||
_moveY = e.clientY;
|
|
||||||
|
|
||||||
var ignore = whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
|
|
||||||
if (ignore) {
|
|
||||||
return; // we only care about honest-to-god left clicks and touch events
|
|
||||||
}
|
|
||||||
var item = e.target;
|
|
||||||
var context = canStart(item);
|
|
||||||
if (!context) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_grabbed = context;
|
|
||||||
eventualMovements();
|
|
||||||
if (e.type === 'mousedown') {
|
|
||||||
if (isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208
|
|
||||||
item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176
|
|
||||||
} else {
|
|
||||||
e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startBecauseMouseMoved (e) {
|
|
||||||
if (!_grabbed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (whichMouseButton(e) === 0) {
|
|
||||||
release({});
|
|
||||||
return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope
|
|
||||||
}
|
|
||||||
// truthy check fixes #239, equality fixes #207
|
|
||||||
if (e.clientX !== void 0 && e.clientX === _moveX && e.clientY !== void 0 && e.clientY === _moveY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (o.ignoreInputTextSelection) {
|
|
||||||
var clientX = getCoord('clientX', e);
|
|
||||||
var clientY = getCoord('clientY', e);
|
|
||||||
var elementBehindCursor = doc.elementFromPoint(clientX, clientY);
|
|
||||||
if (isInput(elementBehindCursor)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var grabbed = _grabbed; // call to end() unsets _grabbed
|
|
||||||
eventualMovements(true);
|
|
||||||
movements();
|
|
||||||
end();
|
|
||||||
start(grabbed);
|
|
||||||
|
|
||||||
var offset = getOffset(_item);
|
|
||||||
_offsetX = getCoord('pageX', e) - offset.left;
|
|
||||||
_offsetY = getCoord('pageY', e) - offset.top;
|
|
||||||
|
|
||||||
classes.add(_copy || _item, 'gu-transit');
|
|
||||||
renderMirrorImage();
|
|
||||||
drag(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
function canStart (item) {
|
|
||||||
if (drake.dragging && _mirror) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isContainer(item)) {
|
|
||||||
return; // don't drag container itself
|
|
||||||
}
|
|
||||||
var handle = item;
|
|
||||||
while (getParent(item) && isContainer(getParent(item)) === false) {
|
|
||||||
if (o.invalid(item, handle)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
item = getParent(item); // drag target should be a top element
|
|
||||||
if (!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var source = getParent(item);
|
|
||||||
if (!source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (o.invalid(item, handle)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var movable = o.moves(item, source, handle, nextEl(item));
|
|
||||||
if (!movable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
item: item,
|
|
||||||
source: source
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function canMove (item) {
|
|
||||||
return !!canStart(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
function manualStart (item) {
|
|
||||||
var context = canStart(item);
|
|
||||||
if (context) {
|
|
||||||
start(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function start (context) {
|
|
||||||
if (isCopy(context.item, context.source)) {
|
|
||||||
_copy = context.item.cloneNode(true);
|
|
||||||
drake.emit('cloned', _copy, context.item, 'copy');
|
|
||||||
}
|
|
||||||
|
|
||||||
_source = context.source;
|
|
||||||
_item = context.item;
|
|
||||||
_initialSibling = _currentSibling = nextEl(context.item);
|
|
||||||
|
|
||||||
drake.dragging = true;
|
|
||||||
drake.emit('drag', _item, _source);
|
|
||||||
}
|
|
||||||
|
|
||||||
function invalidTarget () {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function end () {
|
|
||||||
if (!drake.dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = _copy || _item;
|
|
||||||
drop(item, getParent(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
function ungrab () {
|
|
||||||
_grabbed = false;
|
|
||||||
eventualMovements(true);
|
|
||||||
movements(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function release (e) {
|
|
||||||
ungrab();
|
|
||||||
|
|
||||||
if (!drake.dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = _copy || _item;
|
|
||||||
var clientX = getCoord('clientX', e);
|
|
||||||
var clientY = getCoord('clientY', e);
|
|
||||||
var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
|
|
||||||
var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
|
|
||||||
if (dropTarget && ((_copy && o.copySortSource) || (!_copy || dropTarget !== _source))) {
|
|
||||||
drop(item, dropTarget);
|
|
||||||
} else if (o.removeOnSpill) {
|
|
||||||
remove();
|
|
||||||
} else {
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drop (item, target) {
|
|
||||||
var parent = getParent(item);
|
|
||||||
if (_copy && o.copySortSource && target === _source) {
|
|
||||||
parent.removeChild(_item);
|
|
||||||
}
|
|
||||||
if (isInitialPlacement(target)) {
|
|
||||||
drake.emit('cancel', item, _source, _source);
|
|
||||||
} else {
|
|
||||||
drake.emit('drop', item, target, _source, _currentSibling);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove () {
|
|
||||||
if (!drake.dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = _copy || _item;
|
|
||||||
var parent = getParent(item);
|
|
||||||
if (parent) {
|
|
||||||
parent.removeChild(item);
|
|
||||||
}
|
|
||||||
drake.emit(_copy ? 'cancel' : 'remove', item, parent, _source);
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel (revert) {
|
|
||||||
if (!drake.dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var reverts = arguments.length > 0 ? revert : o.revertOnSpill;
|
|
||||||
var item = _copy || _item;
|
|
||||||
var parent = getParent(item);
|
|
||||||
var initial = isInitialPlacement(parent);
|
|
||||||
if (initial === false && reverts) {
|
|
||||||
if (_copy) {
|
|
||||||
if (parent) {
|
|
||||||
parent.removeChild(_copy);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_source.insertBefore(item, _initialSibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (initial || reverts) {
|
|
||||||
drake.emit('cancel', item, _source, _source);
|
|
||||||
} else {
|
|
||||||
drake.emit('drop', item, parent, _source, _currentSibling);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup () {
|
|
||||||
var item = _copy || _item;
|
|
||||||
ungrab();
|
|
||||||
removeMirrorImage();
|
|
||||||
if (item) {
|
|
||||||
classes.rm(item, 'gu-transit');
|
|
||||||
}
|
|
||||||
if (_renderTimer) {
|
|
||||||
clearTimeout(_renderTimer);
|
|
||||||
}
|
|
||||||
drake.dragging = false;
|
|
||||||
if (_lastDropTarget) {
|
|
||||||
drake.emit('out', item, _lastDropTarget, _source);
|
|
||||||
}
|
|
||||||
drake.emit('dragend', item);
|
|
||||||
_source = _item = _copy = _initialSibling = _currentSibling = _renderTimer = _lastDropTarget = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInitialPlacement (target, s) {
|
|
||||||
var sibling;
|
|
||||||
if (s !== void 0) {
|
|
||||||
sibling = s;
|
|
||||||
} else if (_mirror) {
|
|
||||||
sibling = _currentSibling;
|
|
||||||
} else {
|
|
||||||
sibling = nextEl(_copy || _item);
|
|
||||||
}
|
|
||||||
return target === _source && sibling === _initialSibling;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findDropTarget (elementBehindCursor, clientX, clientY) {
|
|
||||||
var target = elementBehindCursor;
|
|
||||||
while (target && !accepted()) {
|
|
||||||
target = getParent(target);
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
|
|
||||||
function accepted () {
|
|
||||||
var droppable = isContainer(target);
|
|
||||||
if (droppable === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var immediate = getImmediateChild(target, elementBehindCursor);
|
|
||||||
var reference = getReference(target, immediate, clientX, clientY);
|
|
||||||
var initial = isInitialPlacement(target, reference);
|
|
||||||
if (initial) {
|
|
||||||
return true; // should always be able to drop it right back where it was
|
|
||||||
}
|
|
||||||
return o.accepts(_item, target, _source, reference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drag (e) {
|
|
||||||
if (!_mirror) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var clientX = getCoord('clientX', e);
|
|
||||||
var clientY = getCoord('clientY', e);
|
|
||||||
var x = clientX - _offsetX;
|
|
||||||
var y = clientY - _offsetY;
|
|
||||||
|
|
||||||
_mirror.style.left = x + 'px';
|
|
||||||
_mirror.style.top = y + 'px';
|
|
||||||
|
|
||||||
var item = _copy || _item;
|
|
||||||
var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
|
|
||||||
var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
|
|
||||||
var changed = dropTarget !== null && dropTarget !== _lastDropTarget;
|
|
||||||
if (changed || dropTarget === null) {
|
|
||||||
out();
|
|
||||||
_lastDropTarget = dropTarget;
|
|
||||||
over();
|
|
||||||
}
|
|
||||||
var parent = getParent(item);
|
|
||||||
if (dropTarget === _source && _copy && !o.copySortSource) {
|
|
||||||
if (parent) {
|
|
||||||
parent.removeChild(item);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var reference;
|
|
||||||
var immediate = getImmediateChild(dropTarget, elementBehindCursor);
|
|
||||||
if (immediate !== null) {
|
|
||||||
reference = getReference(dropTarget, immediate, clientX, clientY);
|
|
||||||
} else if (o.revertOnSpill === true && !_copy) {
|
|
||||||
reference = _initialSibling;
|
|
||||||
dropTarget = _source;
|
|
||||||
} else {
|
|
||||||
if (_copy && parent) {
|
|
||||||
parent.removeChild(item);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(reference === null && changed) ||
|
|
||||||
reference !== item &&
|
|
||||||
reference !== nextEl(item)
|
|
||||||
) {
|
|
||||||
_currentSibling = reference;
|
|
||||||
dropTarget.insertBefore(item, reference);
|
|
||||||
drake.emit('shadow', item, dropTarget, _source);
|
|
||||||
}
|
|
||||||
function moved (type) { drake.emit(type, item, _lastDropTarget, _source); }
|
|
||||||
function over () { if (changed) { moved('over'); } }
|
|
||||||
function out () { if (_lastDropTarget) { moved('out'); } }
|
|
||||||
}
|
|
||||||
|
|
||||||
function spillOver (el) {
|
|
||||||
classes.rm(el, 'gu-hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
function spillOut (el) {
|
|
||||||
if (drake.dragging) { classes.add(el, 'gu-hide'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMirrorImage () {
|
|
||||||
if (_mirror) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rect = _item.getBoundingClientRect();
|
|
||||||
_mirror = _item.cloneNode(true);
|
|
||||||
_mirror.style.width = getRectWidth(rect) + 'px';
|
|
||||||
_mirror.style.height = getRectHeight(rect) + 'px';
|
|
||||||
classes.rm(_mirror, 'gu-transit');
|
|
||||||
classes.add(_mirror, 'gu-mirror');
|
|
||||||
o.mirrorContainer.appendChild(_mirror);
|
|
||||||
touchy(documentElement, 'add', 'mousemove', drag);
|
|
||||||
classes.add(o.mirrorContainer, 'gu-unselectable');
|
|
||||||
drake.emit('cloned', _mirror, _item, 'mirror');
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeMirrorImage () {
|
|
||||||
if (_mirror) {
|
|
||||||
classes.rm(o.mirrorContainer, 'gu-unselectable');
|
|
||||||
touchy(documentElement, 'remove', 'mousemove', drag);
|
|
||||||
getParent(_mirror).removeChild(_mirror);
|
|
||||||
_mirror = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImmediateChild (dropTarget, target) {
|
|
||||||
var immediate = target;
|
|
||||||
while (immediate !== dropTarget && getParent(immediate) !== dropTarget) {
|
|
||||||
immediate = getParent(immediate);
|
|
||||||
}
|
|
||||||
if (immediate === documentElement) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return immediate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReference (dropTarget, target, x, y) {
|
|
||||||
var horizontal = o.direction === 'horizontal';
|
|
||||||
var reference = target !== dropTarget ? inside() : outside();
|
|
||||||
return reference;
|
|
||||||
|
|
||||||
function outside () { // slower, but able to figure out any position
|
|
||||||
var len = dropTarget.children.length;
|
|
||||||
var i;
|
|
||||||
var el;
|
|
||||||
var rect;
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
el = dropTarget.children[i];
|
|
||||||
rect = el.getBoundingClientRect();
|
|
||||||
if (horizontal && (rect.left + rect.width / 2) > x) { return el; }
|
|
||||||
if (!horizontal && (rect.top + rect.height / 2) > y) { return el; }
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inside () { // faster, but only available if dropped inside a child element
|
|
||||||
var rect = target.getBoundingClientRect();
|
|
||||||
if (horizontal) {
|
|
||||||
return resolve(x > rect.left + getRectWidth(rect) / 2);
|
|
||||||
}
|
|
||||||
return resolve(y > rect.top + getRectHeight(rect) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolve (after) {
|
|
||||||
return after ? nextEl(target) : target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCopy (item, container) {
|
|
||||||
return typeof o.copy === 'boolean' ? o.copy : o.copy(item, container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function touchy (el, op, type, fn) {
|
|
||||||
var touch = {
|
|
||||||
mouseup: 'touchend',
|
|
||||||
mousedown: 'touchstart',
|
|
||||||
mousemove: 'touchmove'
|
|
||||||
};
|
|
||||||
var pointers = {
|
|
||||||
mouseup: 'pointerup',
|
|
||||||
mousedown: 'pointerdown',
|
|
||||||
mousemove: 'pointermove'
|
|
||||||
};
|
|
||||||
var microsoft = {
|
|
||||||
mouseup: 'MSPointerUp',
|
|
||||||
mousedown: 'MSPointerDown',
|
|
||||||
mousemove: 'MSPointerMove'
|
|
||||||
};
|
|
||||||
if (global.navigator.pointerEnabled) {
|
|
||||||
crossvent[op](el, pointers[type], fn);
|
|
||||||
} else if (global.navigator.msPointerEnabled) {
|
|
||||||
crossvent[op](el, microsoft[type], fn);
|
|
||||||
} else {
|
|
||||||
crossvent[op](el, touch[type], fn);
|
|
||||||
crossvent[op](el, type, fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function whichMouseButton (e) {
|
|
||||||
if (e.touches !== void 0) { return e.touches.length; }
|
|
||||||
if (e.which !== void 0 && e.which !== 0) { return e.which; } // see https://github.com/bevacqua/dragula/issues/261
|
|
||||||
if (e.buttons !== void 0) { return e.buttons; }
|
|
||||||
var button = e.button;
|
|
||||||
if (button !== void 0) { // see https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/event.js#L573-L575
|
|
||||||
return button & 1 ? 1 : button & 2 ? 3 : (button & 4 ? 2 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOffset (el) {
|
|
||||||
var rect = el.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
left: rect.left + getScroll('scrollLeft', 'pageXOffset'),
|
|
||||||
top: rect.top + getScroll('scrollTop', 'pageYOffset')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScroll (scrollProp, offsetProp) {
|
|
||||||
if (typeof global[offsetProp] !== 'undefined') {
|
|
||||||
return global[offsetProp];
|
|
||||||
}
|
|
||||||
if (documentElement.clientHeight) {
|
|
||||||
return documentElement[scrollProp];
|
|
||||||
}
|
|
||||||
return doc.body[scrollProp];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElementBehindPoint (point, x, y) {
|
|
||||||
var p = point || {};
|
|
||||||
var state = p.className;
|
|
||||||
var el;
|
|
||||||
p.className += ' gu-hide';
|
|
||||||
el = doc.elementFromPoint(x, y);
|
|
||||||
p.className = state;
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
function never () { return false; }
|
|
||||||
function always () { return true; }
|
|
||||||
function getRectWidth (rect) { return rect.width || (rect.right - rect.left); }
|
|
||||||
function getRectHeight (rect) { return rect.height || (rect.bottom - rect.top); }
|
|
||||||
function getParent (el) { return el.parentNode === doc ? null : el.parentNode; }
|
|
||||||
function isInput (el) { return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || isEditable(el); }
|
|
||||||
function isEditable (el) {
|
|
||||||
if (!el) { return false; } // no parents were editable
|
|
||||||
if (el.contentEditable === 'false') { return false; } // stop the lookup
|
|
||||||
if (el.contentEditable === 'true') { return true; } // found a contentEditable element in the chain
|
|
||||||
return isEditable(getParent(el)); // contentEditable is set to 'inherit'
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextEl (el) {
|
|
||||||
return el.nextElementSibling || manually();
|
|
||||||
function manually () {
|
|
||||||
var sibling = el;
|
|
||||||
do {
|
|
||||||
sibling = sibling.nextSibling;
|
|
||||||
} while (sibling && sibling.nodeType !== 1);
|
|
||||||
return sibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEventHost (e) {
|
|
||||||
// on touchend event, we have to use `e.changedTouches`
|
|
||||||
// see http://stackoverflow.com/questions/7192563/touchend-event-properties
|
|
||||||
// see https://github.com/bevacqua/dragula/issues/34
|
|
||||||
if (e.targetTouches && e.targetTouches.length) {
|
|
||||||
return e.targetTouches[0];
|
|
||||||
}
|
|
||||||
if (e.changedTouches && e.changedTouches.length) {
|
|
||||||
return e.changedTouches[0];
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCoord (coord, e) {
|
|
||||||
var host = getEventHost(e);
|
|
||||||
var missMap = {
|
|
||||||
pageX: 'clientX', // IE8
|
|
||||||
pageY: 'clientY' // IE8
|
|
||||||
};
|
|
||||||
if (coord in missMap && !(coord in host) && missMap[coord] in host) {
|
|
||||||
coord = missMap[coord];
|
|
||||||
}
|
|
||||||
return host[coord];
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = dragula;
|
|
||||||
|
|
||||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
||||||
|
|
||||||
},{"./classes":1,"contra/emitter":5,"crossvent":6}],3:[function(require,module,exports){
|
|
||||||
module.exports = function atoa (a, n) { return Array.prototype.slice.call(a, n); }
|
|
||||||
|
|
||||||
},{}],4:[function(require,module,exports){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var ticky = require('ticky');
|
|
||||||
|
|
||||||
module.exports = function debounce (fn, args, ctx) {
|
|
||||||
if (!fn) { return; }
|
|
||||||
ticky(function run () {
|
|
||||||
fn.apply(ctx || null, args || []);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
},{"ticky":9}],5:[function(require,module,exports){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var atoa = require('atoa');
|
|
||||||
var debounce = require('./debounce');
|
|
||||||
|
|
||||||
module.exports = function emitter (thing, options) {
|
|
||||||
var opts = options || {};
|
|
||||||
var evt = {};
|
|
||||||
if (thing === undefined) { thing = {}; }
|
|
||||||
thing.on = function (type, fn) {
|
|
||||||
if (!evt[type]) {
|
|
||||||
evt[type] = [fn];
|
|
||||||
} else {
|
|
||||||
evt[type].push(fn);
|
|
||||||
}
|
|
||||||
return thing;
|
|
||||||
};
|
|
||||||
thing.once = function (type, fn) {
|
|
||||||
fn._once = true; // thing.off(fn) still works!
|
|
||||||
thing.on(type, fn);
|
|
||||||
return thing;
|
|
||||||
};
|
|
||||||
thing.off = function (type, fn) {
|
|
||||||
var c = arguments.length;
|
|
||||||
if (c === 1) {
|
|
||||||
delete evt[type];
|
|
||||||
} else if (c === 0) {
|
|
||||||
evt = {};
|
|
||||||
} else {
|
|
||||||
var et = evt[type];
|
|
||||||
if (!et) { return thing; }
|
|
||||||
et.splice(et.indexOf(fn), 1);
|
|
||||||
}
|
|
||||||
return thing;
|
|
||||||
};
|
|
||||||
thing.emit = function () {
|
|
||||||
var args = atoa(arguments);
|
|
||||||
return thing.emitterSnapshot(args.shift()).apply(this, args);
|
|
||||||
};
|
|
||||||
thing.emitterSnapshot = function (type) {
|
|
||||||
var et = (evt[type] || []).slice(0);
|
|
||||||
return function () {
|
|
||||||
var args = atoa(arguments);
|
|
||||||
var ctx = this || thing;
|
|
||||||
if (type === 'error' && opts.throws !== false && !et.length) { throw args.length === 1 ? args[0] : args; }
|
|
||||||
et.forEach(function emitter (listen) {
|
|
||||||
if (opts.async) { debounce(listen, args, ctx); } else { listen.apply(ctx, args); }
|
|
||||||
if (listen._once) { thing.off(type, listen); }
|
|
||||||
});
|
|
||||||
return thing;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return thing;
|
|
||||||
};
|
|
||||||
|
|
||||||
},{"./debounce":4,"atoa":3}],6:[function(require,module,exports){
|
|
||||||
(function (global){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var customEvent = require('custom-event');
|
|
||||||
var eventmap = require('./eventmap');
|
|
||||||
var doc = global.document;
|
|
||||||
var addEvent = addEventEasy;
|
|
||||||
var removeEvent = removeEventEasy;
|
|
||||||
var hardCache = [];
|
|
||||||
|
|
||||||
if (!global.addEventListener) {
|
|
||||||
addEvent = addEventHard;
|
|
||||||
removeEvent = removeEventHard;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
add: addEvent,
|
|
||||||
remove: removeEvent,
|
|
||||||
fabricate: fabricateEvent
|
|
||||||
};
|
|
||||||
|
|
||||||
function addEventEasy (el, type, fn, capturing) {
|
|
||||||
return el.addEventListener(type, fn, capturing);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addEventHard (el, type, fn) {
|
|
||||||
return el.attachEvent('on' + type, wrap(el, type, fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeEventEasy (el, type, fn, capturing) {
|
|
||||||
return el.removeEventListener(type, fn, capturing);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeEventHard (el, type, fn) {
|
|
||||||
var listener = unwrap(el, type, fn);
|
|
||||||
if (listener) {
|
|
||||||
return el.detachEvent('on' + type, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fabricateEvent (el, type, model) {
|
|
||||||
var e = eventmap.indexOf(type) === -1 ? makeCustomEvent() : makeClassicEvent();
|
|
||||||
if (el.dispatchEvent) {
|
|
||||||
el.dispatchEvent(e);
|
|
||||||
} else {
|
|
||||||
el.fireEvent('on' + type, e);
|
|
||||||
}
|
|
||||||
function makeClassicEvent () {
|
|
||||||
var e;
|
|
||||||
if (doc.createEvent) {
|
|
||||||
e = doc.createEvent('Event');
|
|
||||||
e.initEvent(type, true, true);
|
|
||||||
} else if (doc.createEventObject) {
|
|
||||||
e = doc.createEventObject();
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
function makeCustomEvent () {
|
|
||||||
return new customEvent(type, { detail: model });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapperFactory (el, type, fn) {
|
|
||||||
return function wrapper (originalEvent) {
|
|
||||||
var e = originalEvent || global.event;
|
|
||||||
e.target = e.target || e.srcElement;
|
|
||||||
e.preventDefault = e.preventDefault || function preventDefault () { e.returnValue = false; };
|
|
||||||
e.stopPropagation = e.stopPropagation || function stopPropagation () { e.cancelBubble = true; };
|
|
||||||
e.which = e.which || e.keyCode;
|
|
||||||
fn.call(el, e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrap (el, type, fn) {
|
|
||||||
var wrapper = unwrap(el, type, fn) || wrapperFactory(el, type, fn);
|
|
||||||
hardCache.push({
|
|
||||||
wrapper: wrapper,
|
|
||||||
element: el,
|
|
||||||
type: type,
|
|
||||||
fn: fn
|
|
||||||
});
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
function unwrap (el, type, fn) {
|
|
||||||
var i = find(el, type, fn);
|
|
||||||
if (i) {
|
|
||||||
var wrapper = hardCache[i].wrapper;
|
|
||||||
hardCache.splice(i, 1); // free up a tad of memory
|
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function find (el, type, fn) {
|
|
||||||
var i, item;
|
|
||||||
for (i = 0; i < hardCache.length; i++) {
|
|
||||||
item = hardCache[i];
|
|
||||||
if (item.element === el && item.type === type && item.fn === fn) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
||||||
|
|
||||||
},{"./eventmap":7,"custom-event":8}],7:[function(require,module,exports){
|
|
||||||
(function (global){
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var eventmap = [];
|
|
||||||
var eventname = '';
|
|
||||||
var ron = /^on/;
|
|
||||||
|
|
||||||
for (eventname in global) {
|
|
||||||
if (ron.test(eventname)) {
|
|
||||||
eventmap.push(eventname.slice(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = eventmap;
|
|
||||||
|
|
||||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
||||||
|
|
||||||
},{}],8:[function(require,module,exports){
|
|
||||||
(function (global){
|
|
||||||
|
|
||||||
var NativeCustomEvent = global.CustomEvent;
|
|
||||||
|
|
||||||
function useNative () {
|
|
||||||
try {
|
|
||||||
var p = new NativeCustomEvent('cat', { detail: { foo: 'bar' } });
|
|
||||||
return 'cat' === p.type && 'bar' === p.detail.foo;
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cross-browser `CustomEvent` constructor.
|
|
||||||
*
|
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent.CustomEvent
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = useNative() ? NativeCustomEvent :
|
|
||||||
|
|
||||||
// IE >= 9
|
|
||||||
'function' === typeof document.createEvent ? function CustomEvent (type, params) {
|
|
||||||
var e = document.createEvent('CustomEvent');
|
|
||||||
if (params) {
|
|
||||||
e.initCustomEvent(type, params.bubbles, params.cancelable, params.detail);
|
|
||||||
} else {
|
|
||||||
e.initCustomEvent(type, false, false, void 0);
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
} :
|
|
||||||
|
|
||||||
// IE <= 8
|
|
||||||
function CustomEvent (type, params) {
|
|
||||||
var e = document.createEventObject();
|
|
||||||
e.type = type;
|
|
||||||
if (params) {
|
|
||||||
e.bubbles = Boolean(params.bubbles);
|
|
||||||
e.cancelable = Boolean(params.cancelable);
|
|
||||||
e.detail = params.detail;
|
|
||||||
} else {
|
|
||||||
e.bubbles = false;
|
|
||||||
e.cancelable = false;
|
|
||||||
e.detail = void 0;
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
|
||||||
|
|
||||||
},{}],9:[function(require,module,exports){
|
|
||||||
var si = typeof setImmediate === 'function', tick;
|
|
||||||
if (si) {
|
|
||||||
tick = function (fn) { setImmediate(fn); };
|
|
||||||
} else {
|
|
||||||
tick = function (fn) { setTimeout(fn, 0); };
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = tick;
|
|
||||||
},{}]},{},[2])(2)
|
|
||||||
});
|
|
1384
app/vendor/sortable.js
vendored
Normal file
1384
app/vendor/sortable.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -276,6 +276,14 @@ func DeleteDocument(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = p.DeletePinnedDocument(documentID)
|
||||||
|
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
log.IfErr(tx.Rollback())
|
||||||
|
writeServerError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.IfErr(tx.Commit())
|
log.IfErr(tx.Commit())
|
||||||
|
|
||||||
writeSuccessEmptyJSON(w)
|
writeSuccessEmptyJSON(w)
|
||||||
|
|
|
@ -319,6 +319,14 @@ func RemoveFolder(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = p.DeletePinnedSpace(id)
|
||||||
|
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
log.IfErr(tx.Rollback())
|
||||||
|
writeServerError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.IfErr(tx.Commit())
|
log.IfErr(tx.Commit())
|
||||||
|
|
||||||
writeSuccessString(w, "{}")
|
writeSuccessString(w, "{}")
|
||||||
|
|
235
core/api/endpoint/pin_endpoint.go
Normal file
235
core/api/endpoint/pin_endpoint.go
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/api/entity"
|
||||||
|
"github.com/documize/community/core/api/request"
|
||||||
|
"github.com/documize/community/core/api/util"
|
||||||
|
"github.com/documize/community/core/log"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddPin saves pinned item.
|
||||||
|
func AddPin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "AddPin"
|
||||||
|
p := request.GetPersister(r)
|
||||||
|
params := mux.Vars(r)
|
||||||
|
userID := params["userID"]
|
||||||
|
|
||||||
|
if !p.Context.Authenticated {
|
||||||
|
writeForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(userID) == 0 {
|
||||||
|
writeMissingDataError(w, method, "userID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writePayloadError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pin entity.Pin
|
||||||
|
err = json.Unmarshal(body, &pin)
|
||||||
|
if err != nil {
|
||||||
|
writePayloadError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pin.RefID = util.UniqueID()
|
||||||
|
pin.OrgID = p.Context.OrgID
|
||||||
|
pin.UserID = p.Context.UserID
|
||||||
|
pin.Pin = strings.TrimSpace(pin.Pin)
|
||||||
|
if len(pin.Pin) > 20 {
|
||||||
|
pin.Pin = pin.Pin[0:20]
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := request.Db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
writeTransactionError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Context.Transaction = tx
|
||||||
|
|
||||||
|
err = p.AddPin(pin)
|
||||||
|
if err != nil {
|
||||||
|
log.IfErr(tx.Rollback())
|
||||||
|
writeGeneralSQLError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.IfErr(tx.Commit())
|
||||||
|
|
||||||
|
newPin, err := p.GetPin(pin.RefID)
|
||||||
|
if err != nil {
|
||||||
|
writeGeneralSQLError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSON(w, newPin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPins returns users' pins.
|
||||||
|
func GetUserPins(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "GetUserPins"
|
||||||
|
p := request.GetPersister(r)
|
||||||
|
params := mux.Vars(r)
|
||||||
|
userID := params["userID"]
|
||||||
|
|
||||||
|
if len(userID) == 0 {
|
||||||
|
writeMissingDataError(w, method, "userID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Context.UserID != userID {
|
||||||
|
writeForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pins, err := p.GetUserPins(userID)
|
||||||
|
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
writeGeneralSQLError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
pins = []entity.Pin{}
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(pins)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeJSONMarshalError(w, method, "pin", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessBytes(w, json)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUserPin removes saved user pin.
|
||||||
|
func DeleteUserPin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "DeleteUserPin"
|
||||||
|
p := request.GetPersister(r)
|
||||||
|
params := mux.Vars(r)
|
||||||
|
userID := params["userID"]
|
||||||
|
pinID := params["pinID"]
|
||||||
|
|
||||||
|
if len(userID) == 0 {
|
||||||
|
writeMissingDataError(w, method, "userID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pinID) == 0 {
|
||||||
|
writeMissingDataError(w, method, "pinID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Context.UserID != userID {
|
||||||
|
writeForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := request.Db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
writeTransactionError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Context.Transaction = tx
|
||||||
|
|
||||||
|
_, err = p.DeletePin(pinID)
|
||||||
|
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
log.IfErr(tx.Rollback())
|
||||||
|
writeGeneralSQLError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.IfErr(tx.Commit())
|
||||||
|
|
||||||
|
util.WriteSuccessEmptyJSON(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePinSequence records order of pinned items.
|
||||||
|
func UpdatePinSequence(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "UpdatePinSequence"
|
||||||
|
p := request.GetPersister(r)
|
||||||
|
params := mux.Vars(r)
|
||||||
|
userID := params["userID"]
|
||||||
|
|
||||||
|
if !p.Context.Authenticated {
|
||||||
|
writeForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(userID) == 0 {
|
||||||
|
writeMissingDataError(w, method, "userID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
writePayloadError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pins []string
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &pins)
|
||||||
|
if err != nil {
|
||||||
|
writePayloadError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := request.Db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
writeTransactionError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Context.Transaction = tx
|
||||||
|
|
||||||
|
for k, v := range pins {
|
||||||
|
err = p.UpdatePinSequence(v, k+1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.IfErr(tx.Rollback())
|
||||||
|
writeGeneralSQLError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.IfErr(tx.Commit())
|
||||||
|
|
||||||
|
newPins, err := p.GetUserPins(userID)
|
||||||
|
if err != nil {
|
||||||
|
writeGeneralSQLError(w, method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSON(w, newPins)
|
||||||
|
}
|
|
@ -221,6 +221,12 @@ func init() {
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"GET", "OPTIONS"}, nil, GetGlobalConfig))
|
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"GET", "OPTIONS"}, nil, GetGlobalConfig))
|
||||||
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"PUT", "OPTIONS"}, nil, SaveGlobalConfig))
|
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"PUT", "OPTIONS"}, nil, SaveGlobalConfig))
|
||||||
|
|
||||||
|
// Pinned items
|
||||||
|
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}", []string{"POST", "OPTIONS"}, nil, AddPin))
|
||||||
|
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}", []string{"GET", "OPTIONS"}, nil, GetUserPins))
|
||||||
|
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, UpdatePinSequence))
|
||||||
|
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, DeleteUserPin))
|
||||||
|
|
||||||
// Single page app handler
|
// Single page app handler
|
||||||
log.IfErr(Add(RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, GetRobots))
|
log.IfErr(Add(RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, GetRobots))
|
||||||
log.IfErr(Add(RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, GetSitemap))
|
log.IfErr(Add(RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, GetSitemap))
|
||||||
|
|
|
@ -385,3 +385,14 @@ type LinkCandidate struct {
|
||||||
Title string `json:"title"` // what we label the link
|
Title string `json:"title"` // what we label the link
|
||||||
Context string `json:"context"` // additional context (e.g. excerpt, parent, file extension)
|
Context string `json:"context"` // additional context (e.g. excerpt, parent, file extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pin defines a saved link to a document or space
|
||||||
|
type Pin struct {
|
||||||
|
BaseEntity
|
||||||
|
OrgID string `json:"orgId"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
FolderID string `json:"folderId"`
|
||||||
|
DocumentID string `json:"documentId"`
|
||||||
|
Pin string `json:"pin"`
|
||||||
|
Sequence int `json:"sequence"`
|
||||||
|
}
|
||||||
|
|
|
@ -16,13 +16,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
|
|
||||||
"github.com/documize/community/core/api/endpoint/models"
|
"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/api/util"
|
"github.com/documize/community/core/api/util"
|
||||||
"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/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddPage inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
|
// AddPage inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
|
||||||
|
|
145
core/api/request/pin.go
Normal file
145
core/api/request/pin.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/api/entity"
|
||||||
|
"github.com/documize/community/core/log"
|
||||||
|
"github.com/documize/community/core/utility"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddPin saves pinned item.
|
||||||
|
func (p *Persister) AddPin(pin entity.Pin) (err error) {
|
||||||
|
|
||||||
|
row := Db.QueryRow("SELECT max(sequence) FROM pin WHERE orgid=? AND userid=?", p.Context.OrgID, p.Context.UserID)
|
||||||
|
var maxSeq int
|
||||||
|
err = row.Scan(&maxSeq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
maxSeq = 99
|
||||||
|
}
|
||||||
|
|
||||||
|
pin.Created = time.Now().UTC()
|
||||||
|
pin.Revised = time.Now().UTC()
|
||||||
|
pin.Sequence = maxSeq + 1
|
||||||
|
|
||||||
|
stmt, err := p.Context.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||||
|
defer utility.Close(stmt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to prepare insert for pin", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec(pin.RefID, pin.OrgID, pin.UserID, pin.FolderID, pin.DocumentID, pin.Pin, pin.Sequence, pin.Created, pin.Revised)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to execute insert for pin", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPin returns requested pinned item.
|
||||||
|
func (p *Persister) GetPin(id string) (pin entity.Pin, err error) {
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
stmt, err := Db.Preparex("SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND refid=?")
|
||||||
|
defer utility.Close(stmt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("Unable to prepare select for pin %s", id), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Get(&pin, p.Context.OrgID, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("Unable to execute select for pin %s", id), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPins returns pinned items for specified user.
|
||||||
|
func (p *Persister) GetUserPins(userID string) (pins []entity.Pin, err error) {
|
||||||
|
err = Db.Select(&pins, "SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND userid=? ORDER BY sequence", p.Context.OrgID, userID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("Unable to execute select pin for org %s and user %s", p.Context.OrgID, userID), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePin updates existing pinned item.
|
||||||
|
func (p *Persister) UpdatePin(pin entity.Pin) (err error) {
|
||||||
|
err = nil
|
||||||
|
pin.Revised = time.Now().UTC()
|
||||||
|
|
||||||
|
var stmt *sqlx.NamedStmt
|
||||||
|
stmt, err = p.Context.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid")
|
||||||
|
defer utility.Close(stmt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("Unable to prepare update for pin %s", pin.RefID), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec(&pin)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("Unable to execute update for pin %s", pin.RefID), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePinSequence updates existing pinned item sequence number
|
||||||
|
func (p *Persister) UpdatePinSequence(pinID string, sequence int) (err error) {
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
stmt, err := p.Context.Transaction.Preparex("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?")
|
||||||
|
defer utility.Close(stmt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(fmt.Sprintf("Unable to prepare update for pin sequence %s", pinID), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec(sequence, time.Now().UTC(), p.Context.OrgID, p.Context.UserID, pinID)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePin removes folder from the store.
|
||||||
|
func (p *Persister) DeletePin(id string) (rows int64, err error) {
|
||||||
|
return p.Base.DeleteConstrained(p.Context.Transaction, "pin", p.Context.OrgID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePinnedSpace removes any pins for specified space.
|
||||||
|
func (p *Persister) DeletePinnedSpace(spaceID string) (rows int64, err error) {
|
||||||
|
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND labelid=\"%s\"", p.Context.OrgID, spaceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePinnedDocument removes any pins for specified document.
|
||||||
|
func (p *Persister) DeletePinnedDocument(documentID string) (rows int64, err error) {
|
||||||
|
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND documentid=\"%s\"", p.Context.OrgID, documentID))
|
||||||
|
}
|
|
@ -351,3 +351,21 @@ CREATE TABLE IF NOT EXISTS `participant` (
|
||||||
INDEX `idx_participant_documentid` (`documentid` ASC))
|
INDEX `idx_participant_documentid` (`documentid` ASC))
|
||||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||||
ENGINE = InnoDB;
|
ENGINE = InnoDB;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `pin`;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `pin` (
|
||||||
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||||
|
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||||
|
`userid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`labelid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`documentid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`sequence` INT UNSIGNED NOT NULL DEFAULT 99,
|
||||||
|
`pin` CHAR(20) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT pk_id PRIMARY KEY (id),
|
||||||
|
INDEX `idx_pin_userid` (`userid` ASC))
|
||||||
|
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||||
|
ENGINE = InnoDB;
|
||||||
|
|
18
core/database/scripts/autobuild/db_00008.sql
Normal file
18
core/database/scripts/autobuild/db_00008.sql
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/* community edition */
|
||||||
|
DROP TABLE IF EXISTS `pin`;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `pin` (
|
||||||
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||||
|
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||||
|
`userid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`labelid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`documentid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`sequence` INT UNSIGNED NOT NULL DEFAULT 99,
|
||||||
|
`pin` CHAR(20) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||||
|
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT pk_id PRIMARY KEY (id),
|
||||||
|
INDEX `idx_pin_userid` (`userid` ASC))
|
||||||
|
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||||
|
ENGINE = InnoDB;
|
|
@ -26,7 +26,7 @@ type ProdInfo struct {
|
||||||
// Product returns product edition details
|
// Product returns product edition details
|
||||||
func Product() (p ProdInfo) {
|
func Product() (p ProdInfo) {
|
||||||
p.Major = "0"
|
p.Major = "0"
|
||||||
p.Minor = "33"
|
p.Minor = "34"
|
||||||
p.Patch = "0"
|
p.Patch = "0"
|
||||||
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
|
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
|
||||||
p.Edition = "Community"
|
p.Edition = "Community"
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue