Browse Source

Feedback form

imwald
Nuša Pukšič 3 months ago
parent
commit
4755ec5233
  1. 16
      assets/controllers/nostr_single_sign_controller.js
  2. 1
      config/packages/twig.yaml
  3. 22
      src/Controller/FeedbackController.php
  4. 6
      templates/components/event_card.html.twig
  5. 34
      templates/feedback/form.html.twig
  6. 3
      templates/layout.html.twig
  7. 45
      templates/pages/article.html.twig

16
assets/controllers/nostr_single_sign_controller.js

@ -23,6 +23,11 @@ export default class extends Controller {
} }
const preview = JSON.parse(JSON.stringify(skeleton)); const preview = JSON.parse(JSON.stringify(skeleton));
preview.pubkey = pubkey; preview.pubkey = pubkey;
// Update content from textarea if present
const textarea = this.element.querySelector('textarea');
if (textarea) {
preview.content = textarea.value;
}
if (this.hasComputedPreviewTarget) { if (this.hasComputedPreviewTarget) {
this.computedPreviewTarget.textContent = JSON.stringify(preview, null, 2); this.computedPreviewTarget.textContent = JSON.stringify(preview, null, 2);
} }
@ -45,18 +50,22 @@ export default class extends Controller {
try { try {
const pubkey = await window.nostr.getPublicKey(); const pubkey = await window.nostr.getPublicKey();
const skeleton = JSON.parse(this.eventValue || '{}'); const skeleton = JSON.parse(this.eventValue || '{}');
// Update content from textarea before signing
const textarea = this.element.querySelector('textarea');
if (textarea) {
skeleton.content = textarea.value;
}
this.ensureCreatedAt(skeleton); this.ensureCreatedAt(skeleton);
this.ensureContent(skeleton); this.ensureContent(skeleton);
skeleton.pubkey = pubkey; skeleton.pubkey = pubkey;
this.showStatus('Signing reading list…'); this.showStatus('Signing feedback…');
const signed = await window.nostr.signEvent(skeleton); const signed = await window.nostr.signEvent(skeleton);
this.showStatus('Publishing…'); this.showStatus('Publishing…');
await this.publishSigned(signed); await this.publishSigned(signed);
this.showSuccess('Published reading list successfully'); this.showSuccess('Published feedback successfully');
} catch (e) { } catch (e) {
console.error(e); console.error(e);
this.showError(e.message || 'Publish failed'); this.showError(e.message || 'Publish failed');
@ -105,4 +114,3 @@ export default class extends Controller {
} }
} }
} }

1
config/packages/twig.yaml

@ -3,6 +3,7 @@ twig:
globals: globals:
project_npub: 'npub1ez09adke4vy8udk3y2skwst8q5chjgqzym9lpq4u58zf96zcl7kqyry2lz' project_npub: 'npub1ez09adke4vy8udk3y2skwst8q5chjgqzym9lpq4u58zf96zcl7kqyry2lz'
dev_npub: 'npub1636uujeewag8zv8593lcvdrwlymgqre6uax4anuq3y5qehqey05sl8qpl4' dev_npub: 'npub1636uujeewag8zv8593lcvdrwlymgqre6uax4anuq3y5qehqey05sl8qpl4'
feature_flag_share_btn: false
when@test: when@test:
twig: twig:

22
src/Controller/FeedbackController.php

@ -0,0 +1,22 @@
<?php
namespace App\Controller;
use App\Util\NostrKeyUtil;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class FeedbackController extends AbstractController
{
#[Route('/feedback', name: 'feedback_form', methods: ['GET'])]
public function form(NostrKeyUtil $keyUtil): Response
{
$recipients = [
$keyUtil->npubToHex('npub1ez09adke4vy8udk3y2skwst8q5chjgqzym9lpq4u58zf96zcl7kqyry2lz'),
$keyUtil->npubToHex('npub1636uujeewag8zv8593lcvdrwlymgqre6uax4anuq3y5qehqey05sl8qpl4'),
];
return $this->render('feedback/form.html.twig', [
'recipients' => $recipients,
]);
}
}

6
templates/components/event_card.html.twig

@ -13,7 +13,7 @@
<span class="event-date">{{ event.created_at|date('M j, Y H:i') }}</span> <span class="event-date">{{ event.created_at|date('M j, Y H:i') }}</span>
</div> </div>
</div> </div>
<div class="event-content line-clamp-5"> <div class="event-content">
{{ event.content|markdown_to_html|mentionify }} {{ event.content|markdown_to_html|mentionify }}
</div> </div>
<div class="event-footer"> <div class="event-footer">
@ -23,8 +23,8 @@
<style> <style>
.embedded-event-card { .embedded-event-card {
border: 1px solid #e1e5e9; border: none;
border-radius: 8px; border-radius: 0;
padding: 12px; padding: 12px;
margin: 8px 0; margin: 8px 0;
background: #f8f9fa; background: #f8f9fa;

34
templates/feedback/form.html.twig

@ -0,0 +1,34 @@
{% extends 'layout.html.twig' %}
{% block title %}Send Feedback{% endblock %}
{% block body %}
<div class="w-container mt-5">
<twig:Atoms:PageHeading heading="Feedback" tagline="Bug reports and feature requests welcome"/>
<form data-controller="nostr-single-sign" data-nostr-single-sign-event-value='{{ {
"kind": 24,
"tags": [...recipients|map(r => ["p", r]), ['client', 'Decent Newsroom']],
"content": ""
}|json_encode(constant('JSON_UNESCAPED_SLASHES')) }}'
data-nostr-single-sign-publish-url-value="/api/nostr/publish"
data-nostr-single-sign-csrf-token-value="{{ csrf_token('feedback') }}"
data-action="submit->nostr-single-sign#signAndPublish">
<div data-nostr-single-sign-target="status"></div>
<div class="mb-3">
<p>To:
{% for r in recipients %}
<twig:Molecules:UserFromNpub :ident="r" />
{% if not loop.last %}and {% endif %}
{% endfor %}
</p>
<label for="feedback-message" class="form-label">Your Message</label>
<textarea id="feedback-message" class="form-control" rows="5" required data-action="input->nostr-single-sign#preparePreview"></textarea>
</div>
<div class="actions">
<button type="submit" class="btn btn-primary" data-nostr-single-sign-target="publishButton">Send Feedback</button>
</div>
<h5 class="mt-4">Preview</h5>
<pre data-nostr-single-sign-target="computedPreview" class="bg-light p-2"></pre>
</form>
</div>
{% endblock %}

3
templates/layout.html.twig

@ -39,6 +39,9 @@
</li> </li>
</ul> </ul>
<twig:UserMenu /> <twig:UserMenu />
<div class="container mt-2 mb-2 text-end">
<a href="{{ path('feedback_form') }}" class="btn btn-outline-primary">Send Feedback</a>
</div>
{% block nav %}{% endblock %} {% block nav %}{% endblock %}
</nav> </nav>
</div> </div>

45
templates/pages/article.html.twig

@ -30,6 +30,51 @@
Copy coordinates Copy coordinates
</button> </button>
{% endif %} {% endif %}
<!-- Share Button Dropdown -->
{% if feature_flag_share_btn %}
<div class="dropdown share-dropdown" style="display:inline-block;position:relative;">
<button class="btn btn-secondary" id="shareBtn" type="button" aria-haspopup="true" aria-expanded="false">
Share
</button>
<div class="dropdown-menu" id="shareDropdown" style="display:none;position:absolute;z-index:1000;min-width:200px;background:#fff;border:1px solid #ccc;padding:0.5em 0;box-shadow:0 2px 8px rgba(0,0,0,0.1);">
<button class="dropdown-item" type="button" onclick="copyShareLink('{{ canonical|e('js') }}', this)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="vertical-align:middle;">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h6m0 0v6m0-6L10 19l-7-7" />
</svg>
Share Newsroom Link
</button>
{# <button class="dropdown-item" type="button" onclick="copyShareLink('30023:{{ article.pubkey }}:{{ article.slug }}', this)">#}
{# <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="vertical-align:middle;">#}
{# <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12H9m12 0A9 9 0 11 3 12a9 9 0 0118 0z" />#}
{# </svg>#}
{# Share naddr#}
{# </button>#}
</div>
</div>
<script>
// Share dropdown toggle
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('shareBtn');
var menu = document.getElementById('shareDropdown');
btn.addEventListener('click', function(e) {
e.stopPropagation();
menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
});
document.addEventListener('click', function() {
menu.style.display = 'none';
});
});
// Copy to clipboard with feedback
function copyShareLink(text, el) {
navigator.clipboard.writeText(text).then(function() {
var orig = el.innerHTML;
el.innerHTML = 'Copied!';
setTimeout(function() { el.innerHTML = orig; }, 1200);
});
}
</script>
{% endif %}
</div> </div>
<article class="w-container"> <article class="w-container">

Loading…
Cancel
Save