mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Clipper: Allow selecting a folder and fixed screenshot taking issue
This commit is contained in:
parent
d6c6ef20d4
commit
89b486a3ee
@ -94,6 +94,7 @@
|
|||||||
title: article.title,
|
title: article.title,
|
||||||
baseUrl: baseUrl(),
|
baseUrl: baseUrl(),
|
||||||
url: location.origin + location.pathname,
|
url: location.origin + location.pathname,
|
||||||
|
parentId: command.parentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
} else if (command.name === "completePageHtml") {
|
} else if (command.name === "completePageHtml") {
|
||||||
@ -107,6 +108,7 @@
|
|||||||
title: pageTitle(),
|
title: pageTitle(),
|
||||||
baseUrl: baseUrl(),
|
baseUrl: baseUrl(),
|
||||||
url: location.origin + location.pathname,
|
url: location.origin + location.pathname,
|
||||||
|
parentId: command.parentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
} else if (command.name === 'screenshot') {
|
} else if (command.name === 'screenshot') {
|
||||||
@ -203,6 +205,7 @@
|
|||||||
title: pageTitle(),
|
title: pageTitle(),
|
||||||
cropRect: selectionArea,
|
cropRect: selectionArea,
|
||||||
url: location.origin + location.pathname,
|
url: location.origin + location.pathname,
|
||||||
|
parentId: command.parentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
browser_.runtime.sendMessage({
|
browser_.runtime.sendMessage({
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
"tabs",
|
"tabs",
|
||||||
"http://*/",
|
"http://*/",
|
||||||
"https://*/",
|
"https://*/",
|
||||||
"<all_urls>"
|
"<all_urls>",
|
||||||
|
"storage"
|
||||||
],
|
],
|
||||||
|
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
|
@ -7,12 +7,13 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #162b3d;
|
background-color: #162b3d;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
color: #5A95C7;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App h2 {
|
.App h2 {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: #5A95C7;
|
|
||||||
padding-left: 10px;
|
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
margin-bottom: .5em;
|
margin-bottom: .5em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -28,13 +29,12 @@
|
|||||||
|
|
||||||
.App .Controls {
|
.App .Controls {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
padding-top: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.App .Controls ul {
|
.App .Controls ul {
|
||||||
flex: 0;
|
flex: 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0 10px;
|
padding-left: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -66,10 +66,8 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
margin: 0 10px 0 10px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
/*border: 2px solid red;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.App .Preview .Info {
|
.App .Preview .Info {
|
||||||
@ -102,6 +100,23 @@
|
|||||||
flex: 0;
|
flex: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.App .Folders {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App .Folders label {
|
||||||
|
flex: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App .Folders select {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.App .StatusBar {
|
.App .StatusBar {
|
||||||
color: #5A95C7;
|
color: #5A95C7;
|
||||||
font-size: .7em;
|
font-size: .7em;
|
||||||
@ -109,8 +124,8 @@
|
|||||||
flex: 0;
|
flex: 0;
|
||||||
flex-direction: 'row';
|
flex-direction: 'row';
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 31px;
|
min-height: 20px;
|
||||||
padding: 0px 10px 5px 10px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App .StatusBar .Led {
|
.App .StatusBar .Led {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import led_red from './led_red.png'; // Tell Webpack this JS file uses this image
|
import led_red from './led_red.png';
|
||||||
import led_green from './led_green.png'; // Tell Webpack this JS file uses this image
|
import led_green from './led_green.png';
|
||||||
import led_orange from './led_orange.png'; // Tell Webpack this JS file uses this image
|
import led_orange from './led_orange.png';
|
||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { bridge } = require('./bridge');
|
const { bridge } = require('./bridge');
|
||||||
@ -27,13 +27,28 @@ class AppComponent extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.clipSimplified_click = () => {
|
||||||
|
bridge().sendCommandToActiveTab({
|
||||||
|
name: 'simplifiedPageHtml',
|
||||||
|
parentId: this.props.selectedFolderId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clipComplete_click = () => {
|
||||||
|
bridge().sendCommandToActiveTab({
|
||||||
|
name: 'completePageHtml',
|
||||||
|
parentId: this.props.selectedFolderId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.clipScreenshot_click = async () => {
|
this.clipScreenshot_click = async () => {
|
||||||
try {
|
try {
|
||||||
const baseUrl = await bridge().clipperServerBaseUrl();
|
const baseUrl = await bridge().clipperServerBaseUrl();
|
||||||
|
|
||||||
bridge().sendCommandToActiveTab({
|
await bridge().sendCommandToActiveTab({
|
||||||
name: 'screenshot',
|
name: 'screenshot',
|
||||||
apiBaseUrl: baseUrl,
|
apiBaseUrl: baseUrl,
|
||||||
|
parentId: this.props.selectedFolderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.close();
|
window.close();
|
||||||
@ -45,18 +60,13 @@ class AppComponent extends Component {
|
|||||||
this.clipperServerHelpLink_click = () => {
|
this.clipperServerHelpLink_click = () => {
|
||||||
bridge().tabsCreate({ url: 'https://joplin.cozic.net/clipper' });
|
bridge().tabsCreate({ url: 'https://joplin.cozic.net/clipper' });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
clipSimplified_click() {
|
this.folderSelect_change = (event) => {
|
||||||
bridge().sendCommandToActiveTab({
|
this.props.dispatch({
|
||||||
name: 'simplifiedPageHtml',
|
type: 'SELECTED_FOLDER_SET',
|
||||||
});
|
id: event.target.value,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
clipComplete_click() {
|
|
||||||
bridge().sendCommandToActiveTab({
|
|
||||||
name: 'completePageHtml',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadContentScripts() {
|
async loadContentScripts() {
|
||||||
@ -149,6 +159,36 @@ class AppComponent extends Component {
|
|||||||
return <div className="StatusBar"><img className="Led" src={led}/><span className="ServerStatus">{ msg }{ helpLink }</span></div>
|
return <div className="StatusBar"><img className="Led" src={led}/><span className="ServerStatus">{ msg }{ helpLink }</span></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.info(this.props.selectedFolderId);
|
||||||
|
|
||||||
|
const foldersComp = () => {
|
||||||
|
const optionComps = [];
|
||||||
|
|
||||||
|
const nonBreakingSpacify = (s) => {
|
||||||
|
// https://stackoverflow.com/a/24437562/561309
|
||||||
|
return s.replace(/ /g, "\u00a0");
|
||||||
|
}
|
||||||
|
|
||||||
|
const addOptions = (folders, depth) => {
|
||||||
|
for (let i = 0; i < folders.length; i++) {
|
||||||
|
const folder = folders[i];
|
||||||
|
optionComps.push(<option key={folder.id} value={folder.id}>{nonBreakingSpacify(' '.repeat(depth) + folder.title)}</option>)
|
||||||
|
if (folder.children) addOptions(folder.children, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addOptions(this.props.folders, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Folders">
|
||||||
|
<label>In notebook: </label>
|
||||||
|
<select defaultValue={this.props.selectedFolderId} onChange={this.folderSelect_change}>
|
||||||
|
{ optionComps }
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div className="Controls">
|
<div className="Controls">
|
||||||
@ -158,6 +198,7 @@ class AppComponent extends Component {
|
|||||||
<li><a className="Button" onClick={this.clipScreenshot_click}>Clip screenshot</a></li>
|
<li><a className="Button" onClick={this.clipScreenshot_click}>Clip screenshot</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{ foldersComp() }
|
||||||
{ warningComponent }
|
{ warningComponent }
|
||||||
<h2>Preview:</h2>
|
<h2>Preview:</h2>
|
||||||
{ previewComponent }
|
{ previewComponent }
|
||||||
@ -174,6 +215,8 @@ const mapStateToProps = (state) => {
|
|||||||
clippedContent: state.clippedContent,
|
clippedContent: state.clippedContent,
|
||||||
contentUploadOperation: state.contentUploadOperation,
|
contentUploadOperation: state.contentUploadOperation,
|
||||||
clipperServer: state.clipperServer,
|
clipperServer: state.clipperServer,
|
||||||
|
folders: state.folders,
|
||||||
|
selectedFolderId: state.selectedFolderId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ class Bridge {
|
|||||||
bodyHtml: command.html,
|
bodyHtml: command.html,
|
||||||
baseUrl: command.baseUrl,
|
baseUrl: command.baseUrl,
|
||||||
url: command.url,
|
url: command.url,
|
||||||
|
parentId: command.parentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content });
|
this.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content });
|
||||||
@ -52,6 +53,33 @@ class Bridge {
|
|||||||
return this.dispatch_(action);
|
return this.dispatch_(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleStateSave(state) {
|
||||||
|
if (this.scheduleStateSaveIID) {
|
||||||
|
clearTimeout(this.scheduleStateSaveIID);
|
||||||
|
this.scheduleStateSaveIID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scheduleStateSaveIID = setTimeout(() => {
|
||||||
|
this.scheduleStateSaveIID = null;
|
||||||
|
|
||||||
|
const toSave = {
|
||||||
|
selectedFolderId: state.selectedFolderId,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.info('Popup: Saving state', toSave);
|
||||||
|
|
||||||
|
this.storageSet(toSave);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreState() {
|
||||||
|
const s = await this.storageGet(null);
|
||||||
|
console.info('Popup: Restoring saved state:', s);
|
||||||
|
if (!s) return;
|
||||||
|
|
||||||
|
if (s.selectedFolderId) this.dispatch({ type: 'SELECTED_FOLDER_SET', id: s.selectedFolderId });
|
||||||
|
}
|
||||||
|
|
||||||
async findClipperServerPort() {
|
async findClipperServerPort() {
|
||||||
this.dispatch({ type: 'CLIPPER_SERVER_SET', foundState: 'searching' });
|
this.dispatch({ type: 'CLIPPER_SERVER_SET', foundState: 'searching' });
|
||||||
|
|
||||||
@ -68,6 +96,9 @@ class Bridge {
|
|||||||
this.clipperServerPortStatus_ = 'found';
|
this.clipperServerPortStatus_ = 'found';
|
||||||
this.clipperServerPort_ = state.port;
|
this.clipperServerPort_ = state.port;
|
||||||
this.dispatch({ type: 'CLIPPER_SERVER_SET', foundState: 'found', port: state.port });
|
this.dispatch({ type: 'CLIPPER_SERVER_SET', foundState: 'found', port: state.port });
|
||||||
|
|
||||||
|
const folders = await this.folderTree();
|
||||||
|
this.dispatch({ type: 'FOLDERS_SET', folders: folders });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -152,6 +183,37 @@ class Bridge {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async folderTree() {
|
||||||
|
return this.clipperApiExec('GET', 'folders');
|
||||||
|
}
|
||||||
|
|
||||||
|
async storageSet(keys) {
|
||||||
|
if (this.browserSupportsPromises_) return this.browser().storage.local.set(keys);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.browser().storage.local.set(keys, () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async storageGet(keys, defaultValue = null) {
|
||||||
|
if (this.browserSupportsPromises_) {
|
||||||
|
try {
|
||||||
|
const r = await this.browser().storage.local.get(keys);
|
||||||
|
return r;
|
||||||
|
} catch (error) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.browser().storage.local.get(keys, (result) => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sendCommandToActiveTab(command) {
|
async sendCommandToActiveTab(command) {
|
||||||
const tabs = await this.tabsQuery({ active: true, currentWindow: true });
|
const tabs = await this.tabsQuery({ active: true, currentWindow: true });
|
||||||
if (!tabs.length) {
|
if (!tabs.length) {
|
||||||
@ -166,6 +228,30 @@ class Bridge {
|
|||||||
await this.tabsSendMessage(tabs[0].id, command);
|
await this.tabsSendMessage(tabs[0].id, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clipperApiExec(method, path, body) {
|
||||||
|
console.info('Popup: ' + method + ' ' + path);
|
||||||
|
|
||||||
|
const baseUrl = await this.clipperServerBaseUrl();
|
||||||
|
|
||||||
|
const fetchOptions = {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body) fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
||||||
|
|
||||||
|
const response = await fetch(baseUrl + "/" + path, fetchOptions)
|
||||||
|
if (!response.ok) {
|
||||||
|
const msg = await response.text();
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
async sendContentToJoplin(content) {
|
async sendContentToJoplin(content) {
|
||||||
console.info('Popup: Sending to Joplin...');
|
console.info('Popup: Sending to Joplin...');
|
||||||
|
|
||||||
@ -176,20 +262,9 @@ class Bridge {
|
|||||||
|
|
||||||
const baseUrl = await this.clipperServerBaseUrl();
|
const baseUrl = await this.clipperServerBaseUrl();
|
||||||
|
|
||||||
const response = await fetch(baseUrl + "/notes", {
|
await this.clipperApiExec('POST', 'notes', content);
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(content)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });
|
||||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: false, errorMessage: response.text() } });
|
|
||||||
} else {
|
|
||||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: true } });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: false, errorMessage: error.message } });
|
this.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: false, errorMessage: error.message } });
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import App from './App';
|
|||||||
|
|
||||||
const { Provider } = require('react-redux');
|
const { Provider } = require('react-redux');
|
||||||
const { bridge } = require('./bridge');
|
const { bridge } = require('./bridge');
|
||||||
const { createStore } = require('redux');
|
const { createStore, applyMiddleware } = require('redux');
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
warning: '',
|
warning: '',
|
||||||
@ -15,8 +15,21 @@ const defaultState = {
|
|||||||
foundState: 'idle',
|
foundState: 'idle',
|
||||||
port: null,
|
port: null,
|
||||||
},
|
},
|
||||||
|
folders: [],
|
||||||
|
selectedFolderId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const reduxMiddleware = store => next => async (action) => {
|
||||||
|
const result = next(action);
|
||||||
|
const newState = store.getState();
|
||||||
|
|
||||||
|
if (['SELECTED_FOLDER_SET'].indexOf(action.type) >= 0) {
|
||||||
|
bridge().scheduleStateSave(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function reducer(state = defaultState, action) {
|
function reducer(state = defaultState, action) {
|
||||||
let newState = state;
|
let newState = state;
|
||||||
|
|
||||||
@ -42,6 +55,16 @@ function reducer(state = defaultState, action) {
|
|||||||
newState = Object.assign({}, state);
|
newState = Object.assign({}, state);
|
||||||
newState.contentUploadOperation = action.operation;
|
newState.contentUploadOperation = action.operation;
|
||||||
|
|
||||||
|
} else if (action.type === 'FOLDERS_SET') {
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.folders = action.folders;
|
||||||
|
|
||||||
|
} else if (action.type === 'SELECTED_FOLDER_SET') {
|
||||||
|
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.selectedFolderId = action.id;
|
||||||
|
|
||||||
} else if (action.type === 'CLIPPER_SERVER_SET') {
|
} else if (action.type === 'CLIPPER_SERVER_SET') {
|
||||||
|
|
||||||
newState = Object.assign({}, state);
|
newState = Object.assign({}, state);
|
||||||
@ -55,9 +78,10 @@ function reducer(state = defaultState, action) {
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = createStore(reducer);
|
const store = createStore(reducer, applyMiddleware(reduxMiddleware));
|
||||||
|
|
||||||
bridge().init(window.browser ? window.browser : window.chrome, !!window.browser, store.dispatch);
|
bridge().init(window.browser ? window.browser : window.chrome, !!window.browser, store.dispatch);
|
||||||
|
bridge().restoreState();
|
||||||
|
|
||||||
console.info('Popup: Creating React app...');
|
console.info('Popup: Creating React app...');
|
||||||
|
|
||||||
|
@ -161,7 +161,10 @@ class BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async all(options = null) {
|
static async all(options = null) {
|
||||||
let q = this.applySqlOptions(options, 'SELECT * FROM `' + this.tableName() + '`');
|
if (!options) options = {};
|
||||||
|
if (!options.fields) options.fields = '*';
|
||||||
|
|
||||||
|
let q = this.applySqlOptions(options, 'SELECT ' + this.db().escapeFields(options.fields) + ' FROM `' + this.tableName() + '`');
|
||||||
return this.modelSelectAll(q.sql);
|
return this.modelSelectAll(q.sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,14 +64,6 @@ class ClipperServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// startState() {
|
|
||||||
// return this.startState_;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// port() {
|
|
||||||
// return this.port_;
|
|
||||||
// }
|
|
||||||
|
|
||||||
htmlToMdParser() {
|
htmlToMdParser() {
|
||||||
if (this.htmlToMdParser_) return this.htmlToMdParser_;
|
if (this.htmlToMdParser_) return this.htmlToMdParser_;
|
||||||
this.htmlToMdParser_ = new HtmlToMd();
|
this.htmlToMdParser_ = new HtmlToMd();
|
||||||
@ -93,8 +85,8 @@ class ClipperServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestNote.parent_id) {
|
if (requestNote.parentId) {
|
||||||
output.parent_id = requestNote.parent_id;
|
output.parent_id = requestNote.parentId;
|
||||||
} else {
|
} else {
|
||||||
const folder = await Folder.defaultFolder();
|
const folder = await Folder.defaultFolder();
|
||||||
if (!folder) throw new Error('Cannot find folder for note');
|
if (!folder) throw new Error('Cannot find folder for note');
|
||||||
@ -227,11 +219,11 @@ class ClipperServer {
|
|||||||
|
|
||||||
this.server_ = require('http').createServer();
|
this.server_ = require('http').createServer();
|
||||||
|
|
||||||
this.server_.on('request', (request, response) => {
|
this.server_.on('request', async (request, response) => {
|
||||||
|
|
||||||
const writeCorsHeaders = (code) => {
|
const writeCorsHeaders = (code, contentType = "application/json") => {
|
||||||
response.writeHead(code, {
|
response.writeHead(code, {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": contentType,
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
|
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
|
||||||
'Access-Control-Allow-Headers': 'X-Requested-With,content-type',
|
'Access-Control-Allow-Headers': 'X-Requested-With,content-type',
|
||||||
@ -245,7 +237,7 @@ class ClipperServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const writeResponseText = (code, text) => {
|
const writeResponseText = (code, text) => {
|
||||||
writeCorsHeaders(code);
|
writeCorsHeaders(code, 'text/plain');
|
||||||
response.write(text);
|
response.write(text);
|
||||||
response.end();
|
response.end();
|
||||||
}
|
}
|
||||||
@ -259,6 +251,11 @@ class ClipperServer {
|
|||||||
if (url.pathname === '/ping') {
|
if (url.pathname === '/ping') {
|
||||||
return writeResponseText(200, 'JoplinClipperServer');
|
return writeResponseText(200, 'JoplinClipperServer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/folders') {
|
||||||
|
const structure = await Folder.allAsTree({ fields: ['id', 'parent_id', 'title'] });
|
||||||
|
return writeResponseJson(200, structure);
|
||||||
|
}
|
||||||
} else if (request.method === 'POST') {
|
} else if (request.method === 'POST') {
|
||||||
if (url.pathname === '/notes') {
|
if (url.pathname === '/notes') {
|
||||||
let body = '';
|
let body = '';
|
||||||
|
@ -127,6 +127,34 @@ class Folder extends BaseItem {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async allAsTree(options = null) {
|
||||||
|
const all = await this.all(options);
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/49387427/561309
|
||||||
|
function getNestedChildren(models, parentId) {
|
||||||
|
const nestedTreeStructure = [];
|
||||||
|
const length = models.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const model = models[i];
|
||||||
|
|
||||||
|
if (model.parent_id == parentId) {
|
||||||
|
const children = getNestedChildren(models, model.id);
|
||||||
|
|
||||||
|
if (children.length > 0) {
|
||||||
|
model.children = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
nestedTreeStructure.push(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nestedTreeStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getNestedChildren(all, '');
|
||||||
|
}
|
||||||
|
|
||||||
static load(id) {
|
static load(id) {
|
||||||
if (id == this.conflictFolderId()) return this.conflictFolder();
|
if (id == this.conflictFolderId()) return this.conflictFolder();
|
||||||
return super.load(id);
|
return super.load(id);
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
"_releases",
|
"_releases",
|
||||||
"ReactNativeClient/lib/csstojs",
|
"ReactNativeClient/lib/csstojs",
|
||||||
"Clipper/joplin-webclipper/popup/build",
|
"Clipper/joplin-webclipper/popup/build",
|
||||||
|
"Clipper/joplin-webclipper/dist",
|
||||||
],
|
],
|
||||||
"path": "."
|
"path": "."
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user