diff --git a/apps/builddao/widget/Compose.jsx b/apps/builddao/widget/Compose.jsx
index 4c388232..92a597da 100644
--- a/apps/builddao/widget/Compose.jsx
+++ b/apps/builddao/widget/Compose.jsx
@@ -1,19 +1,33 @@
-const { Avatar, Button } = VM.require("buildhub.near/widget/components");
+const { Avatar, Button } = VM.require("buildhub.near/widget/components") || {
+ Avatar: () => <>>,
+ Button: () => <>>,
+};
-Avatar = Avatar || (() => <>>);
-Button = Button || (() => <>>);
+const { DraftModal } =
+ VM.require("buildhub.near/widget/components.modals.DraftModal") ||
+ (() => <>>);
const draftKey = props.feed.name || "draft";
const draft = Storage.privateGet(draftKey);
+const autocompleteEnabled = true;
+
if (draft === null) {
return "";
}
+State.init({
+ image: {},
+});
+
const [view, setView] = useState("editor");
const [postContent, setPostContent] = useState("");
const [hideAdvanced, setHideAdvanced] = useState(true);
const [labels, setLabels] = useState([]);
+const [showAccountAutocomplete, setShowAccountAutocomplete] = useState(false);
+const [mentionsArray, setMentionsArray] = useState([]);
+const [mentionInput, setMentionInput] = useState(null);
+const [handler, setHandler] = useState("update");
setPostContent(draft || props.template);
@@ -23,16 +37,6 @@ function generateUID() {
return randomNumber.toString(16).padStart(8, "0");
}
-function tagsFromLabels(labels) {
- return labels.reduce(
- (newLabels, label) => ({
- ...newLabels,
- [label]: "",
- }),
- {}
- );
-}
-
const extractMentions = (text) => {
const mentionRegex =
/@((?:(?:[a-z\d]+[-_])*[a-z\d]+\.)*(?:[a-z\d]+[-_])*[a-z\d]+)/gi;
@@ -102,6 +106,12 @@ const postToCustomFeed = ({ feed, text, labels }) => {
text = checkAndAppendHashtag(text, hashtag);
});
+ const content = {
+ type: "md",
+ image: state.image.cid ? { ipfs_cid: state.image.cid } : undefined,
+ text: text,
+ };
+
const data = {
// [feed.name]: {
// [postId]: {
@@ -117,12 +127,7 @@ const postToCustomFeed = ({ feed, text, labels }) => {
// },
// },
post: {
- main: JSON.stringify({
- type: "md",
- text,
- // tags: tagsFromLabels(labels),
- // postType: feed.name,
- }),
+ main: JSON.stringify(content),
},
index: {
post: JSON.stringify({ key: "main", value: { type: "md" } }),
@@ -165,6 +170,43 @@ const postToCustomFeed = ({ feed, text, labels }) => {
});
};
+function textareaInputHandler(value) {
+ const words = value.split(/\s+/);
+ const allMentiones = words
+ .filter((word) => word.startsWith("@"))
+ .map((mention) => mention.slice(1));
+ const newMentiones = allMentiones.filter(
+ (item) => !mentionsArray.includes(item)
+ );
+ setMentionInput(newMentiones?.[0] ?? "");
+ setMentionsArray(allMentiones);
+ setShowAccountAutocomplete(newMentiones?.length > 0);
+ setPostContent(value);
+ setHandler("update");
+ Storage.privateSet(draftKey, value || "");
+}
+
+function autoCompleteAccountId(id) {
+ let currentIndex = 0;
+ const updatedDescription = postContent.replace(
+ /(?:^|\s)(@[^\s]*)/g,
+ (match) => {
+ if (currentIndex === mentionsArray.indexOf(mentionInput)) {
+ currentIndex++;
+ return ` @${id}`;
+ } else {
+ currentIndex++;
+ return match;
+ }
+ }
+ );
+ setPostContent(updatedDescription);
+ setShowAccountAutocomplete(false);
+ setMentionInput(null);
+ setHandler("autocompleteSelected");
+ Storage.privateSet(draftKey, updatedDescription || "");
+}
+
const PostCreator = styled.div`
display: flex;
flex-direction: column;
@@ -175,6 +217,51 @@ const PostCreator = styled.div`
border-radius: 12px;
margin-bottom: 1rem;
+
+ .upload-image-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f1f3f5;
+ color: #11181c;
+ border-radius: 40px;
+ height: 40px;
+ min-width: 40px;
+ font-size: 0;
+ border: none;
+ cursor: pointer;
+ transition: background 200ms, opacity 200ms;
+
+ &::before {
+ font-size: 16px;
+ }
+
+ &:hover,
+ &:focus {
+ background: #d7dbde;
+ outline: none;
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+
+ span {
+ margin-left: 12px;
+ }
+ }
+
+ .d-inline-block {
+ display: flex !important;
+ gap: 12px;
+ margin: 0 !important;
+
+ .overflow-hidden {
+ width: 40px !important;
+ height: 40px !important;
+ }
+ }
`;
const TextareaWrapper = styled.div`
@@ -403,6 +490,16 @@ const LabelSelect = styled.div`
}
`;
+// To handle ifram refresh in order to trigger initialText change
+const [postUUID, setPostUUID] = useState(generateUID());
+const memoizedPostUUID = useMemo(() => postUUID, [postUUID]);
+
+useEffect(() => {
+ if (postContent === "") {
+ setPostUUID(generateUID());
+ }
+}, [postContent]);
+
const avatarComponent = useMemo(() => {
return (
@@ -414,63 +511,236 @@ const avatarComponent = useMemo(() => {
);
}, [context.accountId]);
-return (
-
- {avatarComponent}
-
- {view === "editor" ? (
-
- {
- setPostContent(v);
- Storage.privateSet(draftKey, v || "");
- },
- }}
- />
-
- ) : (
-
- {
+ const savedDrafts = Storage.privateGet("savedDrafts") || "";
+ const drafts = JSON.parse(savedDrafts);
+ const newDrafts = drafts.filter((draft, i) => !checkedDrafts.includes(i));
+ Storage.privateSet("savedDrafts", JSON.stringify(newDrafts));
+ setCheckDrafts([]);
+};
+
+// handle draft save
+const onSaveDraft = () => {
+ const savedDrafts = Storage.privateGet("savedDrafts") || "";
+ const drafts = JSON.parse(savedDrafts) || [];
+ drafts.push(postContent);
+ Storage.privateSet("savedDrafts", JSON.stringify(drafts));
+};
+
+const DraftLabel = styled.span`
+ display: inline-flex;
+ padding: 12px;
+ align-items: center;
+ gap: 8px;
+
+ color: #fff;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 170%; /* 27.2px */
+`;
+
+const DraftItem = ({ draft, checked, isEdit }) => {
+ return (
+
+
+ {isEdit && (
+
+ )}
+ {draft}
+
+
+ );
+};
+
+const RenderDrafts = () => {
+ const savedDrafts = Storage.privateGet("savedDrafts") || "";
+ const drafts = JSON.parse(savedDrafts);
+
+ const handleDraftSelect = (draft) => {
+ textareaInputHandler(draft);
+ setPostUUID(generateUID());
+ setShowDraftsModal(false);
+ setView("editor");
+ };
+
+ return (
+
+ {drafts.map((draft, i) => (
+
handleDraftSelect(draft)}>
+
+
+ ))}
+ {drafts.length === 0 &&
No drafts saved
}
+
+ );
+};
+
+const EditDrafts = () => {
+ const savedDrafts = Storage.privateGet("savedDrafts") || "";
+ const drafts = JSON.parse(savedDrafts);
+
+ const handleDraftSelect = (draftIndex) => {
+ if (checkedDrafts.includes(draftIndex)) {
+ setCheckDrafts(checkedDrafts.filter((draft) => draft !== draftIndex));
+ } else {
+ setCheckDrafts([...checkedDrafts, draftIndex]);
+ }
+ };
+
+ return (
+
+ {drafts.map((draft, i) => (
+
handleDraftSelect(i)}>
+
-
- )}
+
+ ))}
+ {drafts.length === 0 &&
No drafts saved
}
+ );
+};
-
-
);
diff --git a/apps/builddao/widget/components.jsx b/apps/builddao/widget/components.jsx
index 38499bad..b73c4264 100644
--- a/apps/builddao/widget/components.jsx
+++ b/apps/builddao/widget/components.jsx
@@ -33,7 +33,11 @@ function Pagination({
function Post(props) {
return (
-
+ }
+ props={{ ...props }}
+ />
);
}
diff --git a/apps/builddao/widget/components/AccountAutocomplete.jsx b/apps/builddao/widget/components/AccountAutocomplete.jsx
new file mode 100644
index 00000000..5823f397
--- /dev/null
+++ b/apps/builddao/widget/components/AccountAutocomplete.jsx
@@ -0,0 +1,147 @@
+if (!context.accountId || !props.term) return <>>;
+
+let results = [];
+const filterAccounts = props.filterAccounts ?? []; // hide certain accounts from the list
+const profilesData = Social.get("*/profile/name", "final") || {};
+const followingData = Social.get(
+ `${context.accountId}/graph/follow/**`,
+ "final"
+);
+if (!profilesData) return <>>;
+const profiles = Object.entries(profilesData);
+const term = (props.term || "").replace(/\W/g, "").toLowerCase();
+const limit = 5;
+
+for (let i = 0; i < profiles.length; i++) {
+ let score = 0;
+ const accountId = profiles[i][0];
+ const accountIdSearch = profiles[i][0].replace(/\W/g, "").toLowerCase();
+ const nameSearch = (profiles[i][1]?.profile?.name || "")
+ .replace(/\W/g, "")
+ .toLowerCase();
+ const accountIdSearchIndex = accountIdSearch.indexOf(term);
+ const nameSearchIndex = nameSearch.indexOf(term);
+
+ if (accountIdSearchIndex > -1 || nameSearchIndex > -1) {
+ score += 10;
+
+ if (accountIdSearchIndex === 0) {
+ score += 10;
+ }
+ if (nameSearchIndex === 0) {
+ score += 10;
+ }
+ if (followingData[accountId] === "") {
+ score += 30;
+ }
+
+ results.push({
+ accountId,
+ score
+ });
+ }
+}
+
+results.sort((a, b) => b.score - a.score);
+results = results.slice(0, limit);
+if (filterAccounts?.length > 0) {
+ results = results.filter((item) => !filterAccounts?.includes(item.accountId));
+}
+
+function onResultClick(id) {
+ props.onSelect && props.onSelect(id);
+}
+
+const Wrapper = styled.div`
+ position: relative;
+
+ &::before {
+ content: "";
+ display: block;
+ position: absolute;
+ right: 0;
+ width: 6px;
+ height: 100%;
+ z-index: 10;
+ }
+`;
+
+const Scroller = styled.div`
+ position: relative;
+ display: flex;
+ padding: 6px;
+ gap: 6px;
+ overflow: auto;
+ scroll-behavior: smooth;
+ align-items: center;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ > * {
+ max-width: 175px;
+ flex-grow: 0;
+ flex-shrink: 0;
+
+ button {
+ border: 1px solid #eceef0;
+ background: #fff !important;
+ border-radius: 6px;
+ padding: 3px 6px;
+ transition: all 200ms;
+
+ &:focus,
+ &:hover {
+ border-color: #687076;
+ }
+ }
+ }
+`;
+
+const CloseButton = styled.button`
+ background: none;
+ border: none;
+ display: block;
+ padding: 12px;
+ color white;
+ transition: all 200ms;
+
+ &:hover {
+ transform:scale(1.2);
+ }
+`;
+
+const ProfileCardWrapper = styled.div`
+ opacity: 0.8;
+`;
+
+if (results.length === 0) return <>>;
+
+return (
+
+
+
+
+
+
+ {results.map((result) => {
+ return (
+
+
+
+ );
+ })}
+
+
+);
diff --git a/apps/builddao/widget/components/Checkbox.jsx b/apps/builddao/widget/components/Checkbox.jsx
index 27387079..1f3bed49 100644
--- a/apps/builddao/widget/components/Checkbox.jsx
+++ b/apps/builddao/widget/components/Checkbox.jsx
@@ -7,6 +7,8 @@ const CheckboxLabel = styled.label`
padding: 12px;
align-items: center;
gap: 8px;
+ cursor: pointer;
+ max-width: 100%;
color: #fff;
font-size: 16px;
@@ -15,20 +17,20 @@ const CheckboxLabel = styled.label`
line-height: 170%; /* 27.2px */
`;
-function Checkbox({ value, onChange, label }) {
+function Checkbox({ className, value, onChange, label }) {
return (
-
+
{value ? (
-
+
) : (
-
+
)}
- {label}
+ {label}
);
}
-return { Checkbox };
\ No newline at end of file
+return { Checkbox };
diff --git a/apps/builddao/widget/components/MarkdownEditorIframe.jsx b/apps/builddao/widget/components/MarkdownEditorIframe.jsx
new file mode 100644
index 00000000..ffb2e26b
--- /dev/null
+++ b/apps/builddao/widget/components/MarkdownEditorIframe.jsx
@@ -0,0 +1,64 @@
+const data = props.data ?? "# Hello World\n\n";
+const embedCss = props.embedCss || "";
+
+const code = `
+
+
+
+
+
+
+
+
+
+
+`;
+return (
+
+);
diff --git a/apps/builddao/widget/components/Modal.jsx b/apps/builddao/widget/components/Modal.jsx
index db7409fe..c029e3b7 100644
--- a/apps/builddao/widget/components/Modal.jsx
+++ b/apps/builddao/widget/components/Modal.jsx
@@ -22,7 +22,7 @@ const Content = styled.div`
max-width: 1000px;
padding: 24px;
outline: none !important;
- background: #23242B;
+ background: #23242b;
border-radius: 16px;
color: white;
`;
@@ -36,14 +36,14 @@ const NoButton = styled.button`
`;
const CloseContainer = styled.div`
- display: flex;
- justify-content: flex-end;
- width: 100%;
- padding-bottom: 24px;
+ display: flex;
+ justify-content: flex-end;
+ width: 100%;
+ padding-bottom: 24px;
`;
const Icon = styled.i`
- font-size: 24px;
+ font-size: 24px;
`;
function Modal({ children, open, onOpenChange, toggle, toggleContainerProps }) {
@@ -72,4 +72,4 @@ function Modal({ children, open, onOpenChange, toggle, toggleContainerProps }) {
);
}
-return { Modal };
\ No newline at end of file
+return { Modal };
diff --git a/apps/builddao/widget/components/modals/DraftModal.jsx b/apps/builddao/widget/components/modals/DraftModal.jsx
new file mode 100644
index 00000000..825338a6
--- /dev/null
+++ b/apps/builddao/widget/components/modals/DraftModal.jsx
@@ -0,0 +1,89 @@
+const { Button } = VM.require("buildhub.near/widget/components.Button");
+
+const toggle = props.toggle ??
Open Modal;
+
+const Overlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: grid;
+ place-items: center;
+ overflow-y: auto;
+ z-index: 1000;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(11, 12, 20, 0.5);
+`;
+
+const Content = styled.div`
+ min-width: 500px;
+ max-width: 1000px;
+ padding: 24px;
+ outline: none !important;
+ background: #23242b;
+ border-radius: 16px;
+ color: white;
+
+ @media screen and (max-width: 768px) {
+ max-width: 90%;
+ min-width: 50%;
+ width: 100%;
+ }
+`;
+
+const NoButton = styled.button`
+ background: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ box-shadow: none;
+`;
+
+const CloseContainer = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ width: 100%;
+ padding-bottom: 24px;
+`;
+
+const Icon = styled.i`
+ font-size: 24px;
+`;
+
+function DraftModal({
+ children,
+ open,
+ onOpenChange,
+ toggle,
+ toggleContainerProps,
+ editButton,
+}) {
+ return (
+
+
+ {toggle}
+
+
+
+
+
+
+
+
+ Drafts
+
+
+ {editButton}
+
+ {children}
+
+
+
+
+
+ );
+}
+
+return { DraftModal };