1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-02 20:15:26 +02:00

foundational layer for inserting content and attachment links into content

This commit is contained in:
Harvey Kandola 2016-10-23 18:33:07 -07:00
parent 5ca53ecb04
commit 7db618dea0
20 changed files with 1397 additions and 721 deletions

View file

@ -0,0 +1,59 @@
// 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.Component.extend({
link: service(),
hasSections: false,
hasAttachments: false,
linkName: '',
selection: null,
init() {
this._super(...arguments);
let self = this;
let documentId = this.get('document.id');
let pageId = this.get('page.id');
this.get('link').getCandidates(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);
});
},
didReceiveAttrs() {},
didInsertElement() {},
willDestroyElement() {},
actions: {
onInsertLink() {
let selection = this.get('selection');
let linkName = this.get('linkName');
if (linkName.length) {
selection.title = linkName;
}
if (is.not.null(selection)) {
this.get('onInsertLink')(selection);
}
}
}
});

View file

@ -11,15 +11,24 @@
import Ember from 'ember';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend({
pageBody: "",
appMeta: Ember.inject.service(),
link: service(),
pageBody: "",
drop: null,
showSidebar: false,
didReceiveAttrs() {
this.set('pageBody', this.get('meta.rawBody'));
},
didInsertElement() {
let self = this;
let options = {
selector: "#rich-text-editor",
relative_urls: false,
@ -34,7 +43,7 @@ export default Ember.Component.extend({
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,29 +57,29 @@ 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 | documizeLinkButton",
save_onsavecallback: function () {
Mousetrap.trigger('ctrl+s');
},
setup: function (editor) {
editor.addButton('documizeLinkButton', {
title: 'Insert Link',
icon: false,
image: '/favicon.ico',
onclick: function () {
let showSidebar = !self.get('showSidebar');
self.set('showSidebar', showSidebar);
if (showSidebar) {
self.send('showSidebar');
}
}
});
}
};
@ -91,6 +100,16 @@ export default Ember.Component.extend({
},
actions: {
showSidebar() {
this.set('linkName', tinymce.activeEditor.selection.getContent());
},
onInsertLink(link) {
let linkHTML = this.get('link').buildLink(link);
tinymce.activeEditor.insertContent(linkHTML);
this.set('showSidebar', false);
},
isDirty() {
return is.not.undefined(tinymce) && is.not.undefined(tinymce.activeEditor) && tinymce.activeEditor.isDirty();
},
@ -110,3 +129,7 @@ export default Ember.Component.extend({
}
}
});
// editor.insertContent('&nbsp;<b>It\'s my button!</b>&nbsp;');
// Selects the first paragraph found
// tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]);

54
app/app/services/link.js Normal file
View file

@ -0,0 +1,54 @@
// 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(),
// Returns candidate links using provided parameters
getCandidates(documentId, pageId /*, keywords*/ ) {
return this.get('ajax').request(`links/${documentId}/${pageId}`, {
method: 'GET'
}).then((response) => {
return response;
});
},
buildLink(link) {
let result = "";
let href = "";
let endpoint = this.get('appMeta').get('endpoint');
let orgId = this.get('appMeta').get('orgId');
if (link.linkType === "section") {
href = `/link/${link.linkType}/${link.id}`;
}
if (link.linkType === "file") {
href = `${endpoint}/public/attachments/${orgId}/${link.attachmentId}`;
}
if (link.linkType === "document") {
href = `/link/${link.linkType}/${link.id}`;
}
result = `<a data-link-id='${link.id}' data-link-type='${link.linkType}' href='${href}'>${link.title}</a>`;
console.log(link);
console.log(result);
return result;
}
});

View file

@ -15,6 +15,7 @@
@import "base.scss";
@import "widget/widget.scss";
@import "view/layout.scss";
@import "view/content-linker.scss";
@import "view/page-search.scss";
@import "view/page-documents.scss";
@import "view/page-settings.scss";

View file

@ -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,7 +126,8 @@ 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;
}
}
@ -62,52 +137,81 @@ a.alt {
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}#{"%"}; }
.width-#{$i} {
width: #{$i}#{"%"};
}
$i: $i - 5;
}
.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 +231,12 @@ ul {
}
}
.clearfix:before,
.clearfix:after {
.clearfix:after,
.clearfix:before {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}

View file

@ -0,0 +1,16 @@
.content-linker {
margin: 0 10px;
padding: 20px;
@include border(1px);
.input-control > .page-list {
margin: 0;
padding: 0;
> .item {
margin: 0;
padding: 0;
font-size: 0.9rem;
}
}
}

View file

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

View file

@ -0,0 +1,21 @@
<div class="content-linker">
<form>
<div class="input-control">
<label>Insert Link</label>
<div class="tip">Give the link a clickable name</div>
{{focus-input type="input" value=linkName}}
</div>
{{#if hasSections}}
<div class="input-control">
{{ui-select id="content-linker-section-list" content=candidates.pages action=(action (mut selection)) prompt="Link to existing section" optionValuePath="id" optionLabelPath="title" }}
</div>
{{/if}}
{{#if hasAttachments}}
<div class="input-control">
{{ui-select id="content-linker-attachment-list" content=candidates.attachments action=(action (mut selection)) prompt="Link to file attachment" optionValuePath="id" optionLabelPath="title" }}
</div>
{{/if}}
<div class="regular-button button-blue pull-right" {{ action 'onInsertLink' }}>Insert</div>
<div class="clearfix" />
</form>
</div>

View file

@ -1,3 +1,10 @@
{{#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="{{if showSidebar 'width-70' 'width-100'}} pull-left">
{{focus-textarea value=pageBody id="rich-text-editor" class="mousetrap"}}
</div>
<div class="{{if showSidebar 'width-30' 'no-display'}} pull-left">
{{link/content-linker document=document folder=folder page=page linkName=linkName onInsertLink=(action 'onInsertLink')}}
</div>
{{/section/base-editor}}

File diff suppressed because one or more lines are too long