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:
parent
5ca53ecb04
commit
7db618dea0
20 changed files with 1397 additions and 721 deletions
59
app/app/components/link/content-linker.js
Normal file
59
app/app/components/link/content-linker.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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(' <b>It\'s my button!</b> ');
|
||||
// Selects the first paragraph found
|
||||
// tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]);
|
||||
|
|
54
app/app/services/link.js
Normal file
54
app/app/services/link.js
Normal 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;
|
||||
}
|
||||
});
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
16
app/app/styles/view/content-linker.scss
Normal file
16
app/app/styles/view/content-linker.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}}
|
||||
|
|
21
app/app/templates/components/link/content-linker.hbs
Normal file
21
app/app/templates/components/link/content-linker.hbs
Normal 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>
|
|
@ -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}}
|
||||
|
|
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
Loading…
Add table
Add a link
Reference in a new issue