Add markdown editor with image support

This commit is contained in:
Spencer Pincott
2022-10-06 09:33:46 -04:00
parent c9ffcd8407
commit 858d96f842
5 changed files with 577 additions and 24 deletions

View File

@@ -1,6 +1,7 @@
import app from "firebase/app"
import "firebase/auth"
import "firebase/firestore"
import "firebase/storage";
// store private keys in .env file
// the prefix GATSBY_ is necessary here
@@ -22,6 +23,7 @@ class Firebase {
app.initializeApp(config)
this.auth = app.auth()
this.db = app.firestore()
this.storage = app.storage()
app
.firestore()
.enablePersistence()

View File

@@ -6,6 +6,7 @@ import styled from "@emotion/styled"
import { jsx, css, keyframes } from "@emotion/core"
import { compose } from "recompose"
import { withTheme } from "@emotion/react"
import Editor from "rich-markdown-editor";
import { withFirebase } from "components/firebase"
import { withAuthentication } from "components/session"
@@ -15,6 +16,7 @@ import { SIZES } from "styles/constants"
import Seek from "components/Seek"
import Icon from "components/Icon"
import { resizeImage } from "../utils/file"
const EntryHeading = styled.div`
display: flex;
@@ -50,33 +52,36 @@ const OfflineNotice = styled.div`
font-size: ${SIZES.tiny};
border-radius: 3px;
`
const JournalEntryArea = styled.textarea`
font-family: sans-serif;
const JournalEntryArea = styled(Editor)`
flex-grow: 0.85;
color: ${(props) => props.theme.colors.primary};
caret-color: ${(props) => props.theme.colors.secondary};
color: ${props => props.theme.colors.primary};
caret-color: ${props => props.theme.colors.secondary};
background-color: transparent;
line-height: 1.5;
letter-spacing: 0.5px;
height: calc(100vh - 300px);
width: 100%;
border: none;
resize: none;
outline: none;
font-size: ${SIZES.small};
border-radius: 1px;
margin-top: ${SIZES.tiny};
padding-top: 0px;
padding-bottom: 0px;
padding-left: ${SIZES.medium};
padding-right: ${SIZES.medium};
overflow: scroll;
justify-content: unset !important;
& > div {
height: 100%;
background-color: transparent;
}
&::placeholder {
color: ${(props) => props.theme.colors.tertiary};
color: ${props => {
return props.theme.colors.tertiary}};
}
&::selection {
background: ${(props) => props.theme.colors.hover};
}
&:focus {
box-shadow: 0 0 0 8px ${(props) => props.theme.colors.bodyBackground},
0 0 0 10px ${(props) => props.theme.colors.hover};
&:focus-within {
box-shadow: 0 0 0 8px ${props => props.theme.colors.bodyBackground},
0 0 0 10px ${props => props.theme.colors.hover};
}
`
const Buttons = styled.div`
@@ -153,8 +158,10 @@ class Day extends React.Component {
.get(options)
.then((doc) => {
if (doc.data()) {
console.log("I have data!", doc.data().text)
this.setState({ text: doc.data().text, loading: false })
} else {
console.log("I don't have data!")
this.setState({ text: "", loading: false })
}
})
@@ -173,9 +180,9 @@ class Day extends React.Component {
})
}
onChangeText = (e) => {
handleChange = value => {
if (this.timeout) clearTimeout(this.timeout)
const text = e.target.value
const text = value()
const { year, month, day } = this.props
this.setState({ text, lastEditedAt: new Date() })
@@ -202,6 +209,7 @@ class Day extends React.Component {
saveText = (text, year, month, day) => {
this.setState({ saving: true })
const { firebase, authUser } = this.props
console.log("I am saving data!", text);
firebase.db
.collection("entries")
.doc(`${year}${month}${day}-${authUser.uid}`)
@@ -226,6 +234,16 @@ class Day extends React.Component {
})
}
handleFileUpload = (file) => {
return resizeImage(file).then(optimizedFile => {
const { firebase, authUser } = this.props;
const storageRef = firebase.storage.ref()
const imagesRef = storageRef.child(`IMAGES/${authUser.uid}/${Date.now()}`);
return imagesRef.put(optimizedFile)
.then(snapshot => snapshot.ref.getDownloadURL());
});
}
render() {
const { year, month, day, theme } = this.props
const online = this.context
@@ -282,14 +300,10 @@ class Day extends React.Component {
) : (
<>
<JournalEntryArea
id="entry-text-area"
autoFocus={true}
placeholder="Start writing..."
onChange={(e) => this.onChangeText(e)}
value={text}
css={css`
animation: ${fadeKeyFrames} 0.2s ease-in;
`}
defaultValue={text}
onChange={this.handleChange}
dark={theme.name === "DARK"}
uploadImage={async file => this.handleFileUpload(file)}
/>
<Buttons>
<Icon

39
src/utils/file.js Normal file
View File

@@ -0,0 +1,39 @@
const DEFAULT_MAX_IMAGE_SIZE = 1200;
export const resizeImage = (file, size = DEFAULT_MAX_IMAGE_SIZE) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = readerEvent => {
const image = new Image();
image.onload = () => {
const canvas = document.createElement("canvas");
let width = image.width;
let height = image.height;
if (width > height) {
if (width > size) {
height *= size / width;
width = size;
}
} else {
if (height > size) {
width *= size / height;
height = size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
canvas.toBlob(blobImage => {
blobImage.name = file.name;
resolve(blobImage);
});
};
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
});
};