mirror of
https://github.com/documize/community.git
synced 2025-07-25 08:09:43 +02:00
saving and inserting reusable content blocks
This commit is contained in:
parent
fdbf03b25c
commit
b7fa3b9006
32 changed files with 1334 additions and 768 deletions
|
@ -15,6 +15,7 @@ import NotifierMixin from '../../mixins/notifier';
|
|||
|
||||
export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
||||
documentService: Ember.inject.service('document'),
|
||||
sectionService: Ember.inject.service('section'),
|
||||
document: {},
|
||||
folder: {},
|
||||
showToc: true,
|
||||
|
@ -22,6 +23,13 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
|||
showScrollTool: false,
|
||||
showingSections: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.get('sectionService').getSpaceSectionTemplates(this.get('folder.id')).then((t) => {
|
||||
this.set('templates', t);
|
||||
});
|
||||
},
|
||||
|
||||
didRender() {
|
||||
if (this.session.authenticated) {
|
||||
this.addTooltip(document.getElementById("section-tool"));
|
||||
|
@ -93,6 +101,11 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
|||
this.attrs.onAddSection(section);
|
||||
},
|
||||
|
||||
onInsertTemplate(template) {
|
||||
this.send('showToc');
|
||||
this.attrs.onInsertTemplate(template);
|
||||
},
|
||||
|
||||
scrollTop() {
|
||||
this.set('showScrollTool', false);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
appMeta: Ember.inject.service(),
|
||||
userService: Ember.inject.service('user'),
|
||||
localStorage: Ember.inject.service(),
|
||||
pinned: Ember.inject.service(),
|
||||
drop: null,
|
||||
users: [],
|
||||
menuOpen: false,
|
||||
|
@ -24,7 +25,6 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
name: "",
|
||||
description: ""
|
||||
},
|
||||
pinned: Ember.inject.service(),
|
||||
pinState : {
|
||||
isPinned: false,
|
||||
pinId: '',
|
||||
|
|
|
@ -70,6 +70,16 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
|
|||
},
|
||||
|
||||
actions: {
|
||||
onSaveAsPage(id, title) {
|
||||
let params = {
|
||||
documentId: this.get('document.id'),
|
||||
pageId: id,
|
||||
title: title,
|
||||
};
|
||||
|
||||
this.attrs.onSaveAsPage(params);
|
||||
},
|
||||
|
||||
onDeletePage(id, deleteChildren) {
|
||||
let page = this.get('pages').findBy("id", id);
|
||||
|
||||
|
|
|
@ -18,24 +18,44 @@ const {
|
|||
|
||||
export default Ember.Component.extend(TooltipMixin, {
|
||||
deleteChildren: false,
|
||||
menuOpen: false,
|
||||
saveAsTitle: "",
|
||||
|
||||
|
||||
checkId: computed('page', function () {
|
||||
let id = this.get('page.id');
|
||||
return `delete-check-button-${id}`;
|
||||
}),
|
||||
|
||||
dropTarget: computed('page', function () {
|
||||
menuTarget: computed('page', function () {
|
||||
let id = this.get('page.id');
|
||||
return `page-menu-${id}`;
|
||||
}),
|
||||
deleteButtonId: computed('page', function () {
|
||||
let id = this.get('page.id');
|
||||
return `delete-page-button-${id}`;
|
||||
}),
|
||||
saveAsTarget: computed('page', function () {
|
||||
let id = this.get('page.id');
|
||||
return `saveas-page-button-${id}`;
|
||||
}),
|
||||
saveAsDialogId: computed('page', function () {
|
||||
let id = this.get('page.id');
|
||||
return `save-as-dialog-${id}`;
|
||||
}),
|
||||
saveAsTitleId: computed('page', function () {
|
||||
let id = this.get('page.id');
|
||||
return `save-as-title-${id}`;
|
||||
}),
|
||||
|
||||
didRender() {
|
||||
if (this.get('isEditor')) {
|
||||
let self = this;
|
||||
$(".page-edit-button, .page-delete-button").each(function (i, el) {
|
||||
$(".page-action-button").each(function (i, el) {
|
||||
self.addTooltip(el);
|
||||
});
|
||||
}
|
||||
|
||||
$("#" + this.get('saveAsTitleId')).removeClass('error');
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
|
@ -43,6 +63,14 @@ export default Ember.Component.extend(TooltipMixin, {
|
|||
},
|
||||
|
||||
actions: {
|
||||
onMenuOpen() {
|
||||
if ($('#' + this.get('saveAsDialogId')).is( ":visible" )) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('menuOpen', !this.get('menuOpen'));
|
||||
},
|
||||
|
||||
editPage(id) {
|
||||
this.attrs.onEditPage(id);
|
||||
},
|
||||
|
@ -50,5 +78,21 @@ export default Ember.Component.extend(TooltipMixin, {
|
|||
deletePage(id) {
|
||||
this.attrs.onDeletePage(id, this.get('deleteChildren'));
|
||||
},
|
||||
|
||||
saveAsPage(id) {
|
||||
let titleElem = '#' + this.get('saveAsTitleId');
|
||||
let saveAsTitle = this.get('saveAsTitle');
|
||||
if (is.empty(saveAsTitle)) {
|
||||
$(titleElem).addClass('error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.attrs.onSaveAsPage(id, saveAsTitle);
|
||||
this.set('menuOpen', false);
|
||||
this.set('saveAsTitle', '');
|
||||
$(titleElem).removeClass('error');
|
||||
|
||||
return true;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,6 +14,12 @@ import NotifierMixin from '../../mixins/notifier';
|
|||
|
||||
export default Ember.Component.extend(NotifierMixin, {
|
||||
display: 'section', // which CSS to use
|
||||
hasTemplates: false,
|
||||
|
||||
didReceiveAttrs() {
|
||||
console.log(this.get('templates.length'));
|
||||
this.set('hasTemplates', this.get('templates.length') > 0);
|
||||
},
|
||||
|
||||
didRender() {
|
||||
let self = this;
|
||||
|
@ -34,7 +40,11 @@ export default Ember.Component.extend(NotifierMixin, {
|
|||
},
|
||||
|
||||
addSection(section) {
|
||||
this.attrs.onAction(section);
|
||||
this.attrs.onAddSection(section);
|
||||
},
|
||||
|
||||
insertTemplate(template) {
|
||||
this.attrs.onInsertTemplate(template);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -57,16 +57,12 @@ export default Ember.Component.extend({
|
|||
classes: 'drop-theme-basic',
|
||||
position: self.get('position'),
|
||||
openOn: self.get('open'),
|
||||
constrainToWindow: false,
|
||||
constrainToScrollParent: false,
|
||||
tetherOptions: {
|
||||
offset: self.offset,
|
||||
targetOffset: self.targetOffset,
|
||||
// optimizations: {
|
||||
// moveElement: false
|
||||
// },
|
||||
constraints: [{
|
||||
to: 'window',
|
||||
attachment: 'together'
|
||||
}]
|
||||
targetModifier: 'scroll-handle'
|
||||
},
|
||||
remove: true
|
||||
});
|
||||
|
|
|
@ -24,10 +24,6 @@ export default Ember.Component.extend({
|
|||
|
||||
didReceiveAttrs() {
|
||||
this.set("contentId", 'dropdown-menu-' + stringUtil.makeId(10));
|
||||
|
||||
// if (this.session.get('isMobile')) {
|
||||
// this.set('open', "click");
|
||||
// }
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
|
@ -40,10 +36,14 @@ export default Ember.Component.extend({
|
|||
classes: 'drop-theme-menu',
|
||||
position: self.get('position'),
|
||||
openOn: self.get('open'),
|
||||
constrainToWindow: false,
|
||||
constrainToScrollParent: false,
|
||||
tetherOptions: {
|
||||
offset: "5px 0",
|
||||
targetOffset: "10px 0"
|
||||
}
|
||||
targetOffset: "10px 0",
|
||||
targetModifier: 'scroll-handle',
|
||||
},
|
||||
remove: true
|
||||
});
|
||||
|
||||
if (drop) {
|
||||
|
|
28
app/app/models/page-template.js
Normal file
28
app/app/models/page-template.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||
//
|
||||
// This software (Documize Community Edition) is licensed under
|
||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
//
|
||||
// You can operate outside the AGPL restrictions by purchasing
|
||||
// Documize Enterprise Edition and obtaining a commercial license
|
||||
// by contacting <sales@documize.com>.
|
||||
//
|
||||
// https://documize.com
|
||||
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export default Model.extend({
|
||||
documentId: attr('string'),
|
||||
orgId: attr('string'),
|
||||
contentType: attr('string'),
|
||||
pageType: attr('string'),
|
||||
preset: attr('boolean', { defaultValue: false }),
|
||||
presetId: attr('string'),
|
||||
title: attr('string'),
|
||||
body: attr('string'),
|
||||
firstname: attr('string'),
|
||||
lastname: attr('string'),
|
||||
created: attr(),
|
||||
revised: attr()
|
||||
});
|
|
@ -22,6 +22,8 @@ export default Model.extend({
|
|||
level: attr('number', { defaultValue: 1 }),
|
||||
sequence: attr('number', { defaultValue: 0 }),
|
||||
revisions: attr('number', { defaultValue: 0 }),
|
||||
preset: attr('boolean', { defaultValue: false }),
|
||||
presetId: attr('string'),
|
||||
title: attr('string'),
|
||||
body: attr('string'),
|
||||
rawBody: attr('string'),
|
||||
|
|
|
@ -142,6 +142,51 @@ export default Ember.Controller.extend(NotifierMixin, {
|
|||
});
|
||||
},
|
||||
|
||||
onInsertTemplate(template) {
|
||||
this.audit.record("added-section-template-" + template.get('contentType'));
|
||||
|
||||
let page = {
|
||||
documentId: this.get('model.document.id'),
|
||||
title: `${template.get('title')}`,
|
||||
level: 1,
|
||||
sequence: 0,
|
||||
body: template.get('body'),
|
||||
contentType: template.get('contentType'),
|
||||
pageType: template.get('pageType'),
|
||||
presetId: template.get('id')
|
||||
};
|
||||
|
||||
let meta = {
|
||||
documentId: this.get('model.document.id'),
|
||||
rawBody: "",
|
||||
config: ""
|
||||
};
|
||||
|
||||
let model = {
|
||||
page: page,
|
||||
meta: meta
|
||||
};
|
||||
|
||||
this.get('documentService').addPage(this.get('model.document.id'), model).then((newPage) => {
|
||||
let data = this.get('store').normalize('page', newPage);
|
||||
this.get('store').push(data);
|
||||
|
||||
this.get('documentService').getPages(this.get('model.document.id')).then((pages) => {
|
||||
this.set('model.pages', pages.filterBy('pageType', 'section'));
|
||||
this.set('model.tabs', pages.filterBy('pageType', 'tab'));
|
||||
|
||||
this.get('documentService').getPageMeta(this.get('model.document.id'), newPage.id).then(() => {
|
||||
this.transitionToRoute('document.edit',
|
||||
this.get('model.folder.id'),
|
||||
this.get('model.folder.slug'),
|
||||
this.get('model.document.id'),
|
||||
this.get('model.document.slug'),
|
||||
newPage.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onDocumentDelete() {
|
||||
this.get('documentService').deleteDocument(this.get('model.document.id')).then(() => {
|
||||
this.audit.record("deleted-page");
|
||||
|
|
|
@ -14,6 +14,7 @@ import NotifierMixin from '../../../mixins/notifier';
|
|||
|
||||
export default Ember.Controller.extend(NotifierMixin, {
|
||||
documentService: Ember.inject.service('document'),
|
||||
sectionService: Ember.inject.service('section'),
|
||||
queryParams: ['page'],
|
||||
|
||||
// Jump to the right part of the document.
|
||||
|
@ -85,6 +86,12 @@ export default Ember.Controller.extend(NotifierMixin, {
|
|||
});
|
||||
},
|
||||
|
||||
onSaveAsPage(params) {
|
||||
this.get('sectionService').saveSectionTemplate(params).then(() => {
|
||||
this.showNotification("Published");
|
||||
});
|
||||
},
|
||||
|
||||
onPageDeleted(deletePage) {
|
||||
let documentId = this.get('model.document.id');
|
||||
let pages = this.get('model.pages');
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{{document/document-view document=model.document links=model.links allPages=model.allPages tabs=model.tabs pages=model.pages folder=model.folder folders=model.folders isEditor=model.isEditor
|
||||
gotoPage=(action 'gotoPage') onDeletePage=(action 'onPageDeleted')}}
|
||||
gotoPage=(action 'gotoPage') onSaveAsPage=(action 'onSaveAsPage') onDeletePage=(action 'onPageDeleted')}}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{{#layout/zone-sidebar}}
|
||||
{{document/document-sidebar document=model.document folder=model.folder pages=model.pages page=model.page isEditor=model.isEditor sections=model.sections
|
||||
onAddSection=(action 'onAddSection') changePageSequence=(action 'onPageSequenceChange') changePageLevel=(action 'onPageLevelChange') gotoPage=(action 'gotoPage')}}
|
||||
onAddSection=(action 'onAddSection') onInsertTemplate=(action 'onInsertTemplate') changePageSequence=(action 'onPageSequenceChange') changePageLevel=(action 'onPageLevelChange') gotoPage=(action 'gotoPage')}}
|
||||
{{/layout/zone-sidebar}}
|
||||
|
||||
{{#layout/zone-content}}
|
||||
|
|
|
@ -296,7 +296,7 @@ export default Ember.Service.extend({
|
|||
return this.get('ajax').request(`documents/${documentId}/attachments/${attachmentId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
function isObject(a) {
|
||||
|
|
|
@ -68,5 +68,35 @@ export default BaseService.extend({
|
|||
|
||||
return pages;
|
||||
});
|
||||
},
|
||||
|
||||
/******************************
|
||||
* Reusable section blocks
|
||||
******************************/
|
||||
|
||||
// Saves section as template
|
||||
saveSectionTemplate(payload) {
|
||||
let url = `sections/templates`;
|
||||
|
||||
return this.get('ajax').post(url, {
|
||||
data: JSON.stringify(payload),
|
||||
contentType: 'json'
|
||||
});
|
||||
},
|
||||
|
||||
// Returns all available sections.
|
||||
getSpaceSectionTemplates(folderId) {
|
||||
return this.get('ajax').request(`sections/templates/${folderId}`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
let data = [];
|
||||
|
||||
data = response.map((obj) => {
|
||||
let data = this.get('store').normalize('pageTemplate', obj);
|
||||
return this.get('store').push(data);
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
> .is-a-page {
|
||||
.page-title {
|
||||
> .page-toolbar {
|
||||
opacity: 0.3;
|
||||
opacity: 0.5;
|
||||
@extend .transition-all;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -4,6 +4,17 @@
|
|||
> .canvas {
|
||||
padding: 0;
|
||||
|
||||
> .divider {
|
||||
margin: 30px 0 20px 0;
|
||||
border-top: 1px dotted $color-gray;
|
||||
}
|
||||
|
||||
> .template-caption {
|
||||
text-align: center;
|
||||
color: $color-gray;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
> .list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
@ -97,6 +97,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
> li.danger {
|
||||
color: $color-red;
|
||||
|
||||
&:hover {
|
||||
color: $color-red;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
> li.divider {
|
||||
height: 1px;
|
||||
border-top: 1px solid $color-border;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
gotoPage=(action 'gotoPage')}}
|
||||
{{/if}}
|
||||
{{#if showSections}}
|
||||
{{document/page-wizard display='section' document=document folder=folder sections=sections
|
||||
onCancel=(action 'onCancel') onAction=(action 'onAddSection')}}
|
||||
{{document/page-wizard display='section' document=document folder=folder sections=sections templates=templates
|
||||
onCancel=(action 'onCancel') onAddSection=(action 'onAddSection') onInsertTemplate=(action 'onInsertTemplate')}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
|
||||
{{#if isEditor}}
|
||||
<li class="divider"></li>
|
||||
<li class="item" id="delete-document-button">Delete</li>
|
||||
<li class="item danger" id="delete-document-button">Delete</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
{{/dropdown-menu}}
|
||||
|
@ -74,12 +74,10 @@
|
|||
{{#if session.authenticated}}
|
||||
{{#unless pinState.isPinned}}
|
||||
{{#dropdown-dialog target="pin-document-button" position="bottom right" button="Pin" color="flat-green" onAction=(action 'pin') focusOn="pin-document-name" }}
|
||||
<div>
|
||||
<div class="input-control">
|
||||
<label>Pin Document</label>
|
||||
<div class="tip">A 3 or 4 character name</div>
|
||||
{{input type='text' id="pin-document-name" value=pinState.newName}}
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Pin Document</label>
|
||||
<div class="tip">A 3 or 4 character name</div>
|
||||
{{input type='text' id="pin-document-name" value=pinState.newName}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/unless}}
|
||||
|
@ -87,17 +85,15 @@
|
|||
|
||||
{{#if isEditor}}
|
||||
{{#dropdown-dialog target="save-template-button" position="bottom right" button="Save as Template" color="flat-green" onAction=(action 'saveTemplate') focusOn="new-template-name" }}
|
||||
<div>
|
||||
<div class="input-control">
|
||||
<label>Name</label>
|
||||
<div class="tip">Short name for this type of document</div>
|
||||
{{input type='text' id="new-template-name" value=saveTemplate.name}}
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Excerpt</label>
|
||||
<div class="tip">Explain use case for this template</div>
|
||||
{{textarea value=saveTemplate.description rows="3" id="new-template-desc"}}
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Name</label>
|
||||
<div class="tip">Short name for this type of document</div>
|
||||
{{input type='text' id="new-template-name" value=saveTemplate.name}}
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Excerpt</label>
|
||||
<div class="tip">Explain use case for this template</div>
|
||||
{{textarea value=saveTemplate.description rows="3" id="new-template-desc"}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{{#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')}}
|
||||
{{document/page-heading tagName=page.tagName document=document folder=folder page=page isEditor=isEditor onSaveAsPage=(action 'onSaveAsPage') onDeletePage=(action 'onDeletePage')}}
|
||||
{{section/base-renderer page=page}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,20 +3,46 @@
|
|||
<div id="page-toolbar-{{ page.id }}" class="pull-right page-toolbar hidden-xs hidden-sm">
|
||||
{{#if isEditor}}
|
||||
{{#link-to 'document.edit' folder.id folder.slug document.id document.slug page.id}}
|
||||
<div class="round-button-mono page-edit-button" data-tooltip="Edit" data-tooltip-position="top center">
|
||||
<i class="material-icons">mode_edit</i>
|
||||
<div class="round-button-mono page-action-button" data-tooltip="Edit" data-tooltip-position="top center">
|
||||
<i class="material-icons color-gray">mode_edit</i>
|
||||
</div>
|
||||
{{/link-to}}
|
||||
<div id="delete-page-button-{{page.id}}" class="round-button-mono page-delete-button" data-tooltip="Delete" data-tooltip-position="top center">
|
||||
<i class="material-icons">delete</i>
|
||||
|
||||
<div id="page-menu-{{page.id}}" class="round-button-mono page-action-button" data-tooltip="More options" data-tooltip-position="top center">
|
||||
<i class="material-icons color-gray">more_vert</i>
|
||||
</div>
|
||||
{{#dropdown-dialog target=dropTarget position="top right" button="Delete" color="flat-red" onAction=(action 'deletePage' page.id)}}
|
||||
<p>Are you sure you want to delete <span class="bold">{{page.title}}?</span></p>
|
||||
<p>
|
||||
{{input type="checkbox" id=checkId class="margin-left-20" checked=deleteChildren}}
|
||||
<label for="{{checkId}}"> Delete child pages</label>
|
||||
</p>
|
||||
{{/dropdown-dialog}}
|
||||
|
||||
{{#dropdown-menu target=menuTarget position="bottom right" open="click" onOpenCallback=(action 'onMenuOpen') onCloseCallback=(action 'onMenuOpen')}}
|
||||
<ul class="menu">
|
||||
<li class="item">Duplicate</li>
|
||||
<li class="item">Move</li>
|
||||
<li class="item" id="saveas-page-button-{{page.id}}">Publish</li>
|
||||
<li class="divider"></li>
|
||||
<li class="item danger" id={{deleteButtonId}}>Delete</li>
|
||||
</ul>
|
||||
{{/dropdown-menu}}
|
||||
|
||||
{{#if menuOpen}}
|
||||
{{#dropdown-dialog target=deleteButtonId position="top right" button="Delete" color="flat-red" onAction=(action 'deletePage' page.id)}}
|
||||
<p>Are you sure you want to delete <span class="bold">{{page.title}}?</span></p>
|
||||
<p>
|
||||
{{input type="checkbox" id=checkId class="margin-left-20" checked=deleteChildren}}
|
||||
<label for="{{checkId}}"> Delete child pages</label>
|
||||
</p>
|
||||
{{/dropdown-dialog}}
|
||||
{{#dropdown-dialog id=saveAsDialogId target=saveAsTarget position="top right" button="Publish" color="flat-green" focusOn=saveAsTitleId onAction=(action 'saveAsPage' page.id)}}
|
||||
<div class="form-header">
|
||||
<div class="tip">
|
||||
<span class="bold">{{folder.name}}:</span> Content Block
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-control">
|
||||
<label>Name</label>
|
||||
<div class="tip">Short description to help others understand<br/>this reusable content block</div>
|
||||
{{textarea rows="3" value=saveAsTitle id=saveAsTitleId}}
|
||||
</div>
|
||||
{{/dropdown-dialog}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,5 +19,25 @@
|
|||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#if hasTemplates}}
|
||||
<div class="divider"></div>
|
||||
<div class="template-caption">Published content blocks</div>
|
||||
<ul class="list">
|
||||
{{#each templates as |template|}}
|
||||
<li class="item" {{action 'insertTemplate' template}}>
|
||||
<div class="icon">
|
||||
<img class="img" src="/assets/img/section-saved.png" srcset="/assets/img/section-saved@2x.png" />
|
||||
</div>
|
||||
<div class="details">
|
||||
<div class='title'>
|
||||
{{template.title}}
|
||||
</div>
|
||||
<div class='desc'>{{template.firstname}} {{template.lastname}} · {{time-ago template.created}}</div>
|
||||
</div>
|
||||
<div class="clearfix" />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
BIN
app/public/assets/img/section-saved.png
Normal file
BIN
app/public/assets/img/section-saved.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 329 B |
BIN
app/public/assets/img/section-saved@2x.png
Normal file
BIN
app/public/assets/img/section-saved@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 774 B |
139
app/vendor/tether.js
vendored
139
app/vendor/tether.js
vendored
|
@ -1,4 +1,4 @@
|
|||
/*! tether 1.3.2 */
|
||||
/*! tether 1.4.0 */
|
||||
|
||||
(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
|
@ -23,6 +23,32 @@ if (typeof TetherBase === 'undefined') {
|
|||
|
||||
var zeroElement = null;
|
||||
|
||||
// Same as native getBoundingClientRect, except it takes into account parent <frame> offsets
|
||||
// if the element lies within a nested document (<frame> or <iframe>-like).
|
||||
function getActualBoundingClientRect(node) {
|
||||
var boundingRect = node.getBoundingClientRect();
|
||||
|
||||
// The original object returned by getBoundingClientRect is immutable, so we clone it
|
||||
// We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
|
||||
var rect = {};
|
||||
for (var k in boundingRect) {
|
||||
rect[k] = boundingRect[k];
|
||||
}
|
||||
|
||||
if (node.ownerDocument !== document) {
|
||||
var _frameElement = node.ownerDocument.defaultView.frameElement;
|
||||
if (_frameElement) {
|
||||
var frameRect = getActualBoundingClientRect(_frameElement);
|
||||
rect.top += frameRect.top;
|
||||
rect.bottom += frameRect.top;
|
||||
rect.left += frameRect.left;
|
||||
rect.right += frameRect.left;
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
function getScrollParents(el) {
|
||||
// In firefox if the el is inside an iframe with display: none; window.getComputedStyle() will return null;
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=548397
|
||||
|
@ -51,14 +77,20 @@ function getScrollParents(el) {
|
|||
var overflowX = _style.overflowX;
|
||||
var overflowY = _style.overflowY;
|
||||
|
||||
if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) {
|
||||
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
|
||||
if (position !== 'absolute' || ['relative', 'absolute', 'fixed'].indexOf(style.position) >= 0) {
|
||||
parents.push(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parents.push(document.body);
|
||||
parents.push(el.ownerDocument.body);
|
||||
|
||||
// If the node is within a frame, account for the parent window scroll
|
||||
if (el.ownerDocument !== document) {
|
||||
parents.push(el.ownerDocument.defaultView);
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
|
@ -76,7 +108,7 @@ var getOrigin = function getOrigin() {
|
|||
// are equivilant or not. We place an element at the top left of the page that will
|
||||
// get the same jitter, so we can cancel the two out.
|
||||
var node = zeroElement;
|
||||
if (!node) {
|
||||
if (!node || !document.body.contains(node)) {
|
||||
node = document.createElement('div');
|
||||
node.setAttribute('data-tether-id', uniqueId());
|
||||
extend(node.style, {
|
||||
|
@ -92,13 +124,7 @@ var getOrigin = function getOrigin() {
|
|||
|
||||
var id = node.getAttribute('data-tether-id');
|
||||
if (typeof zeroPosCache[id] === 'undefined') {
|
||||
zeroPosCache[id] = {};
|
||||
|
||||
var rect = node.getBoundingClientRect();
|
||||
for (var k in rect) {
|
||||
// Can't use extend, as on IE9, elements don't resolve to be hasOwnProperty
|
||||
zeroPosCache[id][k] = rect[k];
|
||||
}
|
||||
zeroPosCache[id] = getActualBoundingClientRect(node);
|
||||
|
||||
// Clear the cache when this position call is done
|
||||
defer(function () {
|
||||
|
@ -127,13 +153,7 @@ function getBounds(el) {
|
|||
|
||||
var docEl = doc.documentElement;
|
||||
|
||||
var box = {};
|
||||
// The original object returned by getBoundingClientRect is immutable, so we clone it
|
||||
// We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
|
||||
var rect = el.getBoundingClientRect();
|
||||
for (var k in rect) {
|
||||
box[k] = rect[k];
|
||||
}
|
||||
var box = getActualBoundingClientRect(el);
|
||||
|
||||
var origin = getOrigin();
|
||||
|
||||
|
@ -159,7 +179,11 @@ function getOffsetParent(el) {
|
|||
return el.offsetParent || document.documentElement;
|
||||
}
|
||||
|
||||
var _scrollBarSize = null;
|
||||
function getScrollBarSize() {
|
||||
if (_scrollBarSize) {
|
||||
return _scrollBarSize;
|
||||
}
|
||||
var inner = document.createElement('div');
|
||||
inner.style.width = '100%';
|
||||
inner.style.height = '200px';
|
||||
|
@ -192,7 +216,8 @@ function getScrollBarSize() {
|
|||
|
||||
var width = widthContained - widthScroll;
|
||||
|
||||
return { width: width, height: width };
|
||||
_scrollBarSize = { width: width, height: width };
|
||||
return _scrollBarSize;
|
||||
}
|
||||
|
||||
function extend() {
|
||||
|
@ -252,7 +277,9 @@ function hasClass(el, name) {
|
|||
}
|
||||
|
||||
function getClassName(el) {
|
||||
if (el.className instanceof SVGAnimatedString) {
|
||||
// Can't use just SVGAnimatedString here since nodes within a Frame in IE have
|
||||
// completely separately SVGAnimatedString base classes
|
||||
if (el.className instanceof el.ownerDocument.defaultView.SVGAnimatedString) {
|
||||
return el.className.baseVal;
|
||||
}
|
||||
return el.className;
|
||||
|
@ -371,6 +398,7 @@ var Evented = (function () {
|
|||
})();
|
||||
|
||||
TetherBase.Utils = {
|
||||
getActualBoundingClientRect: getActualBoundingClientRect,
|
||||
getScrollParents: getScrollParents,
|
||||
getBounds: getBounds,
|
||||
getOffsetParent: getOffsetParent,
|
||||
|
@ -429,7 +457,7 @@ var transformKey = (function () {
|
|||
}
|
||||
var el = document.createElement('div');
|
||||
|
||||
var transforms = ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform'];
|
||||
var transforms = ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform'];
|
||||
for (var i = 0; i < transforms.length; ++i) {
|
||||
var key = transforms[i];
|
||||
if (el.style[key] !== undefined) {
|
||||
|
@ -828,7 +856,7 @@ var TetherClass = (function (_Evented) {
|
|||
this.enabled = true;
|
||||
|
||||
this.scrollParents.forEach(function (parent) {
|
||||
if (parent !== document) {
|
||||
if (parent !== _this3.target.ownerDocument) {
|
||||
parent.addEventListener('scroll', _this3.position);
|
||||
}
|
||||
});
|
||||
|
@ -1028,21 +1056,24 @@ var TetherClass = (function (_Evented) {
|
|||
}
|
||||
};
|
||||
|
||||
var doc = this.target.ownerDocument;
|
||||
var win = doc.defaultView;
|
||||
|
||||
var scrollbarSize = undefined;
|
||||
if (document.body.scrollWidth > window.innerWidth) {
|
||||
if (win.innerHeight > doc.documentElement.clientHeight) {
|
||||
scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
|
||||
next.viewport.bottom -= scrollbarSize.height;
|
||||
}
|
||||
|
||||
if (document.body.scrollHeight > window.innerHeight) {
|
||||
if (win.innerWidth > doc.documentElement.clientWidth) {
|
||||
scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
|
||||
next.viewport.right -= scrollbarSize.width;
|
||||
}
|
||||
|
||||
if (['', 'static'].indexOf(document.body.style.position) === -1 || ['', 'static'].indexOf(document.body.parentElement.style.position) === -1) {
|
||||
if (['', 'static'].indexOf(doc.body.style.position) === -1 || ['', 'static'].indexOf(doc.body.parentElement.style.position) === -1) {
|
||||
// Absolute positioning in the body will be relative to the page, not the 'initial containing block'
|
||||
next.page.bottom = document.body.scrollHeight - top - height;
|
||||
next.page.right = document.body.scrollWidth - left - width;
|
||||
next.page.bottom = doc.body.scrollHeight - top - height;
|
||||
next.page.right = doc.body.scrollWidth - left - width;
|
||||
}
|
||||
|
||||
if (typeof this.options.optimizations !== 'undefined' && this.options.optimizations.moveElement !== false && !(typeof this.targetModifier !== 'undefined')) {
|
||||
|
@ -1061,8 +1092,8 @@ var TetherClass = (function (_Evented) {
|
|||
offsetBorder[side.toLowerCase()] = parseFloat(offsetParentStyle['border' + side + 'Width']);
|
||||
});
|
||||
|
||||
offsetPosition.right = document.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right;
|
||||
offsetPosition.bottom = document.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom;
|
||||
offsetPosition.right = doc.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right;
|
||||
offsetPosition.bottom = doc.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom;
|
||||
|
||||
if (next.page.top >= offsetPosition.top + offsetBorder.top && next.page.bottom >= offsetPosition.bottom) {
|
||||
if (next.page.left >= offsetPosition.left + offsetBorder.left && next.page.right >= offsetPosition.right) {
|
||||
|
@ -1155,7 +1186,16 @@ var TetherClass = (function (_Evented) {
|
|||
xPos = -_pos.right;
|
||||
}
|
||||
|
||||
css[transformKey] = 'translateX(' + Math.round(xPos) + 'px) translateY(' + Math.round(yPos) + 'px)';
|
||||
if (window.matchMedia) {
|
||||
// HubSpot/tether#207
|
||||
var retina = window.matchMedia('only screen and (min-resolution: 1.3dppx)').matches || window.matchMedia('only screen and (-webkit-min-device-pixel-ratio: 1.3)').matches;
|
||||
if (!retina) {
|
||||
xPos = Math.round(xPos);
|
||||
yPos = Math.round(yPos);
|
||||
}
|
||||
}
|
||||
|
||||
css[transformKey] = 'translateX(' + xPos + 'px) translateY(' + yPos + 'px)';
|
||||
|
||||
if (transformKey !== 'msTransform') {
|
||||
// The Z transform will keep this in the GPU (faster, and prevents artifacts),
|
||||
|
@ -1207,20 +1247,24 @@ var TetherClass = (function (_Evented) {
|
|||
}
|
||||
|
||||
if (!moved) {
|
||||
var offsetParentIsBody = true;
|
||||
var currentNode = this.element.parentNode;
|
||||
while (currentNode && currentNode.nodeType === 1 && currentNode.tagName !== 'BODY') {
|
||||
if (getComputedStyle(currentNode).position !== 'static') {
|
||||
offsetParentIsBody = false;
|
||||
break;
|
||||
if (this.options.bodyElement) {
|
||||
this.options.bodyElement.appendChild(this.element);
|
||||
} else {
|
||||
var offsetParentIsBody = true;
|
||||
var currentNode = this.element.parentNode;
|
||||
while (currentNode && currentNode.nodeType === 1 && currentNode.tagName !== 'BODY') {
|
||||
if (getComputedStyle(currentNode).position !== 'static') {
|
||||
offsetParentIsBody = false;
|
||||
break;
|
||||
}
|
||||
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
|
||||
currentNode = currentNode.parentNode;
|
||||
}
|
||||
|
||||
if (!offsetParentIsBody) {
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
document.body.appendChild(this.element);
|
||||
if (!offsetParentIsBody) {
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
this.element.ownerDocument.body.appendChild(this.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1240,6 +1284,7 @@ var TetherClass = (function (_Evented) {
|
|||
if (write) {
|
||||
defer(function () {
|
||||
extend(_this8.element.style, writeCSS);
|
||||
_this8.trigger('repositioned');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1280,12 +1325,22 @@ function getBoundingRect(tether, to) {
|
|||
|
||||
if (typeof to.nodeType !== 'undefined') {
|
||||
(function () {
|
||||
var node = to;
|
||||
var size = getBounds(to);
|
||||
var pos = size;
|
||||
var style = getComputedStyle(to);
|
||||
|
||||
to = [pos.left, pos.top, size.width + pos.left, size.height + pos.top];
|
||||
|
||||
// Account any parent Frames scroll offset
|
||||
if (node.ownerDocument !== document) {
|
||||
var win = node.ownerDocument.defaultView;
|
||||
to[0] += win.pageXOffset;
|
||||
to[1] += win.pageYOffset;
|
||||
to[2] += win.pageXOffset;
|
||||
to[3] += win.pageYOffset;
|
||||
}
|
||||
|
||||
BOUNDS_FORMAT.forEach(function (side, i) {
|
||||
side = side[0].toUpperCase() + side.substr(1);
|
||||
if (side === 'Top' || side === 'Left') {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue