diff --git a/src/app/components/fragments/markdown-toolbar/markdown-toolbar.component.ts b/src/app/components/fragments/markdown-toolbar/markdown-toolbar.component.ts index 316f6b778d11027087b9df04454617072226f37b..9f46ef0fe4fc5f88f28f40250544579de787d9db 100644 --- a/src/app/components/fragments/markdown-toolbar/markdown-toolbar.component.ts +++ b/src/app/components/fragments/markdown-toolbar/markdown-toolbar.component.ts @@ -1,6 +1,7 @@ import { Component, Input } from '@angular/core'; import { MarkdownHelpDialogComponent } from '../../dialogs/markdown-help-dialog/markdown-help-dialog.component'; import { MatDialog } from '@angular/material'; +import { Data, GenericDataDialogComponent } from '../../dialogs/generic-data-dialog/generic-data-dialog.component'; @Component({ selector: 'app-markdown-toolbar', @@ -9,7 +10,7 @@ import { MatDialog } from '@angular/material'; }) /** * Component offering markdown action buttons for a textarea. - * Associated via input attribute containing the textare's HTML id. + * Associated via input attribute containing the textarea's HTML id. */ export class MarkdownToolbarComponent { /** @@ -26,15 +27,15 @@ export class MarkdownToolbarComponent { * @type {Button[]} */ buttons: Button[] = [ - new Button('bold', 'Bold', 'format_bold', '**'), - new Button('italic', 'Italic', 'format_italic', '*'), - new Button('title', 'Title', 'title', '## ', ''), - new Button('link', 'Link', 'insert_link', '[', '](https://...)'), - new Button('ul', 'Unordered list', 'format_list_bulleted', '* ', ''), - new Button('ol', 'Ordered list', 'format_list_numbered', '1. ', ''), - new Button('code', 'Code', 'code', '`'), - new Button('quote', 'Quote', 'format_quote', '> ', ''), - new Button('image', 'Image', 'insert_photo', ''), + new Button('bold', 'Bold', 'format_bold', '**{{TEXT}}**'), + new Button('italic', 'Italic', 'format_italic', '*{{TEXT}}*'), + new Button('title', 'Title', 'title', '## {{TEXT}}'), + new Button('link', 'Link', 'insert_link', '[{{TEXT}}]({{URL}})'), + new Button('ul', 'Unordered list', 'format_list_bulleted', '* {{TEXT}}'), + new Button('ol', 'Ordered list', 'format_list_numbered', '1. {{TEXT}}'), + new Button('code', 'Code', 'code', '`{{TEXT}}`'), + new Button('quote', 'Quote', 'format_quote', '> {{TEXT}}'), + new Button('image', 'Image', 'insert_photo', ''), new Button('help', 'Help', 'help', '') ]; @@ -57,32 +58,76 @@ export class MarkdownToolbarComponent { return; } + // Init formatted string with button's format string, placeholders will be replaced later on + let formattedString = button.format; + // Get offset for cursor position (set the cursor to the beginning of the surrounded text) + const cursorPosition = formattedString.indexOf('{{TEXT}}'); + // Handle different buttons here switch (button.id) { case 'help': // Open help dialog and prevent default action this.dialog.open(MarkdownHelpDialogComponent); break; + case 'link': + case 'image': + // Open a configuration dialog + const dialogRef = this.dialog.open(GenericDataDialogComponent, { + data: [ + new Data('TEXT', 'Text', this.getSelectedText()), + new Data('URL', 'URL') + ] + }); + // Wait for dialog to get closed.. + dialogRef.afterClosed().subscribe(result => { + // Cancel if there is no data (dialog got closed without submitting) + if (result === undefined) { + return; + } + // Loop data entries.. + result.forEach(data => { + // Replace placeholder with data provided by the user + formattedString = formattedString.replace(`{{${data.id}}}`, data.value); + }); + // Insert the formatted string and update the cursor position + this.insertTextAtSelection(formattedString, cursorPosition); + }); + break; default: - // Insert the text associated to the button - this.insertTag(button.textBefore, button.textAfter); + // Replace text placeholder with the selected text (keep surrounding tags empty if there was no selection) + formattedString = formattedString.replace('{{TEXT}}', this.getSelectedText()); + // Insert the text associated to the button and update the cursor position + this.insertTextAtSelection(formattedString, cursorPosition); } } - private insertTag(before: string, after: string) { + /** + * Get the selected text of the textarea + * @returns {string} + */ + private getSelectedText() { + return this.textarea.value.slice(this.textarea.selectionStart, this.textarea.selectionEnd); + } + + /** + * Insert the passed string at the cursor position (replaces selected text if there is any) and update the cursor's position to start + * of the inserted text respecting the passed selection offset + * @param {string} formattedString + * @param {number} selectionOffset + */ + private insertTextAtSelection(text: string, selectionOffset: number) { // Get the textarea's text selection positions (no selection when selectionStart == selectionEnd) const selectionStart = this.textarea.selectionStart; const selectionEnd = this.textarea.selectionEnd; // Get textarea's value - const text = this.textarea.value; + const value = this.textarea.value; // Insert the action's text at the cursor's position - this.textarea.value = [text.slice(0, selectionStart), before, text.slice(selectionStart, selectionEnd), - after, text.slice(selectionEnd)].join(''); + this.textarea.value = [value.slice(0, selectionStart), text, value.slice(selectionEnd)].join(''); // Focus the textarea this.textarea.focus(); // Calculate the new cursor position (based on the action's text length and the previous cursor position) - const cursorPosition = selectionStart + before.length; + const cursorPosition = selectionStart + selectionOffset; // Set the cursor to the calculated position this.textarea.setSelectionRange(cursorPosition, cursorPosition); } @@ -118,34 +163,23 @@ class Button { */ icon: string; /** - * Text that gets placed in front of the text selection + * Format string + * TODO: */ - textBefore: string; - /** - * Text that gets placed behind the text selection - */ - textAfter: string; + format: string; /** - * Constructor for creating an instance of {@link Button}, if {@link textAfter} doesn't get passed, - * it will be initialized with the value of {@link textBefore} + * Constructor for creating an instance of {@link Button} * * @param {string} id * @param {string} label * @param {string} icon - * @param {string} textBefore - * @param {string=} textAfter optional - if not passed will be initialized with the value of {@link textBefore} + * @param {string} format */ - constructor(id: string, label: string, icon: string, textBefore: string, textAfter?: string) { + constructor(id: string, label: string, icon: string, format: string) { this.id = id; this.label = label; this.icon = icon; - this.textBefore = textBefore; - // If textAfter is not set, define it with textBefore's value as markdown tags mostly get closed the way they are opened - if (textAfter !== undefined) { - this.textAfter = textAfter; - } else { - this.textAfter = textBefore; - } + this.format = format; } }