Issue #107
This commit is contained in:
parent
e7d7cd1948
commit
9657769e42
|
@ -11,6 +11,7 @@ export default class Selection {
|
||||||
|
|
||||||
constructor(target, body) {
|
constructor(target, body) {
|
||||||
this.underlying = {
|
this.underlying = {
|
||||||
|
'@context': 'http://www.w3.org/ns/anno.jsonld',
|
||||||
type: 'Selection',
|
type: 'Selection',
|
||||||
body: body || [],
|
body: body || [],
|
||||||
target
|
target
|
||||||
|
@ -21,7 +22,7 @@ export default class Selection {
|
||||||
clone = opt_props => {
|
clone = opt_props => {
|
||||||
// Deep-clone
|
// Deep-clone
|
||||||
const cloned = new Selection();
|
const cloned = new Selection();
|
||||||
cloned.underlying = JSON.parse(JSON.stringify(this.underlying));
|
cloned.underlying = JSON.parse(JSON.stringify(this.underlying));
|
||||||
|
|
||||||
if (opt_props)
|
if (opt_props)
|
||||||
cloned.underlying = { ...cloned.underlying, ...opt_props };
|
cloned.underlying = { ...cloned.underlying, ...opt_props };
|
||||||
|
@ -29,6 +30,10 @@ export default class Selection {
|
||||||
return cloned;
|
return cloned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get context() {
|
||||||
|
return this.underlying['@context'];
|
||||||
|
}
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
return this.underlying.type;
|
return this.underlying.type;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +59,7 @@ export default class Selection {
|
||||||
return equals(this.underlying, other.underlying);
|
return equals(this.underlying, other.underlying);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get bodies() {
|
get bodies() {
|
||||||
return (Array.isArray(this.underlying.body)) ?
|
return (Array.isArray(this.underlying.body)) ?
|
||||||
this.underlying.body : [ this.underlying.body ];
|
this.underlying.body : [ this.underlying.body ];
|
||||||
|
@ -77,7 +82,7 @@ export default class Selection {
|
||||||
return this.selector('TextQuoteSelector')?.exact;
|
return this.selector('TextQuoteSelector')?.exact;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************/
|
/*******************************************/
|
||||||
/* Selection-specific properties & methods */
|
/* Selection-specific properties & methods */
|
||||||
/*******************************************/
|
/*******************************************/
|
||||||
|
|
||||||
|
@ -87,7 +92,6 @@ export default class Selection {
|
||||||
|
|
||||||
toAnnotation = () => {
|
toAnnotation = () => {
|
||||||
const a = Object.assign({}, this.underlying, {
|
const a = Object.assign({}, this.underlying, {
|
||||||
'@context': 'http://www.w3.org/ns/anno.jsonld',
|
|
||||||
'type': 'Annotation',
|
'type': 'Annotation',
|
||||||
'id': `#${uuid()}`
|
'id': `#${uuid()}`
|
||||||
});
|
});
|
||||||
|
@ -95,4 +99,4 @@ export default class Selection {
|
||||||
return new WebAnnotation(a);
|
return new WebAnnotation(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,13 +42,17 @@ export default class WebAnnotation {
|
||||||
return this.opts?.readOnly;
|
return this.opts?.readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************/
|
/*************************************/
|
||||||
/* Getters to forward properties of */
|
/* Getters to forward properties of */
|
||||||
/* the underlying annotation */
|
/* the underlying annotation */
|
||||||
/*************************************/
|
/*************************************/
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this.underlying.id;
|
return this.underlying.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get context() {
|
||||||
|
return this.underlying['@context'];
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
|
@ -83,8 +87,8 @@ export default class WebAnnotation {
|
||||||
return (Array.isArray(this.underlying.target)) ?
|
return (Array.isArray(this.underlying.target)) ?
|
||||||
this.underlying.target : [ this.underlying.target ];
|
this.underlying.target : [ this.underlying.target ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
/* Various access helpers and shorthands */
|
/* Various access helpers and shorthands */
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
|
|
||||||
|
@ -115,5 +119,5 @@ export default class WebAnnotation {
|
||||||
get end() {
|
get end() {
|
||||||
return this.selector('TextPositionSelector')?.end;
|
return this.selector('TextPositionSelector')?.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ const bounds = elem => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The popup editor component.
|
* The popup editor component.
|
||||||
*
|
*
|
||||||
* TODO instead of just updating the current annotation state,
|
* TODO instead of just updating the current annotation state,
|
||||||
* we could create a stack of revisions, and allow going back
|
* we could create a stack of revisions, and allow going back
|
||||||
* with CTRL+Z.
|
* with CTRL+Z.
|
||||||
|
@ -38,10 +38,10 @@ export default class Editor extends Component {
|
||||||
const nextBounds = bounds(next.selectedElement);
|
const nextBounds = bounds(next.selectedElement);
|
||||||
|
|
||||||
if (!this.props.annotation?.isEqual(next.annotation)) {
|
if (!this.props.annotation?.isEqual(next.annotation)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentAnnotation: next.annotation,
|
currentAnnotation: next.annotation,
|
||||||
selectionBounds: nextBounds
|
selectionBounds: nextBounds
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ selectionBounds: nextBounds });
|
this.setState({ selectionBounds: nextBounds });
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ export default class Editor extends Component {
|
||||||
// Defaults to true
|
// Defaults to true
|
||||||
const autoPosition =
|
const autoPosition =
|
||||||
this.props.autoPosition === undefined ? true : this.props.autoPosition;
|
this.props.autoPosition === undefined ? true : this.props.autoPosition;
|
||||||
|
|
||||||
if (window?.ResizeObserver) {
|
if (window?.ResizeObserver) {
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
if (!this.state.dragged)
|
if (!this.state.dragged)
|
||||||
|
@ -97,7 +97,7 @@ export default class Editor extends Component {
|
||||||
// Fire setPosition manually *only* for devices that don't support ResizeObserver
|
// Fire setPosition manually *only* for devices that don't support ResizeObserver
|
||||||
if (!this.state.dragged)
|
if (!this.state.dragged)
|
||||||
setPosition(this.props.wrapperEl, this.element.current, this.props.selectedElement, autoPosition);
|
setPosition(this.props.wrapperEl, this.element.current, this.props.selectedElement, autoPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creator and created/modified timestamp metadata **/
|
/** Creator and created/modified timestamp metadata **/
|
||||||
|
@ -122,7 +122,7 @@ export default class Editor extends Component {
|
||||||
getCurrentAnnotation = () =>
|
getCurrentAnnotation = () =>
|
||||||
this.state.currentAnnotation.clone();
|
this.state.currentAnnotation.clone();
|
||||||
|
|
||||||
/** Shorthand **/
|
/** Shorthand **/
|
||||||
updateCurrentAnnotation = (diff, saveImmediately) => this.setState({
|
updateCurrentAnnotation = (diff, saveImmediately) => this.setState({
|
||||||
currentAnnotation: this.state.currentAnnotation.clone(diff)
|
currentAnnotation: this.state.currentAnnotation.clone(diff)
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -130,15 +130,15 @@ export default class Editor extends Component {
|
||||||
this.onOk();
|
this.onOk();
|
||||||
})
|
})
|
||||||
|
|
||||||
onAppendBody = (body, saveImmediately) => this.updateCurrentAnnotation({
|
onAppendBody = (body, saveImmediately) => this.updateCurrentAnnotation({
|
||||||
body: [
|
body: [
|
||||||
...this.state.currentAnnotation.bodies,
|
...this.state.currentAnnotation.bodies,
|
||||||
{ ...body, ...this.creationMeta(body) }
|
{ ...body, ...this.creationMeta(body) }
|
||||||
]
|
]
|
||||||
}, saveImmediately);
|
}, saveImmediately);
|
||||||
|
|
||||||
onUpdateBody = (previous, updated, saveImmediately) => this.updateCurrentAnnotation({
|
onUpdateBody = (previous, updated, saveImmediately) => this.updateCurrentAnnotation({
|
||||||
body: this.state.currentAnnotation.bodies.map(body =>
|
body: this.state.currentAnnotation.bodies.map(body =>
|
||||||
body === previous ? { ...updated, ...this.creationMeta(updated) } : body)
|
body === previous ? { ...updated, ...this.creationMeta(updated) } : body)
|
||||||
}, saveImmediately);
|
}, saveImmediately);
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ export default class Editor extends Component {
|
||||||
body: this.state.currentAnnotation.bodies.filter(b => b !== body)
|
body: this.state.currentAnnotation.bodies.filter(b => b !== body)
|
||||||
}, saveImmediately);
|
}, saveImmediately);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For convenience: an 'append or update' shorthand.
|
* For convenience: an 'append or update' shorthand.
|
||||||
*/
|
*/
|
||||||
onUpsertBody = (arg1, arg2, saveImmediately) => {
|
onUpsertBody = (arg1, arg2, saveImmediately) => {
|
||||||
|
@ -167,21 +167,21 @@ export default class Editor extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advanced method for applying a batch of body changes
|
* Advanced method for applying a batch of body changes
|
||||||
* in one go (append, remove update), optionally saving
|
* in one go (append, remove update), optionally saving
|
||||||
* immediately afterwards. The argument is an array of
|
* immediately afterwards. The argument is an array of
|
||||||
* diff objects with the following structure:
|
* diff objects with the following structure:
|
||||||
*
|
*
|
||||||
* [
|
* [
|
||||||
* { action: 'append', body: bodyToAppend },
|
* { action: 'append', body: bodyToAppend },
|
||||||
* { action: 'update', previous: prevBody, updated: updatedBody }
|
* { action: 'update', previous: prevBody, updated: updatedBody }
|
||||||
* { action: 'remove', body: bodyToRemove },
|
* { action: 'remove', body: bodyToRemove },
|
||||||
*
|
*
|
||||||
* // Normal upsert, previous is optional
|
* // Normal upsert, previous is optional
|
||||||
* { action: 'upsert', previous: prevBody, updated: updatedBody }
|
* { action: 'upsert', previous: prevBody, updated: updatedBody }
|
||||||
*
|
*
|
||||||
* // Auto-upsert based on purpose
|
* // Auto-upsert based on purpose
|
||||||
* { action: 'upsert', body: bodyToUpsert }
|
* { action: 'upsert', body: bodyToUpsert }
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
|
@ -197,7 +197,7 @@ export default class Editor extends Component {
|
||||||
const toRemove = diffs
|
const toRemove = diffs
|
||||||
.filter(d => d.action === 'remove')
|
.filter(d => d.action === 'remove')
|
||||||
.map(d => d.body);
|
.map(d => d.body);
|
||||||
|
|
||||||
const toAppend = [
|
const toAppend = [
|
||||||
...diffs
|
...diffs
|
||||||
.filter(d => (d.action === 'append') || (d.action === 'upsert' && d.updated && !d.previous))
|
.filter(d => (d.action === 'append') || (d.action === 'upsert' && d.updated && !d.previous))
|
||||||
|
@ -208,12 +208,12 @@ export default class Editor extends Component {
|
||||||
.map(d => d.updated)
|
.map(d => d.updated)
|
||||||
];
|
];
|
||||||
|
|
||||||
const toUpdate = [
|
const toUpdate = [
|
||||||
...diffs
|
...diffs
|
||||||
.filter(d => (d.action === 'update') || (d.action === 'upsert' && d.updated && d.previous))
|
.filter(d => (d.action === 'update') || (d.action === 'upsert' && d.updated && d.previous))
|
||||||
.map(d => ({
|
.map(d => ({
|
||||||
previous: d.previous,
|
previous: d.previous,
|
||||||
updated: { ...d.updated, ...this.creationMeta(d.updated) }
|
updated: { ...d.updated, ...this.creationMeta(d.updated) }
|
||||||
})),
|
})),
|
||||||
|
|
||||||
...autoUpserts
|
...autoUpserts
|
||||||
|
@ -222,7 +222,7 @@ export default class Editor extends Component {
|
||||||
|
|
||||||
const updatedBodies = [
|
const updatedBodies = [
|
||||||
// Current bodies
|
// Current bodies
|
||||||
...this.state.currentAnnotation.bodies
|
...this.state.currentAnnotation.bodies
|
||||||
// Remove
|
// Remove
|
||||||
.filter(b => !toRemove.includes(b))
|
.filter(b => !toRemove.includes(b))
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ export default class Editor extends Component {
|
||||||
// Append
|
// Append
|
||||||
...toAppend
|
...toAppend
|
||||||
]
|
]
|
||||||
|
|
||||||
this.updateCurrentAnnotation({ body: updatedBodies }, saveImmediately);
|
this.updateCurrentAnnotation({ body: updatedBodies }, saveImmediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ export default class Editor extends Component {
|
||||||
*/
|
*/
|
||||||
onSetProperty = (property, value) => {
|
onSetProperty = (property, value) => {
|
||||||
// A list of properties the user is NOT allowed to set
|
// A list of properties the user is NOT allowed to set
|
||||||
const isForbidden = [ '@context', 'id', 'type', 'body', 'target' ].includes(property);
|
const isForbidden = [ '@context', 'id', 'type', 'body', 'target' ].includes(property);
|
||||||
|
|
||||||
if (isForbidden)
|
if (isForbidden)
|
||||||
throw new Exception(`Cannot set ${property} - not allowed`);
|
throw new Exception(`Cannot set ${property} - not allowed`);
|
||||||
|
@ -260,25 +260,39 @@ export default class Editor extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel = () =>
|
/**
|
||||||
|
* Adds a URI to the context field
|
||||||
|
*/
|
||||||
|
onAddContext = uri => {
|
||||||
|
const { currentAnnotation } = this.state;
|
||||||
|
const context = Array.isArray(currentAnnotation.context) ?
|
||||||
|
currentAnnotation.context : [ currentAnnotation.context ];
|
||||||
|
|
||||||
|
if (context.indexOf(uri) < 0) {
|
||||||
|
context.push(uri);
|
||||||
|
this.updateCurrentAnnotation({ '@context': context });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel = () =>
|
||||||
this.props.onCancel(this.props.annotation);
|
this.props.onCancel(this.props.annotation);
|
||||||
|
|
||||||
onOk = () => {
|
onOk = () => {
|
||||||
// Removes the state payload from all bodies
|
// Removes the state payload from all bodies
|
||||||
const undraft = annotation =>
|
const undraft = annotation =>
|
||||||
annotation.clone({
|
annotation.clone({
|
||||||
body : annotation.bodies.map(({ draft, ...rest }) => rest)
|
body : annotation.bodies.map(({ draft, ...rest }) => rest)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { currentAnnotation } = this.state;
|
const { currentAnnotation } = this.state;
|
||||||
|
|
||||||
// Current annotation is either a selection (if it was created from
|
// Current annotation is either a selection (if it was created from
|
||||||
// scratch just now) or an annotation (if it existed already and was
|
// scratch just now) or an annotation (if it existed already and was
|
||||||
// selected for editing)
|
// selected for editing)
|
||||||
if (currentAnnotation.bodies.length === 0 && !this.props.allowEmpty) {
|
if (currentAnnotation.bodies.length === 0 && !this.props.allowEmpty) {
|
||||||
if (currentAnnotation.isSelection)
|
if (currentAnnotation.isSelection)
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
else
|
else
|
||||||
this.props.onAnnotationDeleted(this.props.annotation);
|
this.props.onAnnotationDeleted(this.props.annotation);
|
||||||
} else {
|
} else {
|
||||||
if (currentAnnotation.isSelection)
|
if (currentAnnotation.isSelection)
|
||||||
|
@ -288,14 +302,14 @@ export default class Editor extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDelete = () =>
|
onDelete = () =>
|
||||||
this.props.onAnnotationDeleted(this.props.annotation);
|
this.props.onAnnotationDeleted(this.props.annotation);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { currentAnnotation } = this.state;
|
const { currentAnnotation } = this.state;
|
||||||
|
|
||||||
// Use default comment + tag widget unless host app overrides
|
// Use default comment + tag widget unless host app overrides
|
||||||
const widgets = this.props.widgets ?
|
const widgets = this.props.widgets ?
|
||||||
this.props.widgets.map(getWidget) : DEFAULT_WIDGETS;
|
this.props.widgets.map(getWidget) : DEFAULT_WIDGETS;
|
||||||
|
|
||||||
const isReadOnlyWidget = w => w.type.disableDelete ?
|
const isReadOnlyWidget = w => w.type.disableDelete ?
|
||||||
|
@ -305,7 +319,7 @@ export default class Editor extends Component {
|
||||||
env: this.props.env
|
env: this.props.env
|
||||||
}) : false;
|
}) : false;
|
||||||
|
|
||||||
const hasDelete = currentAnnotation &&
|
const hasDelete = currentAnnotation &&
|
||||||
// annotation has bodies or allowEmpty,
|
// annotation has bodies or allowEmpty,
|
||||||
(currentAnnotation.bodies.length > 0 || this.props.allowEmpty) && // AND
|
(currentAnnotation.bodies.length > 0 || this.props.allowEmpty) && // AND
|
||||||
!this.props.readOnly && // we are not in read-only mode AND
|
!this.props.readOnly && // we are not in read-only mode AND
|
||||||
|
@ -313,17 +327,17 @@ export default class Editor extends Component {
|
||||||
!widgets.some(isReadOnlyWidget); // every widget is deletable
|
!widgets.some(isReadOnlyWidget); // every widget is deletable
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
disabled={!this.props.detachable}
|
disabled={!this.props.detachable}
|
||||||
handle=".r6o-draggable"
|
handle=".r6o-draggable"
|
||||||
cancel=".r6o-btn, .r6o-btn *"
|
cancel=".r6o-btn, .r6o-btn *"
|
||||||
onDrag={() => this.setState({ dragged: true })}>
|
onDrag={() => this.setState({ dragged: true })}>
|
||||||
|
|
||||||
<div ref={this.element} className={this.state.dragged ? 'r6o-editor dragged' : 'r6o-editor'}>
|
<div ref={this.element} className={this.state.dragged ? 'r6o-editor dragged' : 'r6o-editor'}>
|
||||||
<div className="r6o-arrow" />
|
<div className="r6o-arrow" />
|
||||||
<div className="r6o-editor-inner">
|
<div className="r6o-editor-inner">
|
||||||
{widgets.map((widget, idx) =>
|
{widgets.map((widget, idx) =>
|
||||||
React.cloneElement(widget, {
|
React.cloneElement(widget, {
|
||||||
key: `${idx}`,
|
key: `${idx}`,
|
||||||
focus: idx === 0,
|
focus: idx === 0,
|
||||||
annotation : currentAnnotation,
|
annotation : currentAnnotation,
|
||||||
|
@ -335,33 +349,34 @@ export default class Editor extends Component {
|
||||||
onUpsertBody: this.onUpsertBody,
|
onUpsertBody: this.onUpsertBody,
|
||||||
onBatchModify: this.onBatchModify,
|
onBatchModify: this.onBatchModify,
|
||||||
onSetProperty: this.onSetProperty,
|
onSetProperty: this.onSetProperty,
|
||||||
onSaveAndClose: this.onOk
|
onAddContext: this.onAddContext,
|
||||||
|
onSaveAndClose: this.onOk
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ this.props.readOnly ? (
|
{ this.props.readOnly ? (
|
||||||
<div className="r6o-footer">
|
<div className="r6o-footer">
|
||||||
<button
|
<button
|
||||||
className="r6o-btn"
|
className="r6o-btn"
|
||||||
onClick={this.onCancel}>{i18n.t('Close')}</button>
|
onClick={this.onCancel}>{i18n.t('Close')}</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={this.props.detachable ? "r6o-footer r6o-draggable" : "r6o-footer"}>
|
className={this.props.detachable ? "r6o-footer r6o-draggable" : "r6o-footer"}>
|
||||||
{ hasDelete && (
|
{ hasDelete && (
|
||||||
<button
|
<button
|
||||||
className="r6o-btn left delete-annotation"
|
className="r6o-btn left delete-annotation"
|
||||||
title={i18n.t('Delete')}
|
title={i18n.t('Delete')}
|
||||||
onClick={this.onDelete}>
|
onClick={this.onDelete}>
|
||||||
<TrashIcon width={12} />
|
<TrashIcon width={12} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="r6o-btn outline"
|
className="r6o-btn outline"
|
||||||
onClick={this.onCancel}>{i18n.t('Cancel')}</button>
|
onClick={this.onCancel}>{i18n.t('Cancel')}</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="r6o-btn "
|
className="r6o-btn "
|
||||||
onClick={this.onOk}>{i18n.t('Ok')}</button>
|
onClick={this.onOk}>{i18n.t('Ok')}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,7 +18,8 @@ export default class WrappedWidget extends Component {
|
||||||
onUpsertBody: (previous, updated, saveImmediately) => props.onUpsertBody(previous, updated, saveImmediately),
|
onUpsertBody: (previous, updated, saveImmediately) => props.onUpsertBody(previous, updated, saveImmediately),
|
||||||
onRemoveBody: (body, saveImmediately) => props.onRemoveBody(body, saveImmediately),
|
onRemoveBody: (body, saveImmediately) => props.onRemoveBody(body, saveImmediately),
|
||||||
onBatchModify: (diffs, saveImmediately) => props.onBatchModify(diffs, saveImmediately),
|
onBatchModify: (diffs, saveImmediately) => props.onBatchModify(diffs, saveImmediately),
|
||||||
onSetProperty: (property, value) => props.onSetProperty(property, value),
|
onSetProperty: (property, value) => props.onSetProperty(property, value),
|
||||||
|
onAddContext: uri => props.onAddContext(uri),
|
||||||
onSaveAndClose: () => props.onSaveAndClose()
|
onSaveAndClose: () => props.onSaveAndClose()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue