Report copy success via tooltip at copy button (#730)
* refactor copy button to show success with tooltip * remove ember-cli-flash * update expected bundlesize
This commit is contained in:
parent
88a51964f1
commit
147f5dace4
25 changed files with 733 additions and 2029 deletions
32
app/components/share-poll-url.hbs
Normal file
32
app/components/share-poll-url.hbs
Normal file
|
@ -0,0 +1,32 @@
|
|||
<div class="box poll-link cr-poll-link">
|
||||
<p>{{t "poll.share.title"}}</p>
|
||||
<p class="link cr-poll-link__link">
|
||||
<small>
|
||||
<code class="cr-poll-link__url" data-test-poll-url>{{this.pollUrl}}</code>
|
||||
</small>
|
||||
<CopyButton
|
||||
@text={{this.pollUrl}}
|
||||
@onSuccess={{this.showCopySuccessMessage}}
|
||||
class="btn btn-secondary cr-poll-link__copy-btn btn-sm"
|
||||
data-test-button="copy-link"
|
||||
>
|
||||
{{t "poll.link.copy-label"}}
|
||||
<span
|
||||
class="oi oi-clipboard"
|
||||
title={{t "poll.link.copy-label"}}
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
|
||||
<BsTooltip
|
||||
@placement="bottom"
|
||||
@triggerEvents="click"
|
||||
data-test-tooltip="copied"
|
||||
>
|
||||
{{t "poll.link.copied"}}
|
||||
</BsTooltip>
|
||||
</CopyButton>
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
{{t "poll.share.notice"}}
|
||||
</small>
|
||||
</div>
|
36
app/components/share-poll-url.ts
Normal file
36
app/components/share-poll-url.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class SharePollUrlComponent extends Component {
|
||||
@service declare router: RouterService;
|
||||
|
||||
@tracked shouldShowCopySuccessMessage = false;
|
||||
|
||||
get pollUrl() {
|
||||
// RouterService.currentURL is relative to the webserver's
|
||||
// directory from which Croodle is served.
|
||||
const relativeUrl = this.router.currentURL;
|
||||
|
||||
// The URL under which Croodle is served, is not known at
|
||||
// build-time. But we can construct it ourself, by parsing
|
||||
// window.location.href and replacing the hash part.
|
||||
const absoluteUrl = new URL(window.location.href);
|
||||
absoluteUrl.hash = relativeUrl;
|
||||
|
||||
return absoluteUrl.toString();
|
||||
}
|
||||
|
||||
@action
|
||||
showCopySuccessMessage() {
|
||||
this.shouldShowCopySuccessMessage = true;
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@glint/environment-ember-loose/registry' {
|
||||
export default interface Registry {
|
||||
SharePollUrl: typeof SharePollUrlComponent;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import type FlashMessagesService from 'ember-cli-flash/services/flash-messages';
|
||||
|
||||
export default class ApplicationController extends Controller {
|
||||
@service declare flashMessages: FlashMessagesService;
|
||||
}
|
||||
export default class ApplicationController extends Controller {}
|
||||
|
|
|
@ -8,11 +8,9 @@ import { generatePassphrase } from '../../utils/encryption';
|
|||
import type RouterService from '@ember/routing/router-service';
|
||||
import type { CreateSettingsRouteModel } from 'croodle/routes/create/settings';
|
||||
import type IntlService from 'ember-intl/services/intl';
|
||||
import type FlashMessagesService from 'ember-cli-flash/services/flash-messages';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class CreateSettings extends Controller {
|
||||
@service declare flashMessages: FlashMessagesService;
|
||||
@service declare intl: IntlService;
|
||||
@service declare router: RouterService;
|
||||
|
||||
|
|
|
@ -4,13 +4,11 @@ import { isPresent, isEmpty } from '@ember/utils';
|
|||
import { action } from '@ember/object';
|
||||
import { DateTime } from 'luxon';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import type FlashMessagesService from 'ember-cli-flash/services/flash-messages';
|
||||
import type IntlService from 'ember-intl/services/intl';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type { PollRouteModel } from 'croodle/routes/poll';
|
||||
|
||||
export default class PollController extends Controller {
|
||||
@service declare flashMessages: FlashMessagesService;
|
||||
@service declare intl: IntlService;
|
||||
@service declare router: RouterService;
|
||||
|
||||
|
@ -22,15 +20,6 @@ export default class PollController extends Controller {
|
|||
@tracked timezoneChoosen = false;
|
||||
@tracked shouldUseLocalTimezone = false;
|
||||
|
||||
get pollUrl() {
|
||||
// consume information, which may cause a change to the URL to ensure
|
||||
// getter is invalided if needed
|
||||
this.router.currentURL;
|
||||
this.encryptionKey;
|
||||
|
||||
return window.location.href;
|
||||
}
|
||||
|
||||
get showExpirationWarning() {
|
||||
const { model: poll } = this;
|
||||
const { expirationDate } = poll;
|
||||
|
@ -66,20 +55,6 @@ export default class PollController extends Controller {
|
|||
return shouldUseLocalTimezone || !poll.timezone ? undefined : poll.timezone;
|
||||
}
|
||||
|
||||
@action
|
||||
linkAction(type: 'copied' | 'selected') {
|
||||
const flashMessages = this.flashMessages;
|
||||
switch (type) {
|
||||
case 'copied':
|
||||
flashMessages.success(`poll.link.copied`);
|
||||
break;
|
||||
|
||||
case 'selected':
|
||||
flashMessages.info(`poll.link.selected`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
useLocalTimezone() {
|
||||
this.shouldUseLocalTimezone = true;
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
@import "ember-bootstrap/input-group";
|
||||
@import "ember-bootstrap/custom-forms";
|
||||
@import "ember-bootstrap/spinners";
|
||||
@import "ember-bootstrap/tooltip";
|
||||
|
||||
// Overriding bootstrap selectors with properties we cannot influence by
|
||||
// changing variables.
|
||||
|
|
|
@ -14,13 +14,5 @@
|
|||
</nav>
|
||||
|
||||
<main class="container cr-main">
|
||||
<div id="messages">
|
||||
{{#each this.flashMessages.queue as |flash|}}
|
||||
<FlashMessage @flash={{flash}}>
|
||||
{{t flash.message}}
|
||||
</FlashMessage>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{outlet}}
|
||||
</main>
|
|
@ -49,31 +49,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-6 offset-lg-1">
|
||||
<div class="box poll-link cr-poll-link">
|
||||
<p>{{t "poll.share.title"}}</p>
|
||||
<p class="link cr-poll-link__link">
|
||||
<small>
|
||||
<code class="cr-poll-link__url">{{this.pollUrl}}</code>
|
||||
</small>
|
||||
<CopyButton
|
||||
@text={{this.pollUrl}}
|
||||
@onError={{fn this.linkAction "selected"}}
|
||||
@onSuccess={{fn this.linkAction "copied"}}
|
||||
class="btn btn-secondary cr-poll-link__copy-btn btn-sm"
|
||||
data-test-button="copy-link"
|
||||
>
|
||||
{{t "poll.link.copy-label"}}
|
||||
<span
|
||||
class="oi oi-clipboard"
|
||||
title={{t "poll.link.copy-label"}}
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</CopyButton>
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
{{t "poll.share.notice"}}
|
||||
</small>
|
||||
</div>
|
||||
<SharePollUrl />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ module.exports = {
|
|||
app: {
|
||||
javascript: {
|
||||
pattern: 'assets/*.js',
|
||||
limit: '430KB',
|
||||
limit: '310KB',
|
||||
compression: 'gzip',
|
||||
},
|
||||
css: {
|
||||
pattern: 'assets/*.css',
|
||||
limit: '16KB',
|
||||
limit: '16.5KB',
|
||||
compression: 'gzip',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@ module.exports = function (defaults) {
|
|||
'bs-button-group',
|
||||
'bs-form',
|
||||
'bs-modal',
|
||||
'bs-tooltip',
|
||||
],
|
||||
},
|
||||
'ember-cli-babel': {
|
||||
|
|
2464
package-lock.json
generated
2464
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -49,7 +49,7 @@
|
|||
"broccoli-asset-rev": "^3.0.0",
|
||||
"concurrently": "^8.2.1",
|
||||
"ember-auto-import": "^2.6.3",
|
||||
"ember-bootstrap": "^5.0.0",
|
||||
"ember-bootstrap": "^6.0.0-2",
|
||||
"ember-cli": "~5.4.0",
|
||||
"ember-cli-app-version": "^6.0.1",
|
||||
"ember-cli-babel": "^8.0.0",
|
||||
|
@ -61,7 +61,6 @@
|
|||
"ember-cli-content-security-policy": "^2.0.0",
|
||||
"ember-cli-dependency-checker": "^3.3.2",
|
||||
"ember-cli-deprecation-workflow": "^2.0.0",
|
||||
"ember-cli-flash": "^4.0.0",
|
||||
"ember-cli-htmlbars": "^6.3.0",
|
||||
"ember-cli-inject-live-reload": "https://gitpkg.now.sh/jelhan/ember-cli-inject-live-reload/lib?39c289e20aa5bd398da6f480e416e53b2973ef9c",
|
||||
"ember-cli-mirage": "^3.0.0",
|
||||
|
@ -72,7 +71,7 @@
|
|||
"ember-composable-helpers": "^5.0.0",
|
||||
"ember-decorators": "^6.1.1",
|
||||
"ember-fetch": "^8.1.2",
|
||||
"ember-intl": "^6.0.0",
|
||||
"ember-intl": "^6.1.2",
|
||||
"ember-load-initializers": "^2.1.2",
|
||||
"ember-math-helpers": "^4.0.0",
|
||||
"ember-modifier": "^4.1.0",
|
||||
|
@ -112,6 +111,9 @@
|
|||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.88.2"
|
||||
},
|
||||
"overrides": {
|
||||
"ember-element-helper": "^0.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
|
|
|
@ -24,20 +24,17 @@ module('Acceptance | view poll', function (hooks) {
|
|||
let pollUrl = `/poll/${poll.id}?encryptionKey=${encryptionKey}`;
|
||||
|
||||
await visit(pollUrl);
|
||||
assert.strictEqual(
|
||||
pageParticipation.url,
|
||||
window.location.href,
|
||||
'share link is shown',
|
||||
);
|
||||
assert
|
||||
.dom('[data-test-poll-url]')
|
||||
.containsText(
|
||||
`#/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`,
|
||||
'share link is shown',
|
||||
);
|
||||
|
||||
await click('.copy-btn');
|
||||
/*
|
||||
* Can't test if link is actually copied to clipboard due to api
|
||||
* restrictions. Due to security it's not allowed to read from clipboard.
|
||||
*
|
||||
* Can't test if flash message is shown due to
|
||||
* https://github.com/poteto/ember-cli-flash/issues/202
|
||||
*/
|
||||
assert
|
||||
.dom('[data-test-tooltip="copied"]')
|
||||
.isVisible('shows success message that URL has been copied');
|
||||
});
|
||||
|
||||
test('shows a warning if poll is about to be expired', async function (assert) {
|
||||
|
|
29
tests/integration/components/share-poll-url-test.js
Normal file
29
tests/integration/components/share-poll-url-test.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'croodle/tests/helpers';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import sinon from 'sinon';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
|
||||
module('Integration | Component | share-poll-url', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupIntl(hooks);
|
||||
|
||||
test('it shows poll url', async function (assert) {
|
||||
sinon.stub(this.owner.lookup('service:router'), 'currentURL').value('/foo');
|
||||
|
||||
await render(hbs`<SharePollUrl />`);
|
||||
|
||||
assert.dom('[data-test-poll-url]').containsText('/foo');
|
||||
});
|
||||
|
||||
test('user can copy the link to the clipboard', async function (assert) {
|
||||
sinon.stub(this.owner.lookup('service:router'), 'currentURL').value('/foo');
|
||||
const execCommandFake = sinon.stub(document, 'execCommand');
|
||||
|
||||
await render(hbs`<SharePollUrl />`);
|
||||
await click('[data-test-button="copy-link"]');
|
||||
assert.ok(execCommandFake.calledOnce);
|
||||
assert.deepEqual(execCommandFake.firstCall.args, ['copy']);
|
||||
});
|
||||
});
|
|
@ -204,9 +204,8 @@ poll:
|
|||
hide: Ocultar
|
||||
show: Mostra
|
||||
link:
|
||||
copied: 'Enllaç copiat al porta-retalls.'
|
||||
copied: 'Copiat!'
|
||||
copy-label: 'Copia l''enllaç al porta-retalls'
|
||||
selected: 'Enllaç seleccionat. Premeu Command + C per copiar.'
|
||||
modal:
|
||||
timezoneDiffers:
|
||||
title: 'En quines zones horàries s''han de presentar les dates?'
|
||||
|
|
|
@ -212,9 +212,8 @@ poll:
|
|||
hide: Verbergen
|
||||
show: Anzeigen
|
||||
link:
|
||||
copied: 'Link in die Zwischenablage kopiert.'
|
||||
copied: 'Kopiert!'
|
||||
copy-label: 'Kopiere Link in die Zwischenablage'
|
||||
selected: 'Link markiert. Drücke Steuerung + C zum Kopieren.'
|
||||
modal:
|
||||
timezoneDiffers:
|
||||
title: 'In welcher Zeitzone sollen die Daten angezeigt werden?'
|
||||
|
|
|
@ -204,9 +204,8 @@ poll:
|
|||
hide: Hide
|
||||
show: Show
|
||||
link:
|
||||
copied: 'Link copied to clipboard.'
|
||||
copied: 'Copied!'
|
||||
copy-label: 'Copy link to clipboard'
|
||||
selected: 'Link selected. Press Command+C to copy.'
|
||||
modal:
|
||||
timezoneDiffers:
|
||||
title: 'In which time zones should the dates be presented?'
|
||||
|
|
|
@ -206,9 +206,8 @@ poll:
|
|||
hide: Esconder
|
||||
show: Mostrar
|
||||
link:
|
||||
copied: 'Enlace copiado al portapapeles.'
|
||||
copied: 'Copiado!'
|
||||
copy-label: 'Copiar enlace al portapapeles'
|
||||
selected: 'Enlace seleccionado. Presiona Comando+C para copiarlo.'
|
||||
modal:
|
||||
timezoneDiffers:
|
||||
title: '¿Que zona horaria deseas utilizar para mostrar los datos?'
|
||||
|
|
|
@ -206,9 +206,8 @@ poll:
|
|||
hide: Caché
|
||||
show: Affiché
|
||||
link:
|
||||
copied: 'Lien copié dans le presse-papiers.'
|
||||
copied: 'Copié!'
|
||||
copy-label: 'Copier le lien dans le presse-papiers'
|
||||
selected: 'Lien sélectionné. Appuyez sur Commande+C pour copier.'
|
||||
modal:
|
||||
timezoneDiffers:
|
||||
title: 'Dans quels fuseaux horaires les dates doivent-elles être présentées ?'
|
||||
|
|
|
@ -205,9 +205,8 @@ poll:
|
|||
hide: Nascondi
|
||||
show: Mostra
|
||||
link:
|
||||
copied: 'Il link è stato copiato.'
|
||||
copied: 'Copiato!'
|
||||
copy-label: 'Copia il link negli appunti'
|
||||
selected: 'Link selezionato. Preme Command+C per copiarlo.'
|
||||
modal:
|
||||
timezoneDiffers:
|
||||
title: 'In quale fuso orario devono essere presentate le date?'
|
||||
|
|
|
@ -31,9 +31,8 @@ poll:
|
|||
expiration-date-warning: Denne avstemmingen utløper {timeToNow} og vil slettes
|
||||
etterpå.
|
||||
link:
|
||||
selected: Lenke valgt. Trykk Command+C for å kopiere.
|
||||
copy-label: Kopier lenke til utklippstavle
|
||||
copied: Lenke kopiert til utklippstavle.
|
||||
copied: Kopiert
|
||||
input:
|
||||
showEvaluation:
|
||||
show: Vis
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import { ComponentLike } from '@glint/template';
|
||||
import type FlashObject from 'ember-cli-flash/flash/object';
|
||||
|
||||
declare module '@glint/environment-ember-loose/registry' {
|
||||
export default interface Registry {
|
||||
FlashMessage: ComponentLike<{
|
||||
BsTooltip: ComponentLike<{
|
||||
Args: {
|
||||
Named: {
|
||||
flash: FlashObject;
|
||||
placement?: 'top' | 'bottom' | 'left' | 'right';
|
||||
triggerEvents?: string | string[];
|
||||
visible?: boolean;
|
||||
};
|
||||
};
|
||||
Blocks: {
|
||||
default: [];
|
||||
};
|
||||
Element: HTMLElement;
|
||||
}>;
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@ declare module '@glint/environment-ember-loose/registry' {
|
|||
CopyButton: ComponentLike<{
|
||||
Args: {
|
||||
Named: {
|
||||
onError: () => void;
|
||||
onSuccess: () => void;
|
||||
onError?: () => void;
|
||||
onSuccess?: () => void;
|
||||
text: string;
|
||||
};
|
||||
};
|
||||
|
|
20
types/ember-cli-flash/flash/object.d.ts
vendored
20
types/ember-cli-flash/flash/object.d.ts
vendored
|
@ -1,20 +0,0 @@
|
|||
// Source: https://github.com/adopted-ember-addons/ember-cli-flash/blob/5e9ca769ce30b168eef1a4a8e4cdf5ad0d538a8d/ember-cli-flash/declarations/flash/object.d.ts
|
||||
declare module 'ember-cli-flash/flash/object' {
|
||||
import EmberObject from '@ember/object';
|
||||
import Evented from '@ember/object/evented';
|
||||
|
||||
class FlashObject extends EmberObject.extend(Evented) {
|
||||
exiting: boolean;
|
||||
exitTimer: number;
|
||||
isExitable: boolean;
|
||||
initializedTime: number;
|
||||
message: string;
|
||||
destroyMessage(): void;
|
||||
exitMessage(): void;
|
||||
preventExit(): void;
|
||||
allowExit(): void;
|
||||
timerTask(): void;
|
||||
exitTimerTask(): void;
|
||||
}
|
||||
export default FlashObject;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Source: https://github.com/adopted-ember-addons/ember-cli-flash/blob/5e9ca769ce30b168eef1a4a8e4cdf5ad0d538a8d/ember-cli-flash/declarations/services/flash-messages.d.ts
|
||||
declare module 'ember-cli-flash/services/flash-messages' {
|
||||
import Service from '@ember/service';
|
||||
import FlashObject from 'ember-cli-flash/flash/object';
|
||||
|
||||
export interface MessageOptions {
|
||||
type: string;
|
||||
priority: number;
|
||||
timeout: number;
|
||||
sticky: boolean;
|
||||
showProgress: boolean;
|
||||
extendedTimeout: number;
|
||||
destroyOnClick: boolean;
|
||||
onDestroy: () => void;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CustomMessageInfo extends Partial<MessageOptions> {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface FlashFunction {
|
||||
(message: string, options?: Partial<MessageOptions>): FlashMessageService;
|
||||
}
|
||||
|
||||
class FlashMessageService extends Service {
|
||||
queue: FlashObject[];
|
||||
readonly arrangedQueue: FlashObject[];
|
||||
readonly isEmpty: boolean;
|
||||
success: FlashFunction;
|
||||
warning: FlashFunction;
|
||||
info: FlashFunction;
|
||||
danger: FlashFunction;
|
||||
alert: FlashFunction;
|
||||
secondary: FlashFunction;
|
||||
add(messageInfo: CustomMessageInfo): this;
|
||||
clearMessages(): this;
|
||||
registerTypes(types: string[]): this;
|
||||
getFlashObject(): FlashObject;
|
||||
peekFirst(): FlashObject | undefined;
|
||||
peekLast(): FlashObject | undefined;
|
||||
readonly flashMessageDefaults: unknown;
|
||||
}
|
||||
|
||||
export default FlashMessageService;
|
||||
}
|
Loading…
Reference in a new issue