Clipper: Resolves #681: Allow adding tags from Web Clipper

This commit is contained in:
Laurent Cozic
2018-09-23 18:03:11 +01:00
parent e7a12bb0dd
commit 77f089654e
7 changed files with 158 additions and 26 deletions
@@ -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({
+24 -5
View File
@@ -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 {
+102 -20
View File
@@ -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);