From 4bfa727e9e28bb4e1fba34ac4388d17a721b2b82 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Thu, 18 Nov 2021 18:39:46 +0100 Subject: [PATCH 01/10] Show a "copied!" tooltip after successfull URL copy --- client/components/cards/cardDetails.jade | 3 +++ client/components/cards/cardDetails.js | 18 ++++++++++++++++-- client/components/cards/cardDetails.styl | 18 +++++++++++++++++- client/lib/utils.js | 10 ++++++++-- i18n/en.i18n.json | 3 ++- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 659f70918..cf049625c 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -22,6 +22,7 @@ template(name="cardDetails") title="{{_ 'copy-card-link-to-clipboard'}}" href="{{ originRelativeUrl }}" ) + span.copied-tooltip {{_ 'copied'}} else unless isPopup a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") @@ -33,6 +34,7 @@ template(name="cardDetails") title="{{_ 'copy-card-link-to-clipboard'}}" href="{{ originRelativeUrl }}" ) + span.copied-tooltip {{_ 'copied'}} h2.card-details-title.js-card-title( class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") +viewer @@ -798,6 +800,7 @@ template(name="cardMorePopup") i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}") input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus") button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}} + .copied-tooltip {{_ 'copied'}} span.clearfix br h2 {{_ 'change-card-parent'}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index b967ca087..b5885d890 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -325,7 +325,14 @@ BlazeComponent.extendComponent({ }, 'click .js-copy-link'(event) { event.preventDefault(); - Utils.copyTextToClipboard(event.target.href); + const promise = Utils.copyTextToClipboard(event.target.href); + if (promise) { + promise.then(() => { + const $tooltip = this.$('span.copied-tooltip'); + $tooltip.css('display', 'inline'); + setTimeout(() => $tooltip.css('display', 'none'), 1000); + }); + } }, 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), 'submit .js-card-description'(event) { @@ -1068,7 +1075,14 @@ BlazeComponent.extendComponent({ return [ { 'click .js-copy-card-link-to-clipboard'(event) { - Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value); + const promise = Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value); + if (promise) { + promise.then(() => { + const $tooltip = this.$('.copied-tooltip'); + $tooltip.css('display', 'inline'); + setTimeout(() => $tooltip.css('display', 'none'), 1000); + }); + } }, 'click .js-delete': Popup.afterConfirm('cardDelete', function () { Popup.close(); diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 202600e2e..feaad903e 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -118,7 +118,8 @@ avatar-radius = 50% .card-copy-button, .card-copy-mobile-button, .close-card-details-mobile-web, - .card-details-menu-mobile-web + .card-details-menu-mobile-web, + .copied-tooltip float: right .close-card-details, @@ -187,6 +188,14 @@ avatar-radius = 50% border-radius: 3px padding: 0px 5px + .copied-tooltip + display: none + margin-right: 10px + padding: 10px; + background-color: #000000df; + color: #fff; + border-radius: 5px; + .card-description textarea min-height: 100px @@ -233,6 +242,13 @@ avatar-radius = 50% .activities padding-top: 10px +.pop-over + .copied-tooltip + display: none + padding: 0px 10px; + background-color: #000000df; + color: #fff; + border-radius: 5px; @media screen and (min-width: 801px) .card-details-maximized padding: 0 diff --git a/client/lib/utils.js b/client/lib/utils.js index ebc76f22f..4bf1ccc20 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -481,6 +481,9 @@ Utils = { try { document.execCommand('copy'); + return Promise.resolve(true); + } catch (e) { + return Promise.reject(false); } finally { document.body.removeChild(textArea); } @@ -489,16 +492,19 @@ Utils = { /** copy the text to the clipboard * @see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript/30810322#30810322 * @param string copy this text to the clipboard + * @return Promise */ copyTextToClipboard(text) { + let ret; if (navigator.clipboard) { - navigator.clipboard.writeText(text).then(function() { + ret = navigator.clipboard.writeText(text).then(function() { }, function(err) { console.error('Async: Could not copy text: ', err); }); } else { - fallbackCopyTextToClipboard(text); + ret = Utils.fallbackCopyTextToClipboard(text); } + return ret; }, }; diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 8e6170397..24a6a1b1f 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -1122,5 +1122,6 @@ "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", "custom-legal-notice-link-url": "Custom legal notice page URL", "acceptance_of_our_legalNotice": "By continuing, you accept our", - "legalNotice": "legal notice" + "legalNotice": "legal notice", + "copied": "copied!" } From 08ee96944589ed37768bddd66a5de804dffe2205 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 00:26:20 +0100 Subject: [PATCH 02/10] Added animation to tooltip "copied!" --- client/components/cards/cardDetails.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index b5885d890..8c0c7d7a5 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -329,8 +329,8 @@ BlazeComponent.extendComponent({ if (promise) { promise.then(() => { const $tooltip = this.$('span.copied-tooltip'); - $tooltip.css('display', 'inline'); - setTimeout(() => $tooltip.css('display', 'none'), 1000); + $tooltip.show(100); + setTimeout(() => $tooltip.hide(100), 1000); }); } }, @@ -1079,8 +1079,8 @@ BlazeComponent.extendComponent({ if (promise) { promise.then(() => { const $tooltip = this.$('.copied-tooltip'); - $tooltip.css('display', 'inline'); - setTimeout(() => $tooltip.css('display', 'none'), 1000); + $tooltip.show(100); + setTimeout(() => $tooltip.hide(100), 1000); }); } }, From 7db1445d09c5998e0fda450265a322fd43e76780 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 00:29:56 +0100 Subject: [PATCH 03/10] Added copy button to all editor's --- client/components/main/editor.jade | 2 + client/components/main/editor.js | 552 +++++++++++++++-------------- client/components/main/editor.styl | 7 + 3 files changed, 296 insertions(+), 265 deletions(-) create mode 100644 client/components/main/editor.styl diff --git a/client/components/main/editor.jade b/client/components/main/editor.jade index dbd617154..bbf65e45e 100644 --- a/client/components/main/editor.jade +++ b/client/components/main/editor.jade @@ -1,4 +1,6 @@ template(name="editor") + span.fa.fa-copy + span.copied-tooltip {{_ 'copied'}} textarea.editor( dir="auto" class="{{class}}" diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 34ada0e24..2992c2938 100644 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -4,283 +4,305 @@ const specialHandles = [ ]; const specialHandleNames = specialHandles.map(m => m.username); -Template.editor.onRendered(() => { - const textareaSelector = 'textarea'; - const mentions = [ - // User mentions - { - match: /\B@([\w.]*)$/, - search(term, callback) { - const currentBoard = Boards.findOne(Session.get('currentBoard')); - callback( - _.union( - currentBoard - .activeMembers() - .map(member => { - const user = Users.findOne(member.userId); - const username = user.username; - const fullName = user.profile && user.profile !== undefined ? user.profile.fullname : ""; - return username.includes(term) || fullName.includes(term) ? fullName + "(" + username + ")" : null; - }) - .filter(Boolean), [...specialHandleNames]) - ); - }, - template(value) { - return value; - }, - replace(username) { - return `@${username} `; - }, - index: 1, - }, - ]; - const enableTextarea = function() { - const $textarea = this.$(textareaSelector); - autosize($textarea); - $textarea.escapeableTextComplete(mentions); - }; - if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) { - const isSmall = Utils.isMiniScreen(); - const toolbar = isSmall - ? [ - ['view', ['fullscreen']], - ['table', ['table']], - ['font', ['bold', 'underline']], - //['fontsize', ['fontsize']], - ['color', ['color']], - ] - : [ - ['style', ['style']], - ['font', ['bold', 'underline', 'clear']], - ['fontsize', ['fontsize']], - ['fontname', ['fontname']], - ['color', ['color']], - ['para', ['ul', 'ol', 'paragraph']], - ['table', ['table']], - //['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled - ['insert', ['link']], //, 'picture']], // modal popup has issue somehow :( - ['view', ['fullscreen', 'codeview', 'help']], - ]; - const cleanPastedHTML = function(input) { - const badTags = [ - 'style', - 'script', - 'applet', - 'embed', - 'noframes', - 'noscript', - 'meta', - 'link', - 'button', - 'form', - ].join('|'); - const badPatterns = new RegExp( - `(?:${[ - `<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`, - `<(${badTags})[^>]*?\\/>`, - ].join('|')})`, - 'gi', - ); - let output = input; - // remove bad Tags - output = output.replace(badPatterns, ''); - // remove attributes ' style="..."' - const badAttributes = new RegExp( - `(?:${[ - 'on\\S+=([\'"]?).*?\\1', - 'href=([\'"]?)javascript:.*?\\2', - 'style=([\'"]?).*?\\3', - 'target=\\S+', - ].join('|')})`, - 'gi', - ); - output = output.replace(badAttributes, ''); - output = output.replace(/( -1) { - return mSummernotes[idx]; - } - return undefined; - }; - inputs.each(function(idx, input) { - mSummernotes[idx] = $(input).summernote({ - placeholder, - callbacks: { - onInit(object) { - const originalInput = this; - $(originalInput).on('submitted', function() { - // when comment is submitted, the original textarea will be set to '', so shall we - if (!this.value) { - const sn = getSummernote(this); - sn && sn.summernote('code', ''); - } - }); - const jEditor = object && object.editable; - const toolbar = object && object.toolbar; - if (jEditor !== undefined) { - jEditor.escapeableTextComplete(mentions); - } - if (toolbar !== undefined) { - const fBtn = toolbar.find('.btn-fullscreen'); - fBtn.on('click', function() { - const $this = $(this), - isActive = $this.hasClass('active'); - $('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually - }); - } - }, - onImageUpload(files) { - const $summernote = getSummernote(this); - if (files && files.length > 0) { - const image = files[0]; - const currentCard = Utils.getCurrentCard(); - const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL; - const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO; - const insertImage = src => { - // process all image upload types to the description/comment window - const img = document.createElement('img'); - img.src = src; - img.setAttribute('width', '100%'); - $summernote.summernote('insertNode', img); - }; - const processData = function(fileObj) { - Utils.processUploadedAttachment( - currentCard, - fileObj, - attachment => { - if ( - attachment && - attachment._id && - attachment.isImage() - ) { - attachment.one('uploaded', function() { - const maxTry = 3; - const checkItvl = 500; - let retry = 0; - const checkUrl = function() { - // even though uploaded event fired, attachment.url() is still null somehow //TODO - const url = attachment.url(); - if (url) { - insertImage( - `${location.protocol}//${location.host}${url}`, - ); - } else { - retry++; - if (retry < maxTry) { - setTimeout(checkUrl, checkItvl); +BlazeComponent.extendComponent({ + onRendered() { + const textareaSelector = 'textarea'; + const mentions = [ + // User mentions + { + match: /\B@([\w.]*)$/, + search(term, callback) { + const currentBoard = Boards.findOne(Session.get('currentBoard')); + callback( + _.union( + currentBoard + .activeMembers() + .map(member => { + const user = Users.findOne(member.userId); + const username = user.username; + const fullName = user.profile && user.profile !== undefined ? user.profile.fullname : ""; + return username.includes(term) || fullName.includes(term) ? fullName + "(" + username + ")" : null; + }) + .filter(Boolean), [...specialHandleNames]) + ); + }, + template(value) { + return value; + }, + replace(username) { + return `@${username} `; + }, + index: 1, + }, + ]; + const enableTextarea = function() { + const $textarea = this.$(textareaSelector); + autosize($textarea); + $textarea.escapeableTextComplete(mentions); + }; + if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) { + const isSmall = Utils.isMiniScreen(); + const toolbar = isSmall + ? [ + ['view', ['fullscreen']], + ['table', ['table']], + ['font', ['bold', 'underline']], + //['fontsize', ['fontsize']], + ['color', ['color']], + ] + : [ + ['style', ['style']], + ['font', ['bold', 'underline', 'clear']], + ['fontsize', ['fontsize']], + ['fontname', ['fontname']], + ['color', ['color']], + ['para', ['ul', 'ol', 'paragraph']], + ['table', ['table']], + //['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled + ['insert', ['link']], //, 'picture']], // modal popup has issue somehow :( + ['view', ['fullscreen', 'codeview', 'help']], + ]; + const cleanPastedHTML = function(input) { + const badTags = [ + 'style', + 'script', + 'applet', + 'embed', + 'noframes', + 'noscript', + 'meta', + 'link', + 'button', + 'form', + ].join('|'); + const badPatterns = new RegExp( + `(?:${[ + `<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`, + `<(${badTags})[^>]*?\\/>`, + ].join('|')})`, + 'gi', + ); + let output = input; + // remove bad Tags + output = output.replace(badPatterns, ''); + // remove attributes ' style="..."' + const badAttributes = new RegExp( + `(?:${[ + 'on\\S+=([\'"]?).*?\\1', + 'href=([\'"]?)javascript:.*?\\2', + 'style=([\'"]?).*?\\3', + 'target=\\S+', + ].join('|')})`, + 'gi', + ); + output = output.replace(badAttributes, ''); + output = output.replace(/( -1) { + return mSummernotes[idx]; + } + return undefined; + }; + inputs.each(function(idx, input) { + mSummernotes[idx] = $(input).summernote({ + placeholder, + callbacks: { + onInit(object) { + const originalInput = this; + $(originalInput).on('submitted', function() { + // when comment is submitted, the original textarea will be set to '', so shall we + if (!this.value) { + const sn = getSummernote(this); + sn && sn.summernote('code', ''); + } + }); + const jEditor = object && object.editable; + const toolbar = object && object.toolbar; + if (jEditor !== undefined) { + jEditor.escapeableTextComplete(mentions); + } + if (toolbar !== undefined) { + const fBtn = toolbar.find('.btn-fullscreen'); + fBtn.on('click', function() { + const $this = $(this), + isActive = $this.hasClass('active'); + $('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually + }); + } + }, + + onImageUpload(files) { + const $summernote = getSummernote(this); + if (files && files.length > 0) { + const image = files[0]; + const currentCard = Utils.getCurrentCard(); + const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL; + const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO; + const insertImage = src => { + // process all image upload types to the description/comment window + const img = document.createElement('img'); + img.src = src; + img.setAttribute('width', '100%'); + $summernote.summernote('insertNode', img); + }; + const processData = function(fileObj) { + Utils.processUploadedAttachment( + currentCard, + fileObj, + attachment => { + if ( + attachment && + attachment._id && + attachment.isImage() + ) { + attachment.one('uploaded', function() { + const maxTry = 3; + const checkItvl = 500; + let retry = 0; + const checkUrl = function() { + // even though uploaded event fired, attachment.url() is still null somehow //TODO + const url = attachment.url(); + if (url) { + insertImage( + `${location.protocol}//${location.host}${url}`, + ); + } else { + retry++; + if (retry < maxTry) { + setTimeout(checkUrl, checkItvl); + } } + }; + checkUrl(); + }); + } + }, + ); + }; + if (MAX_IMAGE_PIXEL) { + const reader = new FileReader(); + reader.onload = function(e) { + const dataurl = e && e.target && e.target.result; + if (dataurl !== undefined) { + // need to shrink image + Utils.shrinkImage({ + dataurl, + maxSize: MAX_IMAGE_PIXEL, + ratio: COMPRESS_RATIO, + toBlob: true, + callback(blob) { + if (blob !== false) { + blob.name = image.name; + processData(blob); } - }; - checkUrl(); + }, }); } - }, - ); - }; - if (MAX_IMAGE_PIXEL) { - const reader = new FileReader(); - reader.onload = function(e) { - const dataurl = e && e.target && e.target.result; - if (dataurl !== undefined) { - // need to shrink image - Utils.shrinkImage({ - dataurl, - maxSize: MAX_IMAGE_PIXEL, - ratio: COMPRESS_RATIO, - toBlob: true, - callback(blob) { - if (blob !== false) { - blob.name = image.name; - processData(blob); - } - }, - }); - } - }; - reader.readAsDataURL(image); - } else { - processData(image); + }; + reader.readAsDataURL(image); + } else { + processData(image); + } } - } - }, - onPaste(e) { - var clipboardData = e.clipboardData; - var pastedData = clipboardData.getData('Text'); + }, + onPaste(e) { + var clipboardData = e.clipboardData; + var pastedData = clipboardData.getData('Text'); - //if pasted data is an image, exit - if (!pastedData.length) { - e.preventDefault(); - return; - } + //if pasted data is an image, exit + if (!pastedData.length) { + e.preventDefault(); + return; + } - // clear up unwanted tag info when user pasted in text - const thisNote = this; - const updatePastedText = function(object) { - const someNote = getSummernote(object); - // Fix Pasting text into a card is adding a line before and after - // (and multiplies by pasting more) by changing paste "p" to "br". - // Fixes https://github.com/wekan/wekan/2890 . - // == Fix Start == - someNote.execCommand('defaultParagraphSeparator', false, 'br'); - // == Fix End == - const original = someNote.summernote('code'); - const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML. - someNote.summernote('code', ''); //clear original - someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code. - }; - setTimeout(function() { - //this kinda sucks, but if you don't do a setTimeout, - //the function is called before the text is really pasted. - updatePastedText(thisNote); - }, 10); + // clear up unwanted tag info when user pasted in text + const thisNote = this; + const updatePastedText = function(object) { + const someNote = getSummernote(object); + // Fix Pasting text into a card is adding a line before and after + // (and multiplies by pasting more) by changing paste "p" to "br". + // Fixes https://github.com/wekan/wekan/2890 . + // == Fix Start == + someNote.execCommand('defaultParagraphSeparator', false, 'br'); + // == Fix End == + const original = someNote.summernote('code'); + const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML. + someNote.summernote('code', ''); //clear original + someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code. + }; + setTimeout(function() { + //this kinda sucks, but if you don't do a setTimeout, + //the function is called before the text is really pasted. + updatePastedText(thisNote); + }, 10); + }, }, - }, - dialogsInBody: true, - spellCheck: true, - disableGrammar: false, - disableDragAndDrop: false, - toolbar, - popover: { - image: [ - ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']], - ['float', ['floatLeft', 'floatRight', 'floatNone']], - ['remove', ['removeMedia']], - ], - link: [['link', ['linkDialogShow', 'unlink']]], - table: [ - ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], - ['delete', ['deleteRow', 'deleteCol', 'deleteTable']], - ], - air: [ - ['color', ['color']], - ['font', ['bold', 'underline', 'clear']], - ], - }, - height: 200, + dialogsInBody: true, + spellCheck: true, + disableGrammar: false, + disableDragAndDrop: false, + toolbar, + popover: { + image: [ + ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']], + ['float', ['floatLeft', 'floatRight', 'floatNone']], + ['remove', ['removeMedia']], + ], + link: [['link', ['linkDialogShow', 'unlink']]], + table: [ + ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], + ['delete', ['deleteRow', 'deleteCol', 'deleteTable']], + ], + air: [ + ['color', ['color']], + ['font', ['bold', 'underline', 'clear']], + ], + }, + height: 200, + }); }); - }); + } + } else { + enableTextarea(); } - } else { - enableTextarea(); + }, + events() { + return [ + { + 'click span.fa.fa-copy'(event) { + const $editor = this.$('textarea.editor'); + const promise = Utils.copyTextToClipboard($editor[0].value); + if (promise) { + promise.then(() => { + const $tooltip = this.$('.copied-tooltip'); + $tooltip.show(100); + setTimeout(() => $tooltip.hide(100), 1000); + }, (err) => { + console.error("error: ", err); + }); + } + }, + } + ] } -}); +}).register('editor'); import DOMPurify from 'dompurify'; diff --git a/client/components/main/editor.styl b/client/components/main/editor.styl new file mode 100644 index 000000000..c5e225dda --- /dev/null +++ b/client/components/main/editor.styl @@ -0,0 +1,7 @@ +.new-comment, +.inlined-form + span.fa.fa-copy + float: right + position: relative + top: 20px + right: 6px From 6d3ecdea55e5ae7df403028f97e36e20d52fb8bf Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 11:22:49 +0100 Subject: [PATCH 04/10] Changed copy icon to a "href" link - mouse hover changes the icon --- client/components/main/editor.jade | 2 +- client/components/main/editor.js | 2 +- client/components/main/editor.styl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/components/main/editor.jade b/client/components/main/editor.jade index bbf65e45e..6e7702ff3 100644 --- a/client/components/main/editor.jade +++ b/client/components/main/editor.jade @@ -1,5 +1,5 @@ template(name="editor") - span.fa.fa-copy + a.fa.fa-copy(title="copy text to clipboard") span.copied-tooltip {{_ 'copied'}} textarea.editor( dir="auto" diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 2992c2938..bfcf3240a 100644 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -286,7 +286,7 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click span.fa.fa-copy'(event) { + 'click a.fa.fa-copy'(event) { const $editor = this.$('textarea.editor'); const promise = Utils.copyTextToClipboard($editor[0].value); if (promise) { diff --git a/client/components/main/editor.styl b/client/components/main/editor.styl index c5e225dda..07e1c627f 100644 --- a/client/components/main/editor.styl +++ b/client/components/main/editor.styl @@ -1,6 +1,6 @@ .new-comment, .inlined-form - span.fa.fa-copy + a.fa.fa-copy float: right position: relative top: 20px From 7444c11c82874a761795a186079ac509cd8cdf00 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 12:07:42 +0100 Subject: [PATCH 05/10] Moved "copied!" code to Utils - same implementation in all files, so it's better to have one function for it --- client/components/cards/cardDetails.js | 20 ++++++-------------- client/components/main/editor.js | 12 +++--------- client/lib/utils.js | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 8c0c7d7a5..428884669 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -326,13 +326,9 @@ BlazeComponent.extendComponent({ 'click .js-copy-link'(event) { event.preventDefault(); const promise = Utils.copyTextToClipboard(event.target.href); - if (promise) { - promise.then(() => { - const $tooltip = this.$('span.copied-tooltip'); - $tooltip.show(100); - setTimeout(() => $tooltip.hide(100), 1000); - }); - } + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); }, 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), 'submit .js-card-description'(event) { @@ -1076,13 +1072,9 @@ BlazeComponent.extendComponent({ { 'click .js-copy-card-link-to-clipboard'(event) { const promise = Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value); - if (promise) { - promise.then(() => { - const $tooltip = this.$('.copied-tooltip'); - $tooltip.show(100); - setTimeout(() => $tooltip.hide(100), 1000); - }); - } + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); }, 'click .js-delete': Popup.afterConfirm('cardDelete', function () { Popup.close(); diff --git a/client/components/main/editor.js b/client/components/main/editor.js index bfcf3240a..fec040a23 100644 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -289,15 +289,9 @@ BlazeComponent.extendComponent({ 'click a.fa.fa-copy'(event) { const $editor = this.$('textarea.editor'); const promise = Utils.copyTextToClipboard($editor[0].value); - if (promise) { - promise.then(() => { - const $tooltip = this.$('.copied-tooltip'); - $tooltip.show(100); - setTimeout(() => $tooltip.hide(100), 1000); - }, (err) => { - console.error("error: ", err); - }); - } + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); }, } ] diff --git a/client/lib/utils.js b/client/lib/utils.js index 4bf1ccc20..eb53a6a4c 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -506,6 +506,21 @@ Utils = { } return ret; }, + + /** show the "copied!" message + * @param promise the promise of Utils.copyTextToClipboard + * @param $tooltip jQuery tooltip element + */ + showCopied(promise, $tooltip) { + if (promise) { + promise.then(() => { + $tooltip.show(100); + setTimeout(() => $tooltip.hide(100), 1000); + }, (err) => { + console.error("error: ", err); + }); + } + }, }; // A simple tracker dependency that we invalidate every time the window is From 5525247f52fe69896f5adc81e4300cba81037ba8 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 12:18:12 +0100 Subject: [PATCH 06/10] Copied! Tooltip of card link showed also "Copied!" at the comments --- client/components/cards/cardDetails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 428884669..adf5a9a5f 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -327,7 +327,7 @@ BlazeComponent.extendComponent({ event.preventDefault(); const promise = Utils.copyTextToClipboard(event.target.href); - const $tooltip = this.$('.copied-tooltip'); + const $tooltip = this.$('.card-details-header .copied-tooltip'); Utils.showCopied(promise, $tooltip); }, 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), From 7a959ef1a2c04869419c47b9035b028571ed5737 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 12:48:35 +0100 Subject: [PATCH 07/10] Added copy button to new Checklist and Checklist-Items editor --- client/components/cards/checklists.jade | 2 ++ client/components/cards/checklists.js | 29 ++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index 06f419282..ed5e77c05 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -63,6 +63,8 @@ template(name="checklistDeleteDialog") button.toggle-delete-checklist-dialog(type="button") {{_ 'cancel'}} template(name="addChecklistItemForm") + a.fa.fa-copy(title="copy text to clipboard") + span.copied-tooltip {{_ 'copied'}} textarea.js-add-checklist-item(rows='1' autofocus) .edit-controls.clearfix button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}} diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index feec56d37..141b45c4a 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -279,9 +279,32 @@ Template.checklists.helpers({ }, }); -Template.addChecklistItemForm.onRendered(() => { - autosize($('textarea.js-add-checklist-item')); -}); +BlazeComponent.extendComponent({ + onRendered() { + autosize(this.$('textarea.js-add-checklist-item')); + }, + canModifyCard() { + return ( + Meteor.user() && + Meteor.user().isBoardMember() && + !Meteor.user().isCommentOnly() && + !Meteor.user().isWorker() + ); + }, + events() { + return [ + { + 'click a.fa.fa-copy'(event) { + const $editor = this.$('textarea'); + const promise = Utils.copyTextToClipboard($editor[0].value); + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + } + ]; + } +}).register('addChecklistItemForm'); Template.editChecklistItemForm.onRendered(() => { autosize($('textarea.js-edit-checklist-item')); From 8bf3841e6c2dae2bdf22203ec7639de200b119d4 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 13:08:11 +0100 Subject: [PATCH 08/10] Added copy button to edit Checklist and Checklist-Items editor --- client/components/cards/checklists.jade | 2 ++ client/components/cards/checklists.js | 29 ++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index ed5e77c05..b27a771db 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -71,6 +71,8 @@ template(name="addChecklistItemForm") a.fa.fa-times-thin.js-close-inlined-form template(name="editChecklistItemForm") + a.fa.fa-copy(title="copy text to clipboard") + span.copied-tooltip {{_ 'copied'}} textarea.js-edit-checklist-item(rows='1' autofocus dir="auto") if $eq type 'item' = item.title diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index 141b45c4a..0d9521635 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -306,9 +306,32 @@ BlazeComponent.extendComponent({ } }).register('addChecklistItemForm'); -Template.editChecklistItemForm.onRendered(() => { - autosize($('textarea.js-edit-checklist-item')); -}); +BlazeComponent.extendComponent({ + onRendered() { + autosize(this.$('textarea.js-edit-checklist-item')); + }, + canModifyCard() { + return ( + Meteor.user() && + Meteor.user().isBoardMember() && + !Meteor.user().isCommentOnly() && + !Meteor.user().isWorker() + ); + }, + events() { + return [ + { + 'click a.fa.fa-copy'(event) { + const $editor = this.$('textarea'); + const promise = Utils.copyTextToClipboard($editor[0].value); + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + } + ]; + } +}).register('editChecklistItemForm'); Template.checklistDeleteDialog.onCreated(() => { const $cardDetails = this.$('.card-details'); From 2566286e86987e8a9e4916893d207d399883a5a1 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Fri, 19 Nov 2021 14:11:10 +0100 Subject: [PATCH 09/10] Copied tooptip did only work at mobile view, now on desktop view too --- client/components/cards/cardDetails.styl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index feaad903e..ac007b169 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -76,6 +76,12 @@ avatar-radius = 50% box-shadow: 0 0 0 2px darken(white, 60%) inset // Other card details +.copied-tooltip + display: none + padding: 0px 10px; + background-color: #000000df; + color: #fff; + border-radius: 5px; .card-details padding: 0 @@ -242,13 +248,6 @@ avatar-radius = 50% .activities padding-top: 10px -.pop-over - .copied-tooltip - display: none - padding: 0px 10px; - background-color: #000000df; - color: #fff; - border-radius: 5px; @media screen and (min-width: 801px) .card-details-maximized padding: 0 From f0cadf858d5fe9d18861994fd36d394f2be4f768 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Wed, 24 Nov 2021 09:43:11 +0100 Subject: [PATCH 10/10] New card comments have now the same color as card comment editing --- client/components/activities/comments.styl | 1 - 1 file changed, 1 deletion(-) diff --git a/client/components/activities/comments.styl b/client/components/activities/comments.styl index ccf24b726..d7492b767 100644 --- a/client/components/activities/comments.styl +++ b/client/components/activities/comments.styl @@ -31,7 +31,6 @@ background-color: #fff border: 0 box-shadow: 0 1px 2px rgba(0, 0, 0, .23) - color: #8c8c8c height: 36px margin: 4px 4px 6px 0 padding: 9px 11px