Skip to content

Commit

Permalink
FEATURE: Upload a picture into a document (see #153).
Browse files Browse the repository at this point in the history
Co-Authored-By: Martin Gandon <[email protected]>
  • Loading branch information
2 people authored and benel committed Jun 19, 2024
1 parent 8cdb731 commit 597670f
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 7 deletions.
29 changes: 24 additions & 5 deletions frontend/src/components/EditableText.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import '../styles/EditableText.css';

import { useState, useEffect } from 'react';
import FormattedText from './FormattedText';
import PassageMarginMenu from './PassageMarginMenu';
import {v4 as uuid} from 'uuid';

function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment, setHighlightedText, backend, setLastUpdate}) {
Expand Down Expand Up @@ -42,14 +43,29 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment,
}, [fragment]);

let handleClick = () => {
setHighlightedText('');
setBeingEdited(true);
updateEditedDocument()
.then((x) => {
setEditedText(parsePassage(x.text));
});
};

let handleImageUrl = (imageTag) => {
backend.getDocument(id).then((editedDocument) => {
let parsedText = parsePassage(editedDocument.text) + imageTag;
let text = (rubric)
? editedDocument.text.replace(PASSAGE, `{${rubric}} ${parsedText}`)
: parsedText;
backend.putDocument({ ...editedDocument, text })
.then(x => {
setEditedText(parsedText);
return x;
})
.then(x => setLastUpdate(x.rev))
.catch(console.error);
});
};

let handleChange = (event) => {
setEditedText(event.target.value);
};
Expand All @@ -70,10 +86,13 @@ function EditableText({id, text, rubric, isPartOf, links, fragment, setFragment,
};

if (!beingEdited) return (
<div className="editable content" onClick={handleClick} title="Edit content...">
<FormattedText {...{setHighlightedText}}>
{editedText || text}
</FormattedText>
<div className="editable content" title="Edit content...">
<div className="formatted-text" onClick={handleClick}>
<FormattedText {...{setHighlightedText}}>
{editedText || text}
</FormattedText>
</div>
<PassageMarginMenu {... {id, backend, handleImageUrl}}/>
</div>
);
return (
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/components/PassageMarginMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import '../styles/PassageMarginMenu.css';

import { Dropdown } from 'react-bootstrap';
import {forwardRef, useRef} from 'react';
import { ThreeDotsVertical } from 'react-bootstrap-icons';

function PassageMarginMenu ({ id, backend, handleImageUrl }) {
const fileInputRef = useRef(null);

const handleClick = () => {
fileInputRef.current.click();
};

const handleFileChange = (event) => {
const file = event.target.files[0];
if (file) backend.putAttachment(id, file, (response) => {
handleImageUrl(`![<IMAGE DESCRIPTION>](${response.url})`);
});
};

return (
<>
<Dropdown title="Block actions...">
<Dropdown.Toggle as={BlockMenuButton}/>
<Dropdown.Menu id="block-actions">
<Dropdown.Item onClick={handleClick}>Add an image</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<input
id="image-input"
type="file"
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFileChange}
/>
</>
);
}

const BlockMenuButton = forwardRef(({ children, onClick }, ref) => (
<ThreeDotsVertical
onClick={(e) => {
e.preventDefault();
onClick(e);
}}
ref={ref} class="editable-button">
{children}
</ThreeDotsVertical>
));

export default PassageMarginMenu;
26 changes: 26 additions & 0 deletions frontend/src/hyperglosae.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,32 @@ function Hyperglosae(logger) {
return x;
});

this.getDocumentMetadata = (id) =>
fetch(`${service}/${id}`, {
method: 'HEAD',
headers: basicAuthentication({ force: false })
});

this.putAttachment = (id, attachment, callback) =>
this.getDocumentMetadata(id).then(x => {
const reader = new FileReader();
reader.readAsArrayBuffer(attachment);
reader.onload = () => {
const arrayBuffer = reader.result;

fetch(`${service}/${id}/${attachment.name}`, {
method: 'PUT',
headers: {
...basicAuthentication({ force: false }),
// ETag is the header that carries the current rev.
'If-Match': x.headers.get('ETag'),
'Content-Type': attachment.type
},
body: arrayBuffer
}).then(response => callback(response));
};
});

this.authenticate = ({name, password}) => {
this.credentials = {name, password};
return fetch(`${service}`, {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/routes/Lectern.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function Lectern({backend}) {
if (isPartOf === id) {
part.source.push(text);
} else {
part.scholia = [...part.scholia || [], {id: x.id, text, isPartOf, rubric: x.key[1]}];
part.scholia = [...part.scholia || [], {id: x.id, rev: x.rev, text, isPartOf, rubric: x.key[1]}];
}
if (i === length - 1) {
return [...whole, part];
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/styles/EditableText.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
.editable:hover {
.content {
display: flex;
}

.formatted-text {
flex: 1;
}

.formatted-text:hover {
background: lightgray;
border-color: black;
}
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/styles/PassageMarginMenu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.editable-button {
visibility: hidden;
margin-top: 0.25rem;
color: crimson;
}

.editable:hover .editable-button {
visibility: visible;
cursor: pointer;
}

#block-actions .dropdown-item:hover {
background-color: lightgray;
}

#block-actions .dropdown-item:active {
color: black;
}

0 comments on commit 597670f

Please sign in to comment.