mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-27 15:24:05 +00:00
add 'send to anki' function
users can configure the ankiconnect to use together with anki
This commit is contained in:
parent
fbfc2d7f43
commit
0a2661f986
85
ankiconnector.cpp
Normal file
85
ankiconnector.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include "ankiconnector.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include "utils.hh"
|
||||
AnkiConnector::AnkiConnector( QObject * parent, Config::Class const & _cfg ) : QObject{ parent }, cfg( _cfg )
|
||||
{
|
||||
mgr = new QNetworkAccessManager( this );
|
||||
connect( mgr, &QNetworkAccessManager::finished, this, &AnkiConnector::finishedSlot );
|
||||
}
|
||||
|
||||
void AnkiConnector::sendToAnki( QString const & word, QString const & text )
|
||||
{
|
||||
//for simplicity. maybe use QJsonDocument in future?
|
||||
QString postTemplate = QString( "{"
|
||||
"\"action\": \"addNote\","
|
||||
"\"version\": 6,"
|
||||
"\"params\": {"
|
||||
" \"note\": {"
|
||||
" \"deckName\": \"%1\","
|
||||
" \"modelName\": \"%2\","
|
||||
" \"fields\":%3,"
|
||||
" \"options\": {"
|
||||
" \"allowDuplicate\": true"
|
||||
" },"
|
||||
" \"tags\": []"
|
||||
"}"
|
||||
"}"
|
||||
"}"
|
||||
"" );
|
||||
|
||||
QJsonObject fields;
|
||||
fields.insert( "Front", word );
|
||||
fields.insert( "Back", text );
|
||||
|
||||
QString postData = postTemplate.arg( cfg.preferences.ankiConnectServer.deck,
|
||||
cfg.preferences.ankiConnectServer.model,
|
||||
Utils::json2String( fields ) );
|
||||
|
||||
// qDebug().noquote() << postData;
|
||||
QUrl url;
|
||||
url.setScheme( "http" );
|
||||
url.setHost( cfg.preferences.ankiConnectServer.host );
|
||||
url.setPort( cfg.preferences.ankiConnectServer.port );
|
||||
QNetworkRequest request( url );
|
||||
request.setTransferTimeout( 3000 );
|
||||
// request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy );
|
||||
request.setHeader( QNetworkRequest::ContentTypeHeader, "applicaion/json" );
|
||||
auto reply = mgr->post( request, postData.toUtf8() );
|
||||
connect( reply,
|
||||
&QNetworkReply::errorOccurred,
|
||||
this,
|
||||
[ this ]( QNetworkReply::NetworkError e )
|
||||
{
|
||||
qWarning() << e;
|
||||
emit this->errorText( tr( "anki: post to anki failed" ) );
|
||||
} );
|
||||
}
|
||||
|
||||
void AnkiConnector::finishedSlot( QNetworkReply * reply )
|
||||
{
|
||||
if( reply->error() == QNetworkReply::NoError )
|
||||
{
|
||||
QByteArray bytes = reply->readAll();
|
||||
QJsonDocument json = QJsonDocument::fromJson( bytes );
|
||||
auto obj = json.object();
|
||||
if( obj.size() != 2 || !obj.contains( "error" ) || !obj.contains( "result" ) ||
|
||||
obj[ "result" ].toString().isEmpty() )
|
||||
{
|
||||
emit errorText( QObject::tr( "anki: post to anki failed" ) );
|
||||
}
|
||||
QString result = obj[ "result" ].toString();
|
||||
|
||||
qDebug() << "anki result:" << result;
|
||||
|
||||
emit errorText( tr( "anki: post to anki success" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "anki connect error" << reply->errorString();
|
||||
emit errorText( "anki:" + reply->errorString() );
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
28
ankiconnector.h
Normal file
28
ankiconnector.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef ANKICONNECTOR_H
|
||||
#define ANKICONNECTOR_H
|
||||
|
||||
#include "config.hh"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
class AnkiConnector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AnkiConnector( QObject * parent, Config::Class const & cfg );
|
||||
|
||||
void sendToAnki( QString const & word, QString const & text );
|
||||
|
||||
private:
|
||||
QNetworkAccessManager * mgr;
|
||||
Config::Class const & cfg;
|
||||
public :
|
||||
signals:
|
||||
void errorText( QString const & );
|
||||
private slots:
|
||||
void finishedSlot(QNetworkReply * reply);
|
||||
};
|
||||
|
||||
#endif // ANKICONNECTOR_H
|
|
@ -348,6 +348,11 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, Au
|
|||
channel = new QWebChannel(ui.definition->page());
|
||||
agent = new ArticleViewAgent(this);
|
||||
attachWebChannelToHtml();
|
||||
ankiConnector = new AnkiConnector( this, cfg );
|
||||
connect( ankiConnector,
|
||||
&AnkiConnector::errorText,
|
||||
this,
|
||||
[ this ]( QString const & errorText ) { emit statusBarMessage( errorText ); } );
|
||||
}
|
||||
|
||||
// explicitly report the minimum size, to avoid
|
||||
|
@ -489,6 +494,10 @@ void ArticleView::showDefinition( QString const & word, QStringList const & dict
|
|||
ui.definition->setCursor( Qt::WaitCursor );
|
||||
}
|
||||
|
||||
void ArticleView::sendToAnki(QString const & word, QString const & text ){
|
||||
ankiConnector->sendToAnki(word,text);
|
||||
}
|
||||
|
||||
void ArticleView::showAnticipation()
|
||||
{
|
||||
ui.definition->setHtml( "" );
|
||||
|
@ -1721,6 +1730,7 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
|
|||
QAction * followLinkExternal = 0;
|
||||
QAction * followLinkNewTab = 0;
|
||||
QAction * lookupSelection = 0;
|
||||
QAction * sendToAnkiAction = 0 ;
|
||||
QAction * lookupSelectionGr = 0;
|
||||
QAction * lookupSelectionNewTab = 0;
|
||||
QAction * lookupSelectionNewTabGr = 0;
|
||||
|
@ -1850,6 +1860,14 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
|
|||
}
|
||||
}
|
||||
|
||||
// add anki menu
|
||||
if( !text.isEmpty() && cfg.preferences.ankiConnectServer.enabled )
|
||||
{
|
||||
QString txt = ui.definition->title();
|
||||
sendToAnkiAction = new QAction( tr( "&Send \"%1\" to anki with selected text." ).arg( txt ), &menu );
|
||||
menu.addAction( sendToAnkiAction );
|
||||
}
|
||||
|
||||
if( text.isEmpty() && !cfg.preferences.storeHistory)
|
||||
{
|
||||
QString txt = ui.definition->title();
|
||||
|
@ -1942,6 +1960,10 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
|
|||
else
|
||||
if ( result == lookupSelection )
|
||||
showDefinition( selectedText, getGroup( ui.definition->url() ), getCurrentArticle() );
|
||||
else if( result = sendToAnkiAction )
|
||||
{
|
||||
sendToAnki( ui.definition->title(), ui.definition->selectedText() );
|
||||
}
|
||||
else
|
||||
if ( result == lookupSelectionGr && groupComboBox )
|
||||
showDefinition( selectedText, groupComboBox->getCurrentGroup(), QString() );
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
|
||||
#include <QtCore5Compat/QRegExp>
|
||||
#endif
|
||||
#include "ankiconnector.h"
|
||||
|
||||
class ResourceToSaveHandler;
|
||||
class ArticleViewAgent ;
|
||||
|
@ -39,6 +40,8 @@ class ArticleView: public QFrame
|
|||
ArticleViewAgent * agent;
|
||||
Ui::ArticleView ui;
|
||||
|
||||
AnkiConnector * ankiConnector;
|
||||
|
||||
QAction pasteAction, articleUpAction, articleDownAction,
|
||||
goBackAction, goForwardAction, selectCurrentArticleAction,
|
||||
copyAsTextAction, inspectAction;
|
||||
|
@ -129,6 +132,7 @@ public:
|
|||
QRegExp const & searchRegExp, unsigned group,
|
||||
bool ignoreDiacritics );
|
||||
|
||||
void sendToAnki(QString const & word, QString const & text );
|
||||
/// Clears the view and sets the application-global waiting cursor,
|
||||
/// which will be restored when some article loads eventually.
|
||||
void showAnticipation();
|
||||
|
|
10
config.cc
10
config.cc
|
@ -948,6 +948,8 @@ Class load()
|
|||
c.preferences.ankiConnectServer.enabled = ( ankiConnectServer.toElement().attribute( "enabled" ) == "1" );
|
||||
c.preferences.ankiConnectServer.host = ankiConnectServer.namedItem( "host" ).toElement().text();
|
||||
c.preferences.ankiConnectServer.port = ankiConnectServer.namedItem( "port" ).toElement().text().toULong();
|
||||
c.preferences.ankiConnectServer.deck = ankiConnectServer.namedItem( "deck" ).toElement().text();
|
||||
c.preferences.ankiConnectServer.model = ankiConnectServer.namedItem( "model" ).toElement().text();
|
||||
}
|
||||
|
||||
if ( !preferences.namedItem( "checkForNewReleases" ).isNull() )
|
||||
|
@ -1901,6 +1903,14 @@ void save( Class const & c )
|
|||
opt = dd.createElement( "port" );
|
||||
opt.appendChild( dd.createTextNode( QString::number( c.preferences.ankiConnectServer.port ) ) );
|
||||
proxy.appendChild( opt );
|
||||
|
||||
opt = dd.createElement( "deck" );
|
||||
opt.appendChild( dd.createTextNode( c.preferences.ankiConnectServer.deck ) );
|
||||
proxy.appendChild( opt );
|
||||
|
||||
opt = dd.createElement( "model" );
|
||||
opt.appendChild( dd.createTextNode( c.preferences.ankiConnectServer.model ) );
|
||||
proxy.appendChild( opt );
|
||||
}
|
||||
|
||||
opt = dd.createElement( "checkForNewReleases" );
|
||||
|
|
|
@ -143,6 +143,8 @@ struct AnkiConnectServer
|
|||
|
||||
QString host;
|
||||
unsigned port;
|
||||
QString deck;
|
||||
QString model;
|
||||
|
||||
AnkiConnectServer();
|
||||
};
|
||||
|
|
|
@ -223,6 +223,7 @@ DEFINES += PROGRAM_VERSION=\\\"$$VERSION\\\"
|
|||
|
||||
# Input
|
||||
HEADERS += folding.hh \
|
||||
ankiconnector.h \
|
||||
article_inspect.h \
|
||||
articlewebpage.h \
|
||||
globalbroadcaster.h \
|
||||
|
@ -364,6 +365,7 @@ FORMS += groups.ui \
|
|||
fulltextsearch.ui
|
||||
|
||||
SOURCES += folding.cc \
|
||||
ankiconnector.cpp \
|
||||
article_inspect.cpp \
|
||||
articlewebpage.cpp \
|
||||
globalbroadcaster.cpp \
|
||||
|
|
|
@ -39,6 +39,20 @@
|
|||
<translation>(c) 2008-2013 Konstantin Isakov (ikm@goldendict.org)</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AnkiConnector</name>
|
||||
<message>
|
||||
<location filename="../ankiconnector.cpp" line="56"/>
|
||||
<source>anki: post to anki failed</source>
|
||||
<translatorcomment>anki:发布成功</translatorcomment>
|
||||
<translation>anki:发布失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ankiconnector.cpp" line="76"/>
|
||||
<source>anki: post to anki success</source>
|
||||
<translation>anki: 发布成功</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ArticleInspector</name>
|
||||
<message>
|
||||
|
@ -315,7 +329,12 @@
|
|||
<translation>引用的音频播放程序不存在。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../articleview.cc" line="1971"/>
|
||||
<location filename="../articleview.cc" line="1867"/>
|
||||
<source>&Send "%1" to anki with selected text.</source>
|
||||
<translation>将“%1”发送到anki并附带选择的文本。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../articleview.cc" line="2016"/>
|
||||
<source>Sound files (*.wav *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;All files (*.*)</source>
|
||||
<translation>声音文件(*.wav *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;所有文件(*.*)</translation>
|
||||
</message>
|
||||
|
@ -3903,7 +3922,27 @@ however, the article from the topmost dictionary is shown.</source>
|
|||
<translation>自定义设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../preferences.ui" line="1147"/>
|
||||
<location filename="../preferences.ui" line="1087"/>
|
||||
<source>Anki Connect</source>
|
||||
<translation>Anki连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../preferences.ui" line="1108"/>
|
||||
<source>http://</source>
|
||||
<translation>http://</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../preferences.ui" line="1152"/>
|
||||
<source>Deck:</source>
|
||||
<translation>牌组:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../preferences.ui" line="1162"/>
|
||||
<source>Model:</source>
|
||||
<translation>模板:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../preferences.ui" line="1214"/>
|
||||
<source>Some sites detect GoldenDict via HTTP headers and block the requests.
|
||||
Enable this option to workaround the problem.</source>
|
||||
<translation>部分网站屏蔽了使用 GoldenDict 浏览器标识(UA)的请求,启用此选项以绕过该问题。</translation>
|
||||
|
@ -4420,6 +4459,12 @@ from Stardict, Babylon and GLS dictionaries</source>
|
|||
<source>Date: %1%2</source>
|
||||
<translation>日期:%1%2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ankiconnector.cpp" line="55"/>
|
||||
<location filename="../ankiconnector.cpp" line="69"/>
|
||||
<source>anki: post to anki failed</source>
|
||||
<translation>anki:发布失败</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickFilterLine</name>
|
||||
|
|
|
@ -327,6 +327,8 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
|
|||
ui.useAnkiConnect->setChecked( p.ankiConnectServer.enabled );
|
||||
ui.ankiHost->setText( p.ankiConnectServer.host );
|
||||
ui.ankiPort->setValue( p.ankiConnectServer.port );
|
||||
ui.ankiModel->setText( p.ankiConnectServer.model );
|
||||
ui.ankiDeck->setText(p.ankiConnectServer.deck);
|
||||
|
||||
connect( ui.customProxy, SIGNAL( toggled( bool ) ),
|
||||
this, SLOT( customProxyToggled( bool ) ) );
|
||||
|
@ -475,6 +477,8 @@ Config::Preferences Preferences::getPreferences()
|
|||
p.ankiConnectServer.enabled = ui.useAnkiConnect->isChecked();
|
||||
p.ankiConnectServer.host = ui.ankiHost->text();
|
||||
p.ankiConnectServer.port = (unsigned)ui.ankiPort->value();
|
||||
p.ankiConnectServer.deck = ui.ankiDeck->text();
|
||||
p.ankiConnectServer.model = ui.ankiModel->text();
|
||||
|
||||
p.checkForNewReleases = ui.checkForNewReleases->isChecked();
|
||||
p.disallowContentFromOtherSites = ui.disallowContentFromOtherSites->isChecked();
|
||||
|
|
131
preferences.ui
131
preferences.ui
|
@ -24,7 +24,7 @@
|
|||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>4</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
|
@ -1092,53 +1092,94 @@ for all program's network requests.</string>
|
|||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Host:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Host:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>http://</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="ankiHost"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="ankiPort">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8080</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_16">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>http://</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="ankiHost"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="ankiPort">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8080</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_16">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_22">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_28">
|
||||
<property name="text">
|
||||
<string>Deck:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="ankiDeck"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Model:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="ankiModel"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_17">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
7
utils.hh
7
utils.hh
|
@ -9,6 +9,8 @@
|
|||
#include <QKeyEvent>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
|
@ -77,6 +79,11 @@ inline bool ignoreKeyEvent(QKeyEvent *keyEvent) {
|
|||
return false;
|
||||
}
|
||||
|
||||
inline QString json2String( const QJsonObject & json )
|
||||
{
|
||||
return QString( QJsonDocument( json ).toJson( QJsonDocument::Compact ) );
|
||||
}
|
||||
|
||||
namespace AtomicInt
|
||||
{
|
||||
|
||||
|
|
Loading…
Reference in a new issue