Allow creating read-only documents through the /new endpoint with readonly=true (fixes issue #48)

This commit is contained in:
Eric Allam
2022-05-03 22:36:27 +01:00
parent d793753a0a
commit fdd972b973
5 changed files with 85 additions and 50 deletions
+57 -43
View File
@@ -16,52 +16,66 @@ export function DocumentTitle() {
} }
}, [updateDoc]); }, [updateDoc]);
const startEditing = useCallback(() => { if (doc.readOnly) {
ref.current?.select(); return (
ref.current?.focus();
}, [ref.current]);
return (
<updateDoc.Form method="post" action={`/actions/${doc.id}/update`}>
<div <div
className="flex justify-center items-center w-full" className="flex justify-center items-center w-full"
title={doc.title} title={doc.title}
> >
<label className="relative block group"> <span
<PencilAltIcon className="h-5 w-5 absolute top-1/2 transform -translate-y-1/2 left-3 text-white opacity-0 transition pointer-events-none group-hover:opacity-80 group-focus:opacity-80" /> className={
<input "min-w-[15vw] border-none text-ellipsis text-slate-300 px-2 pl-10 py-1 rounded-sm bg-transparent placeholder:text-slate-400 focus:bg-black/30 focus:outline-none focus:border-none hover:cursor-text transition dark:bg-transparent dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-black dark:focus:bg-opacity-10"
ref={ref} }
className={ >
"min-w-[15vw] border-none text-ellipsis text-slate-300 px-2 pl-10 py-1 rounded-sm bg-transparent placeholder:text-slate-400 focus:bg-black/30 focus:outline-none focus:border-none hover:bg-black hover:bg-opacity-30 hover:cursor-text transition dark:bg-transparent dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-black dark:focus:bg-opacity-10 dark:hover:bg-black dark:hover:bg-opacity-10" {doc.title}
} </span>
type="text"
name="title"
spellCheck="false"
placeholder="Name your JSON file"
value={editedTitle}
onChange={(e) => setEditedTitle(e.target.value)}
/>
</label>
{match(editedTitle)
.with(doc.title, () => <p className="ml-2 text-transparent">Save</p>)
.with("", () => (
<button
className="ml-2 text-lime-500 hover:text-lime-600 transition"
onClick={() => setEditedTitle(doc.title)}
>
Reset
</button>
))
.otherwise(() => (
<button
type="submit"
className="ml-2 text-lime-500 hover:text-lime-600 transition"
>
Save
</button>
))}
</div> </div>
</updateDoc.Form> );
); } else {
return (
<updateDoc.Form method="post" action={`/actions/${doc.id}/update`}>
<div
className="flex justify-center items-center w-full"
title={doc.title}
>
<label className="relative block group">
<PencilAltIcon className="h-5 w-5 absolute top-1/2 transform -translate-y-1/2 left-3 text-white opacity-0 transition pointer-events-none group-hover:opacity-80 group-focus:opacity-80" />
<input
ref={ref}
className={
"min-w-[15vw] border-none text-ellipsis text-slate-300 px-2 pl-10 py-1 rounded-sm bg-transparent placeholder:text-slate-400 focus:bg-black/30 focus:outline-none focus:border-none hover:bg-black hover:bg-opacity-30 hover:cursor-text transition dark:bg-transparent dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-black dark:focus:bg-opacity-10 dark:hover:bg-black dark:hover:bg-opacity-10"
}
type="text"
name="title"
spellCheck="false"
placeholder="Name your JSON file"
value={editedTitle}
onChange={(e) => setEditedTitle(e.target.value)}
/>
</label>
{match(editedTitle)
.with(doc.title, () => (
<p className="ml-2 text-transparent">Save</p>
))
.with("", () => (
<button
className="ml-2 text-lime-500 hover:text-lime-600 transition"
onClick={() => setEditedTitle(doc.title)}
>
Reset
</button>
))
.otherwise(() => (
<button
type="submit"
className="ml-2 text-lime-500 hover:text-lime-600 transition"
>
Save
</button>
))}
</div>
</updateDoc.Form>
);
}
} }
+7 -1
View File
@@ -142,7 +142,13 @@ function SampleJSONPreview({
}) { }) {
return ( return (
<JsonDocProvider <JsonDocProvider
doc={{ id: "sample", title: "Sample", type: "raw", contents: "" }} doc={{
id: "sample",
title: "Sample",
type: "raw",
readOnly: false,
contents: "",
}}
path={initialSelection} path={initialSelection}
> >
<JsonProvider initialJson={json}> <JsonProvider initialJson={json}>
+1 -1
View File
@@ -81,7 +81,7 @@ export function JsonEditor() {
<CodeEditor <CodeEditor
language="json" language="json"
content={jsonMapped.json} content={jsonMapped.json}
readOnly={false} readOnly={true}
onUpdate={onUpdate} onUpdate={onUpdate}
selection={selection} selection={selection}
/> />
+11 -2
View File
@@ -3,6 +3,7 @@ import { customRandom } from "nanoid";
type BaseJsonDocument = { type BaseJsonDocument = {
id: string; id: string;
title: string; title: string;
readOnly: boolean;
}; };
export type RawJsonDocument = BaseJsonDocument & { export type RawJsonDocument = BaseJsonDocument & {
@@ -17,6 +18,7 @@ export type UrlJsonDocument = BaseJsonDocument & {
export type CreateJsonOptions = { export type CreateJsonOptions = {
ttl?: number; ttl?: number;
readOnly?: boolean;
metadata?: any; metadata?: any;
}; };
@@ -41,11 +43,12 @@ export async function createFromUrl(
options?: CreateJsonOptions options?: CreateJsonOptions
): Promise<JSONDocument> { ): Promise<JSONDocument> {
const docId = createId(); const docId = createId();
const doc = { const doc: JSONDocument = {
id: docId, id: docId,
type: <const>"url", type: <const>"url",
url: url.href, url: url.href,
title: title ?? url.hostname, title: title ?? url.hostname,
readOnly: options?.readOnly ?? false,
}; };
await DOCUMENTS.put(docId, JSON.stringify(doc), { await DOCUMENTS.put(docId, JSON.stringify(doc), {
@@ -62,7 +65,13 @@ export async function createFromRawJson(
options?: CreateJsonOptions options?: CreateJsonOptions
): Promise<JSONDocument> { ): Promise<JSONDocument> {
const docId = createId(); const docId = createId();
const doc = { id: docId, type: <const>"raw", contents, title: filename }; const doc: JSONDocument = {
id: docId,
type: <const>"raw",
contents,
title: filename,
readOnly: options?.readOnly ?? false,
};
await DOCUMENTS.put(docId, JSON.stringify(doc), { await DOCUMENTS.put(docId, JSON.stringify(doc), {
expirationTtl: options?.ttl ?? undefined, expirationTtl: options?.ttl ?? undefined,
+9 -3
View File
@@ -1,4 +1,4 @@
import { LoaderFunction, redirect } from "remix"; import { json, LoaderFunction, redirect } from "remix";
import invariant from "tiny-invariant"; import invariant from "tiny-invariant";
import { sendEvent } from "~/graphJSON.server"; import { sendEvent } from "~/graphJSON.server";
import { import {
@@ -12,6 +12,8 @@ export let loader: LoaderFunction = async ({ request, context }) => {
const jsonUrl = url.searchParams.get("url"); const jsonUrl = url.searchParams.get("url");
const base64EncodedJson = url.searchParams.get("j"); const base64EncodedJson = url.searchParams.get("j");
const ttl = url.searchParams.get("ttl"); const ttl = url.searchParams.get("ttl");
const readOnly = url.searchParams.get("readonly");
const title = url.searchParams.get("title");
if (!jsonUrl && !base64EncodedJson) { if (!jsonUrl && !base64EncodedJson) {
return redirect("/"); return redirect("/");
@@ -27,12 +29,16 @@ export let loader: LoaderFunction = async ({ request, context }) => {
invariant(options.ttl >= 60, "ttl must be at least 60 seconds"); invariant(options.ttl >= 60, "ttl must be at least 60 seconds");
} }
if (typeof readOnly === "string") {
options.readOnly = readOnly === "true";
}
if (jsonUrl) { if (jsonUrl) {
const jsonURL = new URL(jsonUrl); const jsonURL = new URL(jsonUrl);
invariant(jsonURL, "url must be a valid URL"); invariant(jsonURL, "url must be a valid URL");
const doc = await createFromUrl(jsonURL, jsonURL.href, options); const doc = await createFromUrl(jsonURL, title ?? jsonURL.href, options);
context.waitUntil( context.waitUntil(
sendEvent({ sendEvent({
@@ -49,7 +55,7 @@ export let loader: LoaderFunction = async ({ request, context }) => {
if (base64EncodedJson) { if (base64EncodedJson) {
const doc = await createFromRawJson( const doc = await createFromRawJson(
"Untitled", title ?? "Untitled",
atob(base64EncodedJson), atob(base64EncodedJson),
options options
); );