Skip to content

Commit

Permalink
feat: add images to editor
Browse files Browse the repository at this point in the history
  • Loading branch information
rob-at-airwalk committed Jun 29, 2024
1 parent fd0bad8 commit 042c087
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 38 deletions.
Binary file added core
Binary file not shown.
19 changes: 8 additions & 11 deletions src/_components/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,8 @@ interface EditorProps {
context: ContentItem;
defaultContext: ContentItem | undefined;
editorSaveHandler: (arg: string) => Promise<string>;
imageUploadHandler: (image: File, context: ContentItem) => Promise<string>;
imagePreviewHandler: (
imageSource: string,
context: ContentItem
) => Promise<string>;
imageUploadHandler: (image: File) => Promise<any>;
imagePreviewHandler: (imageSource: string) => Promise<string>;
enabled?: boolean;
top: number;
editorRef?: React.MutableRefObject<MDXEditorMethods | null>;
Expand Down Expand Up @@ -202,9 +199,9 @@ const Editor = React.memo(function EditorC({
imagePlugin({
disableImageResize: true,
imageUploadHandler: (image) =>
Promise.resolve(imageUploadHandler(image, context)),
Promise.resolve(imageUploadHandler(image)),
imagePreviewHandler: (imageSource) =>
Promise.resolve(imagePreviewHandler(imageSource, context)),
Promise.resolve(imagePreviewHandler(imageSource)),
}),
toolbarPlugin({
toolbarContents: () => (
Expand All @@ -226,7 +223,7 @@ const Editor = React.memo(function EditorC({
),
}),
],
[initialMarkdown, imageUploadHandler, context, imagePreviewHandler]
[initialMarkdown, imageUploadHandler, imagePreviewHandler]
);

const SaveButton = React.memo(function SaveButton() {
Expand Down Expand Up @@ -306,12 +303,12 @@ const Editor = React.memo(function EditorC({
</Alert>
</Snackbar>
<Snackbar
open={!!success}
open={success}
autoHideDuration={5000}
onClose={() => setSuccess(true)}
onClose={() => setSuccess(false)}
>
<Alert
onClose={() => setSuccess(true)}
onClose={() => setSuccess(false)}
severity='info'
sx={{ width: '100%' }}
>
Expand Down
8 changes: 4 additions & 4 deletions src/_components/Editor/NewContentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ export function NewContentDialog({
setError('');
try {
await handleDialog({ frontmatter });
} catch (err: any) {
setError(err.message);
} finally {
setIsLoading(false);
setTitle('');
setDocType('');
setParent('None');
setSelectedDropDown('');
} catch (err: any) {
setError(err.message);
} finally {
setIsLoading(false);
}
};

Expand Down
7 changes: 4 additions & 3 deletions src/_components/Editor/lib/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,16 @@ export async function createFile({
);
logger.info(response);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
const error = await response.json();
logger.error('Editor:createFile:Commit failed:', error);
throw new Error(`${error.error}`);
}

const result = await response.json();
// console.log('Editor:createFile:Commit successful:', data);
return result;
} catch (e: any) {
// console.error('Editor:createFile:Error committing file:', e.message);
return null;
throw new Error(`${e.message}`);
}
}

Expand Down
115 changes: 111 additions & 4 deletions src/_features/Mdx/EditorWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Box, LinearProgress } from '@mui/material';
import Container from '@mui/material/Container';
import matter from 'gray-matter';
import { usePathname, useRouter } from 'next/navigation';
import path from 'path';
import React, { useEffect, useRef, useState } from 'react';

import { Editor, NewBranchDialog, NewContentDialog } from '@/components/Editor';
Expand Down Expand Up @@ -150,7 +151,7 @@ export default function EditorWrapper({
message: e.message,
});

throw new Error(`Error creating file: ${e.message}`);
throw new Error(`${e.message}`);
}
}

Expand Down Expand Up @@ -202,6 +203,112 @@ export default function EditorWrapper({
router.push(newPathname);
};

const onSave = async (content: string | null) => {
try {
if (!content) {
throw new Error('No content to save');
}
if (!context.file) {
throw new Error('No file to save');
}
const normalizedFile = context.file.replace(/^\/+/, '');

await createFile({
owner: context.owner,
repo: context.repo,
branch: context.branch,
file: normalizedFile,
content,
message: 'file updated from Airview',
});
setMdx(content);
return 'success';
} catch (error: any) {
logger.error('ContentPage:onSave:error: ', error);
throw new Error(`Error saving file: ${error.message}`);
}
};

const imageUploadHandler = async (image: File) => {
logger.debug('imageUploadHandler', context, image);
const formData = new FormData();
formData.append('image', image);
if (!context.file) {
throw new Error('No file to save');
}
const file = `${path.dirname(context.file)}/${image.name
.replace(/[^a-zA-Z0-9.]/g, '')
.toLowerCase()}`;
const fileName = image.name.replace(/[^a-zA-Z0-9.]/g, '').toLowerCase();

return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onloadend = async function uploadImage() {
const base64Image = reader.result;
if (!base64Image) {
throw new Error('error reading image');
}
let imageData: string[] = [];
if (base64Image && typeof base64Image === 'string') {
imageData = base64Image.split(',');
}
if (!imageData || !imageData[1]) {
throw new Error('error reading image');
}

try {
const url = await createFile({
owner: context.owner,
repo: context.repo,
branch: context.branch,
file,
content: imageData[1],
message: 'image uploaded from Airview',
});
if (url) {
resolve(fileName);
} else {
throw new Error('Error uploading image');
}
} catch (error: any) {
throw new Error(error);
}
};

reader.onerror = reject;

reader.readAsDataURL(image);
});
};

const imagePreviewHandler = async (
imageSource: string
// context: ContentItem
) => {
logger.info('imagePreviewHandler', context, imageSource);
if (imageSource.startsWith('http')) return imageSource;
if (!context.file) {
throw new Error('No file context');
}
const file = `${path.dirname(context.file)}/${imageSource.replace(/^\/|^\.\//, '')}`;
const filePath = file.replace(/^\/|^\.\//, ''); // strip leading slash
logger.debug('imagePreviewHandler', filePath);

const response = await fetch(
`/api/github/content?owner=${context.owner}&repo=${context.repo}&branch=${context.branch}&path=${filePath}`
);
if (!response.ok) {
throw new Error(`${response.status}`);
}
// Fetch the image as Blob directly
const blob = await response.blob();

// Create an object URL for the Blob
const imageObjectUrl = URL.createObjectURL(blob);
return imageObjectUrl;
};

return (
<>
<ControlBar
Expand Down Expand Up @@ -232,10 +339,10 @@ export default function EditorWrapper({
context={context}
defaultContext={defaultContext}
editorRef={editorRef}
editorSaveHandler={() => Promise.resolve('')}
editorSaveHandler={onSave}
enabled
imagePreviewHandler={() => Promise.resolve('')}
imageUploadHandler={() => Promise.resolve('')}
imagePreviewHandler={imagePreviewHandler}
imageUploadHandler={imageUploadHandler}
markdown={mdx}
top={220}
/>
Expand Down
32 changes: 24 additions & 8 deletions src/app/api/github/content/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,29 @@ export async function POST(req: NextRequest): Promise<NextResponse | void> {
{ status: 400 }
);
}
let content;
let message;

const { content, message } = JSON.parse(
req?.body ? req.body.toString() : ''
);
try {
// Ensure req.body exists and is not an empty string before parsing
const body = await req.json();

if (body) {
content = body.content;
message = body.message;
}
} catch (error) {
logger.error('Error parsing request body as JSON:', error);
return NextResponse.json(
{
error: 'Missing required parameters: content or message in the body',
},
{ status: 400 }
);
}
// const { content, message } = JSON.parse(
// req?.body ? req.body.toString() : ''
// );
if (!content || !message) {
return NextResponse.json(
{
Expand All @@ -103,11 +122,8 @@ export async function POST(req: NextRequest): Promise<NextResponse | void> {
content,
message
);
NextResponse.json({ response: commitResponse }, { status: 201 });
return NextResponse.json({ response: commitResponse }, { status: 201 });
} catch (err) {
return NextResponse.json(
{ error: `Error in API: ${err}` },
{ status: 500 }
);
return NextResponse.json({ error: `${err}` }, { status: 500 });
}
}
27 changes: 19 additions & 8 deletions src/lib/Github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { cacheRead, cacheWrite } from '@/lib/Redis';
import type { GitHubFile } from '@/lib/Types';

let gitHubInstance: Octokit | undefined;
const logger = getLogger();
const logger = getLogger().child({ namespace: 'lib/Github' });

logger.level = 'error';

interface GitHubConfig {
Expand Down Expand Up @@ -858,7 +859,14 @@ export async function commitFileToBranch(
}

try {
const branchSha = await getBranchSha(owner, repo, branch);
const { data } = await gitHubInstance.rest.repos.getBranch({
owner,
repo,
branch,
});
const branchSha = data.commit.sha;

// const branchSha = await getBranchSha(owner, repo, branch);
const encoding = isBase64(content) ? 'base64' : 'utf-8';

const blob = await gitHubInstance.rest.git.createBlob({
Expand Down Expand Up @@ -900,17 +908,20 @@ export async function commitFileToBranch(
// refresh branch cache
try {
// Store the content in the cache before returning it
const cacheKey = `github:getBranch:${owner}:${repo}:${branch}`;
const cacheKey = `github:branch:${owner}:${repo}:${branch}`;

await cacheWrite(cacheKey, newCommit.data.sha, 600);
} catch (error) {
logger.error(`[GitHub][getBranchSha] Error writing cache: ${error}`);
logger.error(`[getBranchSha] Error writing cache: ${error}`);
}

return newCommit.data;
} catch (error) {
logger.error(
`[GitHub][commitFileToBranch] Error committing file: ${error}`
);
} catch (error: any) {
logger.error(`[commitFileToBranch] Error committing file: ${error as any}`);
const errStr = error.toString();
if (errStr.includes('Update is not a fast forward')) {
throw new Error('Fast Forward Error (File may already exist)');
}
throw new Error(`${error}`);
}
}
Expand Down

0 comments on commit 042c087

Please sign in to comment.