Merge pull request #5735 from opf/feature/24916/file-upload-paste

[24916] File uploads through clipboard paste
pull/5744/head
ulferts 7 years ago committed by GitHub
commit fa1b4983d3
  1. 8
      app/assets/stylesheets/content/work_packages/inplace_editing/_edit_fields.sass
  2. 1
      config/locales/js-en.yml
  3. 73
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/drop-model.ts
  4. 61
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/editor-model.ts
  5. 34
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/field-model.ts
  6. 33
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/markup-model.ts
  7. 29
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/paste-model.ts
  8. 22
      frontend/app/components/work-packages/wp-attachments-formattable-field/models/single-attachment.ts
  9. 159
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.directive.ts
  10. 15
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.enums.ts
  11. 8
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.interfaces.ts
  12. 216
      frontend/app/components/work-packages/wp-attachments-formattable-field/wp-attachments-formattable.models.ts
  13. 4
      frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.test.ts
  14. 2
      frontend/app/components/wp-attachments/wp-attachments-upload/wp-attachments-upload.directive.ts
  15. 4
      frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.directive.html
  16. 5
      frontend/app/components/wp-edit/field-types/wp-edit-wiki-textarea-field.module.ts
  17. 6
      frontend/npm-shrinkwrap.json
  18. 8
      frontend/package.json

@ -164,3 +164,11 @@
// Allow horizontal scrolling in descripton field
.work-packages--details--description .wp-table--cell-span .inplace-edit--read-value--value-span
overflow-x: auto
// Style the attachment hint label of the description field
.wp-edit-field-attachment-label
position: absolute
left: 1px
font-size: 0.8rem
padding-top: 5px
font-style: italic

@ -210,6 +210,7 @@ en:
label_drop_files: Drop files here
label_drop_files_hint: or click to add files
label_add_attachments: "Add attachments"
label_formattable_attachment_hint: "Attach and link files by dropping on this field, or pasting from the clipboard."
label_remove_file: "Delete %{fileName}"
label_remove_watcher: "Remove watcher %{name}"
label_remove_all_files: Delete all files

@ -0,0 +1,73 @@
import IAugmentedJQuery = angular.IAugmentedJQuery;
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
export class DropModel {
public files:File[];
public filesCount:number;
public isUpload:boolean;
public isDelayedUpload:boolean;
public isWebLink:boolean;
public webLinkUrl:string;
protected config:any = {
imageFileTypes: ['jpg', 'jpeg', 'gif', 'png'],
maximumAttachmentFileSize: 0, // initialized during init process from ConfigurationService
};
constructor(protected $location:ng.ILocationService,
protected dataTransfer:any,
protected workPackage:WorkPackageResourceInterface) {
this.files = <File[]>dataTransfer.files;
this.filesCount = this.files.length;
this.isUpload = this._isUpload(dataTransfer);
this.isDelayedUpload = this.workPackage.isNew;
this.isWebLink = !this.isUpload;
this.webLinkUrl = dataTransfer.getData('URL');
}
public isWebImage():boolean {
if (angular.isDefined(this.webLinkUrl)) {
const ext = (this.webLinkUrl.split('.') as any[]).pop();
return (this.config.imageFileTypes.indexOf(ext.toLowerCase()) > -1);
}
return false;
}
public isAttachmentOfCurrentWp():boolean {
if (this.isWebLink) {
// weblink does not point to our server, so it can't be an attachment
if (!(this.webLinkUrl.indexOf(this.$location.host()) > -1)) {
return false;
}
var isAttachment:boolean = false;
this.workPackage.attachments.elements.forEach(attachment => {
if (this.webLinkUrl.indexOf(attachment.href as string) > -1) {
isAttachment = true;
return;
}
});
return isAttachment;
}
return false;
}
public removeHostInformationFromUrl():string {
return this.webLinkUrl.replace(window.location.origin, '');
}
protected _isUpload(dt:DataTransfer):boolean {
if (dt.types && this.filesCount > 0) {
for (let i = 0; i < dt.types.length; i++) {
if (dt.types[i] === 'Files') {
return true;
}
}
}
return false;
}
}

@ -0,0 +1,61 @@
import {IApplyAttachmentMarkup} from '../wp-attachments-formattable.interfaces';
import {InsertMode} from '../wp-attachments-formattable.enums';
import {MarkupModel} from './markup-model';
import IAugmentedJQuery = angular.IAugmentedJQuery;
export class EditorModel implements IApplyAttachmentMarkup {
private currentCaretPosition:number;
public contentToInsert:string = '';
constructor(protected textarea:IAugmentedJQuery, protected markupModel:MarkupModel) {
this.setCaretPosition();
}
public insertWebLink(url:string, insertMode:InsertMode = InsertMode.LINK):void {
this.contentToInsert = this.markupModel.createMarkup(url, insertMode);
}
public insertAttachmentLink(url:string,
insertMode:InsertMode = InsertMode.ATTACHMENT,
addLineBreak?:boolean):void {
this.contentToInsert = (addLineBreak) ?
this.contentToInsert + this.markupModel.createMarkup(url, insertMode, addLineBreak) :
this.markupModel.createMarkup(url, insertMode, addLineBreak);
}
private setCaretPosition():void {
this.currentCaretPosition = (this.textarea[0] as HTMLTextAreaElement).selectionStart;
}
public save():void {
let insertPosition = this.normalizeInputAndGetInsertPosition();
let currentValue = this.textarea.val();
let newValue = currentValue.substring(0, insertPosition) +
this.contentToInsert +
currentValue.substring(this.currentCaretPosition, currentValue.length);
this.textarea.val(newValue).change();
}
/*
* Assure that no whitespace is left before the inlined image as whitespaces will lead to the image being wrapped in a
* <pre><code>image</code><pre> block
* Removes two sources of whitespace:
* * The whitespace prepended to the content to insert
* * Whitespace added by the user on a newline
*/
private normalizeInputAndGetInsertPosition() {
let currentValue = this.textarea.val();
let newlineMatch = currentValue.substring(0, this.currentCaretPosition).match(/\n *$/);
let newlineIndex = newlineMatch && newlineMatch.index || 0;
let whitespacesBeforeCaret = this.currentCaretPosition - newlineIndex;
if (whitespacesBeforeCaret > 0) {
this.contentToInsert = this.contentToInsert.substring(1, this.contentToInsert.length);
}
return (newlineIndex && newlineIndex + 1) || this.currentCaretPosition;
}
}

@ -0,0 +1,34 @@
import {InsertMode} from '../wp-attachments-formattable.enums';
import {IApplyAttachmentMarkup} from '../wp-attachments-formattable.interfaces';
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
import {MarkupModel} from './markup-model';
import IAugmentedJQuery = angular.IAugmentedJQuery;
export class FieldModel implements IApplyAttachmentMarkup {
public contentToInsert:string;
constructor(protected workPackage:WorkPackageResourceInterface, protected markupModel:MarkupModel) {
this.contentToInsert = workPackage.description.raw || '';
}
private addInitialLineBreak():string {
return (this.contentToInsert.length > 0) ? '\r\n' : '';
}
public insertAttachmentLink(url:string, insertMode:InsertMode, addLineBreak?:boolean):void {
this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url,
insertMode,
false);
}
public insertWebLink(url:string, insertMode:InsertMode):void {
this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url,
insertMode,
false);
}
public save():void {
this.workPackage.description.raw = this.contentToInsert;
this.workPackage.save();
}
}

@ -0,0 +1,33 @@
import {InsertMode} from '../wp-attachments-formattable.enums';
import IAugmentedJQuery = angular.IAugmentedJQuery;
export class MarkupModel {
public createMarkup(insertUrl:string, insertMode:InsertMode, addLineBreak:boolean = false):string {
if (angular.isUndefined((insertUrl))) {
return '';
}
var markup:string = ' ';
switch (insertMode) {
case InsertMode.ATTACHMENT:
markup += 'attachment:' + insertUrl.split('/').pop();
break;
case InsertMode.DELAYED_ATTACHMENT:
markup += 'attachment:' + insertUrl;
break;
case InsertMode.INLINE:
markup += '!' + insertUrl + '!';
break;
case InsertMode.LINK:
markup += insertUrl;
break;
}
if (addLineBreak) {
markup += '\r\n';
}
return markup;
}
}

@ -0,0 +1,29 @@
import IAugmentedJQuery = angular.IAugmentedJQuery;
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
export class PasteModel {
public files:File[];
constructor(protected dataTransfer:DataTransfer) {
this.files = this.extractFiles();
}
private extractFiles():File[] {
const files:File[] = [];
const items = this.dataTransfer.items;
if (!items) {
return files;
}
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
//image
const blob = items[i].getAsFile()!;
files.push(blob);
}
}
return files;
}
}

@ -0,0 +1,22 @@
import IAugmentedJQuery = angular.IAugmentedJQuery;
export class SingleAttachmentModel {
protected imageFileExtensions:Array<string> = ['jpeg', 'jpg', 'gif', 'bmp', 'png'];
public fileExtension:string;
public fileName:string;
public isAnImage:boolean;
public url:string;
constructor(protected attachment:any) {
if (angular.isDefined(attachment)) {
this.fileName = attachment.fileName || attachment.name;
this.fileExtension = (this.fileName.split('.') as any[]).pop().toLowerCase();
this.isAnImage = this.imageFileExtensions.indexOf(this.fileExtension) > -1;
this.url = angular.isDefined(attachment.downloadLocation) ? attachment.downloadLocation.$link.href : '';
}
}
}

@ -1,24 +1,19 @@
import {InsertMode, ViewMode} from './wp-attachments-formattable.enums';
import {
DropModel,
EditorModel,
MarkupModel,
FieldModel,
SingleAttachmentModel
} from './wp-attachments-formattable.models';
import {
WorkPackageResourceInterface
} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageSingleViewController} from '../wp-single-view/wp-single-view.directive';
import {WorkPackageEditFormController} from '../../wp-edit/wp-edit-form.directive';
import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {openprojectModule} from '../../../angular-modules';
import {WorkPackageCacheService} from '../work-package-cache.service';
import {WorkPackageEditModeStateService} from '../../wp-edit/wp-edit-mode-state.service';
import {MarkupModel} from './models/markup-model';
import {EditorModel} from './models/editor-model';
import {PasteModel} from './models/paste-model';
import {FieldModel} from './models/field-model';
import {DropModel} from './models/drop-model';
import {SingleAttachmentModel} from './models/single-attachment';
export class WpAttachmentsFormattableController {
private viewMode:ViewMode = ViewMode.SHOW;
constructor(protected $scope:ng.IScope,
protected $element:ng.IAugmentedJQuery,
protected $rootScope:ng.IRootScopeService,
@ -31,6 +26,7 @@ export class WpAttachmentsFormattableController {
protected loadingIndicator:any,
protected keepTab:KeepTabService) {
$element.on('paste', this.handlePaste);
$element.on('drop', this.handleDrop);
$element.on('dragover', this.highlightDroppable);
$element.on('dragleave', this.removeHighlight);
@ -45,59 +41,96 @@ export class WpAttachmentsFormattableController {
evt.preventDefault();
evt.stopPropagation();
const textarea:ng.IAugmentedJQuery = this.$element.find('textarea');
this.viewMode = (textarea.length > 0) ? ViewMode.EDIT : ViewMode.SHOW;
const [, editor] = this.getEditor();
const originalEvent = (evt.originalEvent as DragEvent);
const workPackage:WorkPackageResourceInterface = (this.$scope as any).workPackage;
const dropData:DropModel = new DropModel(this.$location, originalEvent.dataTransfer, workPackage);
var description:any;
if (this.viewMode === ViewMode.EDIT) {
description = new EditorModel(textarea, new MarkupModel());
}
else {
description = new FieldModel(workPackage, new MarkupModel());
}
const dropData:DropModel = new DropModel(this.$location,
originalEvent.dataTransfer,
workPackage);
if (angular.isUndefined(dropData.webLinkUrl) && angular.isUndefined(dropData.files)) {
return;
}
if (dropData.isUpload) {
if (dropData.filesAreValidForUploading()) {
if (!dropData.isDelayedUpload) {
workPackage
.uploadAttachments(<any> dropData.files)
.then(attachments => attachments.elements)
.then((updatedAttachments:any) => {
if (angular.isUndefined(updatedAttachments)) {
return;
}
updatedAttachments = this.sortAttachments(updatedAttachments);
if (dropData.filesCount === 1) {
this.insertSingleAttachment(updatedAttachments, description);
}
else if (dropData.filesCount > 1) {
this.insertMultipleAttachments(dropData, updatedAttachments, description);
}
description.save();
});
}
else {
this.insertDelayedAttachments(dropData, description, workPackage);
}
}
}
else {
this.insertUrls(dropData, description);
this.uploadAndInsert(dropData.files, editor);
} else {
this.insertUrls(dropData, editor);
}
this.openDetailsView(workPackage.id.toString());
this.removeHighlight();
};
}
public handlePaste = (evt:JQueryEventObject):boolean => {
const [viewMode, editor] = this.getEditor();
if (viewMode !== ViewMode.EDIT) {
return true;
}
const pasteEvt = (evt.originalEvent as ClipboardEvent);
const pasteData = new PasteModel(pasteEvt.clipboardData);
const count = pasteData.files.length;
if (count === 0) {
return true;
}
this.uploadAndInsert(pasteData.files, editor);
evt.preventDefault();
evt.stopPropagation();
return false;
}
/**
* Get the editor model for the current view mode.
* This is either the editing model (open textarea field), or the closed field model.
*/
protected getEditor():[ViewMode, EditorModel | FieldModel] {
const textarea:ng.IAugmentedJQuery = this.$element.find('textarea');
let viewMode;
let model;
if (textarea.length > 0) {
viewMode = ViewMode.EDIT;
model = new EditorModel(textarea, new MarkupModel());
} else {
viewMode = ViewMode.SHOW;
model = new FieldModel(this.$scope.workPackage, new MarkupModel());
}
return [viewMode, model];
}
protected uploadAndInsert(files:File[], model:EditorModel | FieldModel) {
const workPackage:WorkPackageResourceInterface = this.$scope.workPackage;
if (workPackage.isNew) {
return this.insertDelayedAttachments(files, model, workPackage);
}
workPackage
.uploadAttachments(files)
.then(attachments => attachments.elements)
.then((updatedAttachments:any) => {
if (angular.isUndefined(updatedAttachments)) {
return;
}
updatedAttachments = this.sortAttachments(updatedAttachments);
if (files.length === 1) {
this.insertSingleAttachment(updatedAttachments, model);
}
else if (files.length > 1) {
this.insertMultipleAttachments(files.length, updatedAttachments, model);
}
model.save();
});
}
protected sortAttachments(updatedAttachments:any) {
updatedAttachments.sort(function (a:any, b:any) {
@ -114,9 +147,9 @@ export class WpAttachmentsFormattableController {
(currentFile.isAnImage) ? InsertMode.INLINE : InsertMode.ATTACHMENT);
}
protected insertMultipleAttachments(dropData:DropModel, updatedAttachments:any, description:any):void {
protected insertMultipleAttachments(count:number, updatedAttachments:any, description:any):void {
for (let i:number = updatedAttachments.length - 1;
i >= updatedAttachments.length - dropData.filesCount;
i >= updatedAttachments.length - count;
i--) {
description.insertAttachmentLink(
updatedAttachments[i].downloadLocation.href,
@ -125,18 +158,18 @@ export class WpAttachmentsFormattableController {
}
}
protected insertDelayedAttachments(dropData:DropModel, description:any, workPackage: WorkPackageResourceInterface):void {
for (var i = 0; i < dropData.files.length; i++) {
var currentFile = new SingleAttachmentModel(dropData.files[i]);
protected insertDelayedAttachments(files:File[], description:any, workPackage:WorkPackageResourceInterface):void {
for (var i = 0; i < files.length; i++) {
var currentFile = new SingleAttachmentModel(files[i]);
var insertMode = currentFile.isAnImage ? InsertMode.INLINE : InsertMode.ATTACHMENT;
description.insertAttachmentLink(dropData.files[i].name.replace(/ /g, '_'), insertMode, true);
workPackage.pendingAttachments.push((dropData.files[i]));
description.insertAttachmentLink(files[i].name.replace(/ /g, '_'), insertMode, true);
workPackage.pendingAttachments.push((files[i]));
}
description.save();
}
protected insertUrls(dropData: DropModel, description:any):void {
protected insertUrls(dropData:DropModel, description:any):void {
const insertUrl:string = dropData.isAttachmentOfCurrentWp() ? dropData.removeHostInformationFromUrl() : dropData.webLinkUrl;
const insertAlternative:InsertMode = dropData.isWebImage() ? InsertMode.INLINE : InsertMode.LINK;
const insertMode:InsertMode = dropData.isAttachmentOfCurrentWp() ? InsertMode.ATTACHMENT : insertAlternative;
@ -148,8 +181,8 @@ export class WpAttachmentsFormattableController {
protected openDetailsView(wpId:string):void {
const stateName = this.$state.current.name as string;
if (stateName.indexOf('work-packages.list') > -1 &&
!this.wpEditModeState.active &&
this.$state.params['workPackageId'] !== wpId) {
!this.wpEditModeState.active &&
this.$state.params['workPackageId'] !== wpId) {
this.loadingIndicator.mainPage = this.$state.go(this.keepTab.currentDetailsState, {
workPackageId: wpId
});

@ -1,14 +1,11 @@
export enum InsertMode {
ATTACHMENT,
DELAYED_ATTACHMENT,
INLINE,
LINK
ATTACHMENT,
DELAYED_ATTACHMENT,
INLINE,
LINK
}
export enum ViewMode {
EDIT,
SHOW,
CREATE
EDIT,
SHOW
}

@ -1,9 +1,9 @@
import {InsertMode} from './wp-attachments-formattable.enums';
export interface IApplyAttachmentMarkup {
contentToInsert: string;
contentToInsert:string;
insertAttachmentLink: (url: string, insertMode: InsertMode, addLineBreak?:boolean) => void;
insertWebLink: (url: string, insertMode: InsertMode) => void;
save: () => void;
insertAttachmentLink:(url:string, insertMode:InsertMode, addLineBreak?:boolean) => void;
insertWebLink:(url:string, insertMode:InsertMode) => void;
save:() => void;
}

@ -1,216 +0,0 @@
import {IApplyAttachmentMarkup} from './wp-attachments-formattable.interfaces';
import {InsertMode} from './wp-attachments-formattable.enums';
import {
WorkPackageResourceInterface
} from '../../api/api-v3/hal-resources/work-package-resource.service';
import IAugmentedJQuery = angular.IAugmentedJQuery;
export class EditorModel implements IApplyAttachmentMarkup {
private currentCaretPosition:number;
public contentToInsert:string = '';
constructor(protected textarea:IAugmentedJQuery, protected markupModel:MarkupModel) {
this.setCaretPosition();
}
public insertWebLink(url:string, insertMode:InsertMode = InsertMode.LINK):void {
this.contentToInsert = this.markupModel.createMarkup(url, insertMode);
}
public insertAttachmentLink(url:string,
insertMode:InsertMode = InsertMode.ATTACHMENT,
addLineBreak?:boolean):void {
this.contentToInsert = (addLineBreak) ?
this.contentToInsert + this.markupModel.createMarkup(url, insertMode, addLineBreak) :
this.markupModel.createMarkup(url, insertMode, addLineBreak);
}
private setCaretPosition():void {
this.currentCaretPosition = (this.textarea[0] as HTMLTextAreaElement).selectionStart;
}
public save():void {
let insertPosition = this.normalizeInputAndGetInsertPosition();
let currentValue = this.textarea.val();
let newValue = currentValue.substring(0, insertPosition) +
this.contentToInsert +
currentValue.substring(this.currentCaretPosition, currentValue.length);
this.textarea.val(newValue).change();
}
/*
* Assure that no whitespace is left before the inlined image as whitespaces will lead to the image being wrapped in a
* <pre><code>image</code><pre> block
* Removes two sources of whitespace:
* * The whitespace prepended to the content to insert
* * Whitespace added by the user on a newline
*/
private normalizeInputAndGetInsertPosition() {
let currentValue = this.textarea.val();
let newlineMatch = currentValue.substring(0, this.currentCaretPosition).match(/\n *$/);
let newlineIndex = newlineMatch && newlineMatch.index || 0;
let whitespacesBeforeCaret = this.currentCaretPosition - newlineIndex;
if (whitespacesBeforeCaret > 0) {
this.contentToInsert = this.contentToInsert.substring(1, this.contentToInsert.length);
}
return (newlineIndex && newlineIndex + 1) || this.currentCaretPosition;
}
}
export class MarkupModel {
public createMarkup(insertUrl:string, insertMode:InsertMode, addLineBreak:boolean = false):string {
if (angular.isUndefined((insertUrl))) {
return '';
}
var markup:string = ' ';
switch (insertMode) {
case InsertMode.ATTACHMENT:
markup += 'attachment:' + insertUrl.split('/').pop();
break;
case InsertMode.DELAYED_ATTACHMENT:
markup += 'attachment:' + insertUrl;
break;
case InsertMode.INLINE:
markup += '!' + insertUrl + '!';
break;
case InsertMode.LINK:
markup += insertUrl;
break;
}
if (addLineBreak) {
markup += '\r\n';
}
return markup;
}
}
export class DropModel {
public files:File[];
public filesCount:number;
public isUpload:boolean;
public isDelayedUpload:boolean;
public isWebLink:boolean;
public webLinkUrl:string;
protected config:any = {
imageFileTypes: ['jpg', 'jpeg', 'gif', 'png'],
maximumAttachmentFileSize: 0, // initialized during init process from ConfigurationService
};
constructor(protected $location:ng.ILocationService,
protected dataTransfer:any,
protected workPackage:WorkPackageResourceInterface) {
this.files = <File[]>dataTransfer.files;
this.filesCount = this.files.length;
this.isUpload = this._isUpload(dataTransfer);
this.isDelayedUpload = this.workPackage.isNew;
this.isWebLink = !this.isUpload;
this.webLinkUrl = dataTransfer.getData('URL');
}
public isWebImage():boolean {
if (angular.isDefined(this.webLinkUrl)) {
const ext = (this.webLinkUrl.split('.') as any[]).pop();
return (this.config.imageFileTypes.indexOf(ext.toLowerCase()) > -1);
}
return false;
}
public isAttachmentOfCurrentWp():boolean {
if (this.isWebLink) {
// weblink does not point to our server, so it can't be an attachment
if (!(this.webLinkUrl.indexOf(this.$location.host()) > -1)) {
return false;
}
var isAttachment:boolean = false;
this.workPackage.attachments.elements.forEach(attachment => {
if (this.webLinkUrl.indexOf(attachment.href as string) > -1) {
isAttachment = true;
return;
}
});
return isAttachment;
}
return false;
}
public filesAreValidForUploading():boolean {
return true;
}
public removeHostInformationFromUrl():string {
return this.webLinkUrl.replace(window.location.origin, '');
}
protected _isUpload(dt:DataTransfer):boolean {
if (dt.types && this.filesCount > 0) {
for (let i = 0; i < dt.types.length; i++) {
if (dt.types[i] === 'Files') {
return true;
}
}
}
return false;
}
}
export class SingleAttachmentModel {
protected imageFileExtensions:Array<string> = ['jpeg', 'jpg', 'gif', 'bmp', 'png'];
public fileExtension:string;
public fileName:string;
public isAnImage:boolean;
public url:string;
constructor(protected attachment:any) {
if (angular.isDefined(attachment)) {
this.fileName = attachment.fileName || attachment.name;
this.fileExtension = (this.fileName.split('.') as any[]).pop().toLowerCase();
this.isAnImage = this.imageFileExtensions.indexOf(this.fileExtension) > -1;
this.url = angular.isDefined(attachment.downloadLocation) ? attachment.downloadLocation.$link.href : '';
}
}
}
export class FieldModel implements IApplyAttachmentMarkup {
public contentToInsert:string;
constructor(protected workPackage:WorkPackageResourceInterface, protected markupModel:MarkupModel) {
this.contentToInsert = workPackage.description.raw || '';
}
private addInitialLineBreak():string {
return (this.contentToInsert.length > 0) ? '\r\n' : '';
}
public insertAttachmentLink(url:string, insertMode:InsertMode, addLineBreak?:boolean):void {
this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url, insertMode, false);
}
public insertWebLink(url:string, insertMode:InsertMode):void {
this.contentToInsert += this.addInitialLineBreak() + this.markupModel.createMarkup(url, insertMode, false);
}
public save():void {
this.workPackage.description.raw = this.contentToInsert;
this.workPackage.save();
}
}

@ -113,10 +113,6 @@ describe('wpAttachmentsUpload directive', () => {
expect(rootElement).to.have.length(1);
});
it('should set the max size property of the element to the configured value', () => {
expect(rootElement.attr('ngf-max-size')).to.equal(mockMaxSize.toString());
});
describe('when clicking the parent element', () => {
var clicked:any;

@ -74,7 +74,7 @@ function wpUploadDirective(): IDirective {
ngf-select
ngf-change="$ctrl.uploadFiles($files)"
ngf-multiple="true"
ngf-max-size="{{ ::$ctrl.maxFileSize }}"
ngf-validate="{ size: {max: ::$ctrl.maxFileSize} }"
tabindex="0"
aria-label="{{ ::$ctrl.text.uploadLabel }}"
click-on-keypress="[13, 32]"

@ -17,6 +17,10 @@
<div class="inplace-edit--preview" ng-show="vm.field.isPreview && !vm.field.isBusy">
<span bind-unescaped-html="vm.field.previewHtml"></span>
</div>
<div ng-if="vm.field.supportsAttachments && !vm.field.isPreview"
class="wp-edit-field-attachment-label">
<span ng-bind="vm.field.text.attachmentLabel"></span>
</div>
<wp-edit-field-controls ng-show="!vm.inEditMode"
field-controller="vm"
on-save="vm.handleUserSubmit()"

@ -59,11 +59,16 @@ export class WikiTextareaEditField extends EditField {
this.fieldVal = workPackage[fieldName];
this.workPackage = workPackage;
this.text = {
attachmentLabel: this.I18n.t('js.label_formattable_attachment_hint'),
save: this.I18n.t('js.inplace.button_save', { attribute: this.schema.name }),
cancel: this.I18n.t('js.inplace.button_cancel', { attribute: this.schema.name })
};
}
public get supportsAttachments() {
return this.name === 'description';
}
public isEmpty(): boolean {
return !(this.value && this.value.raw);
}

@ -3100,9 +3100,9 @@
"resolved": "https://registry.npmjs.org/ng-dialog/-/ng-dialog-0.6.6.tgz"
},
"ng-file-upload": {
"version": "5.0.9",
"from": "ng-file-upload@>=5.0.9 <5.1.0",
"resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-5.0.9.tgz"
"version": "12.2.13",
"from": "ng-file-upload@12.2.13",
"resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-12.2.13.tgz"
},
"ngtemplate-loader": {
"version": "0.1.3",

@ -52,13 +52,13 @@
"angular-animate": "~1.5.9",
"angular-aria": "~1.5.9",
"angular-cache": "~4.6.0",
"angular-context-menu": "opf/angular-context-menu#a908eccaec323cd66973d58af4965694bdff16a1",
"angular-context-menu": "git://github.com/opf/angular-context-menu#a908eccaec323cd66973d58af4965694bdff16a1",
"angular-dragula": "~1.2.8",
"angular-elastic": "2.5.0",
"angular-i18n": "~1.3.0",
"angular-modal": "finnlabs/angular-modal#d45eb9ceb720b8785613ba89ba0f14f8ab197569",
"angular-modal": "git://github.com/finnlabs/angular-modal#d45eb9ceb720b8785613ba89ba0f14f8ab197569",
"angular-sanitize": "~1.3.14",
"angular-truncate": "sparkalow/angular-truncate#fdf60fda265042d12e9414b5354b2cc52f1419de",
"angular-truncate": "git://github.com/sparkalow/angular-truncate#fdf60fda265042d12e9414b5354b2cc52f1419de",
"angular-ui-bootstrap": "^2.2.0",
"angular-ui-router": "^1.0.0-rc.1",
"atoa": "^1.0.0",
@ -94,7 +94,7 @@
"mousetrap": "~1.6.0",
"ng-annotate-loader": "^0.2.0",
"ng-dialog": "^0.6.4",
"ng-file-upload": "~5.0.9",
"ng-file-upload": "^12.2.13",
"ngtemplate-loader": "^0.1.2",
"observable-array": "0.0.4",
"phantomjs-polyfill": "0.0.2",

Loading…
Cancel
Save