decodeAndParseNostrBech32($naddr); if ($hrp !== 'naddr') { throw new \Exception('Invalid naddr'); } foreach ($tlv as $item) { // d tag if ($item['type'] === 0) { $slug = implode('', array_map('chr', $item['value'])); } // relays if ($item['type'] === 1) { $relays[] = implode('', array_map('chr', $item['value'])); } // author if ($item['type'] === 2) { $str = ''; foreach ($item['value'] as $byte) { $str .= str_pad(dechex($byte), 2, '0', STR_PAD_LEFT); } $author = $str; } if ($item['type'] === 3) { // big-endian integer $intValue = 0; foreach ($item['value'] as $byte) { $intValue = ($intValue << 8) | $byte; } $kind = $intValue; } } if ($kind !== KindsEnum::LONGFORM->value) { throw new \Exception('Not a long form article'); } if (empty($relays ?? [])) { // get author npub relays from their config $relays = $nostrClient->getNpubRelays($author); } $nostrClient->getLongFormFromNaddr($slug, $relays ?? null, $author, $kind); if ($slug) { return $this->redirectToRoute('article-slug', ['slug' => $slug]); } throw new \Exception('No article.'); } /** * @throws InvalidArgumentException|CommonMarkException */ #[Route('/article/d/{slug}', name: 'article-slug')] public function article(EntityManagerInterface $entityManager, CacheItemPoolInterface $articlesCache, NostrClient $nostrClient, Converter $converter, $slug): Response { $article = null; // check if an item with same eventId already exists in the db $repository = $entityManager->getRepository(Article::class); $articles = $repository->findBy(['slug' => $slug]); $revisions = count($articles); if ($revisions === 0) { throw $this->createNotFoundException('The article could not be found'); } if ($revisions > 1) { // sort articles by created at date usort($articles, function ($a, $b) { return $b->getCreatedAt() <=> $a->getCreatedAt(); }); // get the last article $article = end($articles); } else { $article = $articles[0]; } $cacheKey = 'article_' . $article->getId(); $cacheItem = $articlesCache->getItem($cacheKey); if (!$cacheItem->isHit()) { $cacheItem->set($converter->convertToHtml($article->getContent())); $articlesCache->save($cacheItem); } // // suggestions // $suggestions = $repository->findBy(['pubkey' => $article->getPubkey()], ['createdAt' => 'DESC'], 3); // // skip current, if listed in suggestions // $suggestions = array_filter($suggestions, function ($s) use ($article) { // return $s->getId() !== $article->getId(); // }); // $suggestions = array_merge($suggestions, $repository->findBy([], ['createdAt' => 'DESC'], 6 - count($suggestions))); // // sort by date // usort($suggestions, function ($a, $b) { // return $b->getCreatedAt() <=> $a->getCreatedAt(); // }); try { $meta = $nostrClient->getNpubMetadata($article->getPubkey()); if ($meta?->content) { $author = (array) json_decode($meta->content); } else { $author = [ 'name' => '' ]; } } catch (\Exception $e) { // Whatever? } return $this->render('Pages/article.html.twig', [ 'article' => $article, 'author' => $author ?? null, 'content' => $cacheItem->get(), //'suggestions' => $suggestions ]); } /** * Create new article * @throws InvalidArgumentException * @throws \Exception */ #[Route('/article-editor/create', name: 'editor-create')] #[Route('/article-editor/edit/{id}', name: 'editor-edit')] public function newArticle(Request $request, EntityManagerInterface $entityManager, CacheItemPoolInterface $articlesCache, WorkflowInterface $articlePublishingWorkflow, Article $article = null): Response { if (!$article) { $article = new Article(); $article->setKind(KindsEnum::LONGFORM); $article->setCreatedAt(new \DateTimeImmutable()); $formAction = $this->generateUrl('editor-create'); } else { $formAction = $this->generateUrl('editor-edit', ['id' => $article->getId()]); } $form = $this->createForm(EditorType::class, $article, ['action' => $formAction]); $form->handleRequest($request); // Step 3: Check if the form is submitted and valid if ($form->isSubmitted() && $form->isValid()) { $user = $this->getUser(); $key = new Key(); $currentPubkey = $key->convertToHex($user->getUserIdentifier()); if ($article->getPubkey() === null) { $article->setPubkey($currentPubkey); } // Check which button was clicked if ($form->getClickedButton() === $form->get('actions')->get('submit')) { // Save button was clicked, handle the "Publish" action $this->addFlash('success', 'Product published!'); } elseif ($form->getClickedButton() === $form->get('actions')->get('draft')) { // Save and Publish button was clicked, handle the "Draft" action $this->addFlash('success', 'Product saved as draft!'); } elseif ($form->getClickedButton() === $form->get('actions')->get('preview')) { // Preview button was clicked, handle the "Preview" action // construct slug from title and save to tags $slugger = new AsciiSlugger(); $slug = $slugger->slug($article->getTitle())->lower(); $article->setSig(''); // clear the sig $article->setSlug($slug); $cacheKey = 'article_' . $currentPubkey . '_' . $article->getSlug(); $cacheItem = $articlesCache->getItem($cacheKey); $cacheItem->set($article); $articlesCache->save($cacheItem); return $this->redirectToRoute('article-preview', ['d' => $article->getSlug()]); } } // load template with content editor return $this->render('pages/editor.html.twig', [ 'article' => $article, 'form' => $this->createForm(EditorType::class, $article)->createView(), ]); } /** * Preview article * @throws InvalidArgumentException * @throws CommonMarkException * @throws \Exception */ #[Route('/article-preview/{d}', name: 'article-preview')] public function preview($d, Converter $converter, CacheItemPoolInterface $articlesCache): Response { $user = $this->getUser(); $key = new Key(); $currentPubkey = $key->convertToHex($user->getUserIdentifier()); $cacheKey = 'article_' . $currentPubkey . '_' . $d; $cacheItem = $articlesCache->getItem($cacheKey); $article = $cacheItem->get(); $content = $converter->convertToHtml($article->getContent()); return $this->render('pages/article.html.twig', [ 'article' => $article, 'content' => $content, 'author' => $user->getMetadata(), ]); } }