Compare commits
8
Commits
1c7720b689
...
v1.27.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9a6304926
|
||
|
|
6fb4ecede3
|
||
|
|
77db625b11
|
||
|
|
6351cfbd07
|
||
|
|
b7e46da3e9
|
||
|
|
3233150cc4
|
||
|
|
822472a0e6
|
||
|
|
7ede81fe5f
|
@@ -1,47 +1,86 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { on } from '@ember/modifier';
|
import { on } from '@ember/modifier';
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import ContributionPhoto from './contribution-photo';
|
import ContributionPhoto from './contribution-photo';
|
||||||
|
import Modal from './modal';
|
||||||
|
import NostrConnect from './nostr-connect';
|
||||||
import eq from 'ember-truth-helpers/helpers/eq';
|
import eq from 'ember-truth-helpers/helpers/eq';
|
||||||
import not from 'ember-truth-helpers/helpers/not';
|
import not from 'ember-truth-helpers/helpers/not';
|
||||||
import restoreScroll from '../modifiers/restore-scroll';
|
import restoreScroll from '../modifiers/restore-scroll';
|
||||||
|
|
||||||
<template>
|
export default class ContributionsTimelineComponent extends Component {
|
||||||
<div class="sidebar">
|
@tracked isNostrConnectModalOpen = false;
|
||||||
<div class="sidebar-header has-back-btn">
|
|
||||||
<button type="button" class="back-btn" {{on "click" @onBack}}>
|
@action
|
||||||
<Icon @name="arrow-left" @size={{20}} @color="#333" />
|
openNostrConnectModal(event) {
|
||||||
</button>
|
event.preventDefault();
|
||||||
<h2 class="sidebar-header-text-centered">
|
this.isNostrConnectModalOpen = true;
|
||||||
<span class="sidebar-header-icon-wrapper">
|
}
|
||||||
<Icon @name="activity" @size={{20}} @color="#898989" />
|
|
||||||
</span>
|
@action
|
||||||
My Contributions
|
closeNostrConnectModal() {
|
||||||
</h2>
|
this.isNostrConnectModalOpen = false;
|
||||||
<button type="button" class="close-btn" {{on "click" @onClose}}>
|
}
|
||||||
<Icon @name="x" @size={{20}} @color="#333" />
|
|
||||||
</button>
|
@action
|
||||||
|
onNostrConnected() {
|
||||||
|
this.closeNostrConnectModal();
|
||||||
|
this.args.onNostrConnected?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="sidebar-header has-back-btn">
|
||||||
|
<button type="button" class="back-btn" {{on "click" @onBack}}>
|
||||||
|
<Icon @name="arrow-left" @size={{20}} @color="#333" />
|
||||||
|
</button>
|
||||||
|
<h2 class="sidebar-header-text-centered">
|
||||||
|
<span class="sidebar-header-icon-wrapper">
|
||||||
|
<Icon @name="activity" @size={{20}} @color="#898989" />
|
||||||
|
</span>
|
||||||
|
My Contributions
|
||||||
|
</h2>
|
||||||
|
<button type="button" class="close-btn" {{on "click" @onClose}}>
|
||||||
|
<Icon @name="x" @size={{20}} @color="#333" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-content" {{restoreScroll @scrollTop}}>
|
||||||
|
{{#if @isLoading}}
|
||||||
|
<div class="sidebar-loading">
|
||||||
|
<Icon @name="loading-ring" @size={{24}} @color="#898989" />
|
||||||
|
</div>
|
||||||
|
{{else if (not @isConnected)}}
|
||||||
|
<p class="empty-state">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
{{on "click" this.openNostrConnectModal}}
|
||||||
|
>Connect your Nostr account</a>
|
||||||
|
to see your contributions.
|
||||||
|
</p>
|
||||||
|
{{else if (not @items.length)}}
|
||||||
|
<p class="empty-state">No contributions yet. Start by adding photos to
|
||||||
|
places.</p>
|
||||||
|
{{else}}
|
||||||
|
<ul class="contributions-list">
|
||||||
|
{{#each @items as |item|}}
|
||||||
|
{{#if (eq item.type "photo")}}
|
||||||
|
<ContributionPhoto @item={{item}} @onSelect={{@onSelect}} />
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content" {{restoreScroll @scrollTop}}>
|
{{#if this.isNostrConnectModalOpen}}
|
||||||
{{#if @isLoading}}
|
<Modal @onClose={{this.closeNostrConnectModal}}>
|
||||||
<div class="sidebar-loading">
|
<NostrConnect @onConnect={{this.onNostrConnected}} />
|
||||||
<Icon @name="loading-ring" @size={{24}} @color="#898989" />
|
</Modal>
|
||||||
</div>
|
{{/if}}
|
||||||
{{else if (not @isConnected)}}
|
</template>
|
||||||
<p class="empty-state">
|
}
|
||||||
Connect your Nostr account to see your contributions.
|
|
||||||
</p>
|
|
||||||
{{else if (not @items.length)}}
|
|
||||||
<p class="empty-state">No contributions yet.</p>
|
|
||||||
{{else}}
|
|
||||||
<ul class="contributions-list">
|
|
||||||
{{#each @items as |item|}}
|
|
||||||
{{#if (eq item.type "photo")}}
|
|
||||||
<ContributionPhoto @item={{item}} @onSelect={{@onSelect}} />
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export default class PlaceDetails extends Component {
|
|||||||
return htmlSafe(
|
return htmlSafe(
|
||||||
parts
|
parts
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
const safeTel = p.replace(/[\s-]+/g, '');
|
const safeTel = p.replace(/[\s()+.-]/g, '');
|
||||||
return `<a href="https://wa.me/${safeTel}" target="_blank" rel="noopener noreferrer">${p}</a>`;
|
return `<a href="https://wa.me/${safeTel}" target="_blank" rel="noopener noreferrer">${p}</a>`;
|
||||||
})
|
})
|
||||||
.join('<br>')
|
.join('<br>')
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ export default class ContributionsController extends Controller {
|
|||||||
this.router.transitionTo(`/place/${item.placeIdentifier}`);
|
this.router.transitionTo(`/place/${item.placeIdentifier}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onNostrConnected() {
|
||||||
|
this.loadContributionsTask.perform(this.nostrAuth.pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
backToMenu() {
|
backToMenu() {
|
||||||
this.router.transitionTo('menu');
|
this.router.transitionTo('menu');
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import ContributionsTimeline from '#components/contributions-timeline';
|
|||||||
@onSelect={{@controller.selectContribution}}
|
@onSelect={{@controller.selectContribution}}
|
||||||
@onBack={{@controller.backToMenu}}
|
@onBack={{@controller.backToMenu}}
|
||||||
@onClose={{@controller.close}}
|
@onClose={{@controller.close}}
|
||||||
|
@onNostrConnected={{@controller.onNostrConnected}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "marco",
|
"name": "marco",
|
||||||
"version": "1.27.0",
|
"version": "1.27.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Unhosted maps app",
|
"description": "Unhosted maps app",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
@@ -39,8 +39,8 @@
|
|||||||
<meta name="msapplication-TileColor" content="#F6E9A6">
|
<meta name="msapplication-TileColor" content="#F6E9A6">
|
||||||
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/main-MT6QR2E9.js"></script>
|
<script type="module" crossorigin src="/assets/main-D3dQ-CGf.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/main-sIiovt6q.css">
|
<link rel="stylesheet" crossorigin href="/assets/main-P9lvOtv_.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="modal-portal"></div>
|
<div id="modal-portal"></div>
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'marco/tests/helpers';
|
import { setupRenderingTest } from 'marco/tests/helpers';
|
||||||
import { render, click } from '@ember/test-helpers';
|
import { render, click } from '@ember/test-helpers';
|
||||||
|
import Service from '@ember/service';
|
||||||
import ContributionsTimeline from 'marco/components/contributions-timeline';
|
import ContributionsTimeline from 'marco/components/contributions-timeline';
|
||||||
|
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
|
class MockToastService extends Service {
|
||||||
|
show() {}
|
||||||
|
}
|
||||||
|
|
||||||
module('Integration | Component | contributions-timeline', function (hooks) {
|
module('Integration | Component | contributions-timeline', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
setupNostrMocks(hooks);
|
||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
|
this.owner.register('service:toast', MockToastService);
|
||||||
|
|
||||||
this.noop = noop;
|
this.noop = noop;
|
||||||
this.emptyItems = [];
|
this.emptyItems = [];
|
||||||
});
|
});
|
||||||
@@ -48,6 +57,28 @@ module('Integration | Component | contributions-timeline', function (hooks) {
|
|||||||
assert.dom('.empty-state').includesText('Connect your Nostr account');
|
assert.dom('.empty-state').includesText('Connect your Nostr account');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('clicking "Connect your Nostr account" opens the Nostr connect modal', async function (assert) {
|
||||||
|
await render(
|
||||||
|
<template>
|
||||||
|
<div id="modal-portal"></div>
|
||||||
|
<ContributionsTimeline
|
||||||
|
@items={{this.emptyItems}}
|
||||||
|
@isLoading={{false}}
|
||||||
|
@isConnected={{false}}
|
||||||
|
@onBack={{this.noop}}
|
||||||
|
@onClose={{this.noop}}
|
||||||
|
@onSelect={{this.noop}}
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom('.nostr-connect-modal').doesNotExist();
|
||||||
|
|
||||||
|
await click('.empty-state a');
|
||||||
|
|
||||||
|
assert.dom('.nostr-connect-modal').exists();
|
||||||
|
});
|
||||||
|
|
||||||
test('it renders an empty state when connected but no contributions', async function (assert) {
|
test('it renders an empty state when connected but no contributions', async function (assert) {
|
||||||
await render(
|
await render(
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -320,14 +320,14 @@ module('Integration | Component | place-details', function (hooks) {
|
|||||||
const links = whatsappBlock.querySelectorAll('a[href^="https://wa.me/"]');
|
const links = whatsappBlock.querySelectorAll('a[href^="https://wa.me/"]');
|
||||||
assert.strictEqual(links.length, 2, 'Rendered exactly 2 WhatsApp links');
|
assert.strictEqual(links.length, 2, 'Rendered exactly 2 WhatsApp links');
|
||||||
|
|
||||||
// Verify it stripped the dashes and spaces for the wa.me URL
|
// Verify it stripped the dashes, spaces and leading plus for the wa.me URL
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
links[0].getAttribute('href'),
|
links[0].getAttribute('href'),
|
||||||
'https://wa.me/+44987654321'
|
'https://wa.me/44987654321'
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
links[1].getAttribute('href'),
|
links[1].getAttribute('href'),
|
||||||
'https://wa.me/+12345678900'
|
'https://wa.me/12345678900'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify it kept the dashes and spaces for the visible text
|
// Verify it kept the dashes and spaces for the visible text
|
||||||
@@ -335,6 +335,33 @@ module('Integration | Component | place-details', function (hooks) {
|
|||||||
assert.dom(links[1]).hasText('+1 234-567 8900');
|
assert.dom(links[1]).hasText('+1 234-567 8900');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it strips parentheses, dots and the leading plus from whatsapp hrefs', async function (assert) {
|
||||||
|
const place = {
|
||||||
|
title: 'Chat Shop',
|
||||||
|
osmTags: {
|
||||||
|
whatsapp: '+504-9850-3802;(504) 9850.3802;+1.234.567.8900',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await render(<template><PlaceDetails @place={{place}} /></template>);
|
||||||
|
|
||||||
|
const links = this.element.querySelectorAll('a[href^="https://wa.me/"]');
|
||||||
|
assert.strictEqual(links.length, 3, 'Rendered exactly 3 WhatsApp links');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
links[0].getAttribute('href'),
|
||||||
|
'https://wa.me/50498503802'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
links[1].getAttribute('href'),
|
||||||
|
'https://wa.me/50498503802'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
links[2].getAttribute('href'),
|
||||||
|
'https://wa.me/12345678900'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('it renders correct OpenStreetMap link for an OSM place', async function (assert) {
|
test('it renders correct OpenStreetMap link for an OSM place', async function (assert) {
|
||||||
const place = {
|
const place = {
|
||||||
title: 'OSM Place',
|
title: 'OSM Place',
|
||||||
|
|||||||
Reference in New Issue
Block a user