clipboard
This commit is contained in:
parent
ad78e23303
commit
ef007d0ab5
|
@ -23,8 +23,8 @@ export class AppWindow {
|
||||||
// tslint:disable-next-line: variable-name
|
// tslint:disable-next-line: variable-name
|
||||||
private _rendererReadyTime: number | null = null;
|
private _rendererReadyTime: number | null = null;
|
||||||
|
|
||||||
private minWidth = 700;
|
private minWidth = 1160;
|
||||||
private minHeight = 600;
|
private minHeight = 800;
|
||||||
|
|
||||||
public constructor(private appIconPath: string) {
|
public constructor(private appIconPath: string) {
|
||||||
const savedWindowState = windowStateKeeper({
|
const savedWindowState = windowStateKeeper({
|
||||||
|
@ -165,7 +165,11 @@ export class AppWindow {
|
||||||
} else {
|
} else {
|
||||||
this.window.loadURL(
|
this.window.loadURL(
|
||||||
url.format({
|
url.format({
|
||||||
pathname: path.join(__dirname, '..', 'ucap-webmessenger-app/index.html'),
|
pathname: path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'ucap-webmessenger-app/index.html'
|
||||||
|
),
|
||||||
protocol: 'file:',
|
protocol: 'file:',
|
||||||
slashes: true
|
slashes: true
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,8 @@ import {
|
||||||
Menu,
|
Menu,
|
||||||
shell,
|
shell,
|
||||||
dialog,
|
dialog,
|
||||||
BrowserWindow
|
BrowserWindow,
|
||||||
|
clipboard
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fse from 'fs-extra';
|
import fse from 'fs-extra';
|
||||||
|
@ -29,7 +30,8 @@ import {
|
||||||
ChatChannel,
|
ChatChannel,
|
||||||
MessengerChannel,
|
MessengerChannel,
|
||||||
MessageChannel,
|
MessageChannel,
|
||||||
AppChannel
|
AppChannel,
|
||||||
|
ClipboardChannel
|
||||||
} from '@ucap-webmessenger/native-electron';
|
} from '@ucap-webmessenger/native-electron';
|
||||||
import { ElectronNotificationService } from '@ucap-webmessenger/electron-notification';
|
import { ElectronNotificationService } from '@ucap-webmessenger/electron-notification';
|
||||||
import { ElectronUpdateWindowService } from '@ucap-webmessenger/electron-update-window';
|
import { ElectronUpdateWindowService } from '@ucap-webmessenger/electron-update-window';
|
||||||
|
@ -729,6 +731,25 @@ ipcMain.on(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.on(ClipboardChannel.Read, (event: IpcMainEvent, ...args: any[]) => {
|
||||||
|
try {
|
||||||
|
const text = clipboard.readText('clipboard');
|
||||||
|
const rtf = clipboard.readRTF('clipboard');
|
||||||
|
const html = clipboard.readHTML('clipboard');
|
||||||
|
const image = clipboard.readImage('clipboard');
|
||||||
|
|
||||||
|
event.returnValue = {
|
||||||
|
text,
|
||||||
|
rtf,
|
||||||
|
html,
|
||||||
|
image: !image.isEmpty() ? image.toBitmap() : undefined,
|
||||||
|
imageDataUrl: !image.isEmpty() ? image.toDataURL() : undefined
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
event.returnValue = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.on(AppChannel.Exit, (event: IpcMainEvent, ...args: any[]) => {
|
ipcMain.on(AppChannel.Exit, (event: IpcMainEvent, ...args: any[]) => {
|
||||||
appExit();
|
appExit();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { share } from 'rxjs/operators';
|
import { share } from 'rxjs/operators';
|
||||||
|
import { FileUtil } from '@ucap-webmessenger/core';
|
||||||
|
|
||||||
export class FileUploadItem {
|
export class FileUploadItem {
|
||||||
file: File;
|
file: File;
|
||||||
|
@ -24,6 +25,24 @@ export class FileUploadItem {
|
||||||
return fileItems;
|
return fileItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromDataUrls(
|
||||||
|
fileNameAppend: string,
|
||||||
|
...dataUrls: string[]
|
||||||
|
): FileUploadItem[] {
|
||||||
|
const fileItems: FileUploadItem[] = [];
|
||||||
|
|
||||||
|
// tslint:disable-next-line: prefer-for-of
|
||||||
|
for (let i = 0; i < dataUrls.length; i++) {
|
||||||
|
fileItems.push(
|
||||||
|
new FileUploadItem(
|
||||||
|
FileUtil.fromDataUrlToFile(fileNameAppend, dataUrls[i])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileItems;
|
||||||
|
}
|
||||||
|
|
||||||
static from(): FileUploadItem {
|
static from(): FileUploadItem {
|
||||||
return new FileUploadItem();
|
return new FileUploadItem();
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,6 +327,7 @@
|
||||||
(clearView)="clearView()"
|
(clearView)="clearView()"
|
||||||
(toggleStickerSelector)="onShowToggleStickerSelector($event)"
|
(toggleStickerSelector)="onShowToggleStickerSelector($event)"
|
||||||
(toggleTranslation)="onShowToggleTranslation($event)"
|
(toggleTranslation)="onShowToggleTranslation($event)"
|
||||||
|
(clipboardPaste)="onClipboardPaste()"
|
||||||
></ucap-chat-form>
|
></ucap-chat-form>
|
||||||
<!-- / REPLY FORM -->
|
<!-- / REPLY FORM -->
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,8 @@ import {
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Inject,
|
Inject,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
ChangeDetectionStrategy
|
ChangeDetectionStrategy,
|
||||||
|
ElementRef
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ucapAnimations,
|
ucapAnimations,
|
||||||
|
@ -130,6 +131,11 @@ import { TranslateService } from '@ngx-translate/core';
|
||||||
import { TranslatePipe } from 'projects/ucap-webmessenger-ui/src/lib/pipes/translate.pipe';
|
import { TranslatePipe } from 'projects/ucap-webmessenger-ui/src/lib/pipes/translate.pipe';
|
||||||
import { TranslateService as UiTranslateService } from '@ucap-webmessenger/ui';
|
import { TranslateService as UiTranslateService } from '@ucap-webmessenger/ui';
|
||||||
import { FileProtocolService } from '@ucap-webmessenger/protocol-file';
|
import { FileProtocolService } from '@ucap-webmessenger/protocol-file';
|
||||||
|
import {
|
||||||
|
ClipboardDialogComponent,
|
||||||
|
ClipboardDialogData,
|
||||||
|
ClipboardDialogResult
|
||||||
|
} from '../dialogs/chat/clipboard.dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-layout-messenger-messages',
|
selector: 'app-layout-messenger-messages',
|
||||||
|
@ -1960,4 +1966,47 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
this.isTranslationProcess = false;
|
this.isTranslationProcess = false;
|
||||||
this.translationPreviewInfo = null;
|
this.translationPreviewInfo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClipboardPaste() {
|
||||||
|
this.nativeService.readFromClipboard().then(async data => {
|
||||||
|
if (!!data.image && !!data.text) {
|
||||||
|
const result = await this.dialogService.open<
|
||||||
|
ClipboardDialogComponent,
|
||||||
|
ClipboardDialogData,
|
||||||
|
ClipboardDialogResult
|
||||||
|
>(ClipboardDialogComponent, {
|
||||||
|
width: '800px',
|
||||||
|
maxWidth: '800px',
|
||||||
|
height: '800px',
|
||||||
|
minHeight: '800px',
|
||||||
|
disableClose: false,
|
||||||
|
data: {
|
||||||
|
content: data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.selected.text) {
|
||||||
|
this.onSendMessage(data.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.selected.image) {
|
||||||
|
const fileUploadItems = FileUploadItem.fromDataUrls(
|
||||||
|
'clipboard',
|
||||||
|
data.imageDataUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
this.onFileSelected(fileUploadItems);
|
||||||
|
}
|
||||||
|
} else if (!!data.image && !data.text) {
|
||||||
|
const fileUploadItems = FileUploadItem.fromDataUrls(
|
||||||
|
'clipboard',
|
||||||
|
data.imageDataUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
this.onFileSelected(fileUploadItems);
|
||||||
|
} else {
|
||||||
|
this.chatForm.replyInput.nativeElement.value = `${this.chatForm.replyInput.nativeElement.value}${data.text}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
<mat-card class="confirm-card mat-elevation-z setting-frame">
|
||||||
|
<mat-card-header cdkDrag cdkDragRootElement=".cdk-overlay-pane" cdkDragHandle>
|
||||||
|
<mat-card-title>{{ 'settings.label' | translate }}</mat-card-title>
|
||||||
|
<button class="icon-button btn-dialog-close" (click)="onClickChoice(false)">
|
||||||
|
<i class="mdi mdi-window-close"></i>
|
||||||
|
</button>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div fxFlex class="setting-tab">
|
||||||
|
<mat-tab-group animationDuration="0ms">
|
||||||
|
<mat-tab *ngIf="data.content.text">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-checkbox #chkText> </mat-checkbox>
|
||||||
|
<span class="title-text">{{
|
||||||
|
'common.file.clipboardType.text' | translate
|
||||||
|
}}</span>
|
||||||
|
</ng-template>
|
||||||
|
<perfect-scrollbar>
|
||||||
|
<div>{{ data.content.text }}</div>
|
||||||
|
</perfect-scrollbar>
|
||||||
|
</mat-tab>
|
||||||
|
<!-- <mat-tab *ngIf="data.content.rtf">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-checkbox #chkRtf="matCheckbox"> </mat-checkbox>
|
||||||
|
<span class="title-text">{{
|
||||||
|
'common.file.clipboardType.rtf' | translate
|
||||||
|
}}</span>
|
||||||
|
</ng-template>
|
||||||
|
<perfect-scrollbar>
|
||||||
|
<div>{{ data.content.rtf }}</div>
|
||||||
|
</perfect-scrollbar>
|
||||||
|
</mat-tab> -->
|
||||||
|
<!-- <mat-tab *ngIf="data.content.html">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-checkbox #chkHtml="matCheckbox"> </mat-checkbox>
|
||||||
|
<span class="title-text">{{
|
||||||
|
'common.file.clipboardType.html' | translate
|
||||||
|
}}</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<perfect-scrollbar>
|
||||||
|
<div fxFlexFill>
|
||||||
|
<table
|
||||||
|
fxFlexFill
|
||||||
|
[innerHTML]="data.content.html | ucapSafeHtml"
|
||||||
|
></table>
|
||||||
|
</div>
|
||||||
|
</perfect-scrollbar>
|
||||||
|
</mat-tab> -->
|
||||||
|
<mat-tab *ngIf="data.content.image && data.content.imageDataUrl">
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<mat-checkbox #chkImage> </mat-checkbox>
|
||||||
|
<span class="title-text">{{
|
||||||
|
'common.file.clipboardType.image' | translate
|
||||||
|
}}</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<perfect-scrollbar>
|
||||||
|
<div>
|
||||||
|
<img [src]="data.content.imageDataUrl" />
|
||||||
|
</div>
|
||||||
|
</perfect-scrollbar>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions class="button-farm flex-row">
|
||||||
|
<button
|
||||||
|
mat-stroked-button
|
||||||
|
(click)="onClickChoice(false)"
|
||||||
|
class="mat-primary"
|
||||||
|
>
|
||||||
|
{{ 'common.messages.no' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-flat-button (click)="onClickChoice(true)" class="mat-primary">
|
||||||
|
{{ 'common.messages.yes' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
|
@ -0,0 +1,127 @@
|
||||||
|
::ng-deep .setting-frame {
|
||||||
|
padding: 16px;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 500px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.mat-dialog-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-card-header {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.btn-dialog-close {
|
||||||
|
font-size: 20px;
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
align-self: flex-start;
|
||||||
|
color: #444444;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mat-card-content {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
.setting-tab {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.title-text {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-tab-group {
|
||||||
|
flex-direction: row;
|
||||||
|
.mat-tab-header {
|
||||||
|
width: 160px;
|
||||||
|
.mat-tab-labels {
|
||||||
|
flex-direction: column;
|
||||||
|
.mat-tab-label {
|
||||||
|
padding: 0 10px;
|
||||||
|
align-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
align-items: self-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mat-ink-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-farm {
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 10px;
|
||||||
|
.mat-primary {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .setting-tab {
|
||||||
|
.mat-tab-group {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mat-tab-header {
|
||||||
|
width: 160px;
|
||||||
|
flex-flow: column;
|
||||||
|
border-right: 1px solid #dddddd;
|
||||||
|
.mat-tab-label-container {
|
||||||
|
.mat-tab-list {
|
||||||
|
.mat-tab-labels {
|
||||||
|
border-bottom: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
.mat-tab-label {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-tab-body-wrapper {
|
||||||
|
.mat-tab-body {
|
||||||
|
.mat-tab-body-conten {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.mat-list-base {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .setting-category {
|
||||||
|
.mat-list-base {
|
||||||
|
position: relative;
|
||||||
|
.mat-list-item {
|
||||||
|
font-size: 15px;
|
||||||
|
.mat-tab-header {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mat-divider {
|
||||||
|
//margin-top: 10px;
|
||||||
|
}
|
||||||
|
.mat-subheader {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ClipboardDialogComponent } from './clipboard.dialog.component';
|
||||||
|
|
||||||
|
describe('ClipboardDialogComponent', () => {
|
||||||
|
let component: ClipboardDialogComponent;
|
||||||
|
let fixture: ComponentFixture<ClipboardDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ClipboardDialogComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ClipboardDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
Inject,
|
||||||
|
AfterViewInit,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA, MatCheckbox } from '@angular/material';
|
||||||
|
|
||||||
|
export interface ClipboardDialogData {
|
||||||
|
content: {
|
||||||
|
text?: string;
|
||||||
|
rtf?: string;
|
||||||
|
html?: string;
|
||||||
|
image?: Buffer;
|
||||||
|
imageDataUrl?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClipboardDialogResult {
|
||||||
|
selected?: {
|
||||||
|
text?: boolean;
|
||||||
|
rtf?: boolean;
|
||||||
|
html?: boolean;
|
||||||
|
image?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-layout-messenger-clipboard',
|
||||||
|
templateUrl: './clipboard.dialog.component.html',
|
||||||
|
styleUrls: ['./clipboard.dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class ClipboardDialogComponent implements OnInit, AfterViewInit {
|
||||||
|
@ViewChild('chkText', { static: false })
|
||||||
|
chkText: MatCheckbox;
|
||||||
|
|
||||||
|
@ViewChild('chkImage', { static: false })
|
||||||
|
chkImage: MatCheckbox;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<ClipboardDialogData, ClipboardDialogResult>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: ClipboardDialogData
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {}
|
||||||
|
|
||||||
|
onClickChoice(choice: boolean): void {
|
||||||
|
let selected: {
|
||||||
|
text?: boolean;
|
||||||
|
rtf?: boolean;
|
||||||
|
html?: boolean;
|
||||||
|
image?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (choice) {
|
||||||
|
selected = {
|
||||||
|
text: !!this.chkText && this.chkText.checked,
|
||||||
|
image: !!this.chkImage && this.chkImage.checked
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dialogRef.close({ selected });
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import { CreateChatDialogComponent } from './create-chat.dialog.component';
|
import { CreateChatDialogComponent } from './create-chat.dialog.component';
|
||||||
import { EditChatRoomDialogComponent } from './edit-chat-room.dialog.component';
|
import { EditChatRoomDialogComponent } from './edit-chat-room.dialog.component';
|
||||||
import { MassDetailComponent } from './mass-detail.component';
|
import { MassDetailComponent } from './mass-detail.component';
|
||||||
|
import { ClipboardDialogComponent } from './clipboard.dialog.component';
|
||||||
|
|
||||||
export const DIALOGS = [
|
export const DIALOGS = [
|
||||||
CreateChatDialogComponent,
|
CreateChatDialogComponent,
|
||||||
EditChatRoomDialogComponent,
|
EditChatRoomDialogComponent,
|
||||||
MassDetailComponent
|
MassDetailComponent,
|
||||||
|
ClipboardDialogComponent
|
||||||
];
|
];
|
||||||
|
|
|
@ -402,6 +402,13 @@
|
||||||
"openDownloadFolder": "Open download folder",
|
"openDownloadFolder": "Open download folder",
|
||||||
"selectFiles": "Select files",
|
"selectFiles": "Select files",
|
||||||
"dropZoneForUpload": "Drop files here to upload.",
|
"dropZoneForUpload": "Drop files here to upload.",
|
||||||
|
"clipboard": "Clipboard",
|
||||||
|
"clipboardType": {
|
||||||
|
"text": "Text",
|
||||||
|
"rtf": "Rich Text Format",
|
||||||
|
"html": "HTML",
|
||||||
|
"image": "Image"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"label": "Type of file",
|
"label": "Type of file",
|
||||||
"images": "Images",
|
"images": "Images",
|
||||||
|
|
|
@ -402,6 +402,13 @@
|
||||||
"openDownloadFolder": "다운로드 폴더 열기",
|
"openDownloadFolder": "다운로드 폴더 열기",
|
||||||
"selectFiles": "파일을 선택하세요",
|
"selectFiles": "파일을 선택하세요",
|
||||||
"dropZoneForUpload": "여기에 파일을 Drop하시면 업로드 됩니다.",
|
"dropZoneForUpload": "여기에 파일을 Drop하시면 업로드 됩니다.",
|
||||||
|
"clipboard": "클립보드",
|
||||||
|
"clipboardType": {
|
||||||
|
"text": "텍스트",
|
||||||
|
"rtf": "리치 텍스트 포맷",
|
||||||
|
"html": "HTML",
|
||||||
|
"image": "이미지"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"label": "파일 종류",
|
"label": "파일 종류",
|
||||||
"images": "이미지",
|
"images": "이미지",
|
||||||
|
|
|
@ -42,6 +42,28 @@ export class FileUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromDataUrlToFile(fileNameAppender: string, dataUrl: string): File {
|
||||||
|
const BASE64_MARKER = ';base64,';
|
||||||
|
// tslint:disable-next-line: variable-name
|
||||||
|
const isDataURI = (_url: string) => _url.split(BASE64_MARKER).length === 2;
|
||||||
|
|
||||||
|
if (!isDataURI(dataUrl)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mime = dataUrl.split(BASE64_MARKER)[0].split(':')[1];
|
||||||
|
const filename =
|
||||||
|
fileNameAppender + new Date().getTime() + '.' + mime.split('/')[1];
|
||||||
|
const bytes = atob(dataUrl.split(BASE64_MARKER)[1]);
|
||||||
|
const writer = new Uint8Array(new ArrayBuffer(bytes.length));
|
||||||
|
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
writer[i] = bytes.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File([writer.buffer], filename, { type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
static fromBlobToString(blob: Blob): Promise<string> {
|
static fromBlobToString(blob: Blob): Promise<string> {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
|
|
|
@ -281,6 +281,24 @@ export class BrowserNativeService extends NativeService {
|
||||||
open(url);
|
open(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readFromClipboard(): Promise<{
|
||||||
|
text?: string;
|
||||||
|
rtf?: string;
|
||||||
|
html?: string;
|
||||||
|
image?: Buffer;
|
||||||
|
imageDataUrl?: string;
|
||||||
|
}> {
|
||||||
|
return new Promise<{
|
||||||
|
text?: string;
|
||||||
|
rtf?: string;
|
||||||
|
html?: string;
|
||||||
|
image?: Buffer;
|
||||||
|
imageDataUrl?: string;
|
||||||
|
}>((resolve, reject) => {
|
||||||
|
resolve({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {
|
constructor(private httpClient: HttpClient) {
|
||||||
super();
|
super();
|
||||||
this.notificationService = new NotificationService();
|
this.notificationService = new NotificationService();
|
||||||
|
|
|
@ -23,7 +23,8 @@ import {
|
||||||
MessengerChannel,
|
MessengerChannel,
|
||||||
MessageChannel,
|
MessageChannel,
|
||||||
ProcessChannel,
|
ProcessChannel,
|
||||||
AppChannel
|
AppChannel,
|
||||||
|
ClipboardChannel
|
||||||
} from '../types/channel.type';
|
} from '../types/channel.type';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { TranslateLoaderService } from '../translate/electron-loader';
|
import { TranslateLoaderService } from '../translate/electron-loader';
|
||||||
|
@ -470,6 +471,28 @@ export class ElectronNativeService implements NativeService {
|
||||||
this.shell.openExternal(url);
|
this.shell.openExternal(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readFromClipboard(): Promise<{
|
||||||
|
text?: string;
|
||||||
|
rtf?: string;
|
||||||
|
html?: string;
|
||||||
|
image?: Buffer;
|
||||||
|
imageDataUrl?: string;
|
||||||
|
}> {
|
||||||
|
return new Promise<{
|
||||||
|
text?: string;
|
||||||
|
rtf?: string;
|
||||||
|
html?: string;
|
||||||
|
image?: Buffer;
|
||||||
|
imageDataUrl?: string;
|
||||||
|
}>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
resolve(this.ipcRenderer.sendSync(ClipboardChannel.Read));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get isElectron() {
|
get isElectron() {
|
||||||
return window && (window as any).process && (window as any).process.type;
|
return window && (window as any).process && (window as any).process.type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,10 @@ export enum IdleStateChannel {
|
||||||
ChangeLimitTime = 'UCAP::idleState::changeLimitTime'
|
ChangeLimitTime = 'UCAP::idleState::changeLimitTime'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ClipboardChannel {
|
||||||
|
Read = 'UCAP::clipboard::read'
|
||||||
|
}
|
||||||
|
|
||||||
export enum AppChannel {
|
export enum AppChannel {
|
||||||
Exit = 'UCAP::app::exit'
|
Exit = 'UCAP::app::exit'
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,4 +88,12 @@ export abstract class NativeService {
|
||||||
): TranslateLoader;
|
): TranslateLoader;
|
||||||
|
|
||||||
abstract openDefaultBrowser(url: string): void;
|
abstract openDefaultBrowser(url: string): void;
|
||||||
|
|
||||||
|
abstract readFromClipboard(): Promise<{
|
||||||
|
text?: string;
|
||||||
|
rtf?: string;
|
||||||
|
html?: string;
|
||||||
|
image?: Buffer;
|
||||||
|
imageDataUrl?: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,7 @@
|
||||||
name="message"
|
name="message"
|
||||||
[matTextareaAutosize]="true"
|
[matTextareaAutosize]="true"
|
||||||
[matAutosizeMaxRows]="20"
|
[matAutosizeMaxRows]="20"
|
||||||
|
(paste)="onPasteReply($event)"
|
||||||
></textarea>
|
></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,9 @@ export class FormComponent implements OnInit {
|
||||||
@Output()
|
@Output()
|
||||||
clearView = new EventEmitter();
|
clearView = new EventEmitter();
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
clipboardPaste = new EventEmitter();
|
||||||
|
|
||||||
@ViewChild('replyForm', { static: false })
|
@ViewChild('replyForm', { static: false })
|
||||||
replyForm: NgForm;
|
replyForm: NgForm;
|
||||||
|
|
||||||
|
@ -84,4 +87,10 @@ export class FormComponent implements OnInit {
|
||||||
onClickTranslation() {
|
onClickTranslation() {
|
||||||
this.toggleTranslation.emit();
|
this.toggleTranslation.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPasteReply(event: ClipboardEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.clipboardPaste.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Pipe({ name: 'ucapSafeHtml' })
|
||||||
|
export class SafeHtmlPipe implements PipeTransform {
|
||||||
|
constructor(private domSanitizer: DomSanitizer) {}
|
||||||
|
|
||||||
|
public transform(value: string) {
|
||||||
|
return this.domSanitizer.bypassSecurityTrustHtml(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,7 @@ import { SecondsToMinutesPipe } from './pipes/seconds-to-minutes.pipe';
|
||||||
import { LinkyPipe } from './pipes/linky.pipe';
|
import { LinkyPipe } from './pipes/linky.pipe';
|
||||||
import { TranslatePipe } from './pipes/translate.pipe';
|
import { TranslatePipe } from './pipes/translate.pipe';
|
||||||
import { DatePipe } from './pipes/date.pipe';
|
import { DatePipe } from './pipes/date.pipe';
|
||||||
|
import { SafeHtmlPipe } from './pipes/safe-html.pipe';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
StringEmptyCheckPipe,
|
StringEmptyCheckPipe,
|
||||||
|
@ -125,7 +126,8 @@ const PIPES = [
|
||||||
TranslatePipe,
|
TranslatePipe,
|
||||||
DatePipe,
|
DatePipe,
|
||||||
StringEmptyCheckPipe,
|
StringEmptyCheckPipe,
|
||||||
StringFormatterPhonePipe
|
StringFormatterPhonePipe,
|
||||||
|
SafeHtmlPipe
|
||||||
];
|
];
|
||||||
const SERVICES = [
|
const SERVICES = [
|
||||||
BottomSheetService,
|
BottomSheetService,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user