mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Load resources in WebView and put todos on top
This commit is contained in:
parent
76914d6c28
commit
f80263ab71
@ -116,11 +116,18 @@ class BaseModel {
|
||||
static applySqlOptions(options, sql, params = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
if (options.orderBy) {
|
||||
sql += ' ORDER BY ' + options.orderBy;
|
||||
if (options.caseInsensitive === true) sql += ' COLLATE NOCASE';
|
||||
if (options.orderByDir) sql += ' ' + options.orderByDir;
|
||||
if (options.order && options.order.length) {
|
||||
let items = [];
|
||||
for (let i = 0; i < options.order.length; i++) {
|
||||
const o = options.order[i];
|
||||
let item = o.by;
|
||||
if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
|
||||
if (o.dir) item += ' ' + o.dir;
|
||||
items.push(item);
|
||||
}
|
||||
sql += ' ORDER BY ' + items.join(', ');
|
||||
}
|
||||
|
||||
if (options.limit) sql += ' LIMIT ' + options.limit;
|
||||
|
||||
return { sql: sql, params: params };
|
||||
|
@ -3,6 +3,7 @@ import { BackHandler, View, Button, TextInput, WebView, Text, StyleSheet, Linkin
|
||||
import { connect } from 'react-redux'
|
||||
import { Log } from 'lib/log.js'
|
||||
import { Note } from 'lib/models/note.js'
|
||||
import { Resource } from 'lib/models/resource.js'
|
||||
import { Folder } from 'lib/models/folder.js'
|
||||
import { BaseModel } from 'lib/base-model.js'
|
||||
import { ActionButton } from 'lib/components/action-button.js';
|
||||
@ -13,6 +14,7 @@ import { Checkbox } from 'lib/components/checkbox.js'
|
||||
import { _ } from 'lib/locale.js';
|
||||
import marked from 'lib/marked.js';
|
||||
import { reg } from 'lib/registry.js';
|
||||
import { shim } from 'lib/shim.js';
|
||||
import { BaseScreenComponent } from 'lib/components/base-screen.js';
|
||||
import { dialogs } from 'lib/dialogs.js';
|
||||
import { globalStyle } from 'lib/components/global-style.js';
|
||||
@ -71,6 +73,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
folder: null,
|
||||
lastSavedNote: null,
|
||||
isLoading: true,
|
||||
resources: {},
|
||||
};
|
||||
|
||||
this.bodyScrollTop_ = 0;
|
||||
@ -244,6 +247,15 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.refreshNoteMetadata(true);
|
||||
}
|
||||
|
||||
async loadResource(id) {
|
||||
const resource = await Resource.load(id);
|
||||
resource.base64 = await shim.readLocalFileBase64(Resource.fullPath(resource));
|
||||
|
||||
let newResources = Object.assign({}, this.state.resources);
|
||||
newResources[id] = resource;
|
||||
this.setState({ resources: newResources });
|
||||
}
|
||||
|
||||
async showOnMap_onPress() {
|
||||
if (!this.state.note.id) return;
|
||||
|
||||
@ -353,6 +365,9 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
hr {
|
||||
border: 1px solid ` + style.htmlDividerColor + `;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
let counter = -1;
|
||||
@ -365,10 +380,32 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
renderer.link = function (href, title, text) {
|
||||
const js = "postMessage(" + JSON.stringify(href) + "); return false;";
|
||||
let output = "<a href='#' onclick='" + js + "'>" + text + '</a>';
|
||||
return output;
|
||||
if (Resource.isResourceUrl(href)) {
|
||||
return '[Resource not yet supported: ' + href + ']'; // TODO: add title
|
||||
} else {
|
||||
const js = "postMessage(" + JSON.stringify(href) + "); return false;";
|
||||
let output = "<a title='" + title + "' href='#' onclick='" + js + "'>" + text + '</a>';
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
renderer.image = (href, title, text) => {
|
||||
const resourceId = Resource.urlToId(href);
|
||||
if (!this.state.resources[resourceId]) {
|
||||
this.loadResource(resourceId);
|
||||
return '';
|
||||
}
|
||||
|
||||
const r = this.state.resources[resourceId];
|
||||
if (r.mime == 'image/png' || r.mime == 'image/jpg' || r.mime == 'image/gif') {
|
||||
const src = 'data:' + r.mime + ';base64,' + r.base64;
|
||||
let output = '<img src="' + src + '"/>';
|
||||
return output;
|
||||
}
|
||||
|
||||
return '[Image: ' + r.title + '(' + r.mime + ')]';
|
||||
}
|
||||
|
||||
let html = note ? '<style>' + normalizeCss + "\n" + css + '</style>' + marked(body, { gfm: true, breaks: true, renderer: renderer }) : '';
|
||||
@ -376,7 +413,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
let elementId = 1;
|
||||
while (html.indexOf('°°JOP°') >= 0) {
|
||||
html = html.replace(/°°JOP°CHECKBOX°([A-Z]+)°(\d+)°°/, function(v, type, index) {
|
||||
const js = "postMessage('checkboxclick:" + type + '_' + index + "'); this.textContent = this.textContent == '☐' ? '☑' : '☐'; return false;";
|
||||
const js = "postMessage('checkboxclick:" + type + ':' + index + "'); this.textContent = this.textContent == '☐' ? '☑' : '☐'; return false;";
|
||||
return '<a href="#" onclick="' + js + '" class="checkbox">' + (type == 'NOTICK' ? '☐' : '☑') + '</a>';
|
||||
});
|
||||
}
|
||||
@ -395,7 +432,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onMessage={(event) => {
|
||||
let msg = event.nativeEvent.data;
|
||||
|
||||
reg.logger().info('postMessage received: ' + msg);
|
||||
//reg.logger().info('postMessage received: ' + msg);
|
||||
|
||||
if (msg.indexOf('checkboxclick:') === 0) {
|
||||
msg = msg.split(':');
|
||||
|
@ -26,8 +26,7 @@ class NotesScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(newProps) {
|
||||
if (newProps.notesOrder.orderBy != this.props.notesOrder.orderBy ||
|
||||
newProps.notesOrder.orderByDir != this.props.notesOrder.orderByDir ||
|
||||
if (newProps.notesOrder !== this.props.notesOrder ||
|
||||
newProps.selectedFolderId != this.props.selectedFolderId ||
|
||||
newProps.selectedTagId != this.props.selectedTagId ||
|
||||
newProps.notesParentType != this.props.notesParentType) {
|
||||
@ -39,8 +38,8 @@ class NotesScreenComponent extends BaseScreenComponent {
|
||||
if (props === null) props = this.props;
|
||||
|
||||
let options = {
|
||||
orderBy: props.notesOrder.orderBy,
|
||||
orderByDir: props.notesOrder.orderByDir,
|
||||
order: props.notesOrder,
|
||||
uncompletedTodosOnTop: props.uncompletedTodosOnTop,
|
||||
};
|
||||
|
||||
const parent = this.parentItem(props);
|
||||
@ -153,6 +152,7 @@ const NotesScreen = connect(
|
||||
notes: state.notes,
|
||||
notesOrder: state.notesOrder,
|
||||
notesSource: state.notesSource,
|
||||
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
||||
};
|
||||
}
|
||||
)(NotesScreenComponent)
|
||||
|
@ -63,14 +63,14 @@ class Note extends BaseItem {
|
||||
return output;
|
||||
}
|
||||
|
||||
static sortNotes(notes, order) {
|
||||
return notes.sort((a, b) => {
|
||||
let r = -1;
|
||||
if (a[order.orderBy] < b[order.orderBy]) r = +1;
|
||||
if (order.orderByDir == 'ASC') r = -r;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
// static sortNotes(notes, order) {
|
||||
// return notes.sort((a, b) => {
|
||||
// // let r = -1;
|
||||
// // if (a[order.orderBy] < b[order.orderBy]) r = +1;
|
||||
// // if (order.orderByDir == 'ASC') r = -r;
|
||||
// // return r;
|
||||
// });
|
||||
// }
|
||||
|
||||
static previewFields() {
|
||||
return ['id', 'title', 'body', 'is_todo', 'todo_completed', 'parent_id', 'updated_time'];
|
||||
@ -95,13 +95,13 @@ class Note extends BaseItem {
|
||||
return results.length ? results[0] : null;
|
||||
}
|
||||
|
||||
static previews(parentId, options = null) {
|
||||
static async previews(parentId, options = null) {
|
||||
if (!options) options = {};
|
||||
if (!options.orderBy) options.orderBy = 'updated_time';
|
||||
if (!options.orderByDir) options.orderByDir = 'DESC';
|
||||
if (!options.order) options.order = { by: 'updated_time', dir: 'DESC' };
|
||||
if (!options.conditions) options.conditions = [];
|
||||
if (!options.conditionsParams) options.conditionsParams = [];
|
||||
if (!options.fields) options.fields = this.previewFields();
|
||||
if (!options.uncompletedTodosOnTop) options.uncompletedTodosOnTop = false;
|
||||
|
||||
if (parentId == Folder.conflictFolderId()) {
|
||||
options.conditions.push('is_conflict = 1');
|
||||
@ -120,16 +120,47 @@ class Note extends BaseItem {
|
||||
options.conditionsParams.push(pattern);
|
||||
}
|
||||
|
||||
let hasNotes = true;
|
||||
let hasTodos = true;
|
||||
if (options.itemTypes && options.itemTypes.length) {
|
||||
if (options.itemTypes.indexOf('note') >= 0 && options.itemTypes.indexOf('todo') >= 0) {
|
||||
// Fetch everything
|
||||
} else if (options.itemTypes.indexOf('note') >= 0) {
|
||||
options.conditions.push('is_todo = 0');
|
||||
} else if (options.itemTypes.indexOf('todo') >= 0) {
|
||||
options.conditions.push('is_todo = 1');
|
||||
if (options.itemTypes.indexOf('note') < 0) {
|
||||
hasNotes = false;
|
||||
} else if (options.itemTypes.indexOf('todo') < 0) {
|
||||
hasTodos = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.uncompletedTodosOnTop && hasTodos) {
|
||||
let cond = options.conditions.slice();
|
||||
cond.push('is_todo = 1');
|
||||
cond.push('(todo_completed <= 0 OR todo_completed IS NULL)');
|
||||
let tempOptions = Object.assign({}, options);
|
||||
tempOptions.conditions = cond;
|
||||
|
||||
let uncompletedTodos = await this.search(tempOptions);
|
||||
|
||||
cond = options.conditions.slice();
|
||||
if (hasNotes && hasTodos) {
|
||||
cond.push('(is_todo = 0 OR (is_todo = 1 AND todo_completed > 0))');
|
||||
} else {
|
||||
cond.push('(is_todo = 1 AND todo_completed > 0)');
|
||||
}
|
||||
|
||||
tempOptions = Object.assign({}, options);
|
||||
tempOptions.conditions = cond;
|
||||
let theRest = await this.search(tempOptions);
|
||||
|
||||
return uncompletedTodos.concat(theRest);
|
||||
}
|
||||
|
||||
if (hasNotes && hasTodos) {
|
||||
|
||||
} else if (hasNotes) {
|
||||
options.conditions.push('is_todo = 0');
|
||||
} else if (hasTodos) {
|
||||
options.conditions.push('is_todo = 1');
|
||||
}
|
||||
|
||||
return this.search(options);
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,15 @@ class Resource extends BaseItem {
|
||||
return this.fsDriver().writeBinaryFile(this.fullPath(resource), content);
|
||||
}
|
||||
|
||||
static isResourceUrl(url) {
|
||||
return url.length === 34 && url[0] === ':' && url[1] === '/';
|
||||
}
|
||||
|
||||
static urlToId(url) {
|
||||
if (!this.isResourceUrl(url)) throw new Error('Not a valid resource URL: ' + url);
|
||||
return url.substr(2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { Resource };
|
@ -42,20 +42,21 @@ class Setting extends BaseModel {
|
||||
this.cancelScheduleSave();
|
||||
this.cache_ = [];
|
||||
return this.modelSelectAll('SELECT * FROM settings').then((rows) => {
|
||||
this.cache_ = rows;
|
||||
this.cache_ = [];
|
||||
|
||||
for (let i = 0; i < this.cache_.length; i++) {
|
||||
let c = this.cache_[i];
|
||||
// Old keys - can be removed later
|
||||
const ignore = ['clientId', 'sync.onedrive.auth', 'syncInterval', 'todoOnTop', 'todosOnTop'];
|
||||
|
||||
if (c.key == 'clientId') continue; // For older clients
|
||||
if (c.key == 'sync.onedrive.auth') continue; // For older clients
|
||||
if (c.key == 'syncInterval') continue; // For older clients
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let c = rows[i];
|
||||
|
||||
if (ignore.indexOf(c.key) >= 0) continue;
|
||||
|
||||
// console.info(c.key + ' = ' + c.value);
|
||||
|
||||
c.value = this.formatValue(c.key, c.value);
|
||||
|
||||
this.cache_[i] = c;
|
||||
this.cache_.push(c);
|
||||
}
|
||||
|
||||
const keys = this.keys();
|
||||
@ -303,6 +304,7 @@ Setting.metadata_ = {
|
||||
recent: _('Non-completed and recently completed ones'),
|
||||
nonCompleted: _('Non-completed ones only'),
|
||||
})},
|
||||
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted todos on top of the lists') },
|
||||
'trackLocation': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Save location with notes') },
|
||||
'sync.interval': { value: 300, type: Setting.TYPE_INT, isEnum: true, public: true, label: () => _('Synchronisation interval'), options: () => {
|
||||
return {
|
||||
|
@ -71,7 +71,7 @@ class ReportService {
|
||||
section.body = [];
|
||||
|
||||
let folders = await Folder.all({
|
||||
orderBy: 'title',
|
||||
order: { by: 'title', dir: 'ASC' },
|
||||
caseInsensitive: true,
|
||||
});
|
||||
|
||||
|
@ -38,6 +38,10 @@ function shimInit() {
|
||||
throw new Error('fetchBlob: ' + method + ' ' + url + ': ' + error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
shim.readLocalFileBase64 = async function(path) {
|
||||
return RNFetchBlob.fs.readFile(path, 'base64')
|
||||
}
|
||||
}
|
||||
|
||||
export { shimInit }
|
@ -13,5 +13,6 @@ shim.fetch = typeof fetch !== 'undefined' ? fetch : null;
|
||||
shim.FormData = typeof FormData !== 'undefined' ? FormData : null;
|
||||
shim.fs = null;
|
||||
shim.FileApiDriverLocal = null;
|
||||
shim.readLocalFileBase64 = () => { throw new Error('Not implemented'); }
|
||||
|
||||
export { shim };
|
@ -49,10 +49,9 @@ let defaultState = {
|
||||
screens: {},
|
||||
loading: true,
|
||||
historyCanGoBack: false,
|
||||
notesOrder: {
|
||||
orderBy: 'updated_time',
|
||||
orderByDir: 'DESC',
|
||||
},
|
||||
notesOrder: [
|
||||
{ by: 'updated_time', dir: 'DESC' },
|
||||
],
|
||||
syncStarted: false,
|
||||
syncReport: {},
|
||||
searchQuery: '',
|
||||
@ -87,6 +86,27 @@ function reducerActionsAreSame(a1, a2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateStateFromSettings(action, newState) {
|
||||
// if (action.type == 'SETTINGS_UPDATE_ALL' || action.key == 'uncompletedTodosOnTop') {
|
||||
// let newNotesOrder = [];
|
||||
// for (let i = 0; i < newState.notesOrder.length; i++) {
|
||||
// const o = newState.notesOrder[i];
|
||||
// if (o.by == 'is_todo') continue;
|
||||
// newNotesOrder.push(o);
|
||||
// }
|
||||
|
||||
// if (newState.settings['uncompletedTodosOnTop']) {
|
||||
// newNotesOrder.unshift({ by: 'is_todo', dir: 'DESC' });
|
||||
// }
|
||||
|
||||
// newState.notesOrder = newNotesOrder;
|
||||
|
||||
// console.info('NEW', newNotesOrder);
|
||||
// }
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
let newState = state;
|
||||
let historyGoingBack = false;
|
||||
@ -179,6 +199,7 @@ const reducer = (state = defaultState, action) => {
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.settings = action.settings;
|
||||
newState = updateStateFromSettings(action, newState);
|
||||
break;
|
||||
|
||||
case 'SETTINGS_UPDATE_ONE':
|
||||
@ -187,6 +208,7 @@ const reducer = (state = defaultState, action) => {
|
||||
let newSettings = Object.assign({}, state.settings);
|
||||
newSettings[action.key] = action.value;
|
||||
newState.settings = newSettings;
|
||||
newState = updateStateFromSettings(action, newState);
|
||||
break;
|
||||
|
||||
// Replace all the notes with the provided array
|
||||
@ -223,7 +245,7 @@ const reducer = (state = defaultState, action) => {
|
||||
|
||||
if (!found && ('parent_id' in modNote) && modNote.parent_id == state.selectedFolderId) newNotes.push(modNote);
|
||||
|
||||
newNotes = Note.sortNotes(newNotes, state.notesOrder);
|
||||
//newNotes = Note.sortNotes(newNotes, state.notesOrder);
|
||||
newState = Object.assign({}, state);
|
||||
newState.notes = newNotes;
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user