mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-25 10:31:05 +00:00
Add Markdown export
This commit is contained in:
parent
2560826b41
commit
2a382b15c1
9 changed files with 170 additions and 22 deletions
|
@ -114,6 +114,10 @@ parameters:
|
||||||
name: export_xml
|
name: export_xml
|
||||||
value: 1
|
value: 1
|
||||||
section: export
|
section: export
|
||||||
|
-
|
||||||
|
name: export_md
|
||||||
|
value: 1
|
||||||
|
section: export
|
||||||
-
|
-
|
||||||
name: import_with_redis
|
name: import_with_redis
|
||||||
value: 0
|
value: 0
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"jms/serializer-bundle": "^5.4",
|
"jms/serializer-bundle": "^5.4",
|
||||||
"laminas/laminas-code": "^4.7.1",
|
"laminas/laminas-code": "^4.7.1",
|
||||||
"lcobucci/jwt": "^4.3",
|
"lcobucci/jwt": "^4.3",
|
||||||
|
"league/html-to-markdown": "^5.1",
|
||||||
"mgargano/simplehtmldom": "^1.5",
|
"mgargano/simplehtmldom": "^1.5",
|
||||||
"mnapoli/piwik-twig-extension": "^3.0",
|
"mnapoli/piwik-twig-extension": "^3.0",
|
||||||
"monolog/monolog": "^2.9",
|
"monolog/monolog": "^2.9",
|
||||||
|
|
93
composer.lock
generated
93
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "6407ed5fbd4b0973ed565c2a136d3a81",
|
"content-hash": "a45ce1bad60f024c66e17b6aca7ad88d",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "babdev/pagerfanta-bundle",
|
"name": "babdev/pagerfanta-bundle",
|
||||||
|
@ -5303,6 +5303,95 @@
|
||||||
],
|
],
|
||||||
"time": "2023-01-02T13:28:00+00:00"
|
"time": "2023-01-02T13:28:00+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/html-to-markdown",
|
||||||
|
"version": "5.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/html-to-markdown.git",
|
||||||
|
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||||
|
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-xml": "*",
|
||||||
|
"php": "^7.2.5 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mikehaertl/php-shellcommand": "^1.1.0",
|
||||||
|
"phpstan/phpstan": "^1.8.8",
|
||||||
|
"phpunit/phpunit": "^8.5 || ^9.2",
|
||||||
|
"scrutinizer/ocular": "^1.6",
|
||||||
|
"unleashedtech/php-coding-standard": "^2.7 || ^3.0",
|
||||||
|
"vimeo/psalm": "^4.22 || ^5.0"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/html-to-markdown"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.2-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\HTMLToMarkdown\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Colin O'Dell",
|
||||||
|
"email": "colinodell@gmail.com",
|
||||||
|
"homepage": "https://www.colinodell.com",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nick Cernis",
|
||||||
|
"email": "nick@cern.is",
|
||||||
|
"homepage": "http://modernnerd.net",
|
||||||
|
"role": "Original Author"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An HTML-to-markdown conversion helper for PHP",
|
||||||
|
"homepage": "https://github.com/thephpleague/html-to-markdown",
|
||||||
|
"keywords": [
|
||||||
|
"html",
|
||||||
|
"markdown"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/html-to-markdown/issues",
|
||||||
|
"source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://www.colinodell.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/colinodell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-07-12T21:21:09+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "masterminds/html5",
|
"name": "masterminds/html5",
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
|
@ -20034,7 +20123,7 @@
|
||||||
"ext-tokenizer": "*",
|
"ext-tokenizer": "*",
|
||||||
"ext-xml": "*"
|
"ext-xml": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "7.4.29"
|
"php": "7.4.29"
|
||||||
},
|
},
|
||||||
|
|
24
migrations/Version20241112193044.php
Normal file
24
migrations/Version20241112193044.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Application\Migrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Wallabag\Doctrine\WallabagMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Added the internal setting to export articles in markdown.
|
||||||
|
*/
|
||||||
|
final class Version20241112193044 extends WallabagMigration
|
||||||
|
{
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('INSERT INTO ' . $this->getTable('internal_setting') . " (name, value, section) VALUES ('export_md', '1', 'export');");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DELETE FROM' . $this->getTable('internal_setting') . " WHERE name = 'export_md';");
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ class ExportController extends AbstractController
|
||||||
* Gets one entry content.
|
* Gets one entry content.
|
||||||
*
|
*
|
||||||
* @Route("/export/{id}.{format}", name="export_entry", requirements={
|
* @Route("/export/{id}.{format}", name="export_entry", requirements={
|
||||||
* "format": "epub|pdf|json|xml|txt|csv",
|
* "format": "epub|pdf|json|xml|txt|csv|md",
|
||||||
* "id": "\d+"
|
* "id": "\d+"
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
|
@ -54,7 +54,7 @@ class ExportController extends AbstractController
|
||||||
* Export all entries for current user.
|
* Export all entries for current user.
|
||||||
*
|
*
|
||||||
* @Route("/export/{category}.{format}", name="export_entries", requirements={
|
* @Route("/export/{category}.{format}", name="export_entries", requirements={
|
||||||
* "format": "epub|pdf|json|xml|txt|csv",
|
* "format": "epub|pdf|json|xml|txt|csv|md",
|
||||||
* "category": "all|unread|starred|archive|tag_entries|untagged|search|annotated|same_domain"
|
* "category": "all|unread|starred|archive|tag_entries|untagged|search|annotated|same_domain"
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace Wallabag\Helper;
|
||||||
use Html2Text\Html2Text;
|
use Html2Text\Html2Text;
|
||||||
use JMS\Serializer\SerializationContext;
|
use JMS\Serializer\SerializationContext;
|
||||||
use JMS\Serializer\SerializerBuilder;
|
use JMS\Serializer\SerializerBuilder;
|
||||||
|
use League\HTMLToMarkdown\HtmlConverter;
|
||||||
use PHPePub\Core\EPub;
|
use PHPePub\Core\EPub;
|
||||||
use PHPePub\Core\Structure\OPF\DublinCore;
|
use PHPePub\Core\Structure\OPF\DublinCore;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
@ -129,10 +130,8 @@ class EntriesExport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use PHPePub to dump a .epub file.
|
* Use PHPePub to dump a .epub file.
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
private function produceEpub()
|
private function produceEpub(): Response
|
||||||
{
|
{
|
||||||
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
|
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
|
||||||
\assert($user instanceof User);
|
\assert($user instanceof User);
|
||||||
|
@ -249,10 +248,8 @@ class EntriesExport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use TCPDF to dump a .pdf file.
|
* Use TCPDF to dump a .pdf file.
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
private function producePdf()
|
private function producePdf(): Response
|
||||||
{
|
{
|
||||||
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
|
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
|
||||||
\assert($user instanceof User);
|
\assert($user instanceof User);
|
||||||
|
@ -326,10 +323,8 @@ class EntriesExport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inspired from CsvFileDumper.
|
* Inspired from CsvFileDumper.
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
private function produceCsv()
|
private function produceCsv(): Response
|
||||||
{
|
{
|
||||||
$delimiter = ';';
|
$delimiter = ';';
|
||||||
$enclosure = '"';
|
$enclosure = '"';
|
||||||
|
@ -372,10 +367,8 @@ class EntriesExport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump a JSON file.
|
* Dump a JSON file.
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
private function produceJson()
|
private function produceJson(): Response
|
||||||
{
|
{
|
||||||
return Response::create(
|
return Response::create(
|
||||||
$this->prepareSerializingContent('json'),
|
$this->prepareSerializingContent('json'),
|
||||||
|
@ -390,10 +383,8 @@ class EntriesExport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump a XML file.
|
* Dump a XML file.
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
private function produceXml()
|
private function produceXml(): Response
|
||||||
{
|
{
|
||||||
return Response::create(
|
return Response::create(
|
||||||
$this->prepareSerializingContent('xml'),
|
$this->prepareSerializingContent('xml'),
|
||||||
|
@ -408,10 +399,8 @@ class EntriesExport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump a TXT file.
|
* Dump a TXT file.
|
||||||
*
|
|
||||||
* @return Response
|
|
||||||
*/
|
*/
|
||||||
private function produceTxt()
|
private function produceTxt(): Response
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$bar = str_repeat('=', 100);
|
$bar = str_repeat('=', 100);
|
||||||
|
@ -432,6 +421,29 @@ class EntriesExport
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump a Markdown file.
|
||||||
|
*/
|
||||||
|
private function produceMd(): Response
|
||||||
|
{
|
||||||
|
$content = '';
|
||||||
|
$converter = new HtmlConverter();
|
||||||
|
$converter->getConfig()->setOption('strip_tags', true);
|
||||||
|
foreach ($this->entries as $entry) {
|
||||||
|
$content .= $converter->convert('<h1>' . $entry->getTitle() . '</h1>' . $entry->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::create(
|
||||||
|
$content,
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-type' => 'text/markdown',
|
||||||
|
'Content-Disposition' => 'attachment; filename="' . $this->getSanitizedFilename() . '.md"',
|
||||||
|
'Content-Transfer-Encoding' => 'UTF-8',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a Serializer object for producing processes that need it (JSON & XML).
|
* Return a Serializer object for producing processes that need it (JSON & XML).
|
||||||
*
|
*
|
||||||
|
|
|
@ -105,6 +105,7 @@
|
||||||
{% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'csv', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">CSV</a></li>{% endif %}
|
{% if craue_setting('export_csv') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'csv', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">CSV</a></li>{% endif %}
|
||||||
{% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'txt', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">TXT</a></li>{% endif %}
|
{% if craue_setting('export_txt') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'txt', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">TXT</a></li>{% endif %}
|
||||||
{% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'xml', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">XML</a></li>{% endif %}
|
{% if craue_setting('export_xml') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'xml', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">XML</a></li>{% endif %}
|
||||||
|
{% if craue_setting('export_md') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', {'category': current_route, 'format': 'md', 'tag': current_tag, 'search_entry[term]': export_search_term, 'currentRoute': previous_route, 'entry': entry}) }}">Markdown</a></li>{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,7 @@
|
||||||
{% if craue_setting('export_json') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'json'}) }}" title="Generate JSON file">JSON</a></li>{% endif %}
|
{% if craue_setting('export_json') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'json'}) }}" title="Generate JSON file">JSON</a></li>{% endif %}
|
||||||
{% if craue_setting('export_txt') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'txt'}) }}" title="Generate TXT file">TXT</a></li>{% endif %}
|
{% if craue_setting('export_txt') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'txt'}) }}" title="Generate TXT file">TXT</a></li>{% endif %}
|
||||||
{% if craue_setting('export_xml') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'xml'}) }}" title="Generate XML file">XML</a></li>{% endif %}
|
{% if craue_setting('export_xml') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'xml'}) }}" title="Generate XML file">XML</a></li>{% endif %}
|
||||||
|
{% if craue_setting('export_md') %}<li><a href="{{ path('export_entry', {'id': entry.id, 'format': 'md'}) }}" title="Generate MD file">Markdown</a></li>{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -307,6 +307,22 @@ class ExportControllerTest extends WallabagTestCase
|
||||||
$this->assertNotEmpty('updated_at', (string) $content->entry[0]->updated_at);
|
$this->assertNotEmpty('updated_at', (string) $content->entry[0]->updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMdExport()
|
||||||
|
{
|
||||||
|
$this->logInAs('admin');
|
||||||
|
$client = $this->getTestClient();
|
||||||
|
$client->request('GET', '/export/all.md');
|
||||||
|
|
||||||
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
||||||
|
|
||||||
|
$headers = $client->getResponse()->headers;
|
||||||
|
$content = $client->getResponse()->getContent();
|
||||||
|
$this->assertSame('text/markdown; charset=UTF-8', $headers->get('content-type'));
|
||||||
|
$this->assertSame('attachment; filename="All articles.md"', $headers->get('content-disposition'));
|
||||||
|
$this->assertSame('UTF-8', $headers->get('content-transfer-encoding'));
|
||||||
|
$this->assertStringContainsString('=================', $content);
|
||||||
|
}
|
||||||
|
|
||||||
public function testJsonExportFromSameDomain()
|
public function testJsonExportFromSameDomain()
|
||||||
{
|
{
|
||||||
$this->logInAs('admin');
|
$this->logInAs('admin');
|
||||||
|
|
Loading…
Reference in a new issue