Added some customisation settings. Added autosync. Better error outputs.

This commit is contained in:
Scharel Clemens 2018-11-24 19:34:01 +01:00
parent 8295729611
commit 958c7704a7
6 changed files with 284 additions and 47 deletions

View file

@ -10,9 +10,13 @@ ApplicationWindow
ConfigurationGroup { ConfigurationGroup {
id: appSettings id: appSettings
path: "/apps/harbour-nextcloudnotes/settings" path: "/apps/harbour-nextcloudnotes/settings"
synchronous: true //synchronous: true
property int currentAccount: value("currentAccount", -1) property int currentAccount: value("currentAccount", -1)
property int autoSyncInterval: value("autoSyncInterval", 0)
property int previewLineCount: value("previewLineCount", 4)
property string groupBy: value("groupBy", "date")
property bool showSeparator: value("showSeparator", false)
} }
ConfigurationValue { ConfigurationValue {

View file

@ -4,22 +4,36 @@ import Sailfish.Silica 1.0
Dialog { Dialog {
id: noteDialog id: noteDialog
function reloadContent() {
dialogHeader.title = account.model.get(noteIndex).title
contentLabel.plainText = account.model.get(noteIndex).content
contentLabel.parse()
}
acceptDestination: Qt.resolvedUrl("EditPage.qml") acceptDestination: Qt.resolvedUrl("EditPage.qml")
acceptDestinationProperties: { account: account; noteIndex: noteIndex } acceptDestinationProperties: { account: account; noteIndex: noteIndex }
Component.onCompleted: acceptDestinationProperties = { account: account, noteIndex: noteIndex } Component.onCompleted: acceptDestinationProperties = { account: account, noteIndex: noteIndex }
onStatusChanged: { onStatusChanged: {
if (status === PageStatus.Active) { if (status === PageStatus.Active) {
dialogHeader.title = account.model.get(noteIndex).title account.getNote(account.model.get(noteIndex).id)
contentLabel.plainText = account.model.get(noteIndex).content reloadContent()
contentLabel.parse()
} }
} }
Connections { Connections {
target: account.model.get(noteIndex) target: account/*.model.get(noteIndex)
onTitleChanged: dialogHeader.title = account.model.get(noteIndex).title onTitleChanged: {
console.log("Title changed")
dialogHeader.title = account.model.get(noteIndex).title
}
onContentChanged: { onContentChanged: {
console.log("Content changed")
contentLabel.plainText = account.model.get(noteIndex).content contentLabel.plainText = account.model.get(noteIndex).content
contentLabel.parse() contentLabel.parse()
}*/
onBusyChanged: {
if (account.busy === false) {
reloadContent()
}
} }
} }
@ -46,6 +60,34 @@ Dialog {
Column { Column {
id: column id: column
width: parent.width width: parent.width
RemorsePopup {
id: remorse
onTriggered: pageStack.pop()
}
PullDownMenu {
busy: account ? account.busy : false
MenuItem {
text: qsTr("Delete")
enabled: account ? true : false
//visible: appSettings.currentAccount >= 0
onClicked: remorse.execute("Deleting", function() { account.deleteNote(account.model.get(noteIndex).id) } )
}
MenuItem {
text: enabled ? qsTr("Reload") : qsTr("Updating...")
enabled: account ? !account.busy : false
//visible: appSettings.currentAccount >= 0
onClicked: account.getNote(account.model.get(noteIndex).id)
}
MenuLabel {
visible: appSettings.currentAccount >= 0
text: account ? (
qsTr("Last update") + ": " + (
new Date(account.update).valueOf() !== 0 ?
new Date(account.update).toLocaleString(Qt.locale(), Locale.ShortFormat) :
qsTr("never"))) : ""
}
}
DialogHeader { DialogHeader {
id: dialogHeader id: dialogHeader
@ -67,7 +109,7 @@ Dialog {
tmpText = tmpText.replace(markdown[i].regex, markdown[i].replace) tmpText = tmpText.replace(markdown[i].regex, markdown[i].replace)
} }
text = tmpText text = tmpText
console.log(text) //console.log(text)
} }
} }
} }

View file

@ -6,6 +6,8 @@ Item {
property string uuid property string uuid
property string name property string name
property url server property url server
property url url
property string version: "v0.2"
property string username property string username
property string password property string password
property date update property date update
@ -13,9 +15,13 @@ Item {
property bool unencryptedConnection property bool unencryptedConnection
property var model: ListModel { } property var model: ListModel { }
property var json: [ ]
//property string file: StandardPaths.data + "/" + uuid + ".json" //property string file: StandardPaths.data + "/" + uuid + ".json"
//property bool saveFile: false //property bool saveFile: false
property bool busy: false property bool busy: false
property int status: 204
property string statusText: "No Content"
//TODO: put content into an array
ConfigurationGroup { ConfigurationGroup {
id: account id: account
@ -25,6 +31,7 @@ Item {
Component.onCompleted: { Component.onCompleted: {
name = account.value("name", "", String) name = account.value("name", "", String)
server = account.value("server", "", String) server = account.value("server", "", String)
url = server + "/index.php/apps/notes/api/" + version + "/notes"
username = account.value("username", "", String) username = account.value("username", "", String)
password = account.value("password", "", String) password = account.value("password", "", String)
update = account.value("update", "", Date) update = account.value("update", "", Date)
@ -49,7 +56,7 @@ Item {
function callApi(method, data) { function callApi(method, data) {
busy = true busy = true
var endpoint = server + "/index.php/apps/notes/api/v0.2/notes" var endpoint = url
if (data && (method === "GET" || method === "PUT" || method === "DELETE")) { if (data && (method === "GET" || method === "PUT" || method === "DELETE")) {
if (data.id) { if (data.id) {
endpoint = endpoint + "/" + data.id endpoint = endpoint + "/" + data.id
@ -65,37 +72,39 @@ Item {
apiReq.onreadystatechange = function() { apiReq.onreadystatechange = function() {
if (apiReq.readyState === XMLHttpRequest.DONE) { if (apiReq.readyState === XMLHttpRequest.DONE) {
if (apiReq.status === 200) { if (apiReq.status === 200) {
console.log("Successfull API request!") //console.log("Successfull API request!")
//console.log(apiReq.responseText) console.log("Network status: " + apiReq.statusText + " (" + apiReq.status + ")")
var json = JSON.parse(apiReq.responseText) json = JSON.parse(apiReq.responseText)
switch(method) { switch(method) {
case "GET": case "GET":
if (Array.isArray(json)) { if (Array.isArray(json)) {
console.log("Got all notes") console.log("Received all notes via API: " + endpoint)
model.clear() model.clear()
for (var element in json) { for (var element in json) {
model.append(json[element]) model.append(json[element])
model.setProperty(element, "date", getDisplayDate(json[element].modified))
} }
update = new Date() update = new Date()
} }
else { else {
console.log("Got a single note") console.log("Received a single note via API: " + endpoint)
for (var i = 0; i < model.count; i++) { for (var i = 0; i < model.count; i++) {
var listItem = model.get(i) var listItem = model.get(i)
if (listItem.id === json.id){ if (listItem.id === json.id){
model.set(i, json) model.set(i, json)
model.setProperty(i, "date", getDisplayDate(json.modified))
} }
} }
} }
break; break;
case "POST": case "POST":
console.log("Created a note") console.log("Created a note via API: " + endpoint)
model.append(json) model.append(json)
model.move(model.count-1, 0, 1) model.move(model.count-1, 0, 1)
break; break;
case "PUT": case "PUT":
console.log("Updated a note") console.log("Updated a note via API: " + endpoint)
for (var i = 0; i < model.count; i++) { for (var i = 0; i < model.count; i++) {
var listItem = model.get(i) var listItem = model.get(i)
if (listItem.id === json.id){ if (listItem.id === json.id){
@ -104,7 +113,7 @@ Item {
} }
break; break;
case "DELETE": case "DELETE":
console.log("Deleted a note") console.log("Deleted a note via API: " + endpoint)
for (var i = 0; i < model.count; i++) { for (var i = 0; i < model.count; i++) {
var listItem = model.get(i) var listItem = model.get(i)
if (listItem.id === data.id){ if (listItem.id === data.id){
@ -127,13 +136,13 @@ Item {
console.log("Note does not exist!") console.log("Note does not exist!")
}*/ }*/
else { else {
console.log("Networking error: " + apiReq.statusText + " (" + apiReq.status + ")") console.log("Network error: " + apiReq.statusText + " (" + apiReq.status + ")")
} }
status = apiReq.status
statusText = apiReq.statusText
//model.sync()
busy = false busy = false
} }
else {
//console.log("HTTP ready state: " + apiReq.readyState)
}
} }
if (method === "GET") { if (method === "GET") {
apiReq.send() apiReq.send()
@ -192,6 +201,27 @@ Item {
} }
} }
// source: https://stackoverflow.com/a/14339782
function getDisplayDate(date) {
var today = new Date()
today.setHours(0)
today.setMinutes(0)
today.setSeconds(0)
today.setMilliseconds(0)
var compDate = new Date(date*1000)
compDate.setHours(0)
compDate.setMinutes(0)
compDate.setSeconds(0)
compDate.setMilliseconds(0)
if (compDate.getTime() === today.getTime()) {
return qsTr("Today")
} else if ((today.getTime() - compDate.getTime()) <= (24 * 60 * 60 *1000)) {
return qsTr("Yesterday")
} else {
return compDate.toLocaleDateString(Qt.locale(), Locale.ShortFormat)
}
}
/*Component.onCompleted: { /*Component.onCompleted: {
if (saveFile) { if (saveFile) {
if (account.name === "") { if (account.name === "") {

View file

@ -4,10 +4,20 @@ import Sailfish.Silica 1.0
Page { Page {
id: page id: page
Timer {
id: autoSyncTimer
interval: appSettings.autoSyncInterval * 1000
repeat: true
running: interval > 0 && appWindow.visible
triggeredOnStart: true
onTriggered: nextcloudAccounts.itemAt(appSettings.currentAccount).getNotes()
onIntervalChanged: console.log("Auto-Sync every " + interval / 1000 + " seconds")
}
onStatusChanged: { onStatusChanged: {
if (status === PageStatus.Active) { if (status === PageStatus.Active) {
if (nextcloudAccounts.count > 0) { if (nextcloudAccounts.count > 0) {
nextcloudAccounts.itemAt(appSettings.currentAccount).getNotes() autoSyncTimer.restart()
} }
else { else {
addAccountHint.restart() addAccountHint.restart()
@ -18,7 +28,6 @@ Page {
SilicaListView { SilicaListView {
id: notesList id: notesList
anchors.fill: parent anchors.fill: parent
spacing: Theme.paddingLarge
PullDownMenu { PullDownMenu {
busy: nextcloudAccounts.itemAt(appSettings.currentAccount) ? nextcloudAccounts.itemAt(appSettings.currentAccount).busy : false busy: nextcloudAccounts.itemAt(appSettings.currentAccount) ? nextcloudAccounts.itemAt(appSettings.currentAccount).busy : false
@ -29,12 +38,12 @@ Page {
} }
MenuItem { MenuItem {
text: qsTr("Add note") text: qsTr("Add note")
enabled: nextcloudAccounts.itemAt(appSettings.currentAccount) ? !nextcloudAccounts.itemAt(appSettings.currentAccount).busy : false enabled: nextcloudAccounts.itemAt(appSettings.currentAccount) ? true : false
visible: appSettings.currentAccount >= 0 visible: appSettings.currentAccount >= 0
onClicked: nextcloudAccounts.itemAt(appSettings.currentAccount).createNote() onClicked: nextcloudAccounts.itemAt(appSettings.currentAccount).createNote()
} }
MenuItem { MenuItem {
text: qsTr("Reload") text: enabled ? qsTr("Reload") : qsTr("Updating...")
enabled: nextcloudAccounts.itemAt(appSettings.currentAccount) ? !nextcloudAccounts.itemAt(appSettings.currentAccount).busy : false enabled: nextcloudAccounts.itemAt(appSettings.currentAccount) ? !nextcloudAccounts.itemAt(appSettings.currentAccount).busy : false
visible: appSettings.currentAccount >= 0 visible: appSettings.currentAccount >= 0
onClicked: nextcloudAccounts.itemAt(appSettings.currentAccount).getNotes() onClicked: nextcloudAccounts.itemAt(appSettings.currentAccount).getNotes()
@ -52,6 +61,12 @@ Page {
header: PageHeader { header: PageHeader {
title: nextcloudAccounts.itemAt(appSettings.currentAccount).name //qsTr("Nextclound Notes") title: nextcloudAccounts.itemAt(appSettings.currentAccount).name //qsTr("Nextclound Notes")
description: nextcloudAccounts.itemAt(appSettings.currentAccount).username + "@" + nextcloudAccounts.itemAt(appSettings.currentAccount).server description: nextcloudAccounts.itemAt(appSettings.currentAccount).username + "@" + nextcloudAccounts.itemAt(appSettings.currentAccount).server
/*BusyIndicator {
running: nextcloudAccounts.itemAt(appSettings.currentAccount) ? nextcloudAccounts.itemAt(appSettings.currentAccount).busy : false
x: Theme.horizontalPageMargin
anchors.verticalCenter: parent.verticalCenter
}*/
/*SearchField { /*SearchField {
width: parent.width width: parent.width
placeholderText: qsTr("Nextcloud Notes") placeholderText: qsTr("Nextcloud Notes")
@ -74,12 +89,17 @@ Page {
id: note id: note
contentHeight: titleLabel.height + previewLabel.height + 2*Theme.paddingSmall contentHeight: titleLabel.height + previewLabel.height + 2*Theme.paddingSmall
Separator {
width: parent.width
color: Theme.primaryColor
anchors.top: titleLabel.top
visible: appSettings.showSeparator && index !== 0
}
IconButton { IconButton {
id: isFavoriteIcon id: isFavoriteIcon
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.paddingSmall
anchors.top: parent.top anchors.top: parent.top
width: Theme.iconSizeMedium
icon.source: (favorite ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") + icon.source: (favorite ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") +
(note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor) (note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor)
onClicked: { onClicked: {
@ -92,7 +112,7 @@ Page {
id: titleLabel id: titleLabel
anchors.left: isFavoriteIcon.right anchors.left: isFavoriteIcon.right
anchors.leftMargin: Theme.paddingSmall anchors.leftMargin: Theme.paddingSmall
anchors.right: parent.right anchors.right: categoryRectangle.left
anchors.rightMargin: Theme.horizontalPageMargin anchors.rightMargin: Theme.horizontalPageMargin
anchors.top: parent.top anchors.top: parent.top
text: title text: title
@ -100,6 +120,27 @@ Page {
color: note.highlighted ? Theme.highlightColor : Theme.primaryColor color: note.highlighted ? Theme.highlightColor : Theme.primaryColor
} }
Rectangle {
id: categoryRectangle
anchors.right: parent.right
anchors.rightMargin: Theme.horizontalPageMargin
anchors.top: parent.top
anchors.topMargin: Theme.paddingSmall
width: categoryLabel.width + Theme.paddingLarge
height: categoryLabel.height + Theme.paddingSmall
color: "transparent"
border.color: Theme.highlightColor
radius: height / 4
visible: appSettings.groupBy !== "category" && categoryLabel.text.length > 0
Label {
id: categoryLabel
anchors.centerIn: parent
text: category
color: Theme.secondaryColor
font.pixelSize: Theme.fontSizeExtraSmall
}
}
Label { Label {
id: previewLabel id: previewLabel
anchors.left: isFavoriteIcon.right anchors.left: isFavoriteIcon.right
@ -107,14 +148,20 @@ Page {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.horizontalPageMargin anchors.rightMargin: Theme.horizontalPageMargin
anchors.top: titleLabel.bottom anchors.top: titleLabel.bottom
anchors.topMargin: Theme.paddingMedium
height: Theme.itemSizeExtraLarge
text: content text: content
font.pixelSize: Theme.fontSizeExtraSmall font.pixelSize: Theme.fontSizeExtraSmall
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Text.Wrap wrapMode: Text.Wrap
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: appSettings.previewLineCount > 0 ? appSettings.previewLineCount : 1
visible: appSettings.previewLineCount > 0
color: note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor color: note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
Component.onCompleted: {
var lines = text.split('\n')
lines.splice(0,1);
text = lines.join('\n');
text = text.replace(/^\s*$(?:\r\n?|\n)/gm, "")
}
} }
onClicked: pageStack.push(Qt.resolvedUrl("NotePage.qml"), { account: nextcloudAccounts.itemAt(appSettings.currentAccount), noteIndex: index } ) onClicked: pageStack.push(Qt.resolvedUrl("NotePage.qml"), { account: nextcloudAccounts.itemAt(appSettings.currentAccount), noteIndex: index } )
@ -138,7 +185,14 @@ Page {
} }
} }
section.property: "category" /*section.property: "category"
section.criteria: ViewSection.FullString
section.labelPositioning: ViewSection.InlineLabels
section.delegate: SectionHeader {
text: section
}*/
section.property: appSettings.groupBy
section.criteria: ViewSection.FullString section.criteria: ViewSection.FullString
section.labelPositioning: ViewSection.InlineLabels section.labelPositioning: ViewSection.InlineLabels
section.delegate: SectionHeader { section.delegate: SectionHeader {
@ -149,16 +203,10 @@ Page {
id: busyIndicator id: busyIndicator
anchors.centerIn: parent anchors.centerIn: parent
size: BusyIndicatorSize.Large size: BusyIndicatorSize.Large
visible: nextcloudAccounts.itemAt(appSettings.currentAccount) ? nextcloudAccounts.itemAt(appSettings.currentAccount).busy && notesList.count === 0 : false visible: nextcloudAccounts.itemAt(appSettings.currentAccount) ? (notesList.count === 0 && nextcloudAccounts.itemAt(appSettings.currentAccount).busy) : false
running: visible running: visible
} }
ViewPlaceholder {
enabled: notesList.count === 0 && !busyIndicator.running && !noLoginPlaceholder.enabled
text: qsTr("No notes yet")
hintText: qsTr("Pull down to add a note")
}
ViewPlaceholder { ViewPlaceholder {
id: noLoginPlaceholder id: noLoginPlaceholder
enabled: nextcloudUUIDs.value.length <= 0 enabled: nextcloudUUIDs.value.length <= 0
@ -166,6 +214,20 @@ Page {
hintText: qsTr("Got to the settings to add an account") hintText: qsTr("Got to the settings to add an account")
} }
ViewPlaceholder {
id: noNotesPlaceholder
enabled: nextcloudAccounts.itemAt(appSettings.currentAccount) ? (nextcloudAccounts.itemAt(appSettings.currentAccount).status === 204 && !busyIndicator.running && !noLoginPlaceholder.enabled) : false
text: qsTr("No notes yet")
hintText: qsTr("Pull down to add a note")
}
ViewPlaceholder {
id: errorPlaceholder
enabled: notesList.count === 0 && !busyIndicator.running && !noNotesPlaceholder.enabled && !noLoginPlaceholder.enabled
text: qsTr("An error occurred")
hintText: nextcloudAccounts.itemAt(appSettings.currentAccount).statusText
}
TouchInteractionHint { TouchInteractionHint {
id: addAccountHint id: addAccountHint
interactionMode: TouchInteraction.Pull interactionMode: TouchInteraction.Pull

View file

@ -29,7 +29,6 @@ Page {
SectionHeader { SectionHeader {
text: qsTr("Accounts") text: qsTr("Accounts")
} }
Label { Label {
id: noAccountsLabel id: noAccountsLabel
visible: nextcloudAccounts.count <= 0 visible: nextcloudAccounts.count <= 0
@ -38,16 +37,15 @@ Page {
color: Theme.secondaryHighlightColor color: Theme.secondaryHighlightColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Repeater { Repeater {
model: nextcloudAccounts.count model: nextcloudAccounts.count
delegate: ListItem { delegate: ListItem {
id: accountListItem id: accountListItem
contentHeight: textSwitch.height contentHeight: accountTextSwitch.height
highlighted: textSwitch.down highlighted: accountTextSwitch.down
TextSwitch { TextSwitch {
id: textSwitch id: accountTextSwitch
automaticCheck: false automaticCheck: false
checked: index === appSettings.currentAccount checked: index === appSettings.currentAccount
text: nextcloudAccounts.itemAt(index).name.length <= 0 ? qsTr("Unnamed account") : nextcloudAccounts.itemAt(index).name text: nextcloudAccounts.itemAt(index).name.length <= 0 ? qsTr("Unnamed account") : nextcloudAccounts.itemAt(index).name
@ -79,7 +77,6 @@ Page {
} }
} }
} }
Button { Button {
text: qsTr("Add account") text: qsTr("Add account")
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -97,6 +94,76 @@ Page {
}) })
} }
} }
SectionHeader {
text: qsTr("Appearance")
}
ComboBox {
id: groupByComboBox
property var names: [qsTr("Date"), qsTr("Category")]
label: qsTr("Group notes by")
menu: ContextMenu {
Repeater {
id: groupByRepeater
model: ["date", "category"]
MenuItem {
text: groupByComboBox.names[index]
Component.onCompleted: {
if (modelData === appSettings.groupBy) {
groupByComboBox.currentIndex = index
}
}
}
}
}
onCurrentIndexChanged: {
appSettings.groupBy = groupByRepeater.model[currentIndex]
}
}
TextSwitch {
text: qsTr("Show separator")
description: qsTr("Show a separator line between the notes")
checked: appSettings.showSeparator
onCheckedChanged: appSettings.showSeparator = checked
}
Slider {
width: parent.width
minimumValue: 0
maximumValue: 20
stepSize: 1
value: appSettings.previewLineCount
valueText: sliderValue + " " + qsTr("lines")
label: qsTr("Number of lines to preview in the list view")
onSliderValueChanged: appSettings.previewLineCount = sliderValue
}
SectionHeader {
text: qsTr("Synchronization")
}
ComboBox {
id: autoSyncComboBox
label: qsTr("Auto-Sync")
description: qsTr("Periodically pull notes from the server")
menu: ContextMenu {
Repeater {
id: autoSyncIntervalRepeater
model: [0, 3, 5, 10, 20, 30, 60, 120, 300, 600]
MenuItem {
text: modelData === 0 ?
qsTr("Disabled") : (qsTr("every") + " " +
(parseInt(modelData / 60) ?
(parseInt(modelData / 60) + " " + qsTr("Minutes")) :
(modelData + " " + qsTr("Seconds"))))
Component.onCompleted: {
if (modelData === appSettings.autoSyncInterval) {
autoSyncComboBox.currentIndex = index
}
}
}
}
}
onCurrentIndexChanged: appSettings.autoSyncInterval = autoSyncIntervalRepeater.model[currentIndex]
}
} }
VerticalScrollDecorator {} VerticalScrollDecorator {}

View file

@ -137,10 +137,6 @@
<source>No account yet</source> <source>No account yet</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Got to the settings to add an account</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Open the settings to configure your Nextcloud accounts</source> <source>Open the settings to configure your Nextcloud accounts</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -149,6 +145,18 @@
<source>Deleting note</source> <source>Deleting note</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Got to the settings to add an account</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>An error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Updating...</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>SettingsPage</name> <name>SettingsPage</name>
@ -188,6 +196,30 @@
<source>Deleting account</source> <source>Deleting account</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Synchronization</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Auto-Sync</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Periodically pull notes from the server</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Disabled</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Minutes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seconds</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>UnencryptedDialog</name> <name>UnencryptedDialog</name>