Browse Source

Lists, reorganizing again

imwald
Nuša Pukšič 3 months ago
parent
commit
6feeaecaf5
  1. 4
      assets/styles/reading-lists.css
  2. 14
      src/Controller/ReadingListWizardController.php
  3. 1
      src/Form/CategoryArticlesType.php
  4. 4
      templates/components/ReadingListQuickInputComponent.html.twig
  5. 53
      templates/reading_list/compose.html.twig
  6. 2
      templates/reading_list/index.html.twig
  7. 4
      templates/reading_list/reading_articles.html.twig
  8. 30
      templates/reading_list/reading_review.html.twig

4
assets/styles/reading-lists.css

@ -117,3 +117,7 @@
.workflow-status-card .badge:hover { .workflow-status-card .badge:hover {
transform: scale(1.05); transform: scale(1.05);
} }
#aCollection li>div {
width: 100%;
}

14
src/Controller/ReadingListWizardController.php

@ -40,10 +40,15 @@ class ReadingListWizardController extends AbstractController
} }
#[Route('/reading-list/wizard/articles', name: 'read_wizard_articles')] #[Route('/reading-list/wizard/articles', name: 'read_wizard_articles')]
public function articles(Request $request): Response public function articles(Request $request, ReadingListManager $readingListManager): Response
{ {
$draft = $this->getDraft($request); $draft = $this->getDraft($request);
if (!$draft) {
$loadSlug = $request->query->get('load');
if ($loadSlug) {
$draft = $readingListManager->loadPublishedListIntoDraft($loadSlug);
$this->saveDraft($request, $draft);
} elseif (!$draft) {
return $this->redirectToRoute('read_wizard_setup'); return $this->redirectToRoute('read_wizard_setup');
} }
@ -61,6 +66,11 @@ class ReadingListWizardController extends AbstractController
if (!$draft->slug) { if (!$draft->slug) {
$draft->slug = $this->slugifyWithRandom($draft->title); $draft->slug = $this->slugifyWithRandom($draft->title);
} }
// If draft articles is still empty, remove the empty string we added
if (count($draft->articles) === 1 && $draft->articles[0] === '') {
$draft->articles = [];
}
$this->saveDraft($request, $draft); $this->saveDraft($request, $draft);
return $this->redirectToRoute('read_wizard_review'); return $this->redirectToRoute('read_wizard_review');
} }

1
src/Form/CategoryArticlesType.php

@ -24,6 +24,7 @@ class CategoryArticlesType extends AbstractType
->add('articles', CollectionType::class, [ ->add('articles', CollectionType::class, [
'entry_type' => TextType::class, 'entry_type' => TextType::class,
'entry_options' => [ 'entry_options' => [
'required' => false,
'attr' => [ 'attr' => [
'placeholder' => '30023:pubkey:slug' 'placeholder' => '30023:pubkey:slug'
], ],

4
templates/components/ReadingListQuickInputComponent.html.twig

@ -1,7 +1,7 @@
<div {{ attributes }}> <div {{ attributes }}>
<form data-action="live#action:prevent" data-live-action-param="addMultiple"> <form data-action="live#action:prevent" data-live-action-param="addMultiple">
<div class="mb-3"> <div class="mb-3">
<label class="form-label"> <label class="form-label d-flex">
<strong>Paste article naddresses or coordinates</strong> <strong>Paste article naddresses or coordinates</strong>
<small class="d-block text-muted">One per line. Supports both formats:</small> <small class="d-block text-muted">One per line. Supports both formats:</small>
<small class="d-block text-muted">• naddr1... (or nostr:naddr1...)</small> <small class="d-block text-muted">• naddr1... (or nostr:naddr1...)</small>
@ -14,7 +14,9 @@
data-model="norender|input" data-model="norender|input"
>{{ input }}</textarea> >{{ input }}</textarea>
</div> </div>
<div class="actions">
<button type="submit" class="btn btn-primary">Add to Reading List</button> <button type="submit" class="btn btn-primary">Add to Reading List</button>
</div>
</form> </form>
{% if error %} {% if error %}

53
templates/reading_list/compose.html.twig

@ -15,40 +15,6 @@
</div> </div>
{% endif %} {% endif %}
<div class="row g-4">
{# Left sidebar - Reading List Preview #}
<div class="col-lg-4">
<div class="sticky-top" style="top: 20px;">
<twig:ReadingListDraftComponent />
</div>
</div>
{# Main content - Simple tabbed interface #}
<div class="col-lg-8">
{# List Selector #}
<div class="card mb-3">
<div class="card-body">
<twig:ReadingListSelectorComponent />
</div>
</div>
{# Tabbed content #}
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="paste-tab" data-bs-toggle="tab" data-bs-target="#paste" type="button" role="tab">
📋 Paste Links
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="search-tab" data-bs-toggle="tab" data-bs-target="#search" type="button" role="tab">
🔍 Search
</button>
</li>
</ul>
<div class="tab-content">
{# Paste tab #}
<div class="tab-pane fade show active" id="paste" role="tabpanel">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Quick Add Articles</h5> <h5 class="card-title">Quick Add Articles</h5>
@ -56,20 +22,11 @@
<twig:ReadingListQuickInputComponent /> <twig:ReadingListQuickInputComponent />
</div> </div>
</div> </div>
</div>
{# Search tab #}
<div class="tab-pane fade" id="search" role="tabpanel">
<div class="card">
<div class="card-body">
<h5 class="card-title">Search & Add Articles</h5>
<p class="text-muted small">Find articles and add them to your list</p>
<twig:SearchComponent :selectMode="true" currentRoute="compose" />
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}
{% block aside %}
{# Future sidebar content can go here #}
<twig:ReadingListDraftComponent />
{% endblock %}

2
templates/reading_list/index.html.twig

@ -23,7 +23,7 @@
<small class="text-muted">slug: {{ item.slug ?: '—' }} • created: {{ item.createdAt|date('Y-m-d H:i') }}</small> <small class="text-muted">slug: {{ item.slug ?: '—' }} • created: {{ item.createdAt|date('Y-m-d H:i') }}</small>
</div> </div>
<div class="d-flex flex-row gap-2"> <div class="d-flex flex-row gap-2">
<a class="btn btn-sm btn-primary" href="{{ path('reading_list_compose') }}">Open Composer</a> <a class="btn btn-sm btn-primary" href="{{ path('read_wizard_articles', {'load': item.slug}) }}">Open Composer</a>
{% if item.slug %} {% if item.slug %}
<a class="btn btn-sm btn-outline-primary" href="{{ path('reading-list', { slug: item.slug, npub: item.pubkey|toNpub }) }}">View</a> <a class="btn btn-sm btn-outline-primary" href="{{ path('reading-list', { slug: item.slug, npub: item.pubkey|toNpub }) }}">View</a>
<span data-controller="copy-to-clipboard"> <span data-controller="copy-to-clipboard">

4
templates/reading_list/reading_articles.html.twig

@ -15,13 +15,15 @@
{{ stimulus_controller('form-collection') }} {{ stimulus_controller('form-collection') }}
data-form-collection-index-value="{{ form.articles|length > 0 ? form.articles|last.vars.name + 1 : 0 }}" data-form-collection-index-value="{{ form.articles|length > 0 ? form.articles|last.vars.name + 1 : 0 }}"
data-form-collection-prototype-value="{{ form_widget(form.articles.vars.prototype)|e('html_attr') }}"> data-form-collection-prototype-value="{{ form_widget(form.articles.vars.prototype)|e('html_attr') }}">
<ul class="d-flex gap-2 list-unstyled" {{ stimulus_target('form-collection', 'collectionContainer') }}> <ul id="aCollection" class="d-flex gap-2 list-unstyled" {{ stimulus_target('form-collection', 'collectionContainer') }}>
{% for item in form.articles %} {% for item in form.articles %}
<li class="d-flex flex-row">{{ form_row(item) }}</li> <li class="d-flex flex-row">{{ form_row(item) }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="actions">
<button type="button" class="btn btn-secondary mt-2" {{ stimulus_action('form-collection', 'addCollectionElement') }}>Add article</button> <button type="button" class="btn btn-secondary mt-2" {{ stimulus_action('form-collection', 'addCollectionElement') }}>Add article</button>
</div> </div>
</div>
<div class="mt-3 d-flex flex-row gap-2 actions"> <div class="mt-3 d-flex flex-row gap-2 actions">
<a class="btn btn-secondary" href="{{ path('read_wizard_setup') }}">Back</a> <a class="btn btn-secondary" href="{{ path('read_wizard_setup') }}">Back</a>

30
templates/reading_list/reading_review.html.twig

@ -4,7 +4,10 @@
<h1>Review & Sign Reading List</h1> <h1>Review & Sign Reading List</h1>
<div class="notice info"> <div class="notice info">
<p>Review your reading list. When ready, click Sign & Publish. Your NIP-07 extension will be used to sign the event.</p> {% if not is_granted('ROLE_USER') %}
<p>A Nostr identity is required so publish the list.</p>
{% endif %}
<p>Review your reading list. When ready, click Sign & Publish. Your NIP-07 extension or signer will be used to sign the event.</p>
</div> </div>
<section class="mb-4"> <section class="mb-4">
@ -18,25 +21,20 @@
{% if draft.articles is defined and draft.articles|length %} {% if draft.articles is defined and draft.articles|length %}
<div class="mt-2"> <div class="mt-2">
<strong>Articles:</strong> <strong>Articles: {{ draft.articles|length }}</strong>
{% if draft.articles|length > 0 %}
<ul> <ul>
{% for a in draft.articles %} {% for a in draft.articles %}
<li><code>{{ a }}</code></li> <li><code>{{ a }}</code></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div>
{% else %} {% else %}
<div class="mt-2"><small>No articles yet.</small></div> <div class="mt-2"><small>No articles yet.</small></div>
{% endif %} {% endif %}
</section>
<section class="mb-4">
<details>
<summary>Show event preview (JSON)</summary>
<div class="mt-2">
<pre class="small">{{ eventJson }}</pre>
</div> </div>
</details> {% else %}
<div class="mt-2"><small>No articles yet.</small></div>
{% endif %}
</section> </section>
<div <div
@ -46,17 +44,15 @@
csrfToken: csrfToken csrfToken: csrfToken
}) }} }) }}
> >
<section class="mb-3">
<h3>Final event (with your pubkey)</h3>
<pre class="small" data-nostr-single-sign-target="computedPreview"></pre>
</section>
<div class="d-flex flex-row gap-2"> <div class="d-flex flex-row gap-2">
<a class="btn btn-secondary" href="{{ path('read_wizard_cancel') }}">Cancel</a> <a class="btn btn-secondary" href="{{ path('read_wizard_cancel') }}">Cancel</a>
<button class="btn btn-primary" data-nostr-single-sign-target="publishButton" data-action="click->nostr-single-sign#signAndPublish">Sign & Publish</button> <button class="btn btn-primary" data-nostr-single-sign-target="publishButton" data-action="click->nostr-single-sign#signAndPublish">Sign & Publish</button>
</div> </div>
<div class="mt-3" data-nostr-single-sign-target="status"></div> <div class="mt-3" data-nostr-single-sign-target="status"></div>
<section class="mb-3">
<h3>Event preview</h3>
<pre class="small" data-nostr-single-sign-target="computedPreview"></pre>
</section>
</div> </div>
{% endblock %} {% endblock %}

Loading…
Cancel
Save