mirror of
https://github.com/documize/community.git
synced 2025-07-24 07:39:43 +02:00
commit
aad1ffb063
54 changed files with 2496 additions and 984 deletions
|
@ -1,49 +1,49 @@
|
|||
{
|
||||
"predef": [
|
||||
"server",
|
||||
"document",
|
||||
"window",
|
||||
"-Promise",
|
||||
"moment",
|
||||
"$",
|
||||
"_",
|
||||
"is",
|
||||
"Mousetrap",
|
||||
"CodeMirror",
|
||||
"Intercom",
|
||||
"Materialize",
|
||||
"tinymce",
|
||||
"Tether",
|
||||
"Tooltip",
|
||||
"Drop",
|
||||
"Dropzone",
|
||||
"dragula",
|
||||
"datetimepicker",
|
||||
"Waypoint"
|
||||
],
|
||||
"browser": true,
|
||||
"boss": true,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false,
|
||||
"eqnull": true,
|
||||
"esnext": true,
|
||||
"unused": true
|
||||
"predef": [
|
||||
"server",
|
||||
"document",
|
||||
"window",
|
||||
"-Promise",
|
||||
"moment",
|
||||
"$",
|
||||
"_",
|
||||
"is",
|
||||
"Mousetrap",
|
||||
"CodeMirror",
|
||||
"Intercom",
|
||||
"Materialize",
|
||||
"tinymce",
|
||||
"Tether",
|
||||
"Tooltip",
|
||||
"Drop",
|
||||
"Dropzone",
|
||||
"dragula",
|
||||
"datetimepicker",
|
||||
"Waypoint"
|
||||
],
|
||||
"browser": true,
|
||||
"boss": true,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false,
|
||||
"eqnull": true,
|
||||
"esnext": true,
|
||||
"unused": true
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
documentService: Ember.inject.service('document'),
|
||||
sectionService: Ember.inject.service('section'),
|
||||
appMeta: Ember.inject.service(),
|
||||
link: Ember.inject.service(),
|
||||
/* Parameters */
|
||||
document: null,
|
||||
// pages: [],
|
||||
|
@ -50,6 +51,10 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
});
|
||||
},
|
||||
|
||||
didRender() {
|
||||
this.contentLinkHandler();
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this.destroyTooltips();
|
||||
|
||||
|
@ -60,6 +65,39 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
}
|
||||
},
|
||||
|
||||
contentLinkHandler() {
|
||||
let links = this.get('link');
|
||||
let doc = this.get('document');
|
||||
let self = this;
|
||||
|
||||
$("a[data-documize='true']").off('click').on('click', function(e) {
|
||||
let link = links.getLinkObject(self.get('meta.outboundLinks'), this);
|
||||
|
||||
// local link? exists?
|
||||
if (link.linkType === "section" && link.documentId === doc.get('id')) {
|
||||
let exists = self.get('pages').findBy('id', link.targetId);
|
||||
|
||||
if (_.isUndefined(exists)) {
|
||||
link.orphan = true;
|
||||
} else {
|
||||
self.attrs.gotoPage(link.targetId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (link.orphan) {
|
||||
$(this).addClass('broken-link');
|
||||
self.showNotification('Broken link!');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
links.linkClick(doc, link);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
confirmDeleteAttachment(id, name) {
|
||||
this.set('deleteAttachment', {
|
||||
|
|
133
app/app/components/document/edit-tools.js
Normal file
133
app/app/components/document/edit-tools.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
// 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';
|
||||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
||||
const {
|
||||
inject: { service }
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Component.extend(TooltipMixin, {
|
||||
link: service(),
|
||||
linkName: '',
|
||||
keywords: '',
|
||||
selection: null,
|
||||
matches: {
|
||||
documents: [],
|
||||
pages: [],
|
||||
attachments: []
|
||||
},
|
||||
tabs: [
|
||||
{ label: 'Section', selected: true },
|
||||
{ label: 'Attachment', selected: false },
|
||||
{ label: 'Search', selected: false }
|
||||
],
|
||||
|
||||
showSections: Ember.computed('tabs.@each.selected', function () {
|
||||
return this.get('tabs').findBy('label', 'Section').selected;
|
||||
}),
|
||||
showAttachments: Ember.computed('tabs.@each.selected', function () {
|
||||
return this.get('tabs').findBy('label', 'Attachment').selected;
|
||||
}),
|
||||
showSearch: Ember.computed('tabs.@each.selected', function () {
|
||||
return this.get('tabs').findBy('label', 'Search').selected;
|
||||
}),
|
||||
hasMatches: Ember.computed('matches', function () {
|
||||
let m = this.get('matches');
|
||||
return m.documents.length || m.pages.length || m.attachments.length;
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
let self = this;
|
||||
|
||||
let folderId = this.get('folder.id');
|
||||
let documentId = this.get('document.id');
|
||||
let pageId = this.get('page.id');
|
||||
|
||||
this.get('link').getCandidates(folderId, documentId, pageId).then(function (candidates) {
|
||||
self.set('candidates', candidates);
|
||||
self.set('hasSections', is.not.null(candidates.pages) && candidates.pages.length);
|
||||
self.set('hasAttachments', is.not.null(candidates.attachments) && candidates.attachments.length);
|
||||
});
|
||||
},
|
||||
|
||||
didRender() {
|
||||
this.addTooltip(document.getElementById("content-linker-button"));
|
||||
this.addTooltip(document.getElementById("content-counter-button"));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this.destroyTooltips();
|
||||
},
|
||||
|
||||
onKeywordChange: function () {
|
||||
Ember.run.debounce(this, this.fetch, 750);
|
||||
}.observes('keywords'),
|
||||
|
||||
fetch() {
|
||||
let keywords = this.get('keywords');
|
||||
let self = this;
|
||||
|
||||
if (_.isEmpty(keywords)) {
|
||||
this.set('matches', { documents: [], pages: [], attachments: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('link').searchCandidates(keywords).then(function (matches) {
|
||||
self.set('matches', matches);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
setSelection(i) {
|
||||
let candidates = this.get('candidates');
|
||||
let matches = this.get('matches');
|
||||
|
||||
this.set('selection', i);
|
||||
|
||||
candidates.pages.forEach(c => {
|
||||
Ember.set(c, 'selected', c.id === i.id);
|
||||
});
|
||||
|
||||
candidates.attachments.forEach(c => {
|
||||
Ember.set(c, 'selected', c.id === i.id);
|
||||
});
|
||||
|
||||
matches.documents.forEach(c => {
|
||||
Ember.set(c, 'selected', c.id === i.id);
|
||||
});
|
||||
|
||||
matches.pages.forEach(c => {
|
||||
Ember.set(c, 'selected', c.id === i.id);
|
||||
});
|
||||
|
||||
matches.attachments.forEach(c => {
|
||||
Ember.set(c, 'selected', c.id === i.id);
|
||||
});
|
||||
},
|
||||
|
||||
onInsertLink() {
|
||||
let selection = this.get('selection');
|
||||
|
||||
if (is.null(selection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.get('onInsertLink')(selection);
|
||||
},
|
||||
|
||||
onTabSelect(tabs) {
|
||||
this.set('tabs', tabs);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -14,67 +14,63 @@ import NotifierMixin from '../../mixins/notifier';
|
|||
import TooltipMixin from '../../mixins/tooltip';
|
||||
|
||||
const {
|
||||
computed
|
||||
computed
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
||||
folderService: Ember.inject.service('folder'),
|
||||
documentService: Ember.inject.service('document'),
|
||||
session: Ember.inject.service(),
|
||||
documentService: Ember.inject.service('document'),
|
||||
session: Ember.inject.service(),
|
||||
appMeta: Ember.inject.service(),
|
||||
|
||||
showToolbar: false,
|
||||
folder: {},
|
||||
busy: false,
|
||||
importedDocuments: [],
|
||||
isFolderOwner: computed.equal('folder.userId', 'session.user.id'),
|
||||
moveFolderId: "",
|
||||
folder: {},
|
||||
busy: false,
|
||||
importedDocuments: [],
|
||||
isFolderOwner: computed.equal('folder.userId', 'session.user.id'),
|
||||
moveFolderId: "",
|
||||
drop: null,
|
||||
|
||||
didReceiveAttrs() {
|
||||
this.set('isFolderOwner', this.get('folder.userId') === this.get("session.user.id"));
|
||||
didReceiveAttrs() {
|
||||
this.set('isFolderOwner', this.get('folder.userId') === this.get("session.user.id"));
|
||||
|
||||
let show = this.get('isFolderOwner') || this.get('hasSelectedDocuments') || this.get('folderService').get('canEditCurrentFolder');
|
||||
this.set('showToolbar', show);
|
||||
|
||||
let targets = _.reject(this.get('folders'), {
|
||||
id: this.get('folder').get('id')
|
||||
});
|
||||
let targets = _.reject(this.get('folders'), {
|
||||
id: this.get('folder').get('id')
|
||||
});
|
||||
|
||||
this.set('movedFolderOptions', targets);
|
||||
},
|
||||
this.set('movedFolderOptions', targets);
|
||||
},
|
||||
|
||||
didRender() {
|
||||
if (this.get('hasSelectedDocuments')) {
|
||||
this.addTooltip(document.getElementById("move-documents-button"));
|
||||
this.addTooltip(document.getElementById("delete-documents-button"));
|
||||
} else {
|
||||
if (this.get('isFolderOwner')) {
|
||||
this.addTooltip(document.getElementById("folder-share-button"));
|
||||
this.addTooltip(document.getElementById("folder-settings-button"));
|
||||
}
|
||||
if (this.get('folderService').get('canEditCurrentFolder')) {
|
||||
if (this.get('hasSelectedDocuments')) {
|
||||
this.addTooltip(document.getElementById("move-documents-button"));
|
||||
this.addTooltip(document.getElementById("delete-documents-button"));
|
||||
} else {
|
||||
if (this.get('isFolderOwner')) {
|
||||
this.addTooltip(document.getElementById("folder-share-button"));
|
||||
this.addTooltip(document.getElementById("folder-settings-button"));
|
||||
}
|
||||
if (this.get('folderService').get('canEditCurrentFolder')) {
|
||||
this.addTooltip(document.getElementById("import-document-button"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didUpdate() {
|
||||
this.setupImport();
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
// this.setupImport();
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
willDestroyElement() {
|
||||
if (is.not.null(this.get('drop'))) {
|
||||
this.get('drop').destroy();
|
||||
this.set('drop', null);
|
||||
}
|
||||
|
||||
this.destroyTooltips();
|
||||
},
|
||||
this.destroyTooltips();
|
||||
},
|
||||
|
||||
setupImport() {
|
||||
// guard against unecessary file upload component init
|
||||
|
@ -135,52 +131,52 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
this.set('drop', dzone);
|
||||
},
|
||||
|
||||
actions: {
|
||||
onDocumentImporting(filename) {
|
||||
this.send("showNotification", `Importing ${filename}`);
|
||||
actions: {
|
||||
onDocumentImporting(filename) {
|
||||
this.send("showNotification", `Importing ${filename}`);
|
||||
|
||||
let documents = this.get('importedDocuments');
|
||||
documents.push(filename);
|
||||
this.set('importedDocuments', documents);
|
||||
},
|
||||
let documents = this.get('importedDocuments');
|
||||
documents.push(filename);
|
||||
this.set('importedDocuments', documents);
|
||||
},
|
||||
|
||||
onDocumentImported(filename /*, document*/ ) {
|
||||
this.send("showNotification", `${filename} ready`);
|
||||
onDocumentImported(filename /*, document*/ ) {
|
||||
this.send("showNotification", `${filename} ready`);
|
||||
|
||||
let documents = this.get('importedDocuments');
|
||||
documents.pop(filename);
|
||||
this.set('importedDocuments', documents);
|
||||
let documents = this.get('importedDocuments');
|
||||
documents.pop(filename);
|
||||
this.set('importedDocuments', documents);
|
||||
|
||||
this.attrs.refresh();
|
||||
this.attrs.refresh();
|
||||
|
||||
if (documents.length === 0) {
|
||||
// this.get('showDocument')(this.get('folder'), document);
|
||||
}
|
||||
},
|
||||
if (documents.length === 0) {
|
||||
// this.get('showDocument')(this.get('folder'), document);
|
||||
}
|
||||
},
|
||||
|
||||
deleteDocuments() {
|
||||
deleteDocuments() {
|
||||
this.attrs.onDeleteDocument();
|
||||
},
|
||||
},
|
||||
|
||||
setMoveFolder(folderId) {
|
||||
this.set('moveFolderId', folderId);
|
||||
setMoveFolder(folderId) {
|
||||
this.set('moveFolderId', folderId);
|
||||
|
||||
let folders = this.get('folders');
|
||||
let folders = this.get('folders');
|
||||
|
||||
folders.forEach(folder => {
|
||||
folder.set('selected', folder.id === folderId);
|
||||
});
|
||||
},
|
||||
folders.forEach(folder => {
|
||||
folder.set('selected', folder.id === folderId);
|
||||
});
|
||||
},
|
||||
|
||||
moveDocuments() {
|
||||
if (this.get("moveFolderId") === "") {
|
||||
return false;
|
||||
}
|
||||
moveDocuments() {
|
||||
if (this.get("moveFolderId") === "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.attrs.onMoveDocument(this.get('moveFolderId'));
|
||||
this.set("moveFolderId", "");
|
||||
this.attrs.onMoveDocument(this.get('moveFolderId'));
|
||||
this.set("moveFolderId", "");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
import Ember from 'ember';
|
||||
import miscUtil from '../../../utils/misc';
|
||||
|
||||
const {
|
||||
inject: { service }
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
link: service(),
|
||||
|
||||
isDirty: false,
|
||||
pageBody: "",
|
||||
|
||||
|
@ -45,6 +52,15 @@ export default Ember.Component.extend({
|
|||
},
|
||||
|
||||
actions: {
|
||||
onInsertLink(link) {
|
||||
let linkMarkdown = this.get('link').buildLink(link);
|
||||
|
||||
miscUtil.insertAtCursor($("#section-markdown-editor")[0], linkMarkdown);
|
||||
this.set('pageBody', $("#section-markdown-editor").val());
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isDirty() {
|
||||
return this.get('isDirty');
|
||||
},
|
||||
|
@ -62,4 +78,4 @@ export default Ember.Component.extend({
|
|||
this.attrs.onAction(page, meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,30 +11,37 @@
|
|||
|
||||
import Ember from 'ember';
|
||||
|
||||
const {
|
||||
inject: { service }
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Component.extend({
|
||||
appMeta: service(),
|
||||
link: service(),
|
||||
pageBody: "",
|
||||
appMeta: Ember.inject.service(),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this.set('pageBody', this.get('meta.rawBody'));
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
let maxHeight = $(document).height() - $(".document-editor > .toolbar").height() - 200;
|
||||
|
||||
let options = {
|
||||
selector: "#rich-text-editor",
|
||||
relative_urls: false,
|
||||
cache_suffix: "?v=430",
|
||||
cache_suffix: "?v=443",
|
||||
browser_spellcheck: false,
|
||||
gecko_spellcheck: false,
|
||||
theme: "modern",
|
||||
statusbar: false,
|
||||
height: $(document).height() - $(".document-editor > .toolbar").height() - 200,
|
||||
height: maxHeight,
|
||||
entity_encoding: "raw",
|
||||
paste_data_images: true,
|
||||
image_advtab: true,
|
||||
image_caption: true,
|
||||
media_live_embeds: true,
|
||||
fontsize_formats: "8pt 10pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 28pt 30pt 32pt 34pt 36pt",
|
||||
fontsize_formats: "8px 10px 12px 14px 18px 24px 36px 40px 50px 60px",
|
||||
formats: {
|
||||
bold: {
|
||||
inline: 'b'
|
||||
|
@ -48,27 +55,12 @@ export default Ember.Component.extend({
|
|||
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
|
||||
'searchreplace wordcount visualblocks visualchars code codesample fullscreen',
|
||||
'insertdatetime media nonbreaking save table directionality',
|
||||
'emoticons template paste textcolor colorpicker textpattern imagetools'
|
||||
'template paste textcolor colorpicker textpattern imagetools'
|
||||
],
|
||||
menu: {
|
||||
edit: {
|
||||
title: 'Edit',
|
||||
items: 'undo redo | cut copy paste pastetext | selectall | searchreplace'
|
||||
},
|
||||
insert: {
|
||||
title: 'Insert',
|
||||
items: 'anchor link media | hr | charmap emoticons | blockquote'
|
||||
},
|
||||
format: {
|
||||
title: 'Format',
|
||||
items: 'bold italic underline strikethrough superscript subscript | formats fonts | removeformat'
|
||||
},
|
||||
table: {
|
||||
title: 'Table',
|
||||
items: 'inserttable tableprops deletetable | cell row column'
|
||||
}
|
||||
},
|
||||
toolbar1: "formatselect fontselect fontsizeselect | bold italic underline | link unlink | image media | codesample | outdent indent | alignleft aligncenter alignright alignjustify | bullist numlist | forecolor backcolor",
|
||||
menu: {},
|
||||
menubar: false,
|
||||
toolbar1: "bold italic underline strikethrough superscript subscript | outdent indent bullist numlist forecolor backcolor | alignleft aligncenter alignright alignjustify | link unlink | table image media | hr codesample",
|
||||
toolbar2: "formatselect fontselect fontsizeselect",
|
||||
save_onsavecallback: function () {
|
||||
Mousetrap.trigger('ctrl+s');
|
||||
}
|
||||
|
@ -91,6 +83,19 @@ export default Ember.Component.extend({
|
|||
},
|
||||
|
||||
actions: {
|
||||
onInsertLink(link) {
|
||||
let userSelection = tinymce.activeEditor.selection.getContent();
|
||||
|
||||
if (is.not.empty(userSelection)) {
|
||||
Ember.set(link, 'title', userSelection);
|
||||
}
|
||||
|
||||
let linkHTML = this.get('link').buildLink(link);
|
||||
tinymce.activeEditor.insertContent(linkHTML);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isDirty() {
|
||||
return is.not.undefined(tinymce) && is.not.undefined(tinymce.activeEditor) && tinymce.activeEditor.isDirty();
|
||||
},
|
||||
|
|
16
app/app/components/ui/ui-checkbox.js
Normal file
16
app/app/components/ui/ui-checkbox.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
// 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';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span'
|
||||
});
|
15
app/app/components/ui/ui-selection.js
Normal file
15
app/app/components/ui/ui-selection.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
// 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';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
});
|
32
app/app/components/ui/ui-tab.js
Normal file
32
app/app/components/ui/ui-tab.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
myWidth: Ember.computed('tabs', function() {
|
||||
let count = this.get('tabs.length');
|
||||
let width = 95 / count;
|
||||
return Ember.String.htmlSafe("width: " + `${width}%;`);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
onTabSelect(tab) {
|
||||
this.get('tabs').forEach(t => {
|
||||
Ember.set(t, 'selected', false);
|
||||
});
|
||||
|
||||
Ember.set(tab, 'selected', true);
|
||||
|
||||
this.attrs.onTabSelect(this.get('tabs'));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
|
@ -18,6 +18,6 @@ export function initialize(application) {
|
|||
|
||||
export default {
|
||||
name: 'eventBus',
|
||||
after: 'session',
|
||||
after: 'application',
|
||||
initialize: initialize
|
||||
};
|
||||
};
|
||||
|
|
20
app/app/initializers/route-injector.js
Normal file
20
app/app/initializers/route-injector.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
// 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
|
||||
|
||||
export function initialize(application) {
|
||||
application.inject('component', 'router', 'router:main');
|
||||
application.inject('service', 'router', 'router:main');
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'route-injector',
|
||||
initialize: initialize
|
||||
};
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
|
@ -16,7 +16,6 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
documentService: Ember.inject.service('document'),
|
||||
folderService: Ember.inject.service('folder'),
|
||||
userService: Ember.inject.service('user'),
|
||||
|
||||
pages: [],
|
||||
attachments: [],
|
||||
users: [],
|
||||
|
@ -107,14 +106,5 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
controller.set('meta', meta);
|
||||
|
||||
this.browser.setMetaDescription(model.get('excerpt'));
|
||||
},
|
||||
|
||||
// Document view needs all white background!
|
||||
activate() {
|
||||
Ember.$('html').addClass('background-color-white');
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
Ember.$('html').removeClass('background-color-white');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
onAttachmentUpload=(action 'onAttachmentUpload')
|
||||
onDocumentDelete=(action 'onDocumentDelete')}}
|
||||
|
||||
{{document/document-view document=model pages=pages attachments=attachments folder=folder folders=folders
|
||||
{{document/document-view document=model meta=meta pages=pages attachments=attachments folder=folder folders=folders
|
||||
isEditor=isEditor
|
||||
gotoPage=(action 'gotoPage')
|
||||
onAttachmentDeleted=(action 'onAttachmentDeleted')
|
||||
onDeletePage=(action 'onPageDeleted')}}
|
||||
{{/layout/zone-content}}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
|
@ -19,4 +19,13 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
this.audit.record("viewed-document");
|
||||
return this.get('documentService').getDocument(params.document_id);
|
||||
},
|
||||
});
|
||||
|
||||
actions: {
|
||||
error(error /*, transition*/ ) {
|
||||
if (error) {
|
||||
this.transitionTo('/not-found');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
|
@ -13,8 +13,7 @@ import Ember from 'ember';
|
|||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||
|
||||
export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
||||
|
||||
beforeModel: function () {
|
||||
beforeModel() {
|
||||
this.transitionTo('folders');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -101,5 +101,5 @@ export default Router.map(function () {
|
|||
path: '/*wildcard'
|
||||
});
|
||||
|
||||
this.route('pods', function () {});
|
||||
// this.route('pods', function () {});
|
||||
});
|
||||
|
|
|
@ -47,6 +47,7 @@ export default Ember.Service.extend({
|
|||
allowAnonymousAccess: true,
|
||||
setupMode: true
|
||||
});
|
||||
|
||||
this.get('localStorage').clearAll();
|
||||
|
||||
return resolve(this);
|
||||
|
|
|
@ -25,6 +25,7 @@ export default Ember.Service.extend({
|
|||
appId: config.APP.intercomKey,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.start();
|
||||
},
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
|
@ -28,5 +28,9 @@ export default Ember.Service.extend({
|
|||
}(wait, times);
|
||||
|
||||
setTimeout(interv, wait);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showNotification(msg) {
|
||||
this.get('eventBus').publish('notifyUser', msg);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import Ember from 'ember';
|
|||
|
||||
export default Ember.Service.extend(Ember.Evented, {
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
let _this = this;
|
||||
|
||||
window.addEventListener("scroll", _.throttle(function() {
|
||||
|
|
131
app/app/services/link.js
Normal file
131
app/app/services/link.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
// 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({
|
||||
sessionService: service('session'),
|
||||
ajax: service(),
|
||||
appMeta: service(),
|
||||
store: service(),
|
||||
|
||||
// Returns candidate links using provided parameters
|
||||
getCandidates(folderId, documentId, pageId) {
|
||||
return this.get('ajax').request(`links/${folderId}/${documentId}/${pageId}`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
// Returns keyword-based candidates
|
||||
searchCandidates(keywords) {
|
||||
let url = "links?keywords=" + encodeURIComponent(keywords);
|
||||
|
||||
return this.get('ajax').request(url, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
// getUsers returns all users for organization.
|
||||
find(keywords) {
|
||||
let url = "search?keywords=" + encodeURIComponent(keywords);
|
||||
|
||||
return this.get('ajax').request(url, {
|
||||
method: "GET"
|
||||
});
|
||||
},
|
||||
|
||||
buildLink(link) {
|
||||
let result = "";
|
||||
let href = "";
|
||||
let endpoint = this.get('appMeta').get('endpoint');
|
||||
let orgId = this.get('appMeta').get('orgId');
|
||||
|
||||
if (link.linkType === "section" || link.linkType === "document") {
|
||||
href = `/link/${link.linkType}/${link.id}`;
|
||||
result = `<a data-documize='true' data-link-space-id='${link.folderId}' data-link-id='${link.id}' data-link-target-document-id='${link.documentId}' data-link-target-id='${link.targetId}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`;
|
||||
}
|
||||
if (link.linkType === "file") {
|
||||
href = `${endpoint}/public/attachments/${orgId}/${link.targetId}`;
|
||||
result = `<a data-documize='true' data-link-space-id='${link.folderId}' data-link-id='${link.id}' data-link-target-document-id='${link.documentId}' data-link-target-id='${link.targetId}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getLinkObject(outboundLinks, a) {
|
||||
let link = {
|
||||
linkId: a.attributes["data-link-id"].value,
|
||||
linkType: a.attributes["data-link-type"].value,
|
||||
documentId: a.attributes["data-link-target-document-id"].value,
|
||||
folderId: a.attributes["data-link-space-id"].value,
|
||||
targetId: a.attributes["data-link-target-id"].value,
|
||||
url: a.attributes["href"].value,
|
||||
orphan: false
|
||||
};
|
||||
|
||||
link.orphan = _.isEmpty(link.linkId) || _.isEmpty(link.documentId) || _.isEmpty(link.folderId) || _.isEmpty(link.targetId);
|
||||
|
||||
// we check latest state of link using database data
|
||||
let existing = outboundLinks.findBy('id', link.linkId);
|
||||
|
||||
if (_.isUndefined(existing)) {
|
||||
link.orphan = true;
|
||||
} else {
|
||||
link.orphan = existing.orphan;
|
||||
}
|
||||
|
||||
return link;
|
||||
},
|
||||
|
||||
linkClick(doc, link) {
|
||||
if (link.orphan) {
|
||||
return;
|
||||
}
|
||||
|
||||
let router = this.get('router');
|
||||
let targetFolder = this.get('store').peekRecord('folder', link.folderId);
|
||||
let targetDocument = this.get('store').peekRecord('document', link.documentId);
|
||||
let folderSlug = is.null(targetFolder) ? "s" : targetFolder.get('slug');
|
||||
let documentSlug = is.null(targetDocument) ? "d" : targetDocument.get('slug');
|
||||
|
||||
// handle section link
|
||||
if (link.linkType === "section") {
|
||||
let options = {};
|
||||
options['page'] = link.targetId;
|
||||
router.transitionTo('document', link.folderId, folderSlug, link.documentId, documentSlug, { queryParams: options });
|
||||
return;
|
||||
}
|
||||
|
||||
// handle document link
|
||||
if (link.linkType === "document") {
|
||||
router.transitionTo('document', link.folderId, folderSlug, link.documentId, documentSlug);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle attachment links
|
||||
if (link.linkType === "file") {
|
||||
window.location.href = link.url;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
doc meta to show inbound and outbound links.
|
||||
*/
|
|
@ -41,6 +41,8 @@ export default SimpleAuthSession.extend({
|
|||
}),
|
||||
|
||||
init: function () {
|
||||
this._super(...arguments);
|
||||
|
||||
this.set('isMac', is.mac());
|
||||
this.set('isMobile', is.mobile());
|
||||
},
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
@import "color.scss";
|
||||
@import "font.scss";
|
||||
@import "functions.scss";
|
||||
|
@ -26,11 +25,11 @@
|
|||
@import "view/document/wysiwyg.scss";
|
||||
@import "view/document/editor.scss";
|
||||
@import "view/document/wizard.scss";
|
||||
@import "view/document/edit-tools.scss";
|
||||
@import "view/common.scss";
|
||||
@import "vendor.scss";
|
||||
@import "responsive.scss";
|
||||
@import "print.scss";
|
||||
|
||||
@import "section/trello.scss";
|
||||
@import "section/gemini.scss";
|
||||
@import "section/github.scss";
|
||||
|
|
|
@ -9,26 +9,100 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
.cursor-not-allowed { cursor: not-allowed !important; }
|
||||
.cursor-auto { cursor: auto; }
|
||||
.vertical-top { vertical-align: top; }
|
||||
.inline-block { display: inline-block; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
.text-center { text-align: center; }
|
||||
.center { margin: 0 auto; }
|
||||
.bold { font-weight: bold; }
|
||||
.italic { font-style: italic; }
|
||||
.text-uppercase { text-transform: uppercase; }
|
||||
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } // requires element to specify width
|
||||
.absolute-center { margin: auto; position: absolute; top: 0; left: 0; bottom: 0; right: 0; }
|
||||
.no-width { white-space: nowrap; width: 1%; }
|
||||
.no-float { float: none !important; }
|
||||
.no-overflow-x { overflow-x: visible !important; }
|
||||
input:-webkit-autofill { -webkit-box-shadow: 0 0 0px 1000px white inset; }
|
||||
img.responsive-img, video.responsive-video { max-width: 100%; height: auto; }
|
||||
.bordered { border: 1px solid $color-border; }
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.cursor-auto {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.vertical-top {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.text-uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
// requires element to specify width
|
||||
|
||||
.absolute-center {
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.no-width {
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
.no-float {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
.no-overflow-x {
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
|
||||
.no-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input:-webkit-autofill {
|
||||
-webkit-box-shadow: 0 0 0 1000px white inset;
|
||||
}
|
||||
|
||||
img.responsive-img,
|
||||
video.responsive-video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border: 1px solid $color-border;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow-y: scroll;
|
||||
|
@ -52,62 +126,97 @@ a {
|
|||
-webkit-font-smoothing: antialiased;
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
|
||||
|
||||
a:hover, a:focus {
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.broken-link {
|
||||
color: $color-red;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
a.alt {
|
||||
color: $color-blue;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
a:hover, a:focus {
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
$i: 150;
|
||||
@while $i > 0 {
|
||||
.margin-#{$i} { margin: #{$i}px; }
|
||||
.margin-top-#{$i} { margin-top: #{$i}px; }
|
||||
.margin-bottom-#{$i} { margin-bottom: #{$i}px; }
|
||||
.margin-right-#{$i} { margin-right: #{$i}px; }
|
||||
.margin-left-#{$i} { margin-left: #{$i}px; }
|
||||
.margin-#{$i} {
|
||||
margin: #{$i}px;
|
||||
}
|
||||
|
||||
.margin-top-#{$i} {
|
||||
margin-top: #{$i}px;
|
||||
}
|
||||
|
||||
.margin-bottom-#{$i} {
|
||||
margin-bottom: #{$i}px;
|
||||
}
|
||||
|
||||
.margin-right-#{$i} {
|
||||
margin-right: #{$i}px;
|
||||
}
|
||||
|
||||
.margin-left-#{$i} {
|
||||
margin-left: #{$i}px;
|
||||
}
|
||||
$i: $i - 5;
|
||||
}
|
||||
|
||||
$i: 150;
|
||||
@while $i > 0 {
|
||||
.padding-#{$i} { padding: #{$i}px; }
|
||||
.padding-top-#{$i} { padding-top: #{$i}px; }
|
||||
.padding-bottom-#{$i} { padding-bottom: #{$i}px; }
|
||||
.padding-right-#{$i} { padding-right: #{$i}px; }
|
||||
.padding-left-#{$i} { padding-left: #{$i}px; }
|
||||
.padding-#{$i} {
|
||||
padding: #{$i}px;
|
||||
}
|
||||
|
||||
.padding-top-#{$i} {
|
||||
padding-top: #{$i}px;
|
||||
}
|
||||
|
||||
.padding-bottom-#{$i} {
|
||||
padding-bottom: #{$i}px;
|
||||
}
|
||||
|
||||
.padding-right-#{$i} {
|
||||
padding-right: #{$i}px;
|
||||
}
|
||||
|
||||
.padding-left-#{$i} {
|
||||
padding-left: #{$i}px;
|
||||
}
|
||||
$i: $i - 5;
|
||||
}
|
||||
|
||||
$i: 100;
|
||||
@while $i > 0 {
|
||||
.width-#{$i} { width: #{$i}#{"%"}; }
|
||||
$i: $i - 5;
|
||||
.width-#{$i} {
|
||||
width: #{$i}#{"%"};
|
||||
}
|
||||
$i: $i - 1;
|
||||
}
|
||||
|
||||
.no-outline
|
||||
{
|
||||
outline:none !important;
|
||||
border:none !important;
|
||||
box-shadow:none !important;
|
||||
.no-outline {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:focus, &:active
|
||||
{
|
||||
border:none !important;
|
||||
box-shadow:none !important;
|
||||
&:active,
|
||||
&:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.no-select
|
||||
{
|
||||
.no-select {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
|
@ -127,11 +236,12 @@ ul {
|
|||
}
|
||||
}
|
||||
|
||||
.clearfix:before,
|
||||
.clearfix:after {
|
||||
.clearfix:after,
|
||||
.clearfix:before {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
|
|
44
app/app/styles/view/document/edit-tools.scss
Normal file
44
app/app/styles/view/document/edit-tools.scss
Normal file
|
@ -0,0 +1,44 @@
|
|||
.edit-tools {
|
||||
margin: 0 0 0 20px;
|
||||
min-height: 600px;
|
||||
|
||||
> .toolbar {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> .item {
|
||||
list-style-type: none;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-counter-dialog {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.content-linker-dialog {
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
overflow-y: auto;
|
||||
|
||||
.link-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.link-item {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
color: $color-gray;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
app/app/styles/widget/widget-checkbox.scss
Normal file
21
app/app/styles/widget/widget-checkbox.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
.checkbox-option {
|
||||
vertical-align: bottom;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
> .material-icons {
|
||||
font-size: 0.9rem;
|
||||
vertical-align: top;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $color-link;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-option-selected {
|
||||
color: $color-link;
|
||||
}
|
34
app/app/styles/widget/widget-selection.scss
Normal file
34
app/app/styles/widget/widget-selection.scss
Normal file
|
@ -0,0 +1,34 @@
|
|||
.widget-selection {
|
||||
> .option {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
text-align: left;
|
||||
@extend .no-select;
|
||||
cursor: pointer;
|
||||
// border: 1px solid $color-border;
|
||||
color: $color-off-black;
|
||||
position: relative;
|
||||
|
||||
> i.material-icons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include ease-in();
|
||||
background-color: $color-off-white;
|
||||
}
|
||||
|
||||
> .selected {
|
||||
background-color: $color-card-active !important;
|
||||
color: $color-primary !important;
|
||||
|
||||
> i.material-icons {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
29
app/app/styles/widget/widget-tab.scss
Normal file
29
app/app/styles/widget/widget-tab.scss
Normal file
|
@ -0,0 +1,29 @@
|
|||
.widget-tab {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
@extend .no-select;
|
||||
|
||||
> .tab {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
background-color: $color-off-white;
|
||||
color: $color-gray;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin-right: -3px;
|
||||
|
||||
&:hover {
|
||||
@include ease-in();
|
||||
background-color: $color-gray;
|
||||
color: $color-off-white;
|
||||
}
|
||||
}
|
||||
|
||||
> .selected {
|
||||
background-color: $color-gray;
|
||||
color: $color-off-white;
|
||||
}
|
||||
}
|
|
@ -2,62 +2,63 @@
|
|||
// Material Design icons from https://design.google.com/icons/
|
||||
|
||||
.material-icons {
|
||||
font-family : "Material Icons";
|
||||
font-weight : normal;
|
||||
font-style : normal;
|
||||
font-size : 1.2rem;
|
||||
display : inline-block;
|
||||
text-transform : none;
|
||||
letter-spacing : normal;
|
||||
word-wrap : normal;
|
||||
-webkit-font-smoothing : antialiased;
|
||||
text-rendering : optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings : "liga";
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 1.2rem;
|
||||
display: inline-block;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: "liga";
|
||||
}
|
||||
|
||||
.transition-shadow {
|
||||
transition: box-shadow .25s;
|
||||
transition: box-shadow 0.25s;
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition: all .25s;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.z-depth-0 {
|
||||
box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.z-depth-tiny {
|
||||
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05), 0 1px 1px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05), 0 1px 1px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.z-depth-half {
|
||||
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.16), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.16), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.z-depth-1 {
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.z-depth-1-half { /* used on hover states */
|
||||
box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15);
|
||||
.z-depth-1-half {
|
||||
/* used on hover states */
|
||||
box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.z-depth-2 {
|
||||
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.z-depth-3 {
|
||||
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19);
|
||||
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.z-depth-4 {
|
||||
box-shadow: 0 16px 28px 0 rgba(0, 0, 0, 0.22), 0 25px 55px 0 rgba(0, 0, 0, 0.21);
|
||||
box-shadow: 0 16px 28px 0 rgba(0, 0, 0, 0.22), 0 25px 55px 0 rgba(0, 0, 0, 0.21);
|
||||
}
|
||||
|
||||
.z-depth-5 {
|
||||
box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2), 0 40px 77px 0 rgba(0, 0, 0, 0.22);
|
||||
box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2), 0 40px 77px 0 rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
@import "widget-avatar";
|
||||
|
@ -70,3 +71,6 @@
|
|||
@import "widget-sidebar-menu";
|
||||
@import "widget-table";
|
||||
@import "widget-tooltip";
|
||||
@import "widget-checkbox";
|
||||
@import "widget-tab";
|
||||
@import "widget-selection";
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
{{#each attachments key="id" as |a index|}}
|
||||
<li class="item">
|
||||
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.extension}}" />
|
||||
<a href="{{ appMeta.endpoint }}/public/attachments/{{ appMeta.orgId }}/{{ a.job }}/{{ a.fileId }}">
|
||||
<a href="{{ appMeta.endpoint }}/public/attachments/{{ appMeta.orgId }}/{{ a.id }}">
|
||||
<span class="file">{{ a.filename }}</span>
|
||||
</a>
|
||||
{{#if isEditor}}
|
||||
|
@ -34,42 +34,41 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="pages">
|
||||
{{#each pages key="id" as |page index|}}
|
||||
<div class="wysiwyg">
|
||||
<div id="page-{{ page.id }}" class="is-a-page" data-id="{{ page.id }}" data-type="{{ page.contentType }}">
|
||||
{{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor onDeletePage=(action 'onDeletePage')}}
|
||||
{{section/base-renderer page=page}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="pages">
|
||||
{{#each pages key="id" as |page index|}}
|
||||
<div class="wysiwyg">
|
||||
<div id="page-{{ page.id }}" class="is-a-page" data-id="{{ page.id }}" data-type="{{ page.contentType }}">
|
||||
{{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor onDeletePage=(action 'onDeletePage')}} {{section/base-renderer page=page}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="dropdown-dialog delete-attachment-dialog">
|
||||
<div class="content">
|
||||
<p>Are you sure you want to delete <span class="bold">{{deleteAttachment.name}}?</span></p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="flat-button" {{action 'cancel'}}>
|
||||
cancel
|
||||
</div>
|
||||
<div class="flat-button flat-red" {{action 'deleteAttachment'}}>
|
||||
delete
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="dropdown-dialog delete-attachment-dialog">
|
||||
<div class="content">
|
||||
<p>Are you sure you want to delete <span class="bold">{{deleteAttachment.name}}?</span></p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="flat-button" {{action 'cancel'}}>
|
||||
cancel
|
||||
</div>
|
||||
<div class="flat-button flat-red" {{action 'deleteAttachment'}}>
|
||||
delete
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if noSections}}
|
||||
<div class="no-sections">
|
||||
<div class="box">
|
||||
<div class="message">Click the
|
||||
<div class="no-sections">
|
||||
<div class="box">
|
||||
<div class="message">Click the
|
||||
<div class="round-button-mono">
|
||||
<i class="material-icons color-gray">add</i>
|
||||
<i class="material-icons color-gray">add</i>
|
||||
<div class="name">section</div>
|
||||
</div>
|
||||
to add a new section to this document</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
to add a new section to this document</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
84
app/app/templates/components/document/edit-tools.hbs
Normal file
84
app/app/templates/components/document/edit-tools.hbs
Normal file
|
@ -0,0 +1,84 @@
|
|||
<div class="edit-tools">
|
||||
|
||||
<ul class="toolbar">
|
||||
<li class="item">
|
||||
<div class="square-button-mono button-gray" id="content-linker-button" data-tooltip="Reference link" data-tooltip-position="left middle">
|
||||
<i class="material-icons color-white">link</i>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{#dropdown-dialog target="content-linker-button" position="bottom right" button="Insert" color="flat-blue" onAction=(action 'onInsertLink')}}
|
||||
<div class="content-linker-dialog">
|
||||
<form>
|
||||
{{ui/ui-tab tabs=tabs onTabSelect=(action 'onTabSelect')}}
|
||||
|
||||
<div class="margin-top-40" />
|
||||
|
||||
{{#if showSections}}
|
||||
<ul class="link-list">
|
||||
{{#each candidates.pages as |p|}}
|
||||
<li class="link-item" {{ action 'setSelection' p }}>
|
||||
{{#ui/ui-selection selected=p.selected}}
|
||||
{{p.title}}
|
||||
{{/ui/ui-selection}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if showAttachments}}
|
||||
<ul class="link-list">
|
||||
{{#each candidates.attachments as |a|}}
|
||||
<li class="link-item" {{ action 'setSelection' a }}>
|
||||
{{#ui/ui-selection selected=a.selected}}
|
||||
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.context}}" />
|
||||
{{ a.title }}
|
||||
{{/ui/ui-selection}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if showSearch}}
|
||||
<div class="input-control">
|
||||
<label>Search</label>
|
||||
<div class="tip">For content or attachments</div>
|
||||
{{focus-input id="content-linker-search" type="input" value=keywords placeholder="keyword search" autocomplete="off"}}
|
||||
</div>
|
||||
{{#unless hasMatches}}
|
||||
Nothing found.
|
||||
{{/unless}}
|
||||
<ul class="link-list">
|
||||
{{#each matches.documents as |m|}}
|
||||
<li class="link-item" {{ action 'setSelection' m }}>
|
||||
{{#ui/ui-selection selected=m.selected}}
|
||||
{{m.title}}
|
||||
{{/ui/ui-selection}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#each matches.pages as |m|}}
|
||||
<li class="link-item" {{ action 'setSelection' m }}>
|
||||
{{#ui/ui-selection selected=m.selected}}
|
||||
{{m.title}}<br/><span class="color-gray">{{m.context}}</span>
|
||||
{{/ui/ui-selection}}
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#each matches.attachments as |a|}}
|
||||
<li class="link-item" {{ action 'setSelection' a }}>
|
||||
{{#ui/ui-selection selected=a.selected}}
|
||||
<img class="icon" src="/assets/img/attachments/{{document/file-icon a.context}}" />
|
||||
{{ a.title }}
|
||||
{{/ui/ui-selection}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
<div class="hide regular-button button-blue pull-right" {{ action 'onInsertLink' }}>Insert</div>
|
||||
<div class="hide clearfix" />
|
||||
</form>
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
|
||||
</div>
|
|
@ -4,12 +4,7 @@
|
|||
<i class="material-icons color-gray">close</i>
|
||||
</div>
|
||||
|
||||
{{folder/start-document
|
||||
savedTemplates=savedTemplates
|
||||
folder=folder
|
||||
editor=folderService.canEditCurrentFolder
|
||||
onEditTemplate=(action 'onEditTemplate')
|
||||
onDocumentTemplate=(action 'onDocumentTemplate')}}
|
||||
{{folder/start-document savedTemplates=savedTemplates folder=folder editor=folderService.canEditCurrentFolder onEditTemplate=(action 'onEditTemplate') onDocumentTemplate=(action 'onDocumentTemplate')}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showScrollTool}}
|
||||
|
@ -35,7 +30,7 @@
|
|||
<i class="material-icons">add</i>
|
||||
<div class="name">Space</div>
|
||||
</div>
|
||||
{{#dropdown-dialog target="add-space-button" position="bottom left" button="Add" color="flat-green" onAction=(action 'addFolder') focusOn="new-folder-name"}}
|
||||
{{#dropdown-dialog target="add-space-button" position="bottom left" button="Add" color="flat-green" onAction=(action 'addFolder') focusOn="new-folder-name" }}
|
||||
<div>
|
||||
<div class="input-control">
|
||||
<label>New space</label>
|
||||
|
@ -92,8 +87,6 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="copyright hidden-xs hidden-sm">
|
||||
<a href="https://documize.com?ref=app-footer" target="_blank">Copyright © 2016 Documize Inc.</a>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
{{#section/base-editor document=document folder=folder page=page isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6">
|
||||
{{focus-textarea id="section-markdown-editor" class="mousetrap bordered" value=pageBody}}
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="col-xs-5 col-sm-5 col-md-5 col-lg-5">
|
||||
<div id="section-markdown-preview" class="mousetrap bordered wysiwyg">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-1 col-sm-1 col-md-1 col-lg-1">
|
||||
{{document/edit-tools document=document folder=folder page=page onInsertLink=(action 'onInsertLink')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/section/base-editor}}
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
{{#section/base-editor document=document folder=folder page=page isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}}
|
||||
{{focus-textarea value=pageBody id="rich-text-editor" class="mousetrap"}}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-11 col-sm-11 col-md-11 col-lg-11">
|
||||
{{focus-textarea value=pageBody id="rich-text-editor" class="mousetrap"}}
|
||||
</div>
|
||||
|
||||
<div class="col-xs-1 col-sm-1 col-md-1 col-lg-1">
|
||||
{{document/edit-tools document=document folder=folder page=page onInsertLink=(action 'onInsertLink')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/section/base-editor}}
|
||||
|
|
8
app/app/templates/components/ui/ui-checkbox.hbs
Normal file
8
app/app/templates/components/ui/ui-checkbox.hbs
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="checkbox-option {{if selected 'checkbox-option-selected'}}">
|
||||
{{#if selected}}
|
||||
<i class="material-icons checkbox-option-selected">radio_button_checked</i>
|
||||
{{else}}
|
||||
<i class="material-icons">radio_button_unchecked</i>
|
||||
{{/if}}
|
||||
{{yield}}
|
||||
</div>
|
6
app/app/templates/components/ui/ui-selection.hbs
Normal file
6
app/app/templates/components/ui/ui-selection.hbs
Normal file
|
@ -0,0 +1,6 @@
|
|||
<div class="widget-selection">
|
||||
<div class="option {{if selected 'selected'}}">
|
||||
{{yield}}
|
||||
<i class="material-icons">check</i>
|
||||
</div>
|
||||
</div>
|
5
app/app/templates/components/ui/ui-tab.hbs
Normal file
5
app/app/templates/components/ui/ui-tab.hbs
Normal file
|
@ -0,0 +1,5 @@
|
|||
<ul class="widget-tab">
|
||||
{{#each tabs as |tab|}}
|
||||
<li style={{myWidth}} class="tab {{if tab.selected 'selected'}}" {{action 'onTabSelect' tab}}>{{tab.label}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
|
@ -1,31 +1,31 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
// from http://thecodeship.com/web-development/alternative-to-javascript-evil-setinterval/
|
||||
function interval(func, wait, times) {
|
||||
var interv = function(w, t) {
|
||||
return function() {
|
||||
if (typeof t === "undefined" || t-- > 0) {
|
||||
setTimeout(interv, w);
|
||||
try {
|
||||
func.call(null);
|
||||
} catch (e) {
|
||||
t = 0;
|
||||
throw e.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
}(wait, times);
|
||||
var interv = function (w, t) {
|
||||
return function () {
|
||||
if (typeof t === "undefined" || t-- > 0) {
|
||||
setTimeout(interv, w);
|
||||
try {
|
||||
func.call(null);
|
||||
} catch (e) {
|
||||
t = 0;
|
||||
throw e.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
}(wait, times);
|
||||
|
||||
setTimeout(interv, wait);
|
||||
setTimeout(interv, wait);
|
||||
}
|
||||
|
||||
// Function wrapping code.
|
||||
|
@ -35,12 +35,34 @@ function interval(func, wait, times) {
|
|||
// e.g. var fun1 = wrapFunction(sayStuff, this, ["Hello, world!"]);
|
||||
// http://stackoverflow.com/questions/899102/how-do-i-store-javascript-functions-in-a-queue-for-them-to-be-executed-eventuall
|
||||
function wrapFunction(fn, context, params) {
|
||||
return function() {
|
||||
fn.apply(context, params);
|
||||
};
|
||||
return function () {
|
||||
fn.apply(context, params);
|
||||
};
|
||||
}
|
||||
|
||||
function insertAtCursor(myField, myValue) {
|
||||
//IE support
|
||||
if (document.selection) {
|
||||
myField.focus();
|
||||
let sel = document.selection.createRange();
|
||||
sel.text = myValue;
|
||||
}
|
||||
//MOZILLA and others
|
||||
else if (myField.selectionStart || myField.selectionStart === '0') {
|
||||
var startPos = myField.selectionStart;
|
||||
var endPos = myField.selectionEnd;
|
||||
myField.value = myField.value.substring(0, startPos) +
|
||||
myValue +
|
||||
myField.value.substring(endPos, myField.value.length);
|
||||
myField.selectionStart = startPos + myValue.length;
|
||||
myField.selectionEnd = startPos + myValue.length;
|
||||
} else {
|
||||
myField.value += myValue;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
interval,
|
||||
wrapFunction
|
||||
};
|
||||
interval,
|
||||
wrapFunction,
|
||||
insertAtCursor
|
||||
};
|
||||
|
|
|
@ -13,50 +13,50 @@
|
|||
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||
var isDevelopment = EmberApp.env() === 'development';
|
||||
|
||||
module.exports = function(defaults) {
|
||||
var app = new EmberApp(defaults, {
|
||||
fingerprint: {
|
||||
enabled: true,
|
||||
extensions: ['js', 'css'],
|
||||
exclude: ['tinymce/**', 'codemirror/**']
|
||||
},
|
||||
module.exports = function (defaults) {
|
||||
var app = new EmberApp(defaults, {
|
||||
fingerprint: {
|
||||
enabled: true,
|
||||
extensions: ['js', 'css'],
|
||||
exclude: ['tinymce/**', 'codemirror/**']
|
||||
},
|
||||
|
||||
minifyJS: {
|
||||
enabled: !isDevelopment,
|
||||
options: {
|
||||
exclude: ['tinymce/**', 'codemirror/**']
|
||||
}
|
||||
},
|
||||
minifyJS: {
|
||||
enabled: !isDevelopment,
|
||||
options: {
|
||||
exclude: ['tinymce/**', 'codemirror/**']
|
||||
}
|
||||
},
|
||||
|
||||
minifyCSS: {
|
||||
enabled: !isDevelopment,
|
||||
options: {
|
||||
exclude: ['tinymce/**', 'codemirror/**']
|
||||
}
|
||||
},
|
||||
minifyCSS: {
|
||||
enabled: !isDevelopment,
|
||||
options: {
|
||||
exclude: ['tinymce/**', 'codemirror/**']
|
||||
}
|
||||
},
|
||||
|
||||
sourcemaps: {
|
||||
enabled: isDevelopment,
|
||||
extensions: ['js']
|
||||
}
|
||||
});
|
||||
sourcemaps: {
|
||||
enabled: isDevelopment,
|
||||
extensions: ['js']
|
||||
}
|
||||
});
|
||||
|
||||
app.import('vendor/dropzone.js');
|
||||
app.import('vendor/is.js');
|
||||
app.import('vendor/md5.js');
|
||||
app.import('vendor/moment.js');
|
||||
app.import('vendor/mousetrap.js');
|
||||
app.import('vendor/table-editor.min.js');
|
||||
app.import('vendor/underscore.js');
|
||||
app.import('vendor/bootstrap.css');
|
||||
app.import('vendor/tether.js');
|
||||
app.import('vendor/drop.js');
|
||||
app.import('vendor/tooltip.js');
|
||||
app.import('vendor/markdown-it.min.js');
|
||||
app.import('vendor/dragula.js');
|
||||
app.import('vendor/dropzone.js');
|
||||
app.import('vendor/is.js');
|
||||
app.import('vendor/md5.js');
|
||||
app.import('vendor/moment.js');
|
||||
app.import('vendor/mousetrap.js');
|
||||
app.import('vendor/table-editor.min.js');
|
||||
app.import('vendor/underscore.js');
|
||||
app.import('vendor/bootstrap.css');
|
||||
app.import('vendor/tether.js');
|
||||
app.import('vendor/drop.js');
|
||||
app.import('vendor/tooltip.js');
|
||||
app.import('vendor/markdown-it.min.js');
|
||||
app.import('vendor/dragula.js');
|
||||
app.import('vendor/datetimepicker.min.js');
|
||||
app.import('vendor/hoverIntent.js');
|
||||
app.import('vendor/waypoints.js');
|
||||
|
||||
return app.toTree();
|
||||
return app.toTree();
|
||||
};
|
||||
|
|
10
app/vendor/markdown-it.min.js
vendored
10
app/vendor/markdown-it.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -40,7 +40,7 @@ func AttachmentDownload(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
params := mux.Vars(r)
|
||||
|
||||
attachment, err := p.GetAttachmentByJobAndFileID(params["orgID"], params["job"], params["fileID"])
|
||||
attachment, err := p.GetAttachment(params["orgID"], params["attachmentID"])
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
writeNotFoundError(w, method, params["fileID"])
|
||||
|
|
167
core/api/endpoint/link_endpoint.go
Normal file
167
core/api/endpoint/link_endpoint.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
// 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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// GetLinkCandidates returns references to documents/sections/attachments.
|
||||
func GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
|
||||
method := "GetLinkCandidates"
|
||||
p := request.GetPersister(r)
|
||||
|
||||
params := mux.Vars(r)
|
||||
folderID := params["folderID"]
|
||||
documentID := params["documentID"]
|
||||
pageID := params["pageID"]
|
||||
|
||||
// parameter check
|
||||
if len(documentID) == 0 {
|
||||
util.WriteMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if len(pageID) == 0 {
|
||||
util.WriteMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
// permission check
|
||||
if !p.CanViewDocument(documentID) {
|
||||
util.WriteForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// We can link to a section within the same document so
|
||||
// let's get all pages for the document and remove "us".
|
||||
pages, err := p.GetPagesWithoutContent(documentID)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
util.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(pages) == 0 {
|
||||
pages = []entity.Page{}
|
||||
}
|
||||
|
||||
pc := []entity.LinkCandidate{}
|
||||
|
||||
for _, p := range pages {
|
||||
if p.RefID != pageID {
|
||||
c := entity.LinkCandidate{
|
||||
RefID: util.UniqueID(),
|
||||
FolderID: folderID,
|
||||
DocumentID: documentID,
|
||||
TargetID: p.RefID,
|
||||
LinkType: "section",
|
||||
Title: p.Title,
|
||||
}
|
||||
pc = append(pc, c)
|
||||
}
|
||||
}
|
||||
|
||||
// We can link to attachment within the same document so
|
||||
// let's get all attachments for the document.
|
||||
files, err := p.GetAttachments(documentID)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
util.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
files = []entity.Attachment{}
|
||||
}
|
||||
|
||||
fc := []entity.LinkCandidate{}
|
||||
|
||||
for _, f := range files {
|
||||
c := entity.LinkCandidate{
|
||||
RefID: util.UniqueID(),
|
||||
FolderID: folderID,
|
||||
DocumentID: documentID,
|
||||
TargetID: f.RefID,
|
||||
LinkType: "file",
|
||||
Title: f.Filename,
|
||||
Context: f.Extension,
|
||||
}
|
||||
|
||||
fc = append(fc, c)
|
||||
}
|
||||
|
||||
// send back the payload
|
||||
var payload struct {
|
||||
Pages []entity.LinkCandidate `json:"pages"`
|
||||
Attachments []entity.LinkCandidate `json:"attachments"`
|
||||
}
|
||||
|
||||
payload.Pages = pc
|
||||
payload.Attachments = fc
|
||||
|
||||
json, err := json.Marshal(payload)
|
||||
|
||||
if err != nil {
|
||||
util.WriteMarshalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteSuccessBytes(w, json)
|
||||
}
|
||||
|
||||
// SearchLinkCandidates endpoint takes a list of keywords and returns a list of document references matching those keywords.
|
||||
func SearchLinkCandidates(w http.ResponseWriter, r *http.Request) {
|
||||
method := "SearchLinkCandidates"
|
||||
p := request.GetPersister(r)
|
||||
|
||||
query := r.URL.Query()
|
||||
keywords := query.Get("keywords")
|
||||
decoded, err := url.QueryUnescape(keywords)
|
||||
log.IfErr(err)
|
||||
|
||||
docs, pages, attachments, err := p.SearchLinkCandidates(decoded)
|
||||
|
||||
if err != nil {
|
||||
util.WriteServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Documents []entity.LinkCandidate `json:"documents"`
|
||||
Pages []entity.LinkCandidate `json:"pages"`
|
||||
Attachments []entity.LinkCandidate `json:"attachments"`
|
||||
}
|
||||
|
||||
payload.Documents = docs
|
||||
payload.Pages = pages
|
||||
payload.Attachments = attachments
|
||||
|
||||
json, err := json.Marshal(payload)
|
||||
|
||||
if err != nil {
|
||||
util.WriteMarshalError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteSuccessBytes(w, json)
|
||||
}
|
|
@ -136,7 +136,7 @@ func init() {
|
|||
log.IfErr(Add(RoutePrefixPublic, "forgot", []string{"POST", "OPTIONS"}, nil, ForgotUserPassword))
|
||||
log.IfErr(Add(RoutePrefixPublic, "reset/{token}", []string{"POST", "OPTIONS"}, nil, ResetUserPassword))
|
||||
log.IfErr(Add(RoutePrefixPublic, "share/{folderID}", []string{"POST", "OPTIONS"}, nil, AcceptSharedFolder))
|
||||
log.IfErr(Add(RoutePrefixPublic, "attachments/{orgID}/{job}/{fileID}", []string{"GET", "OPTIONS"}, nil, AttachmentDownload))
|
||||
log.IfErr(Add(RoutePrefixPublic, "attachments/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, AttachmentDownload))
|
||||
log.IfErr(Add(RoutePrefixPublic, "version", []string{"GET", "OPTIONS"}, nil, version))
|
||||
|
||||
// **** add secure routes
|
||||
|
@ -212,12 +212,15 @@ func init() {
|
|||
log.IfErr(Add(RoutePrefixPrivate, "sections", []string{"POST", "OPTIONS"}, nil, RunSectionCommand))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "sections/refresh", []string{"GET", "OPTIONS"}, nil, RefreshSections))
|
||||
|
||||
// Links
|
||||
log.IfErr(Add(RoutePrefixPrivate, "links/{folderID}/{documentID}/{pageID}", []string{"GET", "OPTIONS"}, nil, GetLinkCandidates))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "links", []string{"GET", "OPTIONS"}, nil, SearchLinkCandidates))
|
||||
|
||||
// Global installation-wide config
|
||||
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"GET", "OPTIONS"}, nil, GetGlobalConfig))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"PUT", "OPTIONS"}, nil, SaveGlobalConfig))
|
||||
|
||||
// **** configure single page app handler.
|
||||
|
||||
// Single page app handler
|
||||
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, "{rest:.*}", nil, nil, web.EmberHandler))
|
||||
|
|
|
@ -222,8 +222,10 @@ func (p *PageMeta) SetDefaults() {
|
|||
|
||||
// DocumentMeta details who viewed the document.
|
||||
type DocumentMeta struct {
|
||||
Viewers []DocumentMetaViewer `json:"viewers"`
|
||||
Editors []DocumentMetaEditor `json:"editors"`
|
||||
Viewers []DocumentMetaViewer `json:"viewers"`
|
||||
Editors []DocumentMetaEditor `json:"editors"`
|
||||
InboundLinks []Link `json:"inboundLinks"`
|
||||
OutboundLinks []Link `json:"outboundLinks"`
|
||||
}
|
||||
|
||||
// DocumentMetaViewer contains the "view" metatdata content.
|
||||
|
@ -342,3 +344,28 @@ type SitemapDocument struct {
|
|||
Folder string
|
||||
Revised time.Time
|
||||
}
|
||||
|
||||
// Link defines a reference between a section and another document/section/attachment.
|
||||
type Link struct {
|
||||
BaseEntity
|
||||
OrgID string `json:"orgId"`
|
||||
FolderID string `json:"folderId"`
|
||||
UserID string `json:"userId"`
|
||||
LinkType string `json:"linkType"`
|
||||
SourceDocumentID string `json:"sourceDocumentId"`
|
||||
SourcePageID string `json:"sourcePageId"`
|
||||
TargetDocumentID string `json:"targetDocumentId"`
|
||||
TargetID string `json:"targetId"`
|
||||
Orphan bool `json:"orphan"`
|
||||
}
|
||||
|
||||
// LinkCandidate defines a potential link to a document/section/attachment.
|
||||
type LinkCandidate struct {
|
||||
RefID string `json:"id"`
|
||||
LinkType string `json:"linkType"`
|
||||
FolderID string `json:"folderId"`
|
||||
DocumentID string `json:"documentId"`
|
||||
TargetID string `json:"targetId"`
|
||||
Title string `json:"title"` // what we label the link
|
||||
Context string `json:"context"` // additional context (e.g. excerpt, parent, file extension)
|
||||
}
|
||||
|
|
|
@ -47,23 +47,23 @@ func (p *Persister) AddAttachment(a entity.Attachment) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// GetAttachmentByJobAndFileID returns the database attachment record specified by the parameters.
|
||||
func (p *Persister) GetAttachmentByJobAndFileID(orgID, job, fileID string) (attachment entity.Attachment, err error) {
|
||||
// GetAttachment returns the database attachment record specified by the parameters.
|
||||
func (p *Persister) GetAttachment(orgID, attachmentID string) (attachment entity.Attachment, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and job=? and fileid=?")
|
||||
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to prepare select for attachment %s/%s", job, fileID), err)
|
||||
log.Error(fmt.Sprintf("Unable to prepare select for attachment %s", attachmentID), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = stmt.Get(&attachment, orgID, job, fileID)
|
||||
err = stmt.Get(&attachment, orgID, attachmentID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select for attachment %s/%s", job, fileID), err)
|
||||
log.Error(fmt.Sprintf("Unable to execute select for attachment %s", attachmentID), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -102,5 +102,10 @@ func (p *Persister) GetAttachmentsWithData(docID string) (attachments []entity.A
|
|||
|
||||
// DeleteAttachment deletes the id record from the database attachment table.
|
||||
func (p *Persister) DeleteAttachment(id string) (rows int64, err error) {
|
||||
return p.Base.DeleteConstrained(p.Context.Transaction, "attachment", p.Context.OrgID, id)
|
||||
rows, err = p.Base.DeleteConstrained(p.Context.Transaction, "attachment", p.Context.OrgID, id)
|
||||
|
||||
// Mark references to this document as orphaned
|
||||
err = p.MarkOrphanAttachmentLink(id)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -107,6 +107,13 @@ func (p *Persister) GetDocumentMeta(id string) (meta entity.DocumentMeta, err er
|
|||
return
|
||||
}
|
||||
|
||||
meta.OutboundLinks, err = p.GetDocumentOutboundLinks(id)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute GetDocumentOutboundLinks for document %s", id), err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -400,6 +407,18 @@ func (p *Persister) DeleteDocument(documentID string) (rows int64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Mark references to this document as orphaned
|
||||
err = p.MarkOrphanDocumentLink(documentID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove all references from this document
|
||||
_, err = p.DeleteSourceDocumentLinks(documentID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "delete-document", documentID, "")
|
||||
|
||||
return p.Base.DeleteConstrained(p.Context.Transaction, "document", p.Context.OrgID, documentID)
|
||||
|
|
292
core/api/request/link.go
Normal file
292
core/api/request/link.go
Normal file
|
@ -0,0 +1,292 @@
|
|||
// 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/api/util"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/utility"
|
||||
)
|
||||
|
||||
// AddContentLink inserts wiki-link into the store.
|
||||
// These links exist when content references another document or content.
|
||||
func (p *Persister) AddContentLink(l entity.Link) (err error) {
|
||||
l.Created = time.Now().UTC()
|
||||
l.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to prepare insert for link", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.LinkType, l.Orphan, l.Created, l.Revised)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to execute insert for link", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SearchLinkCandidates returns matching documents, sections and attachments using keywords.
|
||||
func (p *Persister) SearchLinkCandidates(keywords string) (docs []entity.LinkCandidate,
|
||||
pages []entity.LinkCandidate, attachments []entity.LinkCandidate, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
// find matching documents
|
||||
temp := []entity.LinkCandidate{}
|
||||
likeQuery := "title LIKE '%" + keywords + "%'"
|
||||
|
||||
err = Db.Select(&temp,
|
||||
`SELECT refid as documentid, labelid as folderid,title from document WHERE orgid=? AND `+likeQuery+` AND labelid IN
|
||||
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
|
||||
ORDER BY title`,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.UserID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.UserID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute search links for org %s", p.Context.OrgID), err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range temp {
|
||||
c := entity.LinkCandidate{
|
||||
RefID: util.UniqueID(),
|
||||
FolderID: r.FolderID,
|
||||
DocumentID: r.DocumentID,
|
||||
TargetID: r.DocumentID,
|
||||
LinkType: "document",
|
||||
Title: r.Title,
|
||||
Context: "",
|
||||
}
|
||||
|
||||
docs = append(docs, c)
|
||||
}
|
||||
|
||||
// find matching sections
|
||||
likeQuery = "p.title LIKE '%" + keywords + "%'"
|
||||
temp = []entity.LinkCandidate{}
|
||||
|
||||
err = Db.Select(&temp,
|
||||
`SELECT p.refid as targetid, p.documentid as documentid, p.title as title, d.title as context, d.labelid as folderid from page p
|
||||
LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.labelid IN
|
||||
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
|
||||
ORDER BY p.title`,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.UserID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.UserID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute search links for org %s", p.Context.OrgID), err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range temp {
|
||||
c := entity.LinkCandidate{
|
||||
RefID: util.UniqueID(),
|
||||
FolderID: r.FolderID,
|
||||
DocumentID: r.DocumentID,
|
||||
TargetID: r.TargetID,
|
||||
LinkType: "section",
|
||||
Title: r.Title,
|
||||
Context: r.Context,
|
||||
}
|
||||
|
||||
pages = append(pages, c)
|
||||
}
|
||||
|
||||
// find matching attachments
|
||||
likeQuery = "a.filename LIKE '%" + keywords + "%'"
|
||||
temp = []entity.LinkCandidate{}
|
||||
|
||||
err = Db.Select(&temp,
|
||||
`SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid from attachment a
|
||||
LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.labelid IN
|
||||
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
|
||||
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
|
||||
ORDER BY a.filename`,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.UserID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.OrgID,
|
||||
p.Context.UserID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute search links for org %s", p.Context.OrgID), err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range temp {
|
||||
c := entity.LinkCandidate{
|
||||
RefID: util.UniqueID(),
|
||||
FolderID: r.FolderID,
|
||||
DocumentID: r.DocumentID,
|
||||
TargetID: r.TargetID,
|
||||
LinkType: "file",
|
||||
Title: r.Title,
|
||||
Context: r.Context,
|
||||
}
|
||||
|
||||
attachments = append(attachments, c)
|
||||
}
|
||||
|
||||
if len(docs) == 0 {
|
||||
docs = []entity.LinkCandidate{}
|
||||
}
|
||||
if len(pages) == 0 {
|
||||
pages = []entity.LinkCandidate{}
|
||||
}
|
||||
if len(attachments) == 0 {
|
||||
attachments = []entity.LinkCandidate{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDocumentOutboundLinks returns outbound links for specified document.
|
||||
func (p *Persister) GetDocumentOutboundLinks(documentID string) (links []entity.Link, err error) {
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&links,
|
||||
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
|
||||
FROM link l
|
||||
WHERE l.orgid=? AND l.sourcedocumentid=?`,
|
||||
p.Context.OrgID,
|
||||
documentID)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(links) == 0 {
|
||||
links = []entity.Link{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPageLinks returns outbound links for specified page in document.
|
||||
func (p *Persister) GetPageLinks(documentID, pageID string) (links []entity.Link, err error) {
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&links,
|
||||
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
|
||||
FROM link l
|
||||
WHERE l.orgid=? AND l.sourcedocumentid=? AND l.sourcepageid=?`,
|
||||
p.Context.OrgID,
|
||||
documentID,
|
||||
pageID)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(links) == 0 {
|
||||
links = []entity.Link{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkOrphanDocumentLink marks all link records referencing specified document.
|
||||
func (p *Persister) MarkOrphanDocumentLink(documentID string) (err error) {
|
||||
revised := time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?")
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer utility.Close(stmt)
|
||||
|
||||
_, err = stmt.Exec(revised, p.Context.OrgID, documentID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkOrphanPageLink marks all link records referencing specified page.
|
||||
func (p *Persister) MarkOrphanPageLink(pageID string) (err error) {
|
||||
revised := time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?")
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer utility.Close(stmt)
|
||||
|
||||
_, err = stmt.Exec(revised, p.Context.OrgID, pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MarkOrphanAttachmentLink marks all link records referencing specified attachment.
|
||||
func (p *Persister) MarkOrphanAttachmentLink(attachmentID string) (err error) {
|
||||
revised := time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?")
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer utility.Close(stmt)
|
||||
|
||||
_, err = stmt.Exec(revised, p.Context.OrgID, attachmentID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteSourcePageLinks removes saved links for given source.
|
||||
func (p *Persister) DeleteSourcePageLinks(pageID string) (rows int64, err error) {
|
||||
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcepageid=\"%s\"", p.Context.OrgID, pageID))
|
||||
}
|
||||
|
||||
// DeleteSourceDocumentLinks removes saved links for given document.
|
||||
func (p *Persister) DeleteSourceDocumentLinks(documentID string) (rows int64, err error) {
|
||||
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcedocumentid=\"%s\"", p.Context.OrgID, documentID))
|
||||
}
|
||||
|
||||
// DeleteLink removes saved link from the store.
|
||||
func (p *Persister) DeleteLink(id string) (rows int64, err error) {
|
||||
return p.Base.DeleteConstrained(p.Context.Transaction, "link", p.Context.OrgID, id)
|
||||
}
|
|
@ -20,6 +20,7 @@ import (
|
|||
|
||||
"github.com/documize/community/core/api/endpoint/models"
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/core/api/util"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/utility"
|
||||
)
|
||||
|
@ -286,6 +287,46 @@ func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevis
|
|||
//}
|
||||
//}
|
||||
|
||||
// find any content links in the HTML
|
||||
links := util.GetContentLinks(page.Body)
|
||||
|
||||
// get a copy of previously saved links
|
||||
previousLinks, _ := p.GetPageLinks(page.DocumentID, page.RefID)
|
||||
fmt.Println(len(previousLinks))
|
||||
|
||||
// delete previous content links for this page
|
||||
_, _ = p.DeleteSourcePageLinks(page.RefID)
|
||||
|
||||
// save latest content links for this page
|
||||
for _, link := range links {
|
||||
link.Orphan = false
|
||||
link.OrgID = p.Context.OrgID
|
||||
link.UserID = p.Context.UserID
|
||||
link.SourceDocumentID = page.DocumentID
|
||||
link.SourcePageID = page.RefID
|
||||
|
||||
if link.LinkType == "document" {
|
||||
link.TargetID = ""
|
||||
}
|
||||
|
||||
// We check if there was a previously saved version of this link.
|
||||
// If we find one, we carry forward the orphan flag.
|
||||
for _, p := range previousLinks {
|
||||
if link.TargetID == p.TargetID && link.LinkType == p.LinkType {
|
||||
link.Orphan = p.Orphan
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
err := p.AddContentLink(link)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to insert content links for page %s", page.RefID), err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "update-page", page.DocumentID, page.RefID)
|
||||
|
||||
return
|
||||
|
@ -377,6 +418,12 @@ func (p *Persister) DeletePage(documentID, pageID string) (rows int64, err error
|
|||
_, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pagemeta WHERE orgid='%s' AND pageid='%s'", p.Context.OrgID, pageID))
|
||||
_, err = searches.Delete(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID)
|
||||
|
||||
// delete content links from this page
|
||||
_, err = p.DeleteSourcePageLinks(pageID)
|
||||
|
||||
// mark as orphan links to this page
|
||||
err = p.MarkOrphanPageLink(pageID)
|
||||
|
||||
p.Base.Audit(p.Context, "remove-page", documentID, pageID)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@ package util
|
|||
import "testing"
|
||||
|
||||
func TestHTMLEncoding(t *testing.T) {
|
||||
html(t, "<script>alert('test')</script>", "<script>alert('test')</script>")
|
||||
testHTML(t, "<script>alert('test')</script>", "<script>alert('test')</script>")
|
||||
text(t, "<script>alert('test')</script>", "<script>alert('test')</script>")
|
||||
}
|
||||
|
||||
func html(t *testing.T, in, out string) {
|
||||
func testHTML(t *testing.T, in, out string) {
|
||||
got := EncodeHTMLString(in)
|
||||
if got != out {
|
||||
t.Errorf("EncodeHTMLString `%s` got `%s` expected `%s`\n", in, got, out)
|
||||
|
|
75
core/api/util/links.go
Normal file
75
core/api/util/links.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/documize/community/core/api/entity"
|
||||
)
|
||||
|
||||
// GetContentLinks returns Documize generated <a> links.
|
||||
// such links have an identifying attribute e.g. <a data-documize='true'...
|
||||
func GetContentLinks(body string) (links []entity.Link) {
|
||||
z := html.NewTokenizer(strings.NewReader(body))
|
||||
|
||||
for {
|
||||
tt := z.Next()
|
||||
|
||||
switch {
|
||||
case tt == html.ErrorToken:
|
||||
// End of the document, we're done
|
||||
return
|
||||
case tt == html.StartTagToken:
|
||||
t := z.Token()
|
||||
|
||||
// Check if the token is an <a> tag
|
||||
isAnchor := t.Data == "a"
|
||||
if !isAnchor {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the content link
|
||||
ok, link := getLink(t)
|
||||
if ok {
|
||||
links = append(links, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to pull the href attribute from a Token
|
||||
func getLink(t html.Token) (ok bool, link entity.Link) {
|
||||
ok = false
|
||||
|
||||
// Iterate over all of the Token's attributes until we find an "href"
|
||||
for _, a := range t.Attr {
|
||||
switch a.Key {
|
||||
case "data-documize":
|
||||
ok = true
|
||||
case "data-link-id":
|
||||
link.RefID = strings.TrimSpace(a.Val)
|
||||
case "data-link-space-id":
|
||||
link.FolderID = strings.TrimSpace(a.Val)
|
||||
case "data-link-target-document-id":
|
||||
link.TargetDocumentID = strings.TrimSpace(a.Val)
|
||||
case "data-link-target-id":
|
||||
link.TargetID = strings.TrimSpace(a.Val)
|
||||
case "data-link-type":
|
||||
link.LinkType = strings.TrimSpace(a.Val)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -279,3 +279,56 @@ CREATE TABLE IF NOT EXISTS `userconfig` (
|
|||
UNIQUE INDEX `idx_userconfig_orguserkey` (`orgid`, `userid`, `key` ASC))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
|
||||
DROP TABLE IF EXISTS `share`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `share` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`documentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`userid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`email` NVARCHAR(250) NOT NULL DEFAULT '',
|
||||
`message` NVARCHAR(500) NOT NULL DEFAULT '',
|
||||
`viewed` VARCHAR(500) NOT NULL DEFAULT '',
|
||||
`secret` VARCHAR(200) NOT NULL DEFAULT '',
|
||||
`expires` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`active` BOOL NOT NULL DEFAULT 1,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
|
||||
DROP TABLE IF EXISTS `feedback`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `feedback` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`documentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`userid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`email` NVARCHAR(250) NOT NULL DEFAULT '',
|
||||
`feedback` LONGTEXT,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
|
||||
DROP TABLE IF EXISTS `link`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `link` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`folderid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`userid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`sourcedocumentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`sourcepageid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`linktype` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`targetdocumentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`targetid` CHAR(16) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||
`orphan` BOOL NOT NULL DEFAULT 0,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
|
|
20
core/database/scripts/autobuild/db_00004.sql
Normal file
20
core/database/scripts/autobuild/db_00004.sql
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* community edition */
|
||||
DROP TABLE IF EXISTS `link`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `link` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`folderid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`userid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`sourcedocumentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`sourcepageid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`linktype` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`targetdocumentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`targetid` CHAR(16) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||
`orphan` BOOL NOT NULL DEFAULT 0,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
|
@ -26,7 +26,7 @@ type ProdInfo struct {
|
|||
// Product returns product edition details
|
||||
func Product() (p ProdInfo) {
|
||||
p.Major = "0"
|
||||
p.Minor = "27"
|
||||
p.Minor = "28"
|
||||
p.Patch = "0"
|
||||
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
|
||||
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