1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Add delete folder

This commit is contained in:
Laurent Cozic 2016-12-31 10:48:18 +01:00
parent e751dfc793
commit 4570fcac18
22 changed files with 431 additions and 97 deletions

View File

@ -5,39 +5,78 @@ Item {
id: root
width: parent.width
height: 25
property int mouseAreaDefaultWidth
property Menu contextMenu
signal stoppedEditing;
signal editingAccepted(int index, string text);
function makeEditable(editable) {
if (typeof editable === 'undefined') editable = true;
if (editable === isEditable()) return; // Nothing to do
if (editable) {
label.visible = false
mouseArea.anchors.rightMargin = 10000; // Hack because `mouseArea.visible = false` makes the MouseArea ignore the next click event
textField.visible = true
textField.focus = true
textField.text = display
root.ListView.view.focus = true;
textField.selectAll()
} else {
mouseArea.anchors.rightMargin = 0;
label.visible = true
textField.visible = false
root.stoppedEditing();
}
}
function startEditing() {
makeEditable(true);
}
function stopEditing() {
makeEditable(false);
}
function isEditable() {
return textField.visible;
}
Text {
id: label
text: display
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
MouseArea {
anchors.fill: parent
onClicked: {
root.ListView.view.currentIndex = index
}
onDoubleClicked: {
label.visible = false
textField.visible = true
textField.focus = true
textField.selectAll()
textField.text = display
}
}
}
TextField {
id: textField
text: display
visible: false
width: parent.width
height: parent.height
onAccepted: {
root.ListView.view.model.setData(index, text)
root.editingAccepted(index, text);
}
onEditingFinished: {
label.visible = true
textField.visible = false
stopEditing();
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
root.ListView.view.currentIndex = index
if (mouse.button === Qt.RightButton) {
contextMenu.open();
}
}
onDoubleClicked: {
startEditing();
}
}
}

View File

@ -6,6 +6,20 @@ Item {
property alias model: listView.model
property alias currentIndex: listView.currentIndex
property alias currentItem: listView.currentItem
property string currentItemId
signal stoppedEditing;
signal editingAccepted(int index, string text);
signal deleteButtonClicked(int index);
function startEditing(index) {
currentIndex = model.rowCount() - 1;
currentItem.startEditing();
}
function stopEditing() {
currentItem.stopEditing();
}
Rectangle {
color: "#eeeeff"
@ -14,22 +28,48 @@ Item {
}
ListView {
Connections {
target: model
onDataChanged: {
if (currentItemId !== "") {
var newIndex = model.idToIndex(currentItemId);
currentIndex = newIndex
if (newIndex < 0) currentItemId = "";
}
}
}
onCurrentItemChanged: {
currentItemId = model.idAtIndex(currentIndex);
}
id: listView
highlightMoveVelocity: -1
highlightMoveDuration: 100
anchors.fill: parent
delegate: folderDelegate
ScrollBar.vertical: ScrollBar { }
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
focus: true
// onModelChanged: {
//// listView.model.onDataChanged = function() {
//// console.info("testaaaaaaaaaaaaaaaaaaa")
//// }
// console.info("MODEL CHANGAID")
// }
}
Component {
id: folderDelegate
EditableListItem {}
EditableListItem {
contextMenu:
Menu {
MenuItem {
text: "Delete"
onTriggered: deleteButtonClicked(currentIndex);
}
}
onStoppedEditing: {
root.stoppedEditing();
}
onEditingAccepted: function(index, text) {
root.editingAccepted(index, text);
}
}
}
}

View File

@ -16,7 +16,9 @@ SOURCES += \
webapi.cpp \
synchronizer.cpp \
settings.cpp \
models/foldercollection.cpp
models/foldercollection.cpp \
uuid.cpp \
dispatcher.cpp
RESOURCES += qml.qrc \
database.qrc
@ -45,7 +47,9 @@ HEADERS += \
synchronizer.h \
settings.h \
models/foldercollection.h \
simpletypes.h
simpletypes.h \
uuid.h \
dispatcher.h
DISTFILES +=

View File

@ -5,6 +5,8 @@
#include "models/foldermodel.h"
#include "services/folderservice.h"
#include "settings.h"
#include "uuid.h"
#include "dispatcher.h"
using namespace jop;
@ -38,21 +40,27 @@ Application::Application(int &argc, char **argv) :
connect(rootObject, SIGNAL(currentFolderChanged()), this, SLOT(view_currentFolderChanged()));
connect(rootObject, SIGNAL(currentNoteChanged()), this, SLOT(view_currentNoteChanged()));
connect(rootObject, SIGNAL(addFolderButtonClicked()), this, SLOT(view_addFolderButtonClicked()));
view_.show();
connect(&api_, SIGNAL(requestDone(const QJsonObject&, const QString&)), this, SLOT(api_requestDone(const QJsonObject&, const QString&)));
QString sessionId = settings.value("sessionId").toString();
if (sessionId == "") {
//if (sessionId == "") {
QUrlQuery postData;
postData.addQueryItem("email", "laurent@cozic.net");
postData.addQueryItem("password", "12345678");
postData.addQueryItem("client_id", "B6E12222B6E12222");
api_.post("sessions", QUrlQuery(), postData, "getSession");
} else {
afterSessionInitialization();
}
// } else {
// afterSessionInitialization();
// }
//emit jop::dispatcher().folderCreated("test");
//.folderCreated("tes");
}
void Application::api_requestDone(const QJsonObject& response, const QString& tag) {
@ -91,7 +99,7 @@ void Application::afterSessionInitialization() {
QString sessionId = settings.value("sessionId").toString();
qDebug() << "Session:" << sessionId;
api_.setSessionId(sessionId);
//synchronizer_.start();
synchronizer_.start();
}
void Application::view_currentFolderChanged() {
@ -107,9 +115,18 @@ void Application::view_currentNoteChanged() {
}
void Application::view_addNoteButtonClicked() {
qDebug() << "ici";
}
void Application::view_addFolderButtonClicked() {
// QStringList fields;
// fields << "id";
// VariantVector values;
// values << uuid::createUuid();
// QSqlQuery q = db_.buildSqlQuery(Database::Insert, "folders", fields, values);
// q.exec();
// emit jop::dispatcher().folderCreated("test");
//qDebug() << "Added" << q.lastInsertId().toString();
}

View File

@ -84,13 +84,13 @@ QSqlQuery Database::buildSqlQuery(Database::QueryType type, const QString &table
}
}
qDebug() <<"SQL:"<<sql;
// qDebug() <<"SQL:"<<sql;
QMapIterator<QString, QVariant> i(query.boundValues());
while (i.hasNext()) {
i.next();
qDebug() << i.key() << ":" << i.value().toString();
}
// QMapIterator<QString, QVariant> i(query.boundValues());
// while (i.hasNext()) {
// i.next();
// qDebug() << i.key() << ":" << i.value().toString();
// }
return query;
}

View File

@ -0,0 +1,17 @@
#include "dispatcher.h"
using namespace jop;
Dispatcher::Dispatcher() {
}
Dispatcher instance_;
Dispatcher& jop::dispatcher() {
return instance_;
}
//Dispatcher &Dispatcher::instance() {
// return instance_;
//}

View File

@ -0,0 +1,29 @@
#ifndef DISPATCHER_H
#define DISPATCHER_H
namespace jop {
class Dispatcher : public QObject {
Q_OBJECT
public:
Dispatcher();
//static Dispatcher& instance();
signals:
void folderCreated(const QString& id);
private:
//static Dispatcher& instance_;
};
Dispatcher& dispatcher();
}
#endif // DISPATCHER_H

View File

@ -32,6 +32,24 @@ Item {
onCurrentItemChanged: {
root.currentFolderChanged()
}
onEditingAccepted: function(index, text) {
if (folderList.model.virtualItemShown()) {
folderList.model.addData(text)
} else {
folderList.model.setData(index, text)
}
}
onStoppedEditing: {
if (folderList.model.virtualItemShown()) {
folderList.model.hideVirtualItem();
}
}
onDeleteButtonClicked: {
folderList.model.deleteData(index)
}
}
NoteList {
@ -64,7 +82,10 @@ Item {
id: addButton
anchors.right: parent.right
anchors.bottom: parent.bottom
onAddFolderButtonClicked: root.addFolderButtonClicked()
onAddFolderButtonClicked: {
folderList.model.showVirtualItem();
folderList.startEditing(folderList.model.rowCount() - 1);
}
onAddNoteButtonClicked: root.addNoteButtonClicked()
}

View File

@ -1,5 +1,7 @@
#include "foldercollection.h"
#include "databaseutils.h"
#include "dispatcher.h"
#include "uuid.h"
using namespace jop;
@ -8,25 +10,36 @@ FolderCollection::FolderCollection(Database& db, const QString& parentId, const
db_ = db;
parentId_ = parentId;
orderBy_ = orderBy;
connect(&jop::dispatcher(), SIGNAL(folderCreated(const QString&)), this, SLOT(dispatcher_folderCreated(QString)));
}
Folder FolderCollection::at(int index) const {
if (cache_.size()) return cache_[index];
if (cache_.size()) {
if (index < 0 || index >= count()) {
qWarning() << "Invalid folder index:" << index;
return Folder();
}
QSqlQuery q = db_.query("SELECT id, title FROM folders ORDER BY " + orderBy_);
return cache_[index];
}
QSqlQuery q = db_.query("SELECT " + Folder::dbFields().join(",") + " FROM folders ORDER BY " + orderBy_);
q.exec();
while (q.next()) {
Folder folder;
folder.setId(q.value(0).toString());
folder.setTitle(q.value(1).toString());
folder.fromSqlQuery(q);
cache_.push_back(folder);
}
if (!cache_.size()) {
qWarning() << "Invalid folder index:" << index;
return Folder();
} else {
return at(index);
}
}
// TODO: cache result
int FolderCollection::count() const {
@ -37,19 +50,57 @@ int FolderCollection::count() const {
}
Folder FolderCollection::byId(const QString& id) const {
int index = idToIndex(id);
return at(index);
}
int FolderCollection::idToIndex(const QString &id) const {
int count = this->count();
for (int i = 0; i < count; i++) {
Folder folder = at(i);
if (folder.id() == id) return folder;
if (folder.id() == id) return i;
}
return -1;
}
qWarning() << "Invalid folder ID:" << id;
return Folder();
QString FolderCollection::indexToId(int index) const {
Folder folder = at(index);
return folder.id();
}
void FolderCollection::update(const QString &id, const QStringList &fields, const VariantVector &values) {
void FolderCollection::update(const QString &id, QStringList fields, VariantVector values) {
if (!fields.contains("synced")) {
fields.push_back("synced");
values.push_back(QVariant(0));
}
QSqlQuery q = db_.buildSqlQuery(Database::Update, "folders", fields, values, "id = \"" + id + "\"");
q.exec();
cache_.clear();
emit changed(0, count() - 1, fields);
}
void FolderCollection::add(QStringList fields, VariantVector values) {
fields.push_back("synced");
values.push_back(QVariant(0));
fields.push_back("id");
values.push_back(uuid::createUuid());
QSqlQuery q = db_.buildSqlQuery(Database::Insert, "folders", fields, values);
q.exec();
cache_.clear();
emit changed(0, count() - 1, fields);
}
void FolderCollection::remove(const QString& id) {
QSqlQuery q(db_.database());
q.prepare("DELETE FROM folders WHERE id = :id");
q.bindValue(":id", id);
q.exec();
cache_.clear();
emit changed(0, count(), QStringList());
}
void FolderCollection::dispatcher_folderCreated(const QString &id) {
}

View File

@ -22,7 +22,11 @@ public:
Folder at(int index) const;
int count() const;
Folder byId(const QString &id) const;
void update(const QString& id, const QStringList& fields, const VariantVector& values);
int idToIndex(const QString& id) const;
QString indexToId(int index) const;
void update(const QString& id, QStringList fields, VariantVector values);
void add(QStringList fields, VariantVector values);
void remove(const QString &id);
private:
@ -35,6 +39,10 @@ signals:
void changed(int from, int to, const QStringList& fields);
public slots:
void dispatcher_folderCreated(const QString& id);
};
}

View File

@ -2,20 +2,27 @@
using namespace jop;
FolderModel::FolderModel(Database &database) : QAbstractListModel(), folderCollection_(database, 0, "title") {
FolderModel::FolderModel(Database &database) : QAbstractListModel(), folderCollection_(database, 0, "title"), db_(database), orderBy_("title") {
virtualItemShown_ = false;
connect(&folderCollection_, SIGNAL(changed(int,int,const QStringList&)), this, SLOT(folderCollection_changed(int,int,const QStringList&)));
}
int FolderModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return folderCollection_.count();
return folderCollection_.count() + (virtualItemShown_ ? 1 : 0);
}
// NOTE: to lazy load - send back "Loading..." if item not currently loaded
// queue the item for loading.
// Then batch load them a bit later.
QVariant FolderModel::data(const QModelIndex & index, int role) const {
Folder folder = folderCollection_.at(index.row());
Folder folder;
if (virtualItemShown_ && index.row() == rowCount() - 1) {
folder.setTitle("Untitled");
} else {
folder = folderCollection_.at(index.row());
}
if (role == Qt::DisplayRole) {
return QVariant(folder.title());
@ -32,6 +39,8 @@ bool FolderModel::setData(const QModelIndex &index, const QVariant &value, int r
Folder folder = folderCollection_.at(index.row());
if (role == Qt::EditRole) {
emit dataChanging();
QStringList fields;
fields << "title";
VariantVector values;
@ -44,6 +53,30 @@ bool FolderModel::setData(const QModelIndex &index, const QVariant &value, int r
return false;
}
void FolderModel::showVirtualItem() {
virtualItemShown_ = true;
beginInsertRows(QModelIndex(), this->rowCount() - 1, this->rowCount() - 1);
endInsertRows();
}
void FolderModel::hideVirtualItem() {
beginRemoveRows(QModelIndex(), this->rowCount() - 1, this->rowCount() - 1);
virtualItemShown_ = false;
endRemoveRows();
}
QString FolderModel::idAtIndex(int index) const {
return data(this->index(index), IdRole).toString();
}
int FolderModel::idToIndex(const QString &id) const {
return folderCollection_.idToIndex(id);
}
bool FolderModel::virtualItemShown() const {
return virtualItemShown_;
}
bool FolderModel::setData(int index, const QVariant &value, int role) {
return setData(this->index(index), value, role);
}
@ -56,8 +89,26 @@ QHash<int, QByteArray> FolderModel::roleNames() const {
return roles;
}
void FolderModel::addData(const QString &title) {
emit dataChanging();
QStringList fields;
fields << "title";
VariantVector values;
values << QVariant(title);
folderCollection_.add(fields, values);
}
void FolderModel::deleteData(const int index) {
QString id = folderCollection_.indexToId(index);
folderCollection_.remove(id);
}
void FolderModel::folderCollection_changed(int from, int to, const QStringList& fields) {
beginRemoveRows(QModelIndex(), from, to);
QVector<int> roles;
roles << Qt::DisplayRole;
qDebug() << "update" << from << to;
emit dataChanged(this->index(from), this->index(to), roles);
endRemoveRows();
}

View File

@ -23,11 +23,8 @@ public:
FolderModel(Database& database);
void addFolder(Folder* folder);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
protected:
@ -38,11 +35,25 @@ private:
QList<Folder> folders_;
FolderCollection folderCollection_;
bool virtualItemShown_;
QString orderBy_;
Database& db_;
public slots:
void addData(const QString& title);
void deleteData(const int index);
bool setData(int index, const QVariant &value, int role = Qt::EditRole);
void folderCollection_changed(int from, int to, const QStringList &fields);
void showVirtualItem();
bool virtualItemShown() const;
void hideVirtualItem();
QString idAtIndex(int index) const;
int idToIndex(const QString& id) const;
signals:
void dataChanging();
};

View File

@ -4,7 +4,6 @@
using namespace jop;
Item::Item() {
isPartial_ = true;
synced_ = false;
}
@ -24,6 +23,10 @@ int Item::updatedTime() const {
return updatedTime_;
}
bool Item::synced() const {
return synced_;
}
void Item::setId(const QString& v) {
id_ = v;
}
@ -36,12 +39,8 @@ void Item::setCreatedTime(int v) {
createdTime_ = v;
}
void Item::setIsPartial(bool v) {
isPartial_ = v;
}
bool Item::isPartial() const {
return isPartial_;
void Item::setSynced(bool v) {
synced_ = v;
}
QStringList Item::dbFields() {

View File

@ -15,13 +15,13 @@ public:
QString title() const;
int createdTime() const;
int updatedTime() const;
bool isPartial() const;
bool synced() const;
static QStringList dbFields();
void setId(const QString &v);
void setTitle(const QString& v);
void setCreatedTime(int v);
void setIsPartial(bool v);
void setSynced(bool v);
void fromSqlQuery(const QSqlQuery& query);
@ -33,8 +33,6 @@ private:
time_t updatedTime_;
bool synced_;
bool isPartial_;
};
}

View File

@ -24,8 +24,6 @@ Note NoteCollection::at(int index) const {
int from = indexes[0];
int to = indexes[indexes.size() - 1];
qDebug() << "Getting from" << from << "to" << to;
QSqlQuery q = db_.query("SELECT id, title, body FROM notes WHERE parent_id = :parent_id ORDER BY " + orderBy_ + " LIMIT " + QString::number(to - from + 1) + " OFFSET " + QString::number(from));
q.bindValue(":parent_id", parentId_);
q.exec();
@ -36,7 +34,6 @@ Note NoteCollection::at(int index) const {
note.setId(q.value(0).toString());
note.setTitle(q.value(1).toString());
note.setBody(q.value(2).toString());
note.setIsPartial(true);
cache_.set(noteIndex, note);

View File

@ -64,6 +64,13 @@ CREATE TABLE version (
version INT
);
CREATE TABLE changes (
id INTEGER PRIMARY KEY,
item_id TEXT,
item_type INT,
item_property TEXT
);
--CREATE TABLE mimetypes (
-- id INT,
-- mime TEXT

View File

@ -44,6 +44,8 @@ void Synchronizer::start() {
api_.put("folders/" + folder.id(), QUrlQuery(), data, "putFolder:" + folder.id());
}
return;
for (int i = 0; i < notes.size(); i++) {
Note note = notes[i];
QUrlQuery data;
@ -68,10 +70,10 @@ void Synchronizer::api_requestDone(const QJsonObject& response, const QString& t
}
if (action == "putFolder") {
// qDebug() << "Done folder" << id;
// query = db_.query("UPDATE folders SET synced = 1 WHERE id = ?");
// query.addBindValue(id);
// query.exec();
qDebug() << "Synced folder" << id;
query = db_.query("UPDATE folders SET synced = 1 WHERE id = ?");
query.addBindValue(id);
query.exec();
}
if (action == "putNote") {

View File

@ -4,19 +4,11 @@
namespace jop {
namespace uuid {
//QUuid fromString(const QString& s) {
// // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
// QString mod = s;
// mod.insert(8, '-');
// mod.insert(13, '-');
// mod.insert(18, '-');
// mod.insert(23, '-');
// mod = "{" + mod + "}";
// //qDebug() << mod;
// return QUuid(mod);
//}
QString createUuid(QString s) {
if (s == "") s = QString("%1%2").arg(qrand()).arg(QDateTime::currentMSecsSinceEpoch());
QString hash = QString(QCryptographicHash::hash(s.toUtf8(), QCryptographicHash::Sha256).toHex());
return hash.left(32);
}
}
}

View File

@ -6,7 +6,7 @@
namespace jop {
namespace uuid {
//QUuid fromString(const QString& s);
QString createUuid(QString s = "");
}
}

View File

@ -40,6 +40,13 @@ void WebApi::processQueue() {
QueuedRequest& r = queuedRequests_.takeFirst();
QString url = baseUrl_ + "/" + r.path;
QUrlQuery query = r.query;
if (sessionId_ != "") {
query.addQueryItem("session", sessionId_);
}
url += "?" + query.toString(QUrl::FullyEncoded);
QNetworkRequest* request = new QNetworkRequest(url);
request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
@ -72,11 +79,12 @@ void WebApi::processQueue() {
cmd << "curl";
if (r.method == QNetworkAccessManager::PutOperation) {
cmd << "-X" << "PUT";
cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'";
cmd << url;
}
//qDebug().noquote() << cmd.join(" ");
cmd << "--data" << "'" + r.data.toString(QUrl::FullyEncoded) + "'";
cmd << url;
qDebug().noquote() << cmd.join(" ");
inProgressRequests_.push_back(r);
}

View File

@ -20,7 +20,7 @@ abstract class ApiController extends Controller {
protected $session = null;
protected $user = null;
private $useTestUserAndSession = true;
private $useTestUserAndSession = false;
private $testClientNum = 1;
public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null) {
@ -47,9 +47,14 @@ abstract class ApiController extends Controller {
$s = $this->session();
// TODO: find less hacky way to get request path
$requestPath = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
$requestPath = ltrim($requestPath, '/');
$requestPath = rtrim($requestPath, '?');
// TODO: to keep it simple, only respond to logged in users, but in theory some data
// could be public.
if (!$s || !$this->user()) throw new UnauthorizedException('A session and user are required');
if ($requestPath != 'sessions' && (!$s || !$this->user())) throw new UnauthorizedException('A session and user are required');
BaseModel::setClientId($s ? $s->client_id : 0);
}

38
web/client.php Executable file
View File

@ -0,0 +1,38 @@
<?php
function config($name) {
$config = array(
'baseUrl' => 'http://joplin.local',
);
if (isset($config[$name])) {
return $config[$name];
}
throw new Exception('Unknown config: ' . $name);
}
function execRequest($method, $path, $query = null, $data = null) {
$url = config('baseUrl') . '/' . $path;
if ($query) $url .= '?' . http_build_query($query);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
curl_close($ch);
$output = json_decode($response, true);
if ($output === null) {
return array('error' => 'Cannot decode JSON', 'body' => $response);
}
return $output;
}
$session = execRequest('POST', 'session', null, array(
'email' => 'laurent@cozic.net',
'password' => '12345678',
));
var_dump($session);
die();