Skip to content

Commit 2c889a8

Browse files
WIP
1 parent c726e27 commit 2c889a8

File tree

8 files changed

+694
-1
lines changed

8 files changed

+694
-1
lines changed

packages/api/generated-schema.gql

+83
Original file line numberDiff line numberDiff line change
@@ -9143,6 +9143,18 @@ type Mutation {
91439143
"""
91449144
input: ToggleResponsesPracticeInput!
91459145
): ToggleResponsesPracticePayload
9146+
updateAboutPageContent(
9147+
"""
9148+
The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
9149+
"""
9150+
input: UpdateAboutPageContentInput!
9151+
): UpdateAboutPageContentPayload
9152+
updateAboutPageEnabled(
9153+
"""
9154+
The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
9155+
"""
9156+
input: UpdateAboutPageEnabledInput!
9157+
): UpdateAboutPageEnabledPayload
91469158

91479159
"""Updates a single `Acl` using a unique key and a patch."""
91489160
updateAcl(
@@ -15256,6 +15268,77 @@ type UnsplashUser {
1525615268
username: String!
1525715269
}
1525815270

15271+
"""All input for the `updateAboutPageContent` mutation."""
15272+
input UpdateAboutPageContentInput {
15273+
"""
15274+
An arbitrary string value with no semantic meaning. Will be included in the
15275+
payload verbatim. May be used to track mutations by the client.
15276+
"""
15277+
clientMutationId: String
15278+
content: JSON
15279+
lang: String
15280+
slug: String
15281+
}
15282+
15283+
"""The output of our `updateAboutPageContent` mutation."""
15284+
type UpdateAboutPageContentPayload {
15285+
"""
15286+
The exact same `clientMutationId` that was provided in the mutation input,
15287+
unchanged and unused. May be used by a client to track mutations.
15288+
"""
15289+
clientMutationId: String
15290+
15291+
"""Reads a single `DataSourcesBucket` that is related to this `Project`."""
15292+
dataSourcesBucket: DataSourcesBucket
15293+
project: Project
15294+
15295+
"""An edge for our `Project`. May be used by Relay 1."""
15296+
projectEdge(
15297+
"""The method to use when ordering `Project`."""
15298+
orderBy: [ProjectsOrderBy!] = [PRIMARY_KEY_ASC]
15299+
): ProjectsEdge
15300+
15301+
"""
15302+
Our root query field type. Allows us to run any query from our mutation payload.
15303+
"""
15304+
query: Query
15305+
}
15306+
15307+
"""All input for the `updateAboutPageEnabled` mutation."""
15308+
input UpdateAboutPageEnabledInput {
15309+
"""
15310+
An arbitrary string value with no semantic meaning. Will be included in the
15311+
payload verbatim. May be used to track mutations by the client.
15312+
"""
15313+
clientMutationId: String
15314+
enabled: Boolean
15315+
slug: String
15316+
}
15317+
15318+
"""The output of our `updateAboutPageEnabled` mutation."""
15319+
type UpdateAboutPageEnabledPayload {
15320+
"""
15321+
The exact same `clientMutationId` that was provided in the mutation input,
15322+
unchanged and unused. May be used by a client to track mutations.
15323+
"""
15324+
clientMutationId: String
15325+
15326+
"""Reads a single `DataSourcesBucket` that is related to this `Project`."""
15327+
dataSourcesBucket: DataSourcesBucket
15328+
project: Project
15329+
15330+
"""An edge for our `Project`. May be used by Relay 1."""
15331+
projectEdge(
15332+
"""The method to use when ordering `Project`."""
15333+
orderBy: [ProjectsOrderBy!] = [PRIMARY_KEY_ASC]
15334+
): ProjectsEdge
15335+
15336+
"""
15337+
Our root query field type. Allows us to run any query from our mutation payload.
15338+
"""
15339+
query: Query
15340+
}
15341+
1525915342
"""All input for the `updateAclByBasemapId` mutation."""
1526015343
input UpdateAclByBasemapIdInput {
1526115344
basemapId: Int!

packages/api/migrations/current.sql

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,32 @@
11
-- Enter migration here
22
alter table projects add column if not exists about_page_contents jsonb not null default '{}'::jsonb;
33

4-
alter table projects add column if not exists about_page_enabled boolean not null default false;
4+
alter table projects add column if not exists about_page_enabled boolean not null default false;
5+
6+
create or replace function update_about_page_content(slug text, content jsonb, lang text)
7+
returns projects
8+
language sql
9+
security definer
10+
as $$
11+
update projects
12+
set about_page_contents = jsonb_set(about_page_contents, array[lang], content)
13+
where projects.slug = update_about_page_content.slug
14+
and session_is_admin(projects.id)
15+
returning *;
16+
$$;
17+
18+
grant execute on function update_about_page_content(text, jsonb, text) to seasketch_user;
19+
20+
create or replace function update_about_page_enabled(slug text, enabled boolean)
21+
returns projects
22+
security definer
23+
language sql
24+
as $$
25+
update projects
26+
set about_page_enabled = enabled
27+
where projects.slug = update_about_page_enabled.slug
28+
and session_is_admin(projects.id)
29+
returning *;
30+
$$;
31+
32+
grant execute on function update_about_page_enabled(text, boolean) to seasketch_user;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { Trans, useTranslation } from "react-i18next";
2+
import { useParams } from "react-router-dom";
3+
import {
4+
useProjectMetadataQuery,
5+
useUpdateAboutPageContentsMutation,
6+
} from "../generated/graphql";
7+
import { ProseMirror, useProseMirror } from "use-prosemirror";
8+
import { aboutPage as editorConfig } from "../editor/config";
9+
import { useCallback, useEffect, useRef, useState } from "react";
10+
import { EditorView } from "prosemirror-view";
11+
import { Node, Slice } from "prosemirror-model";
12+
import { EditorState, Transaction } from "prosemirror-state";
13+
import EditorMenuBar from "../editor/EditorMenuBar";
14+
import languages from "../lang/supported";
15+
import { useDebouncedFn } from "beautiful-react-hooks";
16+
import useDebounce from "../useDebounce";
17+
18+
const { schema, plugins } = editorConfig;
19+
20+
export default function AboutPageSettings() {
21+
const { t, i18n } = useTranslation("admin");
22+
const { slug } = useParams<{ slug: string }>();
23+
const { data, loading } = useProjectMetadataQuery({
24+
variables: { slug },
25+
});
26+
const [mutate, mutationState] = useUpdateAboutPageContentsMutation();
27+
const [state, setState] = useProseMirror({ schema });
28+
const [changes, setChanges] = useState(false);
29+
const viewRef = useRef<{ view: EditorView }>();
30+
const [lang, setLang] = useState("EN");
31+
32+
const debouncedDoc = useDebounce(state.doc, 500);
33+
console.log(
34+
loading,
35+
JSON.stringify(
36+
data?.project?.aboutPageContents?.[lang]?.content?.[0]?.content
37+
)
38+
);
39+
40+
// TODO: Problem here with cached projectmetadata setting the contents of the
41+
// editor, and then being updated by the network. stale content shows up after
42+
// refresh.
43+
useEffect(() => {
44+
const doc = data?.project?.aboutPageContents?.[lang];
45+
return;
46+
if (state && doc && state.doc) {
47+
try {
48+
console.log("dispatching", state.doc);
49+
const node = Node.fromJSON(schema, doc);
50+
// const t = state.tr;
51+
// state.tr.replace(
52+
// 0,
53+
// state.doc.content.size,
54+
// new Slice(node.content, 0, 0)
55+
// );
56+
57+
// t.doc = node;
58+
// console.log("t", t, node);
59+
// t.replace(0, state.doc.nodeSize, new Slice(node.content, 0, 0));
60+
// // t.replaceRangeWith(0, state.doc.nodeSize, node);
61+
// console.log(t);
62+
// viewRef.current?.view.dispatch(t);
63+
const newState = EditorState.create({
64+
...state,
65+
schema: state.schema,
66+
plugins: state.plugins,
67+
doc: node,
68+
selection: state.selection,
69+
});
70+
onChange(newState);
71+
} catch (e) {
72+
// do nothing
73+
// console.error(e);
74+
throw e;
75+
}
76+
}
77+
}, [data?.project?.aboutPageContents, lang]);
78+
79+
useEffect(() => {
80+
if (!loading) {
81+
const startingDocument = data?.project?.aboutPageContents?.[lang];
82+
try {
83+
const doc = startingDocument
84+
? Node.fromJSON(schema, startingDocument)
85+
: undefined;
86+
// initial render
87+
const state = EditorState.create({
88+
schema: schema,
89+
plugins,
90+
doc,
91+
});
92+
setState(state);
93+
} catch (e) {
94+
const doc = startingDocument
95+
? Node.fromJSON(schema, {
96+
type: "doc",
97+
content: [
98+
{
99+
type: "paragraph",
100+
content: [{ type: "text", text: "Problem parsing metadata" }],
101+
},
102+
{
103+
type: "paragraph",
104+
content: [
105+
{
106+
type: "text",
107+
text: e.toString(),
108+
},
109+
],
110+
},
111+
],
112+
})
113+
: undefined;
114+
// initial render
115+
const state = EditorState.create({
116+
schema: schema,
117+
plugins,
118+
doc,
119+
});
120+
setState(state);
121+
}
122+
}
123+
}, [loading, setState, lang]);
124+
125+
const [hasChanges, setHasChanges] = useState(false);
126+
127+
const onChange = useCallback(
128+
(state: EditorState) => {
129+
setState(state);
130+
setHasChanges(true);
131+
},
132+
[setState]
133+
);
134+
135+
useEffect(() => {
136+
if (debouncedDoc && hasChanges) {
137+
mutate({
138+
variables: {
139+
content: debouncedDoc?.toJSON(),
140+
lang,
141+
slug,
142+
},
143+
});
144+
setHasChanges(false);
145+
}
146+
}, [debouncedDoc]);
147+
148+
return (
149+
<>
150+
<div className="relative">
151+
<div className="shadow sm:rounded-md sm:overflow-hidden">
152+
<div className="px-4 py-5 bg-white sm:p-6">
153+
<h3 className="text-lg font-medium leading-6 text-gray-900">
154+
{t("About Page")}
155+
</h3>
156+
{/* {mutationState.error && <p>{mutationState.error.message}</p>} */}
157+
<p className="mt-1 text-sm leading-5 text-gray-500">
158+
<Trans ns="admin">
159+
When enabled, the about page will be accessible from the project
160+
sidebar. It will also be presented to users when they first
161+
visit the project. If your project supports multiple languages,
162+
use the dropdown to switch between languages and provide
163+
translated content.
164+
</Trans>
165+
</p>
166+
<div className="py-4">
167+
<div className="bg-gray-50 rounded shadow">
168+
<EditorMenuBar
169+
view={viewRef.current?.view}
170+
state={state}
171+
schema={schema}
172+
>
173+
<div className="border-r-1 w-1 border-gray-300 border-r h-5 mx-2"></div>
174+
<select className="text-sm ml-2 px-3 py-0.5 rounded border-gray-300">
175+
<option value="EN">{t("English")}</option>
176+
{data?.project?.supportedLanguages?.map((l) => {
177+
const lang = languages.find((lang) => lang.code === l);
178+
if (!lang) {
179+
return null;
180+
}
181+
return (
182+
<option key={l} value={l!}>
183+
{lang.localName || lang.name}
184+
</option>
185+
);
186+
})}
187+
</select>
188+
</EditorMenuBar>
189+
</div>
190+
191+
<div className="pt-4 flex-1 overflow-y-auto">
192+
<ProseMirror
193+
className="metadata small-variant"
194+
state={state}
195+
onChange={onChange}
196+
// @ts-ignore
197+
ref={viewRef}
198+
/>
199+
</div>
200+
</div>
201+
</div>
202+
</div>
203+
</div>
204+
</>
205+
);
206+
}

packages/client/src/admin/Settings.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { TranslateIcon } from "@heroicons/react/outline";
5353
import TranslatedPropControl from "../components/TranslatedPropControl";
5454
import bytes from "bytes";
5555
import ProjectAPIKeys from "./ProjectAPIKeys";
56+
import AboutPageSettings from "./AboutPageSettings";
5657

5758
export default function Settings() {
5859
const { data } = useCurrentProjectMetadata();
@@ -92,6 +93,9 @@ export default function Settings() {
9293
<div className="mx-auto max-w-3xl px-4 sm:px-6 md:px-8">
9394
<MapExtentSettings />
9495
</div>
96+
<div className="mx-auto max-w-3xl px-4 sm:px-6 md:px-8">
97+
<AboutPageSettings />
98+
</div>
9599
{/* Disabled until fixed */}
96100
{/* <div className="mx-auto max-w-3xl px-4 sm:px-6 md:px-8">
97101
<DataBucketSettings />

packages/client/src/editor/config.ts

+13
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ const forumPostSchema = new Schema({
223223
}),
224224
});
225225

226+
const aboutPageSchema = new Schema({
227+
nodes: addListNodes(baseSchema.spec.nodes, "paragraph block*", "block"),
228+
marks: baseMarks,
229+
});
230+
226231
export const metadata = {
227232
schema: metadataSchema,
228233
plugins: exampleSetup({ schema: metadataSchema, menuBar: false }),
@@ -251,3 +256,11 @@ export const forumPosts = {
251256
menuBar: false,
252257
}),
253258
};
259+
260+
export const aboutPage = {
261+
schema: aboutPageSchema,
262+
plugins: exampleSetup({
263+
schema: aboutPageSchema,
264+
menuBar: false,
265+
}),
266+
};

0 commit comments

Comments
 (0)