mirror of
https://github.com/laurent22/joplin.git
synced 2025-03-29 21:21:15 +02:00
Clipper: Resolves #681: Allow adding tags from Web Clipper
This commit is contained in:
parent
e7a12bb0dd
commit
77f089654e
Clipper/joplin-webclipper
ReactNativeClient/lib
@ -82,6 +82,7 @@
|
||||
base_url: baseUrl(),
|
||||
url: location.origin + location.pathname + location.search,
|
||||
parent_id: command.parent_id,
|
||||
tags: command.tags || '',
|
||||
};
|
||||
}
|
||||
|
||||
@ -214,6 +215,7 @@
|
||||
crop_rect: selectionArea,
|
||||
url: location.origin + location.pathname,
|
||||
parent_id: command.parent_id,
|
||||
tags: command.tags,
|
||||
};
|
||||
|
||||
browser_.runtime.sendMessage({
|
||||
|
@ -100,21 +100,40 @@
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.App .Folders {
|
||||
.App .Folders,
|
||||
.App .Tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-items: top;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.App .Folders label {
|
||||
.App .Folders label,
|
||||
.App .Tags label {
|
||||
flex: 0;
|
||||
white-space: nowrap;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.App .Folders select {
|
||||
.App .Folders select,
|
||||
.App .Tags .AwesompleteInput {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.App .Tags .AwesompleteInput, .awesomplete {
|
||||
display: flex !important;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.App .Tags input {
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.App .ClearTagButton {
|
||||
margin-left: .5em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.App .StatusBar {
|
||||
|
@ -14,6 +14,7 @@ class AppComponent extends Component {
|
||||
|
||||
this.state = ({
|
||||
contentScriptLoaded: false,
|
||||
selectedTags: [],
|
||||
});
|
||||
|
||||
this.confirm_click = () => {
|
||||
@ -31,6 +32,7 @@ class AppComponent extends Component {
|
||||
bridge().sendCommandToActiveTab({
|
||||
name: 'simplifiedPageHtml',
|
||||
parent_id: this.props.selectedFolderId,
|
||||
tags: this.state.selectedTags.join(','),
|
||||
});
|
||||
}
|
||||
|
||||
@ -38,6 +40,7 @@ class AppComponent extends Component {
|
||||
bridge().sendCommandToActiveTab({
|
||||
name: 'completePageHtml',
|
||||
parent_id: this.props.selectedFolderId,
|
||||
tags: this.state.selectedTags.join(','),
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,6 +48,7 @@ class AppComponent extends Component {
|
||||
bridge().sendCommandToActiveTab({
|
||||
name: 'selectedHtml',
|
||||
parent_id: this.props.selectedFolderId,
|
||||
tags: this.state.selectedTags.join(','),
|
||||
});
|
||||
}
|
||||
|
||||
@ -56,6 +60,7 @@ class AppComponent extends Component {
|
||||
name: 'screenshot',
|
||||
api_base_url: baseUrl,
|
||||
parent_id: this.props.selectedFolderId,
|
||||
tags: this.state.selectedTags.join(','),
|
||||
});
|
||||
|
||||
window.close();
|
||||
@ -74,6 +79,41 @@ class AppComponent extends Component {
|
||||
id: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
this.tagCompChanged = this.tagCompChanged.bind(this);
|
||||
this.onAddTagClick = this.onAddTagClick.bind(this);
|
||||
this.onClearTagButtonClick = this.onClearTagButtonClick.bind(this);
|
||||
}
|
||||
|
||||
onAddTagClick(event) {
|
||||
const newTags = this.state.selectedTags.slice();
|
||||
newTags.push('');
|
||||
this.setState({ selectedTags: newTags });
|
||||
this.focusNewTagInput_ = true;
|
||||
}
|
||||
|
||||
onClearTagButtonClick(event) {
|
||||
const index = event.target.getAttribute('data-index');
|
||||
const newTags = this.state.selectedTags.slice();
|
||||
newTags.splice(index, 1);
|
||||
this.setState({ selectedTags: newTags });
|
||||
}
|
||||
|
||||
tagCompChanged(event) {
|
||||
const index = Number(event.target.getAttribute('data-index'));
|
||||
const value = event.target.value;
|
||||
|
||||
if (this.state.selectedTags.length <= index) {
|
||||
const newTags = this.state.selectedTags.slice();
|
||||
newTags.push(value);
|
||||
this.setState({ selectedTags: newTags });
|
||||
} else {
|
||||
if (this.state.selectedTags[index] !== value) {
|
||||
const newTags = this.state.selectedTags.slice();
|
||||
newTags[index] = value;
|
||||
this.setState({ selectedTags: newTags });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadContentScripts() {
|
||||
@ -89,6 +129,19 @@ class AppComponent extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.focusNewTagInput_) {
|
||||
this.focusNewTagInput_ = false;
|
||||
let lastRef = null;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const ref = this.refs['tagSelector' + i];
|
||||
if (!ref) break;
|
||||
lastRef = ref;
|
||||
}
|
||||
if (lastRef) lastRef.focus();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.contentScriptLoaded) return 'Loading...';
|
||||
|
||||
@ -119,24 +172,17 @@ class AppComponent extends Component {
|
||||
<p className="Info">{ msg }</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
if (hasContent) {
|
||||
previewComponent = (
|
||||
<div className="Preview">
|
||||
<input className={"Title"} value={content.title} onChange={this.contentTitle_change}/>
|
||||
<div className={"BodyWrapper"}>
|
||||
<div className={"Body"} dangerouslySetInnerHTML={{__html: content.body_html}}></div>
|
||||
</div>
|
||||
<a className={"Confirm Button"} onClick={this.confirm_click}>Confirm</a>
|
||||
} else if (hasContent) {
|
||||
previewComponent = (
|
||||
<div className="Preview">
|
||||
<h2>Preview:</h2>
|
||||
<input className={"Title"} value={content.title} onChange={this.contentTitle_change}/>
|
||||
<div className={"BodyWrapper"}>
|
||||
<div className={"Body"} dangerouslySetInnerHTML={{__html: content.body_html}}></div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
previewComponent = (
|
||||
<div className="Preview">
|
||||
<p className="Info">(No preview yet)</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<a className={"Confirm Button"} onClick={this.confirm_click}>Confirm</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const clipperStatusComp = () => {
|
||||
@ -166,8 +212,6 @@ class AppComponent extends Component {
|
||||
return <div className="StatusBar"><img alt={foundState} className="Led" src={led}/><span className="ServerStatus">{ msg }{ helpLink }</span></div>
|
||||
}
|
||||
|
||||
console.info(this.props.selectedFolderId);
|
||||
|
||||
const foldersComp = () => {
|
||||
const optionComps = [];
|
||||
|
||||
@ -196,6 +240,37 @@ class AppComponent extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const tagsComp = () => {
|
||||
const comps = [];
|
||||
for (let i = 0; i < this.state.selectedTags.length; i++) {
|
||||
comps.push(<div>
|
||||
<input
|
||||
ref={'tagSelector' + i}
|
||||
data-index={i}
|
||||
key={i}
|
||||
type="text"
|
||||
list="tags"
|
||||
value={this.state.selectedTags[i]}
|
||||
onChange={this.tagCompChanged}
|
||||
onInput={this.tagCompChanged}
|
||||
/>
|
||||
<a data-index={i} href="#" className="ClearTagButton" onClick={this.onClearTagButtonClick}>[x]</a>
|
||||
</div>);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{comps}
|
||||
<a className="AddTagButton" href="#" onClick={this.onAddTagClick}>Add tag</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const tagDataListOptions = [];
|
||||
for (let i = 0; i < this.props.tags.length; i++) {
|
||||
const tag = this.props.tags[i];
|
||||
tagDataListOptions.push(<option key={tag.id}>{tag.title}</option>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="Controls">
|
||||
@ -207,8 +282,14 @@ class AppComponent extends Component {
|
||||
</ul>
|
||||
</div>
|
||||
{ foldersComp() }
|
||||
<div className="Tags">
|
||||
<label>Tags:</label>
|
||||
{tagsComp()}
|
||||
<datalist id="tags">
|
||||
{tagDataListOptions}
|
||||
</datalist>
|
||||
</div>
|
||||
{ warningComponent }
|
||||
<h2>Preview:</h2>
|
||||
{ previewComponent }
|
||||
{ clipperStatusComp() }
|
||||
</div>
|
||||
@ -224,6 +305,7 @@ const mapStateToProps = (state) => {
|
||||
contentUploadOperation: state.contentUploadOperation,
|
||||
clipperServer: state.clipperServer,
|
||||
folders: state.folders,
|
||||
tags: state.tags,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
};
|
||||
};
|
||||
|
@ -28,6 +28,7 @@ class Bridge {
|
||||
base_url: command.base_url,
|
||||
source_url: command.url,
|
||||
parent_id: command.parent_id,
|
||||
tags: command.tags || '',
|
||||
};
|
||||
|
||||
this.dispatch({ type: 'CLIPPED_CONTENT_SET', content: content });
|
||||
@ -122,6 +123,9 @@ class Bridge {
|
||||
|
||||
const folders = await this.folderTree();
|
||||
this.dispatch({ type: 'FOLDERS_SET', folders: folders });
|
||||
|
||||
const tags = await this.clipperApiExec('GET', 'tags');
|
||||
this.dispatch({ type: 'TAGS_SET', tags: tags });
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -16,6 +16,7 @@ const defaultState = {
|
||||
port: null,
|
||||
},
|
||||
folders: [],
|
||||
tags: [],
|
||||
selectedFolderId: null,
|
||||
env: 'prod',
|
||||
};
|
||||
@ -65,6 +66,11 @@ function reducer(state = defaultState, action) {
|
||||
newState.selectedFolderId = action.folders[0].id;
|
||||
}
|
||||
|
||||
} else if (action.type === 'TAGS_SET') {
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.tags = action.tags;
|
||||
|
||||
} else if (action.type === 'SELECTED_FOLDER_SET') {
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
|
@ -3,6 +3,7 @@ const urlParser = require("url");
|
||||
const Note = require('lib/models/Note');
|
||||
const Folder = require('lib/models/Folder');
|
||||
const Resource = require('lib/models/Resource');
|
||||
const Tag = require('lib/models/Tag');
|
||||
const Setting = require('lib/models/Setting');
|
||||
const { shim } = require('lib/shim');
|
||||
const md5 = require('md5');
|
||||
@ -257,6 +258,10 @@ class ClipperServer {
|
||||
const structure = await Folder.allAsTree({ fields: ['id', 'parent_id', 'title'] });
|
||||
return writeResponseJson(200, structure);
|
||||
}
|
||||
|
||||
if (url.pathname === '/tags') {
|
||||
return writeResponseJson(200, await Tag.all({ fields: ['id', 'title'] }));
|
||||
}
|
||||
} else if (request.method === 'POST') {
|
||||
if (url.pathname === '/notes') {
|
||||
let body = '';
|
||||
@ -278,6 +283,11 @@ class ClipperServer {
|
||||
|
||||
note = await Note.save(note);
|
||||
|
||||
if (requestNote.tags) {
|
||||
const tagTitles = requestNote.tags.split(',');
|
||||
await Tag.setNoteTagsByTitles(note.id, tagTitles);
|
||||
}
|
||||
|
||||
if (requestNote.image_data_url) {
|
||||
await this.attachImageFromDataUrl_(note, requestNote.image_data_url, requestNote.crop_rect);
|
||||
}
|
||||
|
@ -173,7 +173,16 @@ function shimInit() {
|
||||
if (shim.isElectron()) {
|
||||
const nativeImage = require('electron').nativeImage;
|
||||
let image = nativeImage.createFromDataURL(imageDataUrl);
|
||||
if (options.cropRect) image = image.crop(options.cropRect);
|
||||
if (image.isEmpty()) throw new Error('Could not convert data URL to image');
|
||||
if (options.cropRect) {
|
||||
// Crop rectangle values need to be rounded or the crop() call will fail
|
||||
const c = options.cropRect;
|
||||
if ('x' in c) c.x = Math.round(c.x);
|
||||
if ('y' in c) c.y = Math.round(c.y);
|
||||
if ('width' in c) c.width = Math.round(c.width);
|
||||
if ('height' in c) c.height = Math.round(c.height);
|
||||
image = image.crop(c);
|
||||
}
|
||||
const mime = mimeUtils.fromDataUrl(imageDataUrl);
|
||||
await shim.writeImageToFile(image, mime, filePath);
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user