Search
Search

Transaction: EftYTK6...EPXK

Signed by
Receiver
Status
Succeeded
Transaction Fee
0.00518 
Deposit Value
0 
Gas Used
52 Tgas
Attached Gas
300 Tgas
Created
September 23, 2024 at 7:37:19pm
Hash
EftYTK6yJSukcQAKUCHD4ZRce3fobHNccCpjNaocEPXK

Actions

Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devhub.near": { "widget": { "devhub.entity.addon.blog.Page": { "": "const { getAccountCommunityPermissions } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n) || {\n getAccountCommunityPermissions: () => {},\n};\nconst imagelink =\n \"https://ipfs.near.social/ipfs/bafkreiajzvmy7574k7mp3if6u53mdukfr3hoc2kjkhjadt6x56vqhd5swy\";\n\nfunction Page({ data, onEdit, labels, accountId }) {\n const { category, title, description, subtitle, date, content } = data;\n const handle = labels?.[1]; // community-handle\n const permissions = getAccountCommunityPermissions({\n account_id: accountId,\n community_handle: handle,\n });\n const isAllowedToEdit = permissions?.can_configure ?? false;\n const Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n margin-bottom: 2rem;\n position: relative;\n ${category &&\n `\n span.category {\n color: ${\n category.toLowerCase() === \"news\"\n ? \"#F40303\"\n : category.toLowerCase() === \"guide\"\n ? \"#004BE1\"\n : category.toLowerCase() === \"reference\" && \"#FF7A00\"\n };\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n text-transform: uppercase;\n }\n `}\n\n span.date {\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin: 1.5rem 0;\n }\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1.5rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n .edit-icon {\n position: absolute;\n top: 20px;\n right: 20px;\n cursor: pointer;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n `;\n\n const BackgroundImage = styled.img`\n width: 100%;\n height: auto;\n object-fit: cover;\n margin-bottom: 1rem;\n `;\n\n const options = { year: \"numeric\", month: \"short\", day: \"numeric\" };\n const formattedDate = new Date(date).toLocaleString(\"en-US\", options);\n\n return (\n <>\n <BackgroundImage src={imagelink} />\n <Container>\n {isAllowedToEdit && (\n <div className=\"edit-icon\" onClick={onEdit}>\n <div class=\"bi bi-pencil-square\" style={{ fontSize: \"30px\" }}></div>\n </div>\n )}\n {category && <span className=\"category\">{category}</span>}\n <h1>{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <span className=\"date\">{formattedDate}</span>\n <p>{description}</p>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"\n }\n props={{ content: content }}\n />\n </Container>\n </>\n );\n}\n\nreturn { Page };\n" }, "devhub.entity.addon.blogv2.editor.form": { "": "const FormContainer = styled.div`\n & > *:not(:last-child) {\n margin-bottom: 1rem;\n }\n`;\n\nconst {\n title,\n setTitle,\n subtitle,\n setSubtitle,\n options,\n category,\n setCategory,\n description,\n setDescription,\n debouncedUpdateState,\n author,\n setAuthor,\n date,\n setDate,\n content,\n setContent,\n addonParameters,\n} = props;\n\nconst InputContainer = ({ heading, description, children }) => {\n return (\n <div className=\"d-flex flex-column gap-1 gap-sm-2 w-100\">\n <b className=\"h6 mb-0\">{heading}</b>\n {description && (\n <div className=\"text-muted w-100 text-sm\">{description}</div>\n )}\n {children}\n </div>\n );\n};\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: title,\n placeholder: \"Title\",\n onChange: (e) => setTitle(e.target.value),\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n required: true,\n name: \"title\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n }}\n />\n );\n}, []);\n\nconst SubtitleComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: subtitle,\n placeholder: \"Subtitle\",\n onChange: (e) => setSubtitle(e.target.value),\n skipPaddingGap: true,\n inputProps: {\n required: true,\n max: 80,\n name: \"subtitle\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n }}\n />\n );\n}, []);\n\nconst CategorySelect = useMemo(() => {\n return (\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.addon.blogv2.editor.CategoryDropdown\"\n }\n props={{\n options,\n selectedValue: category,\n onChange: setCategory,\n }}\n />\n );\n}, []);\n\nconst DescriptionInput = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setDescription(e.target.value),\n value: description,\n multiline: true,\n placeholder: \"Description\",\n inputProps: {\n max: 500,\n name: \"description\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n skipPaddingGap: true,\n key: \"description-input-field\",\n }}\n />\n );\n}, []);\n\nconst AuthorInput = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setAuthor(e.target.value),\n value: author,\n placeholder: \"Author\",\n inputProps: {\n name: \"author\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n skipPaddingGap: true,\n key: \"author-input-field\",\n }}\n />\n );\n}, []);\n\nconst DateInput = () => {\n return (\n <input\n name=\"date\"\n type=\"date\"\n value={date}\n className=\"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\"\n onChange={(e) => setDate(e.target.value)}\n />\n );\n};\n\nconst ComposeEmbeddCSS = `\n .CodeMirror {\n border: none !important;\n min-height: 50px !important;\n }\n\n .editor-toolbar {\n border: none !important;\n }\n\n .CodeMirror-scroll{\n min-height: 50px !important;\n max-height: 600px !important;\n }\n`;\n\nconst ContentEditor = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Compose\"\n props={{\n data: content,\n onChange: setContent,\n height: \"250\",\n embeddCSS: ComposeEmbeddCSS,\n isTailwind: true,\n }}\n />\n );\n}, []);\n\nreturn (\n <FormContainer id=\"blog-editor-form\">\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your blog in a few words. This will appear on your blog card and on the top of your blog.\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Subtitle\"\n description=\"Provide a brief subtitle for the blog\"\n >\n {SubtitleComponent}\n </InputContainer>\n {addonParameters.categoriesEnabled === \"enabled\" ? (\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Choose the category that fits your blog best. Set up your categories\n in the blog settings.\n </>\n }\n >\n {CategorySelect}\n </InputContainer>\n ) : (\n <></>\n )}\n\n <InputContainer\n heading=\"Description\"\n description=\"Provide a brief description for the blog. This will appear on the blog card.\"\n >\n {DescriptionInput}\n </InputContainer>\n {addonParameters.authorEnabled === \"disabled\" ? (\n <></>\n ) : (\n <InputContainer heading=\"Author\" description=\"Who wrote this blog?\">\n {AuthorInput}\n </InputContainer>\n )}\n <InputContainer\n heading=\"Visible Publish Date\"\n description=\"What date do you want to have the blog published under?\"\n >\n <DateInput />\n </InputContainer>\n\n <InputContainer\n heading=\"Content\"\n description=\"Write your blog here. Use Markdown to format your content.\"\n >\n {ContentEditor}\n </InputContainer>\n </FormContainer>\n);\n" }, "devhub.entity.proposal.Proposal": { "": "const { href, getLinkUsingCurrentGateway } = VM.require(\n \"devhub.near/widget/core.lib.url\"\n) || {\n href: () => {},\n getLinkUsingCurrentGateway: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\n\nconst accountId = context.accountId;\n/*\n---props---\nprops.id: number;\nprops.timestamp: number; optional\naccountId: string\nblockHeight:number\n*/\n\nconst TIMELINE_STATUS = {\n DRAFT: \"DRAFT\",\n REVIEW: \"REVIEW\",\n APPROVED: \"APPROVED\",\n REJECTED: \"REJECTED\",\n CANCELED: \"CANCELLED\",\n APPROVED_CONDITIONALLY: \"APPROVED_CONDITIONALLY\",\n PAYMENT_PROCESSING: \"PAYMENT_PROCESSING\",\n FUNDED: \"FUNDED\",\n};\n\nconst DecisionStage = [\n TIMELINE_STATUS.APPROVED,\n TIMELINE_STATUS.REJECTED,\n TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n];\n\nconst Container = styled.div`\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .fw-bold {\n font-weight: 600 !important;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n .description-box {\n font-size: 14px;\n }\n\n .draft-info-container {\n background-color: #ecf8fb;\n }\n\n .review-info-container {\n background-color: #fef6ee;\n }\n\n .text-sm {\n font-size: 13px !important;\n }\n\n .flex-1 {\n flex: 1;\n }\n\n .flex-3 {\n flex: 3;\n }\n\n .circle {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n border: 1px solid grey;\n }\n\n .green-fill {\n background-color: rgb(4, 164, 110) !important;\n border-color: rgb(4, 164, 110) !important;\n color: white !important;\n }\n\n .yellow-fill {\n border-color: #ff7a00 !important;\n }\n\n .vertical-line {\n width: 2px;\n height: 180px;\n background-color: lightgrey;\n }\n\n @media screen and (max-width: 970px) {\n .vertical-line {\n height: 135px !important;\n }\n\n .vertical-line-sm {\n height: 70px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n @media screen and (max-width: 570px) {\n .vertical-line {\n height: 180px !important;\n }\n\n .vertical-line-sm {\n height: 75px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .vertical-line-sm {\n width: 2px;\n height: 70px;\n background-color: lightgrey;\n }\n\n .form-check-input:disabled ~ .form-check-label,\n .form-check-input[disabled] ~ .form-check-label {\n opacity: 1;\n }\n\n .form-check-input {\n border-color: black !important;\n }\n\n .grey-btn {\n background-color: #687076;\n border: none;\n color: white;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .drop-btn {\n max-width: none !important;\n }\n\n .dropdown-menu {\n width: 100%;\n border-radius: 0.375rem !important;\n }\n\n .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n .border-vertical {\n border-top: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n border-bottom: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n }\n\n button.px-0 {\n padding-inline: 0px !important;\n }\n\n red-icon i {\n color: red;\n }\n\n input[type=\"radio\"] {\n min-width: 13px;\n }\n`;\n\nconst ProposalContainer = styled.div`\n border: 1px solid lightgrey;\n overflow: auto;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\nconst Text = styled.p`\n display: block;\n margin: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 400;\n color: #687076;\n white-space: nowrap;\n`;\n\nconst Actions = styled.div`\n display: flex;\n align-items: center;\n gap: 12px;\n margin: -6px -6px 6px;\n`;\n\nconst Avatar = styled.div`\n width: 40px;\n height: 40px;\n pointer-events: none;\n\n img {\n object-fit: cover;\n border-radius: 40px;\n width: 100%;\n height: 100%;\n }\n`;\n\nconst LinkProfile = ({ account, children }) => {\n return (\n <Link href={`/near/widget/ProfilePage?accountId=${account}`}>\n {children}\n </Link>\n );\n};\n\nconst stepsArray = [1, 2, 3, 4, 5];\n\nconst { id, timestamp } = props;\nconst proposal = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n});\n\nif (!proposal) {\n return (\n <div\n style={{ height: \"50vh\" }}\n className=\"d-flex justify-content-center align-items-center w-100\"\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n}\nif (timestamp && proposal) {\n proposal.snapshot =\n proposal.snapshot_history.find((item) => item.timestamp === timestamp) ??\n proposal.snapshot;\n}\n\nconst { snapshot } = proposal;\n\nconst authorId = proposal.author_id;\nconst blockHeight = parseInt(proposal.social_db_post_block_height);\nconst item = {\n type: \"social\",\n path: `devhub.near/post/main`,\n blockHeight,\n};\nconst comments = Social.index(\"comment\", item, { subscribe: true }) ?? [];\n\nconst commentAuthors = [\n ...new Set(comments.map((comment) => comment.accountId)),\n];\n\nconst proposalURL = `https://devhub.near.page/proposal/${proposal.id}`;\n\nconst KycVerificationStatus = () => {\n const isVerified = true;\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <img\n src={\n isVerified\n ? \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\"\n : \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\"\n }\n height={40}\n />\n <div className=\"d-flex flex-column\">\n <div className=\"h6 mb-0\">KYC Verified</div>\n <div className=\"text-sm\">Expires on Aug 24, 2024</div>\n </div>\n </div>\n );\n};\n\nconst SidePanelItem = ({ title, children, hideBorder, ishidden }) => {\n return (\n <div\n style={{ gap: \"8px\" }}\n className={\n ishidden\n ? \"d-none\"\n : \"d-flex flex-column pb-3 \" + (!hideBorder && \" border-bottom\")\n }\n >\n <div className=\"h6 mb-0\">{title} </div>\n <div className=\"text-muted\">{children}</div>\n </div>\n );\n};\n\nconst proposalStatusOptions = [\n {\n label: \"Draft\",\n value: { status: TIMELINE_STATUS.DRAFT },\n },\n {\n label: \"Review\",\n value: {\n status: TIMELINE_STATUS.REVIEW,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n kyc_verified: false,\n },\n },\n {\n label: \"Approved\",\n value: {\n status: TIMELINE_STATUS.APPROVED,\n kyc_verified: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Approved-Conditionally\",\n value: {\n status: TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n kyc_verified: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Rejected\",\n value: {\n status: TIMELINE_STATUS.REJECTED,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n kyc_verified: true,\n },\n },\n {\n label: \"Canceled\",\n value: {\n status: TIMELINE_STATUS.CANCELED,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n kyc_verified: true,\n },\n },\n {\n label: \"Payment-processing\",\n value: {\n status: TIMELINE_STATUS.PAYMENT_PROCESSING,\n kyc_verified: true,\n test_transaction_sent: false,\n request_for_trustees_created: false,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Funded\",\n value: {\n status: TIMELINE_STATUS.FUNDED,\n trustees_released_payment: true,\n kyc_verified: true,\n test_transaction_sent: true,\n request_for_trustees_created: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n];\n\nconst LinkedProposals = () => {\n const linkedProposalsData = [];\n snapshot.linked_proposals.map((item) => {\n const data = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: item,\n });\n if (data !== null) {\n linkedProposalsData.push(data);\n }\n });\n\n return (\n <div className=\"d-flex flex-column gap-3\">\n {linkedProposalsData.map((item) => {\n return (\n <a\n href={`?page=proposal&id=${item.id}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: item.snapshot.editor_id,\n }}\n />\n <div className=\"d-flex flex-column\" style={{ maxWidth: 250 }}>\n <div className=\"text-truncate\">\n <LinkProfile account={item.snapshot.name}>\n <b>{item.snapshot.name}</b>\n </LinkProfile>\n </div>\n <div className=\"text-sm text-muted\">\n created on {readableDate(item.snapshot.timestamp / 1000000)}\n </div>\n </div>\n </div>\n </a>\n );\n })}\n </div>\n );\n};\n\nconst CheckBox = ({ value, isChecked, label, disabled, onClick }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n data-testid={label}\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={isChecked}\n disabled={!isModerator || !showTimelineSetting || disabled}\n onChange={(e) => onClick(e.target.checked)}\n />\n <label style={{ width: \"90%\" }} class=\"form-check-label text-black\">\n {label}\n </label>\n </div>\n );\n};\n\nconst RadioButton = ({ value, isChecked, label }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n value={value}\n checked={isChecked}\n disabled={true}\n />\n <label class=\"form-check-label text-black\">{label}</label>\n </div>\n );\n};\n\nconst isAllowedToEditProposal = Near.view(\n \"devhub.near\",\n \"is_allowed_to_edit_proposal\",\n {\n proposal_id: proposal.id,\n editor: accountId,\n }\n);\n\nconst isModerator = Near.view(\"devhub.near\", \"has_moderator\", {\n account_id: accountId,\n});\n\nconst editProposal = ({ timeline }) => {\n const body = {\n proposal_body_version: \"V0\",\n name: snapshot.name,\n description: snapshot.description,\n category: snapshot.category,\n summary: snapshot.summary,\n linked_proposals: snapshot.linked_proposals,\n requested_sponsorship_usd_amount: snapshot.requested_sponsorship_usd_amount,\n requested_sponsorship_paid_in_currency:\n snapshot.requested_sponsorship_paid_in_currency,\n receiver_account: snapshot.receiver_account,\n supervisor: supervisor || null,\n requested_sponsor: snapshot.requested_sponsor,\n timeline: timeline,\n };\n const args = {\n labels: [],\n body: body,\n id: proposal.id,\n };\n\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nconst editProposalStatus = ({ timeline }) => {\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal_versioned_timeline\",\n args: {\n id: proposal.id,\n timeline: { timeline_version: \"V1\", ...timeline },\n },\n gas: 270000000000000,\n },\n ]);\n setEditProposalTimelineCalled(true);\n};\n\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\nconst [isEditProposalTimelineCalled, setEditProposalTimelineCalled] =\n useState(false);\nconst [showTimeLineStatusSubmittedToast, setShowTimeLineStatusSubmittedToast] =\n useState(false);\nconst [showTimelineSetting, setShowTimelineSetting] = useState(false);\nconst proposalStatus = useCallback(\n () =>\n proposalStatusOptions.find(\n (i) => i.value.status === snapshot.timeline.status\n ),\n [snapshot]\n);\nconst [updatedProposalStatus, setUpdatedProposalStatus] = useState({});\n\nuseEffect(() => {\n if (isEditProposalTimelineCalled) {\n setShowTimeLineStatusSubmittedToast(true);\n setEditProposalTimelineCalled(false);\n }\n setUpdatedProposalStatus({\n ...proposalStatus(),\n value: { ...proposalStatus().value, ...snapshot.timeline },\n });\n}, [proposal]);\n\nconst [paymentHashes, setPaymentHashes] = useState([\"\"]);\nconst [supervisor, setSupervisor] = useState(snapshot.supervisor);\n\nconst selectedStatusIndex = useMemo(\n () =>\n proposalStatusOptions.findIndex((i) => {\n return updatedProposalStatus.value.status === i.value.status;\n }),\n [updatedProposalStatus]\n);\n\nconst TimelineItems = ({ title, children, value, values }) => {\n const indexOfCurrentItem = proposalStatusOptions.findIndex((i) =>\n Array.isArray(values)\n ? values.includes(i.value.status)\n : value === i.value.status\n );\n let color = \"transparent\";\n let statusIndex = selectedStatusIndex;\n\n // index 2,3,4,5 is of decision\n if (selectedStatusIndex === 3 || selectedStatusIndex === 2) {\n statusIndex = 2;\n }\n if (statusIndex === indexOfCurrentItem) {\n color = \"#FEF6EE\";\n }\n if (\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status === TIMELINE_STATUS.FUNDED\n ) {\n color = \"#EEFEF0\";\n }\n // reject\n if (statusIndex === 4 && indexOfCurrentItem === 2) {\n color = \"#FF7F7F\";\n }\n // cancelled\n if (statusIndex === 5 && indexOfCurrentItem === 2) {\n color = \"#F4F4F4\";\n }\n\n return (\n <div\n className=\"p-2 rounded-3\"\n style={{\n backgroundColor: color,\n }}\n >\n <div className=\"h6 text-black\"> {title}</div>\n <div className=\"text-sm\">{children}</div>\n </div>\n );\n};\n\nconst link = href({\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"create-proposal\",\n id: proposal.id,\n timestamp: timestamp,\n },\n});\n\nconst createdDate =\n proposal.snapshot_history?.[proposal.snapshot_history.length - 1]\n ?.timestamp ?? snapshot.timestamp;\n\nreturn (\n <Container className=\"d-flex flex-column gap-2 w-100 mt-4\">\n <Widget\n src=\"near/widget/DIG.Toast\"\n props={{\n title: \"Timeline status submitted successfully\",\n type: \"success\",\n open: showTimeLineStatusSubmittedToast,\n onOpenChange: (v) => setShowTimeLineStatusSubmittedToast(v),\n trigger: <></>,\n providerProps: { duration: 3000 },\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.ConfirmReviewModal\"}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[1].value });\n },\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.ConfirmCancelModal\"}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[5].value });\n },\n }}\n />\n <div className=\"d-flex px-3 px-lg-0 justify-content-between\">\n <div className=\"d-flex gap-2 align-items-center h3\">\n <div>{snapshot.name}</div>\n <div className=\"text-muted\">#{proposal.id}</div>\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.ShareLinkButton\"\n props={{\n postType: \"proposal\",\n url: proposalURL,\n }}\n />\n {((isAllowedToEditProposal &&\n snapshot.timeline.status === TIMELINE_STATUS.DRAFT) ||\n isModerator) && (\n <Link to={link} style={{ textDecoration: \"none\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Edit\",\n classNames: { root: \"grey-btn btn-sm\" },\n }}\n />\n </Link>\n )}\n </div>\n </div>\n <div className=\"d-flex flex-wrap flex-md-nowrap px-3 px-lg-0 gap-2 align-items-center text-sm pb-3 w-100\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n size: \"sm\",\n }}\n />\n <div className=\"w-100 d-flex flex-wrap flex-md-nowrap gap-1 align-items-center\">\n <div className=\"fw-bold text-truncate\">\n <LinkProfile account={authorId}>{authorId}</LinkProfile>\n </div>\n <div>created on {readableDate(createdDate / 1000000)}</div>\n </div>\n </div>\n <div className=\"card no-border rounded-0 full-width-div px-3 px-lg-0\">\n <div className=\"container-xl py-4\">\n {snapshot.timeline.status === TIMELINE_STATUS.DRAFT &&\n isAllowedToEditProposal && (\n <div className=\"draft-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in draft mode and open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n The author can still refine the proposal and build consensus\n before sharing it with sponsors. Click “Ready for review” when\n you want to start the official review process. This will lock\n the editing function, but comments are still open.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Ready for review\",\n classNames: { root: \"grey-btn btn-sm\" },\n onClick: () => setReviewModal(true),\n }}\n />\n </div>\n </div>\n )}\n {snapshot.timeline.status === TIMELINE_STATUS.REVIEW &&\n isAllowedToEditProposal && (\n <div className=\"review-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in review mode and still open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n You can’t edit the proposal, but comments are open. Only\n moderators can make changes. Click “Cancel Proposal” to cancel\n your proposal. This changes the status to Canceled, signaling\n to sponsors that it’s no longer active or relevant.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: (\n <div className=\"d-flex align-items-center gap-1\">\n <i class=\"bi bi-trash3\"></i> Cancel Proposal\n </div>\n ),\n classNames: { root: \"btn-outline-danger btn-sm\" },\n onClick: () => setCancelModal(true),\n }}\n />\n </div>\n </div>\n )}\n <div className=\"my-4\">\n <div className=\"d-flex flex-wrap gap-6\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-3 order-2 order-md-1\"\n >\n <div\n className=\"d-flex gap-2 flex-1\"\n style={{\n zIndex: 99,\n background: \"white\",\n position: \"relative\",\n }}\n >\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: authorId,\n }}\n />\n </div>\n <ProposalContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-1 align-items-center p-2 px-3 \">\n <div\n className=\"fw-bold text-truncate\"\n style={{ maxWidth: \"60%\" }}\n >\n <LinkProfile account={authorId}>{authorId}</LinkProfile>\n </div>\n <div\n className=\"text-muted\"\n style={{ minWidth: \"fit-content\" }}\n >\n ・{\" \"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: createdDate,\n }}\n />\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: authorId,\n blockHeight: blockHeight,\n }}\n />\n </div>\n )}\n </div>\n </Header>\n <div className=\"d-flex flex-column gap-1 p-2 px-3 description-box\">\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n PROPOSAL CATEGORY\n </div>\n <div>\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CategoryDropdown\"\n }\n props={{\n selectedValue: snapshot.category,\n disabled: true,\n }}\n />\n </div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n SUMMARY\n </div>\n <div>{snapshot.summary}</div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n DESCRIPTION\n </div>\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: snapshot.description,\n embeddCSS: `\n body {\n font-size: 14px;\n }\n `,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-3\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: authorId,\n }}\n />\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CommentIcon\"\n }\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: proposalURL,\n }}\n />\n </div>\n </div>\n </ProposalContainer>\n </div>\n <div className=\"border-bottom pb-4 mt-4\">\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CommentsAndLogs\"\n }\n props={{\n ...props,\n id: proposal.id,\n item: item,\n snapshotHistory: [...proposal.snapshot_history, snapshot],\n }}\n />\n </div>\n <div\n style={{\n position: \"relative\",\n zIndex: 99,\n backgroundColor: \"white\",\n }}\n className=\"pt-4\"\n >\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n ...props,\n item: item,\n notifyAccountId: authorId,\n id: proposal.id,\n sortedRelevantUsers: [\n authorId,\n snapshot.supervisor,\n snapshot.requested_sponsor,\n ...commentAuthors,\n ].filter((user) => user !== accountId),\n }}\n />\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"d-flex flex-column gap-4 flex-1 order-1 order-md-2\"\n >\n <SidePanelItem title=\"Author\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: authorId,\n noOverlay: true,\n }}\n />\n </SidePanelItem>\n <SidePanelItem\n title={\n \"Linked Proposals \" + `(${snapshot.linked_proposals.length})`\n }\n ishidden={!snapshot.linked_proposals.length}\n >\n <LinkedProposals />\n </SidePanelItem>\n <SidePanelItem title=\"Funding Ask\">\n <div className=\"h4 text-black\">\n {snapshot.requested_sponsorship_usd_amount && (\n <div className=\"d-flex flex-column gap-1\">\n <div>\n {parseInt(\n snapshot.requested_sponsorship_usd_amount\n ).toLocaleString()}{\" \"}\n USD\n </div>\n <div className=\"text-sm text-muted\">\n Requested in{\" \"}\n {snapshot.requested_sponsorship_paid_in_currency}\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Wallet Address\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.receiver_account,\n noOverlay: true,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Verification Status\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: snapshot.receiver_account,\n showGetVerifiedBtn:\n accountId === snapshot.receiver_account ||\n accountId === authorId,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Requested Sponsor\">\n {snapshot.requested_sponsor && (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.requested_sponsor,\n noOverlay: true,\n }}\n />\n )}\n </SidePanelItem>\n <SidePanelItem title=\"Supervisor\">\n {snapshot.supervisor ? (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.supervisor,\n noOverlay: true,\n }}\n />\n ) : (\n \"No Supervisor\"\n )}\n </SidePanelItem>\n <SidePanelItem\n hideBorder={true}\n title={\n <div>\n <div className=\"d-flex justify-content-between align-content-center\">\n Timeline\n {isModerator && (\n <div onClick={() => setShowTimelineSetting(true)}>\n <i class=\"bi bi-gear\"></i>\n </div>\n )}\n </div>\n {showTimelineSetting && (\n <div className=\"mt-2 d-flex flex-column gap-2\">\n <h6 className=\"mb-0\">Proposal Status</h6>\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: proposalStatusOptions,\n selectedValue: updatedProposalStatus,\n onUpdate: (v) => {\n setUpdatedProposalStatus({\n ...v,\n value: {\n ...v.value,\n ...updatedProposalStatus.value,\n status: v.value.status,\n },\n });\n },\n }}\n />\n </div>\n )}\n </div>\n }\n >\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"d-flex gap-3 mt-2\">\n <div className=\"d-flex flex-column\">\n {stepsArray.map((_, index) => {\n const indexOfCurrentItem = index;\n let color = \"\";\n let statusIndex = selectedStatusIndex;\n // index 2,3,4 is of decision\n if (\n selectedStatusIndex === 3 ||\n selectedStatusIndex === 2 ||\n selectedStatusIndex === 4 ||\n selectedStatusIndex === 5\n ) {\n statusIndex = 2;\n }\n if (selectedStatusIndex === 6) {\n statusIndex = 3;\n }\n const current = statusIndex === indexOfCurrentItem;\n const completed =\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED;\n return (\n <div className=\"d-flex flex-column align-items-center gap-1\">\n <div\n className={\n \"circle \" +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n >\n {completed && (\n <div\n className=\"d-flex justify-content-center align-items-center\"\n style={{ height: \"110%\" }}\n >\n <i class=\"bi bi-check\"></i>\n </div>\n )}\n </div>\n\n {index !== stepsArray.length - 1 && (\n <div\n className={\n \"vertical-line\" +\n (index === stepsArray.length - 2\n ? \"-sm \"\n : \" \") +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n ></div>\n )}\n </div>\n );\n })}\n </div>\n <div className=\"d-flex flex-column gap-3\">\n <TimelineItems\n title=\"1) Draft\"\n value={TIMELINE_STATUS.DRAFT}\n >\n <div>\n Once an author submits a proposal, it is in draft mode\n and open for community comments. The author can still\n make changes to the proposal during this stage and\n submit it for official review when ready.\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"2) Review\"\n value={TIMELINE_STATUS.REVIEW}\n >\n <div className=\"d-flex flex-column gap-2\">\n Sponsors who agree to consider the proposal may\n request attestations from work groups.\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n sponsor_requested_review: value,\n },\n }))\n }\n label=\"Sponsor provides feedback or requests reviews\"\n isChecked={\n updatedProposalStatus.value\n .sponsor_requested_review\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n label=\"Reviewer completes attestations (Optional)\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n reviewer_completed_attestation: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .reviewer_completed_attestation\n }\n />\n <CheckBox\n value={updatedProposalStatus.value.kyc_verified}\n label=\"Sponsor verifies KYC/KYB\"\n disabled={selectedStatusIndex !== 1}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n kyc_verified: value,\n },\n }))\n }\n isChecked={updatedProposalStatus.value.kyc_verified}\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"3) Decision\"\n values={[\n TIMELINE_STATUS.APPROVED,\n TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n TIMELINE_STATUS.REJECTED,\n ]}\n >\n <div className=\"d-flex flex-column gap-2\">\n <div>Sponsor makes a final decision:</div>\n <RadioButton\n value=\"\"\n label={\n <>\n <div className=\"fw-bold\">Approved</div>\n <span>\n Recipient will receive invoice instructions\n within 1 business day at the email used for\n KYC/KYB verification.\n </span>\n </>\n }\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.PAYMENT_PROCESSING ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n }\n />\n <RadioButton\n value=\"\"\n label={\n <>\n <div className=\"fw-bold\">\n Approved - Conditional{\" \"}\n </div>\n <span>\n Requires follow up from recipient. Moderators\n will provide further details.\n </span>\n </>\n }\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED_CONDITIONALLY\n }\n />\n <RadioButton\n value=\"Reject\"\n label={<div className=\"fw-bold\">Rejected</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.REJECTED\n }\n />\n <RadioButton\n value=\"Canceled\"\n label={<div className=\"fw-bold\">Canceled</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.CANCELED\n }\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"4) Payment Processing\"\n value={TIMELINE_STATUS.PAYMENT_PROCESSING}\n >\n <div className=\"d-flex flex-column gap-2\">\n <CheckBox\n value={\n updatedProposalStatus.value.test_transaction_sent\n }\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor confirmed sponsorship and shared funding steps with recipient\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n {/* Not needed for Alpha testing */}\n {/* <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor sends test transaction\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor creates funding request from Trustees\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n request_for_trustees_created: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .request_for_trustees_created\n }\n /> */}\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"5) Funded\"\n value={TIMELINE_STATUS.FUNDED}\n >\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes?.length > 1 ? (\n paymentHashes.slice(0, -1).map((link, index) => (\n <a\n key={index}\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i className=\"bi bi-arrow-up-right\"></i>\n </a>\n ))\n ) : updatedProposalStatus.value.payouts.length > 0 ? (\n <div>\n {updatedProposalStatus.value.payouts.map(\n (link) => {\n return (\n <a\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i class=\"bi bi-arrow-up-right\"></i>\n </a>\n );\n }\n )}\n </div>\n ) : (\n \"No Payouts yet\"\n )}\n </div>\n </TimelineItems>\n </div>\n </div>\n {showTimelineSetting && (\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Supervisor</label>\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n placeholder: \"Enter Supervisor\",\n onUpdate: setSupervisor,\n }}\n />\n </div>\n {updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED && (\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Payment Link</label>\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes.map((item, index) => (\n <div className=\"d-flex gap-2 justify-content-between align-items-center\">\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n onChange: (e) => {\n const updatedHashes = [...paymentHashes];\n updatedHashes[index] = e.target.value;\n setPaymentHashes(updatedHashes);\n },\n skipPaddingGap: true,\n placeholder: \"Enter URL\",\n }}\n />\n <div style={{ minWidth: 20 }}>\n {index !== paymentHashes.length - 1 ? (\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none w-100\",\n },\n label: <i class=\"bi bi-trash3 h6\"></i>,\n onClick: () => {\n const updatedHashes = [\n ...paymentHashes,\n ];\n updatedHashes.splice(index, 1);\n setPaymentHashes(updatedHashes);\n },\n }}\n />\n ) : (\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"green-btn shadow-none border-0 w-100\",\n },\n label: <i class=\"bi bi-plus-lg\"></i>,\n onClick: () =>\n setPaymentHashes([\n ...paymentHashes,\n \"\",\n ]),\n }}\n />\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n <div className=\"d-flex gap-2 align-items-center justify-content-end text-sm\">\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Cancel\",\n classNames: {\n root: \"btn-outline-danger border-0 shadow-none btn-sm\",\n },\n onClick: () => {\n setShowTimelineSetting(false);\n setUpdatedProposalStatus(proposalStatus);\n },\n }}\n />\n\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n disabled:\n ((updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED_CONDITIONALLY ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.PAYMENT_PROCESSING) &&\n !updatedProposalStatus.value.kyc_verified) ||\n (!supervisor &&\n DecisionStage.includes(\n updatedProposalStatus.value.status\n )),\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n if (snapshot.supervisor !== supervisor) {\n editProposal({\n timeline: updatedProposalStatus.value,\n });\n } else if (\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n ) {\n editProposalStatus({\n timeline: {\n ...updatedProposalStatus.value,\n payouts: !paymentHashes[0]\n ? []\n : paymentHashes.filter(\n (item) => item !== \"\"\n ),\n },\n });\n } else {\n editProposalStatus({\n timeline: updatedProposalStatus.value,\n });\n }\n setShowTimelineSetting(false);\n },\n }}\n />\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n </div>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.entity.post.Post": { "": "// Ideally, this would be a page\n\nconst { href } = VM.require(\"devhub.near/widget/core.lib.url\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devhub.near/widget/core.lib.common\"\n);\n\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.near/widget/devhub.entity.post.draft\"\n);\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst ButtonWithHover = styled.button`\n background-color: #fff;\n transition: all 300ms;\n border-radius: 0.5rem;\n\n &:hover {\n background-color: #e9ecef;\n color: #000;\n }\n\n &:disabled {\n background-color: #fff;\n color: #b7b7b7;\n }\n`;\n\nconst LikeLoadingSpinner = (\n <span\n className=\"like-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n);\n\nconst postId = props.post.id ?? (props.id ? parseInt(props.id) : 0);\n\nconst [isLikeClicked, setIsLikeClicked] = useState(false);\nconst [numLikes, setNumLikes] = useState(null);\n\nconst post =\n props.post ??\n Near.view(\"devgovgigs.near\", \"get_post\", { post_id: postId });\n\nif (!post) {\n return <div>Loading ...</div>;\n}\n\nif (isLikeClicked && numLikes !== post.likes.length) {\n setIsLikeClicked(false);\n}\n\nsetNumLikes(post.likes.length);\n\nconst referral = props.referral;\nconst currentTimestamp = props.timestamp ?? post.snapshot.timestamp;\nconst compareTimestamp = props.compareTimestamp ?? \"\";\nconst swapTimestamps = currentTimestamp < compareTimestamp;\n\nconst snapshotHistory = post.snapshot_history;\n\nconst snapshot =\n currentTimestamp === post.snapshot.timestamp\n ? post.snapshot\n : (snapshotHistory &&\n snapshotHistory.find((s) => s.timestamp === currentTimestamp)) ??\n null;\n\nconst compareSnapshot =\n compareTimestamp === post.snapshot.timestamp\n ? post.snapshot\n : (snapshotHistory &&\n snapshotHistory.find((s) => s.timestamp === compareTimestamp)) ??\n null;\n\n// If this post is displayed under another post. Used to limit the size.\nconst isUnderPost = props.isUnderPost ? true : false;\n\nconst parentId = Near.view(\"devgovgigs.near\", \"get_parent_id\", {\n post_id: postId,\n});\n\nconst childPostIdsUnordered =\n Near.view(\"devgovgigs.near\", \"get_children_ids\", {\n post_id: postId,\n }) ?? [];\n\nconst childPostIds = props.isPreview ? [] : childPostIdsUnordered.reverse();\nconst expandable = props.isPreview ? false : props.expandable ?? false;\nconst defaultExpanded = expandable ? props.defaultExpanded : true;\n\nfunction readableDate(timestamp) {\n var a = new Date(timestamp);\n return a.toDateString() + \" \" + a.toLocaleTimeString();\n}\n\nconst timestamp = readableDate(\n snapshot.timestamp ? snapshot.timestamp / 1000000 : Date.now()\n);\n\nconst postSearchKeywords = props.searchKeywords ? (\n <div style={{ \"font-family\": \"monospace\" }} key=\"post-search-keywords\">\n <span>Found keywords: </span>\n\n {props.searchKeywords.map((tag) => (\n <Widget\n src={\"devhub.near/widget/devhub.components.atom.Tag\"}\n props={{ linkTo: \"Feed\", tag }}\n />\n ))}\n </div>\n) : (\n <div key=\"post-search-keywords\"></div>\n);\n\nconst searchKeywords = props.searchKeywords ? (\n <div class=\"mb-4\" key=\"search-keywords\">\n <small class=\"text-muted\">{postSearchKeywords}</small>\n </div>\n) : (\n <div key=\"search-keywords\"></div>\n);\n\nconst allowedToEdit =\n !props.isPreview &&\n Near.view(\"devgovgigs.near\", \"is_allowed_to_edit\", {\n post_id: postId,\n editor: context.accountId,\n });\n\nconst btnEditorWidget = (postType, name) => {\n return (\n <li>\n <a\n class=\"dropdown-item\"\n role=\"button\"\n onClick={() =>\n State.update({ postType, editorType: \"EDIT\", showEditor: true })\n }\n >\n {name}\n </a>\n </li>\n );\n};\n\nconst editControl = allowedToEdit ? (\n <div class=\"btn-group\" role=\"group\">\n <a\n class=\"card-link px-2\"\n role=\"button\"\n title=\"Edit post\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n type=\"button\"\n >\n <div class=\"bi bi-pencil-square\"></div>\n </a>\n\n <ul class=\"dropdown-menu\">\n {btnEditorWidget(\"Idea\", \"Edit as an idea\")}\n {btnEditorWidget(\"Solution\", \"Edit as a solution\")}\n {btnEditorWidget(\"Attestation\", \"Edit as an attestation\")}\n {btnEditorWidget(\"Sponsorship\", \"Edit as a sponsorship\")}\n {btnEditorWidget(\"Comment\", \"Edit as a comment\")}\n </ul>\n </div>\n) : (\n <div></div>\n);\n\nconst shareButton = props.isPreview ? (\n <div></div>\n) : (\n <Link\n class=\"card-link text-dark\"\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"post\", id: postId },\n })}\n role=\"button\"\n target=\"_blank\"\n title=\"Open in new tab\"\n >\n <div class=\"bi bi-share\"></div>\n </Link>\n);\n\nconst ProfileCardContainer = styled.div`\n @media screen and (max-width: 960px) {\n width: 100%;\n }\n`;\n\n// card-header\nconst header = (\n <div key=\"header\">\n <small class=\"text-muted\">\n <div class=\"row justify-content-between\">\n <div class=\"d-flex align-items-center flex-wrap\">\n <ProfileCardContainer>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.ProfileCard\"\n }\n props={{\n accountId: post.author_id,\n }}\n />\n </ProfileCardContainer>\n\n <div class=\"d-flex ms-auto\">\n {editControl}\n {timestamp}\n\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.History\"}\n props={{\n post,\n timestamp: currentTimestamp,\n }}\n />\n {shareButton}\n </div>\n </div>\n </div>\n </small>\n </div>\n);\n\n// const emptyIcons = {\n// Idea: \"bi-lightbulb\",\n// Comment: \"bi-chat\",\n// Solution: \"bi-rocket\",\n// Attestation: \"bi-check-circle\",\n// Sponsorship: \"bi-cash-coin\",\n// Github: \"bi-github\",\n// Like: \"bi-heart\",\n// Reply: \"bi-reply\",\n// };\n\nconst emptyIcons = {\n Idea: \"💡\",\n Comment: \"bi-chat\",\n Solution: \"🚀\",\n Attestation: \"✅\",\n Sponsorship: \"🪙\",\n Github: \"bi-github\",\n Like: \"bi-heart\",\n Reply: \"bi-reply\",\n};\n\nconst fillIcons = {\n Idea: \"💡\",\n Comment: \"bi-chat-fill\",\n Solution: \"🚀\",\n Attestation: \"✅\",\n Sponsorship: \"🪙\",\n Github: \"bi-github\",\n Like: \"bi-heart-fill\",\n Reply: \"bi-reply-fill\",\n};\n\n// Trigger saving this widget.\n\nconst borders = {\n Idea: \"border-light\",\n Comment: \"border-light\",\n Solution: \"border-light\",\n Attestation: \"border-light\",\n Sponsorship: \"border-light\",\n Github: \"border-light\",\n};\n\nconst containsLike = props.isPreview\n ? false\n : post.likes.find((l) => l.author_id == context.accountId);\nconst likeBtnClass = containsLike ? fillIcons.Like : emptyIcons.Like;\n// This must be outside onLike, because Near.view returns null at first, and when the view call finished, it returns true/false.\n// If checking this inside onLike, it will give `null` and we cannot tell the result is true or false.\nlet grantNotify = Near.view(\n \"social.near\",\n \"is_write_permission_granted\",\n {\n predecessor_id: \"devgovgigs.near\",\n key: context.accountId + \"/index/notify\",\n }\n);\n\nconst userStorageDeposit = Near.view(\n \"social.near\",\n \"storage_balance_of\",\n {\n account_id: context.accountId,\n }\n);\n\nif (grantNotify === null || userStorageDeposit === null) {\n return;\n}\n\nconst onLike = () => {\n if (!context.accountId) {\n return;\n }\n\n let likeTxn = [\n {\n contractName: \"devgovgigs.near\",\n methodName: \"add_like\",\n args: {\n post_id: postId,\n },\n gas: Big(10).pow(14),\n },\n ];\n\n if (grantNotify === false) {\n likeTxn.unshift({\n contractName: \"social.near\",\n methodName: \"grant_write_permission\",\n args: {\n predecessor_id: \"devgovgigs.near\",\n keys: [context.accountId + \"/index/notify\"],\n },\n gas: Big(10).pow(14),\n deposit: getDepositAmountForWriteAccess(userStorageDeposit),\n });\n }\n\n setIsLikeClicked(true);\n Near.call(likeTxn);\n};\n\nconst btnCreatorWidget = (postType, icon, name, desc) => {\n return (\n <li class=\"py-1\">\n <a\n class=\"dropdown-item text-decoration-none d-flex align-items-center lh-sm\"\n style={{ color: \"rgb(55,109,137)\" }}\n role=\"button\"\n onClick={() =>\n State.update({ postType, editorType: \"CREATE\", showEditor: true })\n }\n >\n <i class={`bi ${icon}`} style={{ fontSize: \"1.5rem\" }}>\n {\" \"}\n </i>\n\n <div class=\"ps-2 text-wrap\" style={{ width: \"18rem\" }}>\n <div>{name}</div>\n <small class=\"fw-light text-secondary\">{desc}</small>\n </div>\n </a>\n </li>\n );\n};\n\nconst FooterButtonsContianer = styled.div`\n width: 66.66666667%;\n\n @media screen and (max-width: 960px) {\n width: 100%;\n }\n`;\n\nconst buttonsFooter = props.isPreview ? null : (\n <div class=\"row\" key=\"buttons-footer\">\n <FooterButtonsContianer>\n <div class=\"btn-group\" role=\"group\" aria-label=\"Basic outlined example\">\n <ButtonWithHover\n type=\"button\"\n class=\"btn d-flex align-items-center\"\n style={{ border: \"0px\" }}\n onClick={onLike}\n disabled={isLikeClicked}\n >\n <i class={`bi ${likeBtnClass}`}> </i>\n {isLikeClicked ? LikeLoadingSpinner : <></>}\n {post.likes.length == 0 ? (\n \"Like\"\n ) : (\n <Widget\n src=\"devhub.near/widget/devhub.components.layout.LikeButton.Faces\"\n props={{\n likesByUsers: Object.fromEntries(\n post.likes.map(({ author_id }) => [author_id, \"\"])\n ),\n }}\n />\n )}\n </ButtonWithHover>\n\n <div class=\"btn-group\" role=\"group\">\n <ButtonWithHover\n type=\"button\"\n class=\"btn\"\n style={{ border: \"0px\" }}\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n ↪ Reply\n </ButtonWithHover>\n <ul class=\"dropdown-menu\">\n {btnCreatorWidget(\n \"Idea\",\n emptyIcons.Idea,\n \"Idea\",\n \"Get feedback from the community about a problem, opportunity, or need.\"\n )}\n {btnCreatorWidget(\n \"Solution\",\n emptyIcons.Solution,\n \"Solution\",\n \"Provide a specific proposal or implementation to an idea, optionally requesting funding.\"\n )}\n {btnCreatorWidget(\n \"Attestation\",\n emptyIcons.Attestation,\n \"Attestation\",\n \"Formally review or validate a solution as a recognized expert.\"\n )}\n {btnCreatorWidget(\n \"Sponsorship\",\n emptyIcons.Sponsorship,\n \"Sponsorship\",\n \"Offer to fund projects, events, or proposals that match your needs.\"\n )}\n <li>\n <hr class=\"dropdown-divider\" />\n </li>\n {btnCreatorWidget(\n \"Comment\",\n emptyIcons.Comment,\n \"Comment\",\n \"Ask a question, provide information, or share a resource that is relevant to the thread.\"\n )}\n </ul>\n </div>\n {childPostIds.length > 0 && (\n <ButtonWithHover\n type=\"button\"\n class=\"btn\"\n style={{ border: \"0px\" }}\n data-bs-toggle=\"collapse\"\n href={`#collapseChildPosts${postId}`}\n aria-expanded={defaultExpanded}\n aria-controls={`collapseChildPosts${postId}`}\n onClick={() =>\n State.update({ expandReplies: !state.expandReplies })\n }\n >\n <i\n class={`bi bi-chevron-${state.expandReplies ? \"up\" : \"down\"}`}\n ></i>{\" \"}\n {`${state.expandReplies ? \"Collapse\" : \"Expand\"} Replies (${\n childPostIds.length\n })`}\n </ButtonWithHover>\n )}\n\n {isUnderPost || !parentId ? (\n <div key=\"link-to-parent\"></div>\n ) : (\n <Link\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"post\", id: parentId },\n })}\n >\n <ButtonWithHover\n type=\"button\"\n style={{ border: \"0px\" }}\n className=\"btn\"\n key=\"link-to-parent\"\n >\n <i class=\"bi bi-arrow-90deg-up\"></i>Go to parent\n </ButtonWithHover>\n </Link>\n )}\n </div>\n </FooterButtonsContianer>\n </div>\n);\n\nconst tokenMapping = {\n NEAR: \"NEAR\",\n USDT: {\n NEP141: {\n address: \"usdt.tether-token.near\",\n },\n },\n USDC: {\n NEP141: {\n address:\n \"17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1\",\n },\n },\n // Add more tokens here as needed\n};\n\nconst reverseTokenMapping = Object.keys(tokenMapping).reduce(\n (reverseMap, key) => {\n const value = tokenMapping[key];\n if (typeof value === \"object\") {\n reverseMap[JSON.stringify(value)] = key;\n }\n return reverseMap;\n },\n {}\n);\n\nfunction tokenResolver(token) {\n if (typeof token === \"string\") {\n return token;\n } else if (typeof token === \"object\") {\n const tokenString = reverseTokenMapping[JSON.stringify(token)];\n return tokenString || null;\n } else {\n return null; // Invalid input\n }\n}\n\nconst isDraft =\n (draftState?.parent_post_id === postId &&\n draftState?.postType === state.postType) ||\n (draftState?.edit_post_id === postId &&\n draftState?.postType === state.postType);\n\nconst setExpandReplies = (value) => {\n State.update({ expandReplies: value });\n};\n\nconst setEditorState = (value) => {\n if (draftState && !value) {\n // clear the draft state since user initiated cancel\n onDraftStateChange(null);\n }\n State.update({ showEditor: value });\n};\n\nlet amount = null;\nlet token = null;\nlet supervisor = null;\n\nif (state.postType === \"Solution\") {\n const amountMatch = post.snapshot.description.match(\n /Requested amount: (\\d+(\\.\\d+)?) (\\w+)/\n );\n amount = amountMatch ? parseFloat(amountMatch[1]) : null;\n token = amountMatch ? amountMatch[3] : null;\n\n const sponsorMatch = post.snapshot.description.match(\n /Requested sponsor: @([^\\s]+)/\n );\n supervisor = sponsorMatch ? sponsorMatch[1] : null;\n}\n\nconst seekingFunding = amount !== null || token !== null || supervisor !== null;\n\nfunction Editor() {\n return (\n <div class=\"row mt-2\" id={`accordion${postId}`} key=\"editors-footer\">\n <div\n key={`${state.postType}${state.editorType}${postId}`}\n className={\"w-100\"}\n >\n {state.editorType === \"CREATE\" ? (\n <>\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.PostEditor\"}\n props={{\n postType: state.postType,\n onDraftStateChange,\n draftState:\n draftState?.parent_post_id == postId ? draftState : undefined,\n parentId: postId,\n mode: \"Create\",\n transactionHashes: props.transactionHashes,\n setExpandReplies,\n setEditorState: setEditorState,\n }}\n />\n </>\n ) : (\n <>\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.PostEditor\"}\n props={{\n postType: state.postType,\n postId,\n mode: \"Edit\",\n author_id: post.author_id,\n labels: post.snapshot.labels,\n name: post.snapshot.name,\n description: post.snapshot.description,\n amount: post.snapshot.amount || amount,\n token: tokenResolver(post.snapshot.sponsorship_token || token),\n supervisor:\n post.snapshot.post.snapshot.supervisor || supervisor,\n seekingFunding: seekingFunding,\n githubLink: post.snapshot.github_link,\n onDraftStateChange,\n draftState:\n draftState?.edit_post_id == postId ? draftState : undefined,\n setEditorState: setEditorState,\n transactionHashes: props.transactionHashes,\n setExpandReplies,\n }}\n />\n </>\n )}\n </div>\n </div>\n );\n}\n\nconst renamedPostType =\n snapshot.post_type == \"Submission\" ? \"Solution\" : snapshot.post_type;\n\nconst tags = post.snapshot.labels ? (\n <div\n class=\"card-title d-flex flex-wrap align-items-center\"\n style={{ margin: \"20px 0\" }}\n key=\"post-labels\"\n >\n {post.snapshot.labels.map((tag, idx) => (\n <div className=\"d-flex align-items-center my-3 me-3\">\n <Link\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"feed\", tag: tag },\n })}\n >\n <div\n onClick={() => {\n if (typeof props.updateTagInParent === \"function\") {\n props.updateTagInParent(tag);\n }\n }}\n className=\"d-flex gap-3 align-items-center\"\n style={{ cursor: \"pointer\", textDecoration: \"none\" }}\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.atom.Tag\"}\n props={{\n tag,\n black: true,\n }}\n />\n </div>\n </Link>\n {idx !== post.snapshot.labels.length - 1 && (\n <span className=\"ms-3\">•</span>\n )}\n </div>\n ))}\n </div>\n) : (\n <div key=\"post-labels\"></div>\n);\n\nconst Title = styled.h5`\n margin: 1rem 0;\n\n color: #151515;\n font-size: 1.15rem;\n font-style: normal;\n font-weight: 700;\n line-height: 1.625rem; /* 55.556% */\n`;\n\nconst postTitle =\n snapshot.post_type == \"Comment\" ? (\n <div key=\"post-title\"></div>\n ) : (\n <Title key=\"post-title\">\n {emptyIcons[snapshot.post_type]} {renamedPostType}: {snapshot.name}\n </Title>\n );\n\nconst postExtra =\n snapshot.post_type == \"Sponsorship\" ? (\n <div key=\"post-extra\">\n <h6 class=\"card-subtitle mb-2 text-muted\">\n Maximum amount: {snapshot.amount}{\" \"}\n {tokenResolver(snapshot.sponsorship_token)}\n </h6>\n <h6 class=\"card-subtitle mb-2 text-muted\">\n Supervisor:{\" \"}\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.ProfileLine\"}\n props={{ accountId: snapshot.supervisor }}\n />\n </h6>\n </div>\n ) : (\n <div></div>\n );\n\nconst childPostHasDraft = childPostIds.find(\n (childId) =>\n childId == draftState?.edit_post_id || childId == draftState?.parent_post_id\n);\nif (\n (childPostHasDraft || state.childrenOfChildPostsHasDraft) &&\n props.expandParent\n) {\n props.expandParent();\n}\n\nconst postsList =\n props.isPreview || childPostIds.length == 0 ? (\n <div key=\"posts-list\"></div>\n ) : (\n <div class=\"row\" key=\"posts-list\">\n <div\n class={`collapse mt-3 ${\n defaultExpanded ||\n childPostHasDraft ||\n state.childrenOfChildPostsHasDraft ||\n state.expandReplies\n ? \"show\"\n : \"\"\n }`}\n id={`collapseChildPosts${postId}`}\n >\n {childPostIds.map((childId) => (\n <div key={childId} style={{ marginBottom: \"0.5rem\" }}>\n <Widget\n src=\"devhub.near/widget/devhub.entity.post.Post\"\n props={{\n id: childId,\n isUnderPost: true,\n onDraftStateChange,\n draftState,\n expandParent: () =>\n State.update({ childrenOfChildPostsHasDraft: true }),\n referral: `subpost${childId}of${postId}`,\n }}\n />\n </div>\n ))}\n </div>\n </div>\n );\n\nconst LimitedMarkdown = styled.div`\n max-height: 20em;\n`;\n\n// Determine if located in the post page.\nconst isInList = props.isInList;\nconst contentArray = snapshot.description.split(\"\\n\");\nconst needClamp = isInList && contentArray.length > 5;\n\ninitState({\n clamp: needClamp,\n expandReplies: defaultExpanded,\n});\n\nconst clampedContent = needClamp\n ? contentArray.slice(0, 3).join(\"\\n\")\n : snapshot.description;\n\nconst SeeMore = styled.a`\n cursor: pointer;\n color: #00b774 !important;\n font-weight: bold;\n`;\n\n// Should make sure the posts under the currently top viewed post are limited in size.\nconst descriptionArea = isUnderPost ? (\n <LimitedMarkdown className=\"overflow-auto\" key=\"description-area\">\n {/* {widget(\"components.molecule.markdown-viewer\", {\n text: snapshot.description,\n })} */}\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{\n content: snapshot.description,\n }}\n />\n </LimitedMarkdown>\n) : (\n <div className=\"w-100 overflow-auto\">\n <div class={state.clamp ? \"clamp\" : \"\"}>\n {/* {widget(\"components.molecule.markdown-viewer\", {\n text: state.clamp ? clampedContent : snapshot.description,\n })} */}\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{\n content: state.clamp ? clampedContent : snapshot.description,\n }}\n />\n </div>\n {state.clamp ? (\n <div class=\"d-flex justify-content-start\">\n <SeeMore onClick={() => State.update({ clamp: false })}>\n See more\n </SeeMore>\n </div>\n ) : (\n <></>\n )}\n </div>\n);\n\nconst timestampElement = (_snapshot) => {\n return (\n <Link\n class=\"text-muted\"\n href={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"post\",\n id: postId,\n timestamp: _snapshot.timestamp,\n compareTimestamp: null,\n referral,\n },\n })}\n >\n {readableDate(_snapshot.timestamp / 1000000).substring(4)}\n\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n accountId: _snapshot.editor_id,\n style: {\n width: \"1.25em\",\n height: \"1.25em\",\n },\n imageStyle: {\n transform: \"translateY(-12.5%)\",\n },\n }}\n />\n {_snapshot.editor_id.substring(0, 8)}\n </Link>\n );\n};\n\nfunction combineText(_snapshot) {\n return (\n \"## \" +\n _snapshot.post_type +\n \": \" +\n _snapshot.name +\n \"\\n\" +\n _snapshot.description\n );\n}\n\nconst CardContainer = styled.div`\n padding: 1.5rem 3rem !important;\n border-radius: 16px !important;\n border: 1px solid rgba(129, 129, 129, 0.3) !important;\n background: #fffefe !important;\n\n @media screen and (max-width: 960px) {\n padding: 1rem !important;\n }\n`;\n\nreturn (\n <CardContainer className={`card ${borders[snapshot.post_type]} attractable`}>\n {header}\n <div className=\"card-body\" style={{ padding: 0 }}>\n {searchKeywords}\n {compareSnapshot ? (\n <div\n class=\"border rounded\"\n style={{ marginTop: \"16px\", marginBottom: \"16px\" }}\n >\n <div class=\"d-flex justify-content-end\" style={{ fontSize: \"12px\" }}>\n <div class=\"d-flex w-50 justify-content-end mt-1 me-2\">\n {timestampElement(snapshot)}\n {snapshot !== compareSnapshot && (\n <>\n <div class=\"mx-1 align-self-center\">\n <i class=\"bi bi-file-earmark-diff\" />\n </div>\n {timestampElement(compareSnapshot)}\n </>\n )}\n </div>\n </div>\n <div className=\"w-100 overflow-auto\">\n <Widget\n src=\"markeljan.near/widget/MarkdownDiff\"\n props={{\n post: post,\n currentCode: combineText(\n swapTimestamps ? compareSnapshot : snapshot\n ),\n prevCode: combineText(\n swapTimestamps ? snapshot : compareSnapshot\n ),\n showLineNumber: true,\n }}\n />\n </div>\n </div>\n ) : (\n <>\n {postTitle}\n {postExtra}\n {descriptionArea}\n </>\n )}\n {tags}\n {buttonsFooter}\n {!props.isPreview && (isDraft || state.showEditor) && <Editor />}\n {postsList}\n </div>\n </CardContainer>\n);\n" }, "devhub.entity.addon.blogv2.Page": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst httpbin = fetch(`https://httpbin.org/headers`);\n\nconst { getCommunity } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n) || {\n getCommunity: () => {},\n};\nconst imagelink =\n \"https://ipfs.near.social/ipfs/bafkreiajzvmy7574k7mp3if6u53mdukfr3hoc2kjkhjadt6x56vqhd5swy\";\n\nconst { data, onEdit, community: handle, isAllowedToEdit } = props;\n\nconst {\n category,\n title,\n subtitle,\n publishedAt: date,\n content,\n author,\n communityAddonId,\n} = data;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n margin-bottom: 2rem;\n position: relative;\n\n span.news {\n color: #f40303;\n }\n\n span.reference {\n color: #ff7a00;\n }\n\n span.guide {\n color: #004be1;\n }\n\n span.category {\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n text-transform: uppercase;\n }\n\n div.date {\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin: 1.5rem 0;\n width: 100%;\n }\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1.5rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n .edit-icon {\n position: absolute;\n top: 20px;\n right: 20px;\n cursor: pointer;\n }\n\n .share-icon {\n position: absolute;\n top: 25px;\n right: 55px;\n cursor: pointer;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n`;\n\nconst BackgroundImage = styled.img`\n width: 100%;\n height: auto;\n object-fit: cover;\n margin-bottom: 1rem;\n`;\n\nif (!handle) {\n return <div>Community handle not found</div>;\n}\n\nconst community = getCommunity({ handle: handle });\n\nlet communityConfig = (community.addons || []).find(\n (addon) => addon.id === communityAddonId\n);\n\nif (communityConfig === undefined) {\n return <div>Community addon not found</div>;\n}\nconst parameters = JSON.parse(communityConfig.parameters || {}) || {};\n\n// Make sure only the categories that are configured in the settings are displayed.\nlet categoryIsOptionInSettings = true;\nlet categoriesInSettings = (parameters?.categories || []).map((c) => c.value);\nif (\n categoriesInSettings.length > 0 &&\n !categoriesInSettings.includes(category) &&\n category !== \"\" &&\n category !== null\n) {\n categoryIsOptionInSettings = false;\n}\n\nconst options = { year: \"numeric\", month: \"short\", day: \"numeric\" };\nconst formattedDate = new Date(date).toLocaleString(\"en-US\", options);\n\nreturn (\n <>\n <BackgroundImage src={imagelink} />\n <Container>\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.ShareButton\"\n props={{\n className: \"share-icon\",\n size: \"35px\",\n postType: \"blog\",\n externalLink: href({\n gateway: httpbin?.body?.headers?.Origin.slice(8) ?? \"near.social\",\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"blogv2\",\n community: community.handle,\n id: data.id,\n },\n }),\n }}\n />\n {isAllowedToEdit && (\n <div className=\"edit-icon\" onClick={onEdit}>\n <div class=\"bi bi-pencil-square\" style={{ fontSize: \"30px\" }}></div>\n </div>\n )}\n {category &&\n parameters.categoriesEnabled === \"enabled\" &&\n categoryIsOptionInSettings && (\n <span\n className={`category ${category && category.toLowerCase()}`}\n data-testid=\"blog-category\"\n >\n {category}\n </span>\n )}\n <h1 data-testid=\"blog-title\">{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <div className=\"d-flex flex-row justify-content-between date\">\n {author && parameters.authorEnabled !== \"disabled\" && (\n <div data-testid=\"blog-author\">{author}</div>\n )}\n <div data-testid=\"blog-date\">{formattedDate}</div>\n </div>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{ content: content }}\n />\n </Container>\n </>\n);\n" }, "devhub.entity.proposal.Feed": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\") || {\n href: () => {},\n};\n\nconst instance = props.instance ?? \"\";\n\nconst {\n contract,\n rfpFeedIndexerQueryName,\n proposalFeedAnnouncement,\n availableCategoryOptions,\n proposalFeedIndexerQueryName,\n indexerHasuraRole,\n isDevhub,\n isInfra,\n isEvents,\n} = VM.require(`${instance}/widget/config.data`);\n\nconst loader = (\n <div className=\"d-flex justify-content-center align-items-center w-100\">\n <Widget src={\"devhub.near/widget/devhub.components.molecule.Spinner\"} />\n </div>\n);\n\nif (!contract) {\n return loader;\n}\n\nfunction isNumber(v) {\n return typeof v === \"number\";\n}\n\nconst Container = styled.div`\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n @media screen and (max-width: 768px) {\n font-size: 13px;\n }\n\n .text-sm {\n font-size: 13px;\n }\n\n .bg-grey {\n background-color: #f4f4f4;\n }\n\n .border-bottom {\n border-bottom: 1px solid grey;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .proposal-card {\n border-left: none !important;\n border-right: none !important;\n border-bottom: none !important;\n &:hover {\n background-color: #f4f4f4;\n }\n }\n\n @media screen and (max-width: 768px) {\n .theme-btn {\n padding: 0.5rem 0.8rem !important;\n min-height: 32px;\n }\n }\n\n a.no-space {\n display: inline-block;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n\n .text-center {\n text-align: center;\n }\n\n .btn-grey-outline {\n background-color: #fafafa;\n border: 1px solid #e6e8eb;\n color: #11181c;\n\n &:hover {\n background-color: #e6e8eb;\n }\n\n &:active {\n border: 2px solid #e6e8eb;\n }\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n width: 100%;\n\n .text-normal {\n font-weight: normal !important;\n }\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst FeedItem = ({ proposal, index }) => {\n const accountId = proposal.author_id;\n const profile = Social.get(`${accountId}/profile/**`, \"final\");\n // We will have to get the proposal from the contract to get the block height.\n const blockHeight = parseInt(proposal.social_db_post_block_height);\n const item = {\n type: \"social\",\n path: `${contract}/post/main`,\n blockHeight: blockHeight,\n };\n\n const isLinked = isNumber(proposal.linked_rfp);\n const rfpData = proposal.rfpData;\n\n return (\n <a\n href={href({\n widgetSrc: `${instance}/widget/app`,\n params: {\n page: \"proposal\",\n id: proposal.proposal_id,\n },\n })}\n onClick={(e) => e.stopPropagation()}\n style={{ textDecoration: \"none\" }}\n >\n <div\n className={\n \"proposal-card d-flex justify-content-between gap-2 text-muted cursor-pointer p-3 w-100 flex-wrap flex-sm-nowrap \" +\n (index !== 0 && \" border\")\n }\n >\n <div className=\"d-flex gap-4 w-100\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2 w-100 text-wrap\">\n <div className=\"d-flex gap-2 align-items-center flex-wrap w-100\">\n <div className=\"h6 mb-0 text-black\">{proposal.name}</div>\n {(isInfra || isEvents) && (\n <Widget\n src={`devhub.near/widget/devhub.entity.proposal.MultiSelectLabelsDropdown`}\n props={{\n selected: proposal.labels,\n disabled: true,\n hideDropdown: true,\n onChange: () => {},\n availableOptions: availableCategoryOptions,\n }}\n />\n )}\n {isDevhub && (\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CategoryTag\"\n }\n props={{\n category: proposal.category,\n }}\n />\n )}\n </div>\n {isLinked && rfpData && (\n <div className=\"text-sm text-muted d-flex gap-1 align-items-center\">\n <i class=\"bi bi-link-45deg\"></i>\n In response to RFP :\n <a\n className=\"text-decoration-underline flex-1\"\n href={href({\n widgetSrc: `${instance}/widget/app`,\n params: {\n page: \"rfp\",\n id: rfpData.rfp_id,\n },\n })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {rfpData.name}\n </a>\n </div>\n )}\n <div className=\"d-flex gap-2 align-items-center flex-wrap flex-sm-nowrap text-sm w-100\">\n <div>#{proposal.proposal_id} ・ </div>\n <div className=\"text-truncate\">\n By {profile.name ?? accountId} ・{\" \"}\n </div>\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: proposal.timestamp,\n }}\n />\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: accountId,\n instance,\n }}\n />\n\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.CommentIcon\"}\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n </div>\n </div>\n </div>\n <div className=\"align-self-center\" style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: proposal.timeline.status,\n }}\n />\n </div>\n </div>\n </a>\n );\n};\n\nconst getProposal = (proposal_id) => {\n return Near.asyncView(contract, \"get_proposal\", {\n proposal_id,\n });\n};\n\nconst FeedPage = () => {\n const QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\n\n State.init({\n data: [],\n author: \"\",\n stage: \"\",\n sort: \"\",\n category: \"\",\n input: \"\",\n loading: false,\n searchLoader: false,\n makeMoreLoader: false,\n aggregatedCount: null,\n currentlyDisplaying: 0,\n });\n\n const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${proposalFeedIndexerQueryName}_bool_exp = {}) {\n ${proposalFeedIndexerQueryName}(\n offset: $offset\n limit: $limit\n order_by: {proposal_id: desc}\n where: $where\n ) {\n author_id\n block_height\n name\n category\n summary\n editor_id\n proposal_id\n ts\n timeline\n views\n labels\n linked_rfp\n }\n ${proposalFeedIndexerQueryName}_aggregate(\n order_by: {proposal_id: desc}\n where: $where\n ) {\n aggregate {\n count\n }\n }\n }`;\n\n function fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": indexerHasuraRole },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n }\n\n function separateNumberAndText(str) {\n const numberRegex = /\\d+/;\n\n if (numberRegex.test(str)) {\n const number = str.match(numberRegex)[0];\n const text = str.replace(numberRegex, \"\").trim();\n return { number: parseInt(number), text };\n } else {\n return { number: null, text: str.trim() };\n }\n }\n\n const buildWhereClause = () => {\n let where = {};\n if (state.author) {\n where = { author_id: { _eq: state.author }, ...where };\n }\n\n if (state.category) {\n if (isInfra || isEvents) {\n where = { labels: { _contains: state.category }, ...where };\n } else {\n where = { category: { _eq: state.category }, ...where };\n }\n }\n\n if (state.stage) {\n // timeline is stored as jsonb\n where = {\n timeline: { _cast: { String: { _regex: `${state.stage}` } } },\n ...where,\n };\n }\n if (state.input) {\n const { number, text } = separateNumberAndText(state.input);\n if (number) {\n where = { proposal_id: { _eq: number }, ...where };\n }\n\n if (text) {\n where = {\n _or: [\n { name: { _iregex: `${text}` } },\n { summary: { _iregex: `${text}` } },\n { description: { _iregex: `${text}` } },\n ],\n ...where,\n };\n }\n }\n\n return where;\n };\n\n const makeMoreItems = () => {\n State.update({ makeMoreLoader: true });\n fetchProposals(state.data.length);\n };\n\n const fetchProposals = (offset) => {\n if (!offset) {\n offset = 0;\n }\n if (state.loading) return;\n State.update({ loading: true });\n const FETCH_LIMIT = 10;\n const variables = {\n limit: FETCH_LIMIT,\n offset,\n where: buildWhereClause(),\n };\n fetchGraphQL(query, \"GetLatestSnapshot\", variables).then(async (result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[proposalFeedIndexerQueryName];\n const totalResult =\n result.body.data[`${proposalFeedIndexerQueryName}_aggregate`];\n const promises = data.map((item) => {\n if (isNumber(item.linked_rfp)) {\n return fetchGraphQL(rfpQuery, \"GetLatestSnapshot\", {}).then(\n (result) => {\n const rfpData = result.body.data?.[rfpQueryName];\n return { ...item, rfpData: rfpData[0] };\n }\n );\n } else {\n return Promise.resolve(item);\n }\n });\n Promise.all(promises).then((res) => {\n State.update({ aggregatedCount: totalResult.aggregate.count });\n fetchBlockHeights(res, offset);\n });\n }\n }\n });\n };\n\n useEffect(() => {\n State.update({ searchLoader: true });\n fetchProposals();\n }, [state.author, state.sort, state.category, state.stage]);\n\n const mergeItems = (newItems) => {\n const items = [\n ...new Set([...newItems, ...state.data].map((i) => JSON.stringify(i))),\n ].map((i) => JSON.parse(i));\n // Sorting in the front end\n if (state.sort === \"proposal_id\" || state.sort === \"\") {\n items.sort((a, b) => b.proposal_id - a.proposal_id);\n } else if (state.sort === \"views\") {\n items.sort((a, b) => b.views - a.views);\n }\n\n return items;\n };\n\n const fetchBlockHeights = (data, offset) => {\n let promises = data.map((item) => getProposal(item.proposal_id));\n Promise.all(promises).then((blockHeights) => {\n data = data.map((item, index) => ({\n ...item,\n timeline: JSON.parse(item.timeline),\n social_db_post_block_height:\n blockHeights[index].social_db_post_block_height,\n }));\n if (offset) {\n let newData = mergeItems(data);\n State.update({\n data: newData,\n currentlyDisplaying: newData.length,\n loading: false,\n searchLoader: false,\n makeMoreLoader: false,\n });\n } else {\n State.update({\n data,\n currentlyDisplaying: data.length,\n loading: false,\n searchLoader: false,\n makeMoreLoader: false,\n });\n }\n });\n };\n\n useEffect(() => {\n const handler = setTimeout(() => {\n fetchProposals();\n }, 1000);\n\n return () => {\n clearTimeout(handler);\n };\n }, [state.input]);\n\n return (\n <Container className=\"w-100 py-4 px-2 d-flex flex-column gap-3\">\n <div className=\"d-flex justify-content-between flex-wrap gap-2 align-items-center\">\n <Heading>\n Proposals\n <span className=\"text-muted text-normal\">\n ({state.aggregatedCount ?? state.data.length}){\" \"}\n </span>\n </Heading>\n <div className=\"d-flex flex-wrap gap-4 align-items-center\">\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-input\"\n }\n props={{\n search: state.input,\n className: \"w-xs-100\",\n onSearch: (input) => {\n State.update({ input });\n },\n onEnter: () => {\n fetchProposals();\n },\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.feature.proposal-search.by-sort\"}\n props={{\n onStateChange: (select) => {\n State.update({ sort: select.value });\n },\n }}\n />\n <div className=\"d-flex gap-4 align-items-center\">\n {!isInfra && (\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-category\"\n }\n props={{\n categoryOptions: availableCategoryOptions,\n onStateChange: (select) => {\n State.update({ category: select.value });\n },\n }}\n />\n )}\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-stage\"\n }\n props={{\n onStateChange: (select) => {\n State.update({ stage: select.value });\n },\n }}\n />\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-author\"\n }\n props={{\n contract,\n onAuthorChange: (select) => {\n State.update({ author: select.value });\n },\n }}\n />\n </div>\n </div>\n <div className=\"mt-2 mt-xs-0\">\n <Link\n to={href({\n widgetSrc: `${instance}/widget/app`,\n params: { page: \"create-proposal\" },\n })}\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: (\n <div className=\"d-flex gap-2 align-items-center\">\n <div>\n <i class=\"bi bi-plus-circle-fill\"></i>\n </div>\n Submit Proposal\n </div>\n ),\n classNames: { root: \"theme-btn\" },\n }}\n />\n </Link>\n </div>\n </div>\n <div style={{ minHeight: \"50vh\" }}>\n {!Array.isArray(state.data) ? (\n loader\n ) : (\n <div className=\"card no-border rounded-0 mt-4 py-3 full-width-div\">\n <div className=\"container-xl\">\n {proposalFeedAnnouncement}\n <div className=\"mt-4 border rounded-2\">\n {state.aggregatedCount === 0 ? (\n <div class=\"alert alert-danger m-2\" role=\"alert\">\n No proposals found for selected filter.{\" \"}\n </div>\n ) : state.searchLoader ? (\n loader\n ) : state.aggregatedCount > 0 ? (\n state.data.map((item, index) => {\n return (\n <div\n key={item.proposal_id}\n className={\n (index !== state.data.length - 1 &&\n \"border-bottom \") +\n index ===\n 0 && \" rounded-top-2\"\n }\n >\n <FeedItem proposal={item} index={index} />\n </div>\n );\n })\n ) : (\n loader\n )}\n </div>\n {state.aggregatedCount > 0 &&\n state.aggregatedCount > state.data.length && (\n <div className=\"my-3 container-xl\">\n {state.makeMoreLoader ? (\n loader\n ) : (\n <div>\n {!state.loading && (\n <div className=\"w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-grey-outline w-100 \",\n label: \"text-center w-100\",\n },\n label: \"Load More\",\n onClick: () => makeMoreItems(),\n }}\n />\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n </Container>\n );\n};\n\nreturn FeedPage(props);\n" }, "devhub.entity.community.Sidebar": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst { community } = props;\n\nconst CommunitySummary = () => {\n return (\n <>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{\n content: community.bio_markdown,\n }}\n />\n <small class=\"text-muted mb-3\">\n <Link\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"feed\", tag: community.tag },\n })}\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.atom.Tag\"}\n props={{ tag: community.tag }}\n />\n </Link>\n </small>\n </>\n );\n};\n\nreturn community === null ? (\n <div>Loading...</div>\n) : (\n <div class=\"d-flex flex-column align-items-end\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.community.Tile\"}\n props={{\n fullWidth: true,\n minHeight: 0,\n noBorder: true,\n children: <CommunitySummary />,\n style: { marginTop: \"0.5rem\" },\n }}\n />\n\n <Widget\n src={\"devhub.near/widget/devhub.entity.community.Tile\"}\n props={{\n heading: \"Admins\",\n\n children: (community?.admins ?? []).map((accountId) => (\n <div key={accountId} className=\"d-flex\" style={{ fontWeight: 500 }}>\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ accountId }}\n />\n </div>\n )),\n\n fullWidth: true,\n minHeight: 0,\n noBorder: true,\n }}\n />\n </div>\n);\n" }, "devhub.components.molecule.SimpleMDE": { "": "/**\n * iframe embedding a SimpleMDE component\n * https://github.com/sparksuite/simplemde-markdown-editor\n */\nconst { getLinkUsingCurrentGateway } = VM.require(\n \"devhub.near/widget/core.lib.url\"\n) || { getLinkUsingCurrentGateway: () => {} };\nconst data = props.data;\nconst onChange = props.onChange ?? (() => {});\nconst onChangeKeyup = props.onChangeKeyup ?? (() => {}); // in case where we want immediate action\nconst height = props.height ?? \"390\";\nconst className = props.className ?? \"w-100\";\nconst embeddCSS = props.embeddCSS;\nconst sortedRelevantUsers = props.sortedRelevantUsers || [];\n\nState.init({\n iframeHeight: height,\n message: props.data,\n});\n\nconst profilesData = Social.get(\"*/profile/name\", \"final\") ?? {};\nconst followingData =\n Social.get(`${context.accountId}/graph/follow/**`, \"final\") ?? {};\n\n// SIMPLEMDE CONFIG //\nconst fontFamily = props.fontFamily ?? \"sans-serif\";\nconst alignToolItems = props.alignToolItems ?? \"right\";\nconst placeholder = props.placeholder ?? \"\";\nconst showAccountAutoComplete = props.showAutoComplete ?? false;\nconst showProposalIdAutoComplete = props.showProposalIdAutoComplete ?? false;\nconst autoFocus = props.autoFocus ?? false;\n\nconst queryName =\n \"polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot\";\nconst query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) {\n${queryName}(\n offset: $offset\n limit: $limit\n order_by: {proposal_id: desc}\n where: $where\n) {\n name\n proposal_id\n}\n}`;\n\nconst proposalLink = getLinkUsingCurrentGateway(\n `devhub.near/widget/app?page=proposal&id=`\n);\n\nconst code = `\n<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n <style>\n body { \n margin: auto;\n font-family: ${fontFamily};\n overflow: visible;\n font-size:14px !important;\n }\n\n @media screen and (max-width: 768px) {\n body {\n font-size: 12px;\n }\n }\n \n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n\n .dropdown-item:hover,\n .dropdown-item:focus {\n background-color:rgb(0, 236, 151) !important;\n color:white !important;\n outline: none !important;\n }\n\n .editor-toolbar {\n text-align: ${alignToolItems};\n }\n \n .CodeMirror {\n min-height:200px !important; // for autocomplete to be visble \n }\n\n .CodeMirror-scroll {\n min-height:200px !important; // for autocomplete to be visble \n }\n\n ${embeddCSS}\n\n </style>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css\">\n <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65\" crossorigin=\"anonymous\">\n</head>\n<body>\n<div class=\"dropdown\">\n <button style=\"display: none\" type=\"button\" data-bs-toggle=\"dropdown\">\n Dropdown button\n </button>\n\n <ul class=\"dropdown-menu\" id=\"mentiondropdown\" style=\"position: absolute;\">\n</div>\n<div class=\"dropdown\">\n <button style=\"display: none\" type=\"button\" data-bs-toggle=\"dropdown\">\n Dropdown button\n </button>\n <ul class=\"dropdown-menu\" id=\"referencedropdown\" style=\"position: absolute;\">\n</div>\n</ul>\n\n<textarea></textarea>\n\n<script src=\"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js\" integrity=\"sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3\" crossorigin=\"anonymous\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js\" integrity=\"sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V\" crossorigin=\"anonymous\"></script>\n<script>\nlet codeMirrorInstance;\nlet isEditorInitialized = false;\nlet followingData = {};\nlet profilesData = {};\nlet proposalLink = '';\nlet query = '';\nlet showAccountAutoComplete = ${showAccountAutoComplete};\nlet showProposalIdAutoComplete = ${showProposalIdAutoComplete};\n\nfunction getSuggestedAccounts(term) {\n let results = [];\n\n term = (term || \"\").replace(/\\W/g, \"\").toLowerCase();\n let limit = 5;\n if (term.length < 2) {\n results = [${sortedRelevantUsers\n .map((u) => \"{accountId:'\" + u + \"', score: 60}\")\n .join(\",\")}];\n limit = ${5 + sortedRelevantUsers.length};\n }\n\n const profiles = Object.entries(profilesData);\n\n for (let i = 0; i < profiles.length; i++) {\n let score = 0;\n const accountId = profiles[i][0];\n const accountIdSearch = profiles[i][0].replace(/\\W/g, \"\").toLowerCase();\n const nameSearch = (profiles[i][1]?.profile?.name || \"\")\n .replace(/\\W/g, \"\")\n .toLowerCase();\n const accountIdSearchIndex = accountIdSearch.indexOf(term);\n const nameSearchIndex = nameSearch.indexOf(term);\n\n if (accountIdSearchIndex > -1 || nameSearchIndex > -1) {\n score += 10;\n\n if (accountIdSearchIndex === 0) {\n score += 10;\n }\n if (nameSearchIndex === 0) {\n score += 10;\n }\n if (followingData[accountId] === \"\") {\n score += 30;\n }\n\n results.push({\n accountId,\n score,\n });\n }\n }\n\n results.sort((a, b) => b.score - a.score);\n results = results.slice(0, limit);\n\n return results;\n}\n\nasync function asyncFetch(endpoint, { method, headers, body }) {\n try {\n const response = await fetch(endpoint, {\n method: method,\n headers: headers,\n body: body\n });\n\n if (!response.ok) {\n throw new Error(\"HTTP error!\");\n }\n\n return await response.json();\n } catch (error) {\n console.error('Error fetching data:', error);\n throw error;\n }\n}\n\nfunction extractNumbers(str) {\n let numbers = \"\";\n for (let i = 0; i < str.length; i++) {\n if (!isNaN(str[i])) {\n numbers += str[i];\n }\n }\n return numbers;\n};\n\nasync function getSuggestedProposals(id) {\n let results = [];\n const variables = {\n limit: 5,\n offset: 0,\n where: {},\n };\n if (id) {\n const proposalId = extractNumbers(id);\n if (proposalId) {\n variables[\"where\"] = { proposal_id: { _eq: id } };\n } else {\n variables[\"where\"] = {\n _or: [\n { name: { _iregex: id } },\n { summary: { _iregex: id } },\n { description: { _iregex: id } },\n ],\n };\n }\n }\n await asyncFetch(\"https://near-queryapi.api.pagoda.co/v1/graphql\", {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"polyprogrammist_near\" },\n body: JSON.stringify({\n query: query,\n variables: variables,\n operationName: \"GetLatestSnapshot\",\n }),\n })\n .then((res) => {\n const proposals =\n res?.data?.[\n \"polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot\"\n ];\n results = proposals;\n })\n .catch((error) => {\n console.error(error);\n });\n return results;\n};\n\n// Initializes SimpleMDE element and attaches to text-area\nconst simplemde = new SimpleMDE({\n forceSync: true,\n toolbar: [\n \"heading\",\n \"bold\",\n \"italic\",\n \"|\", // adding | creates a divider in the toolbar\n \"quote\",\n \"code\",\n \"link\",\n {\n name: \"image\",\n action: function customFunction(editor) {\n const loadingIndicator = document.createElement('div');\n loadingIndicator.textContent = 'Uploading...';\n loadingIndicator.style.position = 'absolute';\n loadingIndicator.style.top = '10px'; // Adjust position as needed\n loadingIndicator.style.right = '10px';\n loadingIndicator.style.display = 'none'; // Initially hidden\n loadingIndicator.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';\n loadingIndicator.style.border = '1px solid #ccc';\n loadingIndicator.style.padding = '5px';\n loadingIndicator.style.borderRadius = '5px';\n document.body.appendChild(loadingIndicator); // Append to the body or desired container\n\n\n const fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.accept = 'image/*';\n\n fileInput.addEventListener('change', async function(event) {\n const file = event.target.files[0];\n if (file) {\n loadingIndicator.style.display = 'block';\n try {\n const response = await fetch(\"https://ipfs.near.social/add\", {\n method: \"POST\",\n headers: {\n Accept: \"application/json\"\n },\n body: file\n });\n const data = await response.json();\n if (data && data.cid) {\n const imgSrc = 'https://ipfs.near.social/ipfs/' + data.cid\n const imgMarkdown = \"![\" + imgSrc + \"](\" + imgSrc + \")\";\n editor.codemirror.replaceRange(imgMarkdown, editor.codemirror.getCursor());\n editor.codemirror.focus();\n } else {\n console.error('Image upload failed:', data);\n }\n } catch (error) {\n console.error('Error uploading image:', error);\n }\n finally {\n // Hide the loading indicator when done\n loadingIndicator.style.display = 'none';\n }\n }\n });\n \n fileInput.click();\n },\n className: \"fa fa-picture-o\",\n title: \"Upload Image\",\n },\n ],\n placeholder: \\`${placeholder}\\`,\n initialValue: \"\",\n insertTexts: {\n link: [\"[\", \"]()\"],\n },\n spellChecker: false,\n renderingConfig: {\n\t\tsingleLineBreaks: false,\n\t\tcodeSyntaxHighlighting: true,\n\t},\n autofocus:${autoFocus}\n});\n\ncodeMirrorInstance = simplemde.codemirror;\n\n/**\n * Sends message to Widget to update content\n */\nconst updateContent = () => {\n const content = simplemde.value();\n window.parent.postMessage({ handler: \"update\", content }, \"*\");\n};\n\n/**\n * Sends message to Widget to update iframe height\n */\nconst updateIframeHeight = () => {\n const iframeHeight = document.body.scrollHeight;\n window.parent.postMessage({ handler: \"resize\", height: iframeHeight }, \"*\");\n};\n\n// On Change\nsimplemde.codemirror.on('blur', () => {\n updateContent();\n});\n\nsimplemde.codemirror.on('keyup', () => {\n updateIframeHeight();\n const content = simplemde.value();\n window.parent.postMessage({ handler: \"updateOnKeyup\", content }, \"*\");\n});\n\n\nif (showAccountAutoComplete) {\n let mentionToken;\n let mentionCursorStart;\n const dropdown = document.getElementById(\"mentiondropdown\");\n\n simplemde.codemirror.on(\"keydown\", () => {\n if (mentionToken && event.key === 'ArrowDown') {\n dropdown.querySelector('button').focus();\n event.preventDefault();\n return false;\n }\n });\n\n simplemde.codemirror.on(\"keyup\", (cm, event) => {\n const cursor = cm.getCursor();\n const token = cm.getTokenAt(cursor);\n\n const createMentionDropDownOptions = () => {\n const mentionInput = cm.getRange(mentionCursorStart, cursor);\n dropdown.innerHTML = getSuggestedAccounts(mentionInput)\n .map(\n (item) =>\n '<li><button class=\"dropdown-item cursor-pointer w-100 text-wrap\">' + item?.accountId + '</button></li>'\n )\n .join(\"\");\n\n dropdown.querySelectorAll(\"li\").forEach((li) => {\n li.addEventListener(\"click\", () => {\n const selectedText = li.textContent.trim();\n simplemde.codemirror.replaceRange(selectedText, mentionCursorStart, cursor);\n mentionToken = null;\n dropdown.classList.remove(\"show\");\n cm.focus();\n });\n });\n }\n // show dropwdown only when @ is at first place or when there is a space before @\n if (!mentionToken && (token.string === \"@\" && cursor.ch === 1 || token.string === \"@\" && cm.getTokenAt({line:cursor.line, ch: cursor.ch - 1}).string == ' ')) {\n mentionToken = token;\n mentionCursorStart = cursor;\n // Calculate cursor position relative to the iframe's viewport\n const rect = cm.charCoords(cursor);\n const x = rect.left;\n const y = rect.bottom;\n\n // Create dropdown with options\n dropdown.style.top = y + \"px\";\n dropdown.style.left = x + \"px\";\n\n createMentionDropDownOptions();\n\n dropdown.classList.add(\"show\");\n\n // Close dropdown on outside click\n document.addEventListener(\"click\", function(event) {\n if (!dropdown.contains(event.target)) {\n mentionToken = null;\n dropdown.classList.remove(\"show\");\n }\n });\n } else if (mentionToken && token.string.match(/[^@a-z0-9.]/)) {\n mentionToken = null;\n dropdown.classList.remove(\"show\");\n } else if (mentionToken) {\n createMentionDropDownOptions();\n }\n});\n}\n\nif (showProposalIdAutoComplete) {\n let proposalId;\n let referenceCursorStart;\n const dropdown = document.getElementById(\"referencedropdown\");\n const loader = document.createElement('div');\n loader.className = 'loader';\n loader.textContent = 'Loading...';\n\n simplemde.codemirror.on(\"keydown\", () => {\n if (proposalId && event.key === 'ArrowDown') {\n dropdown.querySelector('button').focus();\n event.preventDefault();\n return false;\n }\n });\n\n simplemde.codemirror.on(\"keyup\", (cm, event) => {\n const cursor = cm.getCursor();\n const token = cm.getTokenAt(cursor);\n\n const createReferenceDropDownOptions = async () => {\n try {\n const proposalIdInput = cm.getRange(referenceCursorStart, cursor);\n dropdown.innerHTML = ''; // Clear previous content\n dropdown.appendChild(loader); // Show loader\n\n const suggestedProposals = await getSuggestedProposals(proposalIdInput);\n dropdown.innerHTML = suggestedProposals\n .map(\n (item) =>\n '<li><button class=\"dropdown-item cursor-pointer w-100 text-wrap\">' + \"#\" + item?.proposal_id + \" \" + item.name + '</button></li>'\n )\n .join(\"\");\n\n dropdown.querySelectorAll(\"li\").forEach((li) => {\n li.addEventListener(\"click\", () => {\n const selectedText = li.textContent.trim();\n const startIndex = selectedText.indexOf('#') + 1; \n const endIndex = selectedText.indexOf(' ', startIndex);\n const id = endIndex !== -1 ? selectedText.substring(startIndex, endIndex) : selectedText.substring(startIndex);\n const link = proposalLink + id;\n const adjustedStart = {\n line: referenceCursorStart.line,\n ch: referenceCursorStart.ch - 1\n };\n simplemde.codemirror.replaceRange(\"[\" + selectedText + \"]\" + \"(\" + link + \")\", adjustedStart, cursor);\n proposalId = null;\n dropdown.classList.remove(\"show\");\n cm.focus();\n });\n });\n } catch (error) {\n console.error('Error fetching data:', error);\n // Handle error: Remove loader\n dropdown.innerHTML = ''; // Clear previous content\n } finally {\n // Remove loader\n dropdown.removeChild(loader);\n }\n }\n\n // show dropwdown only when there is space before # or it's first char\n if (!proposalId && (token.string === \"#\" && cursor.ch === 1 || token.string === \"#\" && cm.getTokenAt({line:cursor.line, ch: cursor.ch - 1}).string == ' ')) {\n proposalId = token;\n referenceCursorStart = cursor;\n // Calculate cursor position relative to the iframe's viewport\n const rect = cm.charCoords(cursor);\n const x = rect.left;\n const y = rect.bottom;\n\n // Create dropdown with options\n dropdown.style.top = y + \"px\";\n dropdown.style.left = x + \"px\";\n\n createReferenceDropDownOptions();\n\n dropdown.classList.add(\"show\");\n\n // Close dropdown on outside click\n document.addEventListener(\"click\", function(event) {\n if (!dropdown.contains(event.target)) {\n proposalId = null;\n dropdown.classList.remove(\"show\");\n }\n });\n } else if (proposalId && (token.string.match(/[^#a-z0-9.]/) || !token.string)) {\n proposalId = null;\n dropdown.classList.remove(\"show\");\n } else if (proposalId) {\n createReferenceDropDownOptions();\n }\n});\n\n}\n\nwindow.addEventListener(\"message\", (event) => {\n if (!isEditorInitialized && event.data !== \"\") {\n simplemde.value(event.data.content);\n isEditorInitialized = true;\n } else {\n if (event.data.handler === 'refreshEditor' || event.data.handler === 'committed') {\n codeMirrorInstance.getDoc().setValue(event.data.content);\n }\n }\n if (event.data.followingData) {\n followingData = event.data.followingData;\n }\n if (event.data.profilesData) {\n profilesData = JSON.parse(event.data.profilesData);\n }\n if (event.data.query) {\n query = event.data.query;\n }\n if (event.data.proposalLink) {\n proposalLink = event.data.proposalLink;\n }\n});\n</script>\n</body>\n</html>\n`;\n\nreturn (\n <iframe\n className={className}\n style={{\n height: `${state.iframeHeight}px`,\n maxHeight: \"410px\",\n minHeight: \"250px\",\n }}\n srcDoc={code}\n message={{\n content: props.data?.content ?? \"\",\n followingData,\n profilesData: JSON.stringify(profilesData),\n query: query,\n handler: props.data.handler,\n proposalLink: proposalLink,\n }}\n onMessage={(e) => {\n switch (e.handler) {\n case \"update\":\n {\n onChange(e.content);\n }\n break;\n case \"resize\":\n {\n const offset = 10;\n State.update({ iframeHeight: e.height + offset });\n }\n break;\n case \"updateOnKeyup\":\n {\n onChangeKeyup(e.content);\n }\n break;\n }\n }}\n />\n);\n" }, "devhub.components.molecule.Compose": { "": "const EmbeddCSS = `\n .CodeMirror {\n margin-inline:10px;\n border-radius:5px;\n }\n\n .editor-toolbar {\n border: none !important;\n }\n`;\n\nconst Wrapper = styled.div`\n .nav-link {\n color: inherit !important;\n }\n\n .card-header {\n padding-bottom: 0px !important;\n }\n`;\n\nconst Compose = ({\n data,\n onChange,\n autocompleteEnabled,\n placeholder,\n height,\n embeddCSS,\n showProposalIdAutoComplete,\n onChangeKeyup,\n handler,\n sortedRelevantUsers,\n isTailwind,\n}) => {\n State.init({\n data: data,\n selectedTab: \"editor\",\n autoFocus: false,\n });\n\n useEffect(() => {\n if (typeof onChange === \"function\") {\n onChange(state.data);\n }\n }, [state.data]);\n\n useEffect(() => {\n // for clearing editor after txn approval/ showing draft state\n if (data !== state.data || handler !== state.handler) {\n State.update({ data: data, handler: handler });\n }\n }, [data, handler]);\n\n // classes are different for tailwind and bootstrap\n if (isTailwind) {\n return (\n <div>\n <div className=\"bg-white shadow-sm rounded-lg\">\n <ul\n className=\"p-2 flex flex-wrap text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:border-gray-700 dark:text-gray-400\"\n style={{ position: \"relative\" }}\n >\n <li className=\"me-2\">\n <button\n className={\n \"inline-block p-3 rounded-t-lg \" +\n (state.selectedTab === \"editor\" &&\n \" active text-blue-600 bg-gray-100\")\n }\n onClick={() =>\n State.update({ selectedTab: \"editor\", autoFocus: true })\n }\n >\n Write\n </button>\n </li>\n <li className=\"me-2\">\n <button\n className={\n \"inline-block p-3 rounded-t-lg \" +\n (state.selectedTab === \"preview\" &&\n \" active text-blue-600 bg-gray-100\")\n }\n onClick={() => State.update({ selectedTab: \"preview\" })}\n >\n Preview\n </button>\n </li>\n </ul>\n\n {state.selectedTab === \"editor\" ? (\n <>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDE\"\n }\n props={{\n data: { handler: state.handler, content: state.data },\n onChange: (content) => {\n State.update({ data: content, handler: \"update\" });\n },\n placeholder: placeholder,\n height,\n embeddCSS: embeddCSS || EmbeddCSS,\n showAutoComplete: autocompleteEnabled,\n showProposalIdAutoComplete: showProposalIdAutoComplete,\n autoFocus: state.autoFocus,\n onChangeKeyup: onChangeKeyup,\n sortedRelevantUsers,\n }}\n />\n </>\n ) : (\n <div className=\"p-3\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: state.data,\n embeddCSS: `\n body{\n font-size:14px;\n }\n `,\n }}\n />\n </div>\n )}\n </div>\n </div>\n );\n } else\n return (\n <Wrapper>\n <div className=\"card\">\n <div className=\"card-header\" style={{ position: \"relative\" }}>\n <div>\n <ul class=\"nav nav-tabs\">\n <li class=\"nav-item\">\n <button\n class={`nav-link ${\n state.selectedTab === \"editor\" ? \"active\" : \"\"\n }`}\n onClick={() =>\n State.update({ selectedTab: \"editor\", autoFocus: true })\n }\n >\n Write\n </button>\n </li>\n <li class=\"nav-item\">\n <button\n class={`nav-link ${\n state.selectedTab === \"preview\" ? \"active\" : \"\"\n }`}\n onClick={() => State.update({ selectedTab: \"preview\" })}\n >\n Preview\n </button>\n </li>\n </ul>\n </div>\n </div>\n\n {state.selectedTab === \"editor\" ? (\n <>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDE\"\n }\n props={{\n data: { handler: state.handler, content: state.data },\n onChange: (content) => {\n State.update({ data: content, handler: \"update\" });\n },\n placeholder: placeholder,\n height,\n embeddCSS: embeddCSS || EmbeddCSS,\n showAutoComplete: autocompleteEnabled,\n showProposalIdAutoComplete: showProposalIdAutoComplete,\n autoFocus: state.autoFocus,\n onChangeKeyup: onChangeKeyup,\n sortedRelevantUsers,\n }}\n />\n </>\n ) : (\n <div className=\"card-body\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: state.data,\n embeddCSS: `\n body {\n font-size: 14px;\n }\n `,\n }}\n />\n </div>\n )}\n </div>\n </Wrapper>\n );\n};\n\nreturn Compose(props);\n" }, "devhub.components.organism.Configurator": { "": "const Struct = VM.require(\"devhub.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\n\nconst useForm = ({ initialValues, onUpdate, stateKey }) => {\n const initialFormState = {\n hasUnsubmittedChanges: false,\n values: initialValues ?? {},\n };\n\n const formState = state[stateKey] ?? null;\n\n const formReset = () =>\n State.update((lastKnownComponentState) => ({\n ...lastKnownComponentState,\n [stateKey]: initialFormState,\n hasUnsubmittedChanges: false,\n }));\n\n const formUpdate =\n ({ path, via: customFieldUpdate, ...params }) =>\n (fieldInput) => {\n const transformFn = (node) => {\n if (typeof customFieldUpdate === \"function\") {\n return customFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n } else {\n return Struct.defaultFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n }\n };\n const updatedValues = Struct.deepFieldUpdate(\n formState?.values ?? {},\n path,\n (node) => transformFn(node)\n );\n State.update((lastKnownComponentState) => ({\n ...lastKnownComponentState,\n [stateKey]: {\n hasUnsubmittedChanges: !Struct.isEqual(\n updatedValues,\n initialFormState.values\n ),\n values: updatedValues,\n },\n }));\n\n if (typeof onUpdate === \"function\") {\n onUpdate(updatedValues);\n }\n };\n\n return {\n hasUnsubmittedChanges: formState?.hasUnsubmittedChanges ?? false,\n values: {\n ...(initialValues ?? {}),\n ...(formState?.values ?? {}),\n },\n reset: formReset,\n stateKey,\n update: formUpdate,\n };\n};\n\nconst ValueView = styled.div`\n & > p {\n margin: 0;\n }\n`;\n\nconst fieldParamsByType = {\n array: {\n name: \"components.molecule.Input\",\n inputProps: { type: \"text\" },\n },\n\n boolean: {\n name: \"components.atom.Toggle\",\n },\n\n string: {\n name: \"components.molecule.Input\",\n inputProps: { type: \"text\" },\n },\n};\n\nconst defaultFieldsRender = ({ schema, form, isEditable }) => (\n <>\n {Object.entries(schema).map(\n (\n [key, { format, inputProps, noop, label, order, style, ...fieldProps }],\n idx\n ) => {\n const fieldKey = `${idx}-${key}`,\n fieldValue = form.values[key];\n\n const fieldType = Array.isArray(fieldValue)\n ? \"array\"\n : typeof (fieldValue ?? \"\");\n\n const isDisabled = noop ?? inputProps.disabled ?? false;\n\n const viewClassName = [\n (fieldValue?.length ?? 0) > 0 ? \"\" : \"text-muted\",\n \"m-0\",\n ].join(\" \");\n\n return (\n <>\n <div\n className={[\n \"d-flex gap-3\",\n isEditable || noop ? \"d-none\" : \"\",\n ].join(\" \")}\n key={fieldKey}\n style={{ order }}\n >\n <label className=\"fw-bold w-25\">{label}</label>\n\n <ValueView className={[viewClassName, \"w-75\"].join(\" \")}>\n {format !== \"markdown\" ? (\n <span>\n {(fieldType === \"array\" && format === \"comma-separated\"\n ? fieldValue\n .filter((string) => string.length > 0)\n .join(\", \")\n : fieldValue\n )?.toString?.() || \"none\"}\n </span>\n ) : (fieldValue?.length ?? 0) > 0 ? (\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"\n }\n props={{\n content: fieldValue,\n }}\n />\n ) : (\n <span>none</span>\n )}\n </ValueView>\n </div>\n <Widget\n src={`devhub.near/widget/devhub.${\n (fieldParamsByType[fieldType] ?? fieldParamsByType[\"string\"])\n .name\n }`}\n props={{\n ...fieldProps,\n className: [\n \"w-100\",\n fieldProps.className ?? \"\",\n isEditable && !noop ? \"\" : \"d-none\",\n ].join(\" \"),\n\n disabled: isDisabled,\n format,\n key: `${fieldKey}--editable`,\n label,\n onChange: form.update({ path: [key] }),\n style: { ...style, order },\n\n value:\n fieldType === \"array\" && format === \"comma-separated\"\n ? fieldValue.join(\", \")\n : fieldValue,\n\n inputProps: {\n ...(inputProps ?? {}),\n disabled: isDisabled,\n\n title:\n noop ?? false\n ? \"Temporarily disabled due to technical reasons.\"\n : inputProps.title,\n\n ...(fieldParamsByType[fieldType].inputProps ?? {}),\n tabIndex: order,\n },\n }}\n />\n </>\n );\n }\n )}\n </>\n);\n\nconst Configurator = ({\n actionsAdditional,\n cancelLabel,\n classNames,\n externalState,\n fieldsRender: customFieldsRender,\n formatter: toFormatted,\n isValid,\n isActive,\n onCancel,\n onChange,\n onSubmit,\n schema,\n submitIcon,\n submitLabel,\n hideSubmitBtn,\n}) => {\n const fieldsRender = customFieldsRender || defaultFieldsRender;\n\n const initialValues = Struct.typeMatch(schema)\n ? Struct.pick(externalState ?? {}, Object.keys(schema))\n : {};\n\n const form = useForm({ initialValues, onUpdate: onChange, stateKey: \"form\" });\n\n const formFormattedValues = toFormatted\n ? toFormatted(form.values)\n : form.values;\n\n const internalValidation = () =>\n Object.keys(schema).every((key) => {\n const fieldDefinition = schema[key];\n const value = form.values[key];\n if (!value || value.length === 0) {\n return !fieldDefinition.inputProps.required;\n } else if (\n fieldDefinition.inputProps.min &&\n fieldDefinition.inputProps.min > value?.length\n ) {\n return false;\n } else if (\n fieldDefinition.inputProps.max &&\n fieldDefinition.inputProps.max < value?.length\n ) {\n return false;\n } else if (\n fieldDefinition.inputProps.allowCommaAndSpace === false &&\n /^[^,\\s]*$/.test(value) === false\n ) {\n return false;\n } else if (\n fieldDefinition.inputProps.validUrl === true &&\n /^(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/.test(\n value\n ) === false\n ) {\n return false;\n }\n return true;\n });\n\n const isFormValid = () => {\n return internalValidation() && (!isValid || isValid(formFormattedValues));\n };\n\n const onCancelClick = () => {\n form.reset();\n if (onCancel) onCancel();\n };\n\n const onSubmitClick = () => {\n if (onSubmit && isFormValid()) {\n onSubmit(formFormattedValues);\n }\n };\n\n return (\n <div className=\"flex-grow-1 d-flex flex-column gap-4\">\n <div className={`d-flex flex-column gap-${isActive ? 1 : 4}`}>\n {fieldsRender({\n form,\n isEditable: isActive,\n schema,\n })}\n </div>\n {isActive && !hideSubmitBtn && (\n <div className=\"d-flex align-items-center justify-content-end gap-3 mt-auto\">\n {actionsAdditional ? (\n <div className=\"me-auto\">{actionsAdditional}</div>\n ) : null}\n\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-danger shadow-none border-0\" },\n label: cancelLabel || \"Cancel\",\n onClick: onCancelClick,\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: classNames.submit || \"btn-success\" },\n disabled: !form.hasUnsubmittedChanges || !isFormValid(),\n icon: submitIcon || {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: submitLabel || \"Submit\",\n onClick: onSubmitClick,\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\nreturn Configurator(props);\n" }, "config.css": { "": "const CssContainer = styled.div`\n .theme-btn {\n background-color: var(--theme-color) !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n a {\n color: inherit;\n text-decoration: inherit;\n }\n\n .attractable {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n transition: box-shadow 0.6s;\n\n &:hover {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n }\n }\n`;\n\nreturn { CssContainer };\n" }, "devhub.entity.proposal.CommentsAndLogs": { "": "const { getLinkUsingCurrentGateway } = VM.require(\n \"devhub.near/widget/core.lib.url\"\n) || { getLinkUsingCurrentGateway: () => {} };\nconst snapshotHistory = props.snapshotHistory;\nconst proposalId = props.id;\n\nconst Wrapper = styled.div`\n position: relative;\n .log-line {\n position: absolute;\n left: 7%;\n top: -30px;\n bottom: 0;\n z-index: 1;\n width: 1px;\n background-color: var(--bs-border-color);\n z-index: 1;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n\n .fw-bold {\n font-weight: 600 !important;\n }\n\n .inline-flex {\n display: -webkit-inline-box !important;\n align-items: center !important;\n gap: 0.25rem !important;\n margin-right: 2px;\n flex-wrap: wrap;\n }\n`;\n\nconst CommentContainer = styled.div`\n border: 1px solid lightgrey;\n overflow: auto;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\n// check snapshot history all keys and values for differences\nfunction getDifferentKeysWithValues(obj1, obj2) {\n return Object.keys(obj1)\n .filter((key) => {\n if (key !== \"editor_id\" && obj2.hasOwnProperty(key)) {\n const value1 = obj1[key];\n const value2 = obj2[key];\n\n if (typeof value1 === \"object\" && typeof value2 === \"object\") {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else if (Array.isArray(value1) && Array.isArray(value2)) {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else {\n return value1 !== value2;\n }\n }\n return false;\n })\n .map((key) => ({\n key,\n originalValue: obj1[key],\n modifiedValue: obj2[key],\n }));\n}\n\nState.init({\n data: null,\n socialComments: null,\n changedKeysListWithValues: null,\n snapshotHistoryLength: 0,\n});\n\nfunction sortTimelineAndComments() {\n const comments = Social.index(\"comment\", props.item, { subscribe: true });\n\n if (snapshotHistory.length > state.snapshotHistoryLength) {\n const changedKeysListWithValues = snapshotHistory\n .slice(1)\n .map((item, index) => {\n const startingPoint = snapshotHistory[index]; // Set comparison to the previous item\n // we don't show timeline_version in logs\n delete startingPoint.timeline.timeline_version;\n delete item.timeline.timeline_version;\n if (\n startingPoint.timeline.kyc_verified === undefined &&\n item.timeline.kyc_verified === false\n ) {\n startingPoint.timeline.kyc_verified = false;\n }\n\n return {\n editorId: item.editor_id,\n ...getDifferentKeysWithValues(startingPoint, item),\n };\n });\n\n // add log for accepting terms and condition\n changedKeysListWithValues.unshift({\n 0: {\n key: \"timestamp\",\n originalValue: \"0\",\n modifiedValue: snapshotHistory[0].timestamp,\n },\n 1: {\n key: \"terms_and_condition\",\n originalValue: \"\",\n modifiedValue: \"accepted\",\n },\n editorId: snapshotHistory[0].editor_id,\n });\n\n State.update({\n changedKeysListWithValues,\n snapshotHistoryLength: snapshotHistory.length,\n });\n }\n\n // sort comments and timeline logs by time\n const snapShotTimeStamp = Array.isArray(snapshotHistory)\n ? snapshotHistory.map((i) => {\n return { blockHeight: null, timestamp: parseFloat(i.timestamp / 1e6) };\n })\n : [];\n\n const commentsTimeStampPromise = Array.isArray(comments)\n ? Promise.all(\n comments.map((item) => {\n return asyncFetch(\n `https://api.near.social/time?blockHeight=${item.blockHeight}`\n ).then((res) => {\n const timeMs = parseFloat(res.body);\n return {\n blockHeight: item.blockHeight,\n timestamp: timeMs,\n };\n });\n })\n ).then((res) => res)\n : Promise.resolve([]);\n\n commentsTimeStampPromise.then((commentsTimeStamp) => {\n const combinedArray = [...snapShotTimeStamp, ...commentsTimeStamp];\n combinedArray.sort((a, b) => a.timestamp - b.timestamp);\n State.update({ data: combinedArray, socialComments: comments });\n });\n}\n\nsortTimelineAndComments();\nconst Comment = ({ commentItem }) => {\n const { accountId, blockHeight } = commentItem;\n const item = {\n type: \"social\",\n path: `${accountId}/post/comment`,\n blockHeight,\n };\n const content = JSON.parse(Social.get(item.path, blockHeight) ?? \"null\");\n const link = `https://devhub.near.page/proposal/${proposalId}?accountId=${accountId}&blockHeight=${blockHeight}`;\n const hightlightComment =\n parseInt(props.blockHeight ?? \"\") === blockHeight &&\n props.accountId === accountId;\n\n return (\n <div style={{ zIndex: 99, background: \"white\" }}>\n <div className=\"d-flex gap-2 flex-1\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n </div>\n <CommentContainer\n id={`${accountId.replace(/[^a-z0-9]/g, \"\")}${blockHeight}`}\n style={{ border: hightlightComment ? \"2px solid black\" : \"\" }}\n className=\"rounded-2 flex-1\"\n >\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n <div className=\"text-muted\">\n <Link href={`/near/widget/ProfilePage?accountId=${accountId}`}>\n <span className=\"fw-bold text-black\">{accountId}</span>\n </Link>\n commented ・{\" \"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight: blockHeight,\n }}\n />\n </div>\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: accountId,\n blockHeight: blockHeight,\n contentPath: `/post/comment`,\n contentType: \"comment\",\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"p-2 px-3\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: content.text,\n embeddCSS: `\n body {\n font-size:14px;\n }\n `,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-3\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item: item,\n notifyAccountId: accountId,\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: link,\n }}\n />\n </div>\n </div>\n </CommentContainer>\n </div>\n </div>\n );\n};\n\nfunction capitalizeFirstLetter(string) {\n const updated = string.replace(\"_\", \" \");\n return updated.charAt(0).toUpperCase() + updated.slice(1).toLowerCase();\n}\n\nfunction parseTimelineKeyAndValue(timeline, originalValue, modifiedValue) {\n const oldValue = originalValue[timeline];\n const newValue = modifiedValue[timeline];\n switch (timeline) {\n case \"status\":\n return (\n oldValue !== newValue && (\n <span className=\"inline-flex\">\n moved proposal from{\" \"}\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: oldValue,\n }}\n />\n to{\" \"}\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: newValue,\n }}\n />\n stage\n </span>\n )\n );\n case \"sponsor_requested_review\": {\n if (!oldValue && newValue) {\n return <span>completed review</span>;\n } else if (oldValue && !newValue) return <span>unmarked review</span>;\n return null;\n }\n case \"reviewer_completed_attestation\":\n return !oldValue && newValue && <span>completed attestation</span>;\n case \"kyc_verified\":\n return !oldValue && newValue && <span>verified KYC/KYB</span>;\n case \"test_transaction_sent\":\n return (\n !oldValue &&\n newValue && (\n <span>\n confirmed sponsorship and shared funding steps with recipient\n </span>\n )\n );\n case \"payouts\":\n return <span>updated the funding payment links.</span>;\n // we don't have this step for now\n // case \"request_for_trustees_created\":\n // return !oldValue && newValue && <span>successfully created request for trustees</span>;\n default:\n return null;\n }\n}\n\nconst AccountProfile = ({ accountId }) => {\n return (\n <span className=\"inline-flex fw-bold text-black\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n size: \"sm\",\n showAccountId: true,\n }}\n />\n </span>\n );\n};\n\nconst parseProposalKeyAndValue = (key, modifiedValue, originalValue) => {\n switch (key) {\n case \"terms_and_condition\": {\n return (\n <span>\n accepted\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.AcceptedTerms\"}\n props={{ proposalId: proposalId }}\n />\n </span>\n );\n }\n case \"name\":\n return <span>changed title</span>;\n case \"summary\":\n case \"description\":\n return <span>changed {key}</span>;\n case \"category\":\n return (\n <span>\n changed category from {originalValue} to {modifiedValue}\n </span>\n );\n case \"linked_proposals\":\n return <span>updated linked proposals</span>;\n case \"requested_sponsorship_usd_amount\":\n return (\n <span>\n changed sponsorship amount from {originalValue} to {modifiedValue}\n </span>\n );\n case \"requested_sponsorship_paid_in_currency\":\n return (\n <span>\n changed sponsorship currency from {originalValue} to {modifiedValue}\n </span>\n );\n case \"receiver_account\":\n return (\n <span className=\"inline-flex\">\n changed receiver account from{\" \"}\n <AccountProfile accountId={originalValue} />\n to <AccountProfile accountId={modifiedValue} />\n </span>\n );\n case \"supervisor\":\n return !originalValue && modifiedValue ? (\n <span className=\"inline-flex\">\n added\n <AccountProfile accountId={modifiedValue} />\n as supervisor\n </span>\n ) : (\n <span className=\"inline-flex\">\n changed receiver account from{\" \"}\n <AccountProfile accountId={originalValue} />\n to <AccountProfile accountId={modifiedValue} />\n </span>\n );\n case \"requested_sponsor\":\n return (\n <span className=\"inline-flex\">\n changed sponsor from <AccountProfile accountId={originalValue} />\n to <AccountProfile accountId={modifiedValue} />\n </span>\n );\n case \"timeline\": {\n const modifiedKeys = Object.keys(modifiedValue);\n const originalKeys = Object.keys(originalValue);\n return modifiedKeys.map((i, index) => {\n const text = parseTimelineKeyAndValue(i, originalValue, modifiedValue);\n return (\n text && (\n <span key={index} className=\"inline-flex\">\n {text}\n {text && \"・\"}\n </span>\n )\n );\n });\n }\n default:\n return null;\n }\n};\n\nconst LogIconContainer = styled.div`\n margin-left: 50px;\n z-index: 99;\n\n @media screen and (max-width: 768px) {\n margin-left: 10px;\n }\n`;\n\nconst Log = ({ timestamp }) => {\n const updatedData = useMemo(\n () =>\n state.changedKeysListWithValues.find((obj) =>\n Object.values(obj).some(\n (value) =>\n value && parseFloat(value.modifiedValue / 1e6) === timestamp\n )\n ),\n [state.changedKeysListWithValues, timestamp]\n );\n\n const editorId = updatedData.editorId;\n const valuesArray = Object.values(updatedData ?? {});\n // if valuesArray length is 2 that means it only has timestamp and editorId\n if (!updatedData || valuesArray.length === 2) {\n return <></>;\n }\n\n return valuesArray.map((i, index) => {\n if (i.key && i.key !== \"timestamp\" && i.key !== \"proposal_body_version\") {\n return (\n <LogIconContainer\n className=\"d-flex gap-3 align-items-center\"\n key={index}\n >\n <img\n src=\"https://ipfs.near.social/ipfs/bafkreiffqrxdi4xqu7erf46gdlwuodt6dm6rji2jtixs3iionjvga6rhdi\"\n height={30}\n />\n <div\n className={\n \"flex-1 gap-1 w-100 text-wrap text-muted align-items-center \" +\n (i.key === \"timeline\" &&\n Object.keys(i.originalValue ?? {}).length > 1\n ? \"\"\n : \"inline-flex\")\n }\n >\n <span\n className=\"inline-flex fw-bold text-black\"\n style={{ marginRight: 0 }}\n >\n <AccountProfile accountId={editorId} showAccountId={true} />\n </span>\n {parseProposalKeyAndValue(i.key, i.modifiedValue, i.originalValue)}\n {i.key !== \"timeline\" && \"・\"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockTimestamp: timestamp * 1000000,\n }}\n />\n </div>\n </LogIconContainer>\n );\n }\n });\n};\n\nif (Array.isArray(state.data)) {\n return (\n <Wrapper>\n <div\n className=\"log-line\"\n style={{ height: state.data.length > 2 ? \"110%\" : \"150%\" }}\n ></div>\n <div className=\"d-flex flex-column gap-4\">\n {state.data.map((i, index) => {\n if (i.blockHeight) {\n const item = state.socialComments.find(\n (t) => t.blockHeight === i.blockHeight\n );\n return <Comment commentItem={item} />;\n } else {\n return <Log timestamp={i.timestamp} key={index} />;\n }\n })}\n </div>\n </Wrapper>\n );\n}\n" }, "devhub.entity.addon.wiki.Viewer": { "": "const { content, title, subtitle, textAlign } = props;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n\n margin: 0 auto;\n text-align: ${(p) => p.textAlign ?? \"left\"};\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n\n a {\n color: #0000ee;\n }\n`;\n\nconst Content = styled.div`\n margin: 20px 0;\n text-align: left;\n`;\n\nconst Title = styled.h1`\n margin-bottom: 10px;\n`;\n\nconst Subtitle = styled.p`\n margin-bottom: 20px;\n`;\n\nconst CenteredMessage = styled.div`\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: ${(p) => p.height ?? \"100%\"};\n`;\n\nif (!content) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>No Wiki Configured</h2>\n </CenteredMessage>\n );\n} else {\n return (\n <Container textAlign={textAlign}>\n <h1>{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <Content>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"\n }\n props={{ content: content }}\n />\n </Content>\n </Container>\n );\n}\n" }, "devhub.components.molecule.SimpleMDEViewer": { "": "const content = props.content ?? \"\";\nconst height = props.height;\nconst embeddCSS = props.embeddCSS;\nconst [iframeHeight, setIframeHeight] = useState(\"100px\");\n\nconst code = `\n <!doctype html>\n <html>\n <head>\n <meta charset=\"utf-8\" />\n <style>\n body { \n margin: 0;\n overflow: ${height ? \"auto\" : \"hidden\"};\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n line-height:1.5;\n }\n\n blockquote {\n margin: 1em 0;\n padding-left: 1.5em;\n border-left: 4px solid #ccc;\n color: #666;\n font-style: italic;\n font-size: inherit;\n }\n\n .no-margin {\n margin: 0px !important;\n }\n\n h1, h2, h3, h4, h5, h6 {\n font-weight:500;\n margin-block:5px;\n }\n\n p {\n white-space: pre-wrap;\n }\n\n img {\n height: 200px;\n object-fit:contain;\n width: -webkit-fill-available;\n }\n \n ${embeddCSS}\n </style>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css\">\n <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65\" crossorigin=\"anonymous\">\n </head> \n <body>\n <div id=\"content\"></div>\n <script src=\"https://cdn.jsdelivr.net/npm/marked/marked.min.js\"></script>\n <script>\n \n \n\n function updateContent(content) {\n document.getElementById('content').innerHTML = marked.parse(content)\n \n const newHeight = Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight,\n document.body.offsetHeight,\n document.documentElement.offsetHeight\n );\n \n window.parent.postMessage({\n handler: 'IFRAME_HEIGHT',\n height: newHeight\n }, '*');\n }\n\n window.addEventListener('message', (event) => {\n if (event.data.content) {\n updateContent(event.data.content);\n }\n });\n\n window.addEventListener('load', () => {\n window.parent.postMessage({\n handler: 'IFRAME_HEIGHT',\n height: document.body.scrollHeight\n }, '*');\n });\n </script>\n </body>\n </html>\n `;\n\nreturn (\n <iframe\n srcDoc={code}\n message={{\n content: content,\n }}\n style={{ height: height ?? iframeHeight }}\n className=\"w-100\"\n onMessage={(e) => {\n switch (e.handler) {\n case \"IFRAME_HEIGHT\": {\n setIframeHeight(`${e.height}px`);\n break;\n }\n }\n }}\n />\n);\n" } } } } }

Transaction Execution Plan

Convert Transaction To Receipt
Gas Burned:
8 Tgas
Tokens Burned:
0.00085 
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
43 Tgas
Tokens Burned:
0.00434 
Called method: 'set' in contract: social.near
Arguments:
{ "data": { "devhub.near": { "widget": { "devhub.entity.addon.blog.Page": { "": "const { getAccountCommunityPermissions } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n) || {\n getAccountCommunityPermissions: () => {},\n};\nconst imagelink =\n \"https://ipfs.near.social/ipfs/bafkreiajzvmy7574k7mp3if6u53mdukfr3hoc2kjkhjadt6x56vqhd5swy\";\n\nfunction Page({ data, onEdit, labels, accountId }) {\n const { category, title, description, subtitle, date, content } = data;\n const handle = labels?.[1]; // community-handle\n const permissions = getAccountCommunityPermissions({\n account_id: accountId,\n community_handle: handle,\n });\n const isAllowedToEdit = permissions?.can_configure ?? false;\n const Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n margin-bottom: 2rem;\n position: relative;\n ${category &&\n `\n span.category {\n color: ${\n category.toLowerCase() === \"news\"\n ? \"#F40303\"\n : category.toLowerCase() === \"guide\"\n ? \"#004BE1\"\n : category.toLowerCase() === \"reference\" && \"#FF7A00\"\n };\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n text-transform: uppercase;\n }\n `}\n\n span.date {\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin: 1.5rem 0;\n }\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1.5rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n .edit-icon {\n position: absolute;\n top: 20px;\n right: 20px;\n cursor: pointer;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n `;\n\n const BackgroundImage = styled.img`\n width: 100%;\n height: auto;\n object-fit: cover;\n margin-bottom: 1rem;\n `;\n\n const options = { year: \"numeric\", month: \"short\", day: \"numeric\" };\n const formattedDate = new Date(date).toLocaleString(\"en-US\", options);\n\n return (\n <>\n <BackgroundImage src={imagelink} />\n <Container>\n {isAllowedToEdit && (\n <div className=\"edit-icon\" onClick={onEdit}>\n <div class=\"bi bi-pencil-square\" style={{ fontSize: \"30px\" }}></div>\n </div>\n )}\n {category && <span className=\"category\">{category}</span>}\n <h1>{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <span className=\"date\">{formattedDate}</span>\n <p>{description}</p>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"\n }\n props={{ content: content }}\n />\n </Container>\n </>\n );\n}\n\nreturn { Page };\n" }, "devhub.entity.addon.blogv2.editor.form": { "": "const FormContainer = styled.div`\n & > *:not(:last-child) {\n margin-bottom: 1rem;\n }\n`;\n\nconst {\n title,\n setTitle,\n subtitle,\n setSubtitle,\n options,\n category,\n setCategory,\n description,\n setDescription,\n debouncedUpdateState,\n author,\n setAuthor,\n date,\n setDate,\n content,\n setContent,\n addonParameters,\n} = props;\n\nconst InputContainer = ({ heading, description, children }) => {\n return (\n <div className=\"d-flex flex-column gap-1 gap-sm-2 w-100\">\n <b className=\"h6 mb-0\">{heading}</b>\n {description && (\n <div className=\"text-muted w-100 text-sm\">{description}</div>\n )}\n {children}\n </div>\n );\n};\n\nconst TitleComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: title,\n placeholder: \"Title\",\n onChange: (e) => setTitle(e.target.value),\n skipPaddingGap: true,\n inputProps: {\n max: 80,\n required: true,\n name: \"title\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n }}\n />\n );\n}, []);\n\nconst SubtitleComponent = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: subtitle,\n placeholder: \"Subtitle\",\n onChange: (e) => setSubtitle(e.target.value),\n skipPaddingGap: true,\n inputProps: {\n required: true,\n max: 80,\n name: \"subtitle\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n }}\n />\n );\n}, []);\n\nconst CategorySelect = useMemo(() => {\n return (\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.addon.blogv2.editor.CategoryDropdown\"\n }\n props={{\n options,\n selectedValue: category,\n onChange: setCategory,\n }}\n />\n );\n}, []);\n\nconst DescriptionInput = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setDescription(e.target.value),\n value: description,\n multiline: true,\n placeholder: \"Description\",\n inputProps: {\n max: 500,\n name: \"description\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n skipPaddingGap: true,\n key: \"description-input-field\",\n }}\n />\n );\n}, []);\n\nconst AuthorInput = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n onChange: (e) => setAuthor(e.target.value),\n value: author,\n placeholder: \"Author\",\n inputProps: {\n name: \"author\",\n className:\n \"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\",\n },\n skipPaddingGap: true,\n key: \"author-input-field\",\n }}\n />\n );\n}, []);\n\nconst DateInput = () => {\n return (\n <input\n name=\"date\"\n type=\"date\"\n value={date}\n className=\"block w-full rounded-md border-0 px-1 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6\"\n onChange={(e) => setDate(e.target.value)}\n />\n );\n};\n\nconst ComposeEmbeddCSS = `\n .CodeMirror {\n border: none !important;\n min-height: 50px !important;\n }\n\n .editor-toolbar {\n border: none !important;\n }\n\n .CodeMirror-scroll{\n min-height: 50px !important;\n max-height: 600px !important;\n }\n`;\n\nconst ContentEditor = useMemo(() => {\n return (\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Compose\"\n props={{\n data: content,\n onChange: setContent,\n height: \"250\",\n embeddCSS: ComposeEmbeddCSS,\n isTailwind: true,\n }}\n />\n );\n}, []);\n\nreturn (\n <FormContainer id=\"blog-editor-form\">\n <InputContainer\n heading=\"Title\"\n description=\"Highlight the essence of your blog in a few words. This will appear on your blog card and on the top of your blog.\"\n >\n {TitleComponent}\n </InputContainer>\n <InputContainer\n heading=\"Subtitle\"\n description=\"Provide a brief subtitle for the blog\"\n >\n {SubtitleComponent}\n </InputContainer>\n {addonParameters.categoriesEnabled === \"enabled\" ? (\n <InputContainer\n heading=\"Category\"\n description={\n <>\n Choose the category that fits your blog best. Set up your categories\n in the blog settings.\n </>\n }\n >\n {CategorySelect}\n </InputContainer>\n ) : (\n <></>\n )}\n\n <InputContainer\n heading=\"Description\"\n description=\"Provide a brief description for the blog. This will appear on the blog card.\"\n >\n {DescriptionInput}\n </InputContainer>\n {addonParameters.authorEnabled === \"disabled\" ? (\n <></>\n ) : (\n <InputContainer heading=\"Author\" description=\"Who wrote this blog?\">\n {AuthorInput}\n </InputContainer>\n )}\n <InputContainer\n heading=\"Visible Publish Date\"\n description=\"What date do you want to have the blog published under?\"\n >\n <DateInput />\n </InputContainer>\n\n <InputContainer\n heading=\"Content\"\n description=\"Write your blog here. Use Markdown to format your content.\"\n >\n {ContentEditor}\n </InputContainer>\n </FormContainer>\n);\n" }, "devhub.entity.proposal.Proposal": { "": "const { href, getLinkUsingCurrentGateway } = VM.require(\n \"devhub.near/widget/core.lib.url\"\n) || {\n href: () => {},\n getLinkUsingCurrentGateway: () => {},\n};\nconst { readableDate } = VM.require(\n \"devhub.near/widget/core.lib.common\"\n) || { readableDate: () => {} };\n\nconst accountId = context.accountId;\n/*\n---props---\nprops.id: number;\nprops.timestamp: number; optional\naccountId: string\nblockHeight:number\n*/\n\nconst TIMELINE_STATUS = {\n DRAFT: \"DRAFT\",\n REVIEW: \"REVIEW\",\n APPROVED: \"APPROVED\",\n REJECTED: \"REJECTED\",\n CANCELED: \"CANCELLED\",\n APPROVED_CONDITIONALLY: \"APPROVED_CONDITIONALLY\",\n PAYMENT_PROCESSING: \"PAYMENT_PROCESSING\",\n FUNDED: \"FUNDED\",\n};\n\nconst DecisionStage = [\n TIMELINE_STATUS.APPROVED,\n TIMELINE_STATUS.REJECTED,\n TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n];\n\nconst Container = styled.div`\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .fw-bold {\n font-weight: 600 !important;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n .description-box {\n font-size: 14px;\n }\n\n .draft-info-container {\n background-color: #ecf8fb;\n }\n\n .review-info-container {\n background-color: #fef6ee;\n }\n\n .text-sm {\n font-size: 13px !important;\n }\n\n .flex-1 {\n flex: 1;\n }\n\n .flex-3 {\n flex: 3;\n }\n\n .circle {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n border: 1px solid grey;\n }\n\n .green-fill {\n background-color: rgb(4, 164, 110) !important;\n border-color: rgb(4, 164, 110) !important;\n color: white !important;\n }\n\n .yellow-fill {\n border-color: #ff7a00 !important;\n }\n\n .vertical-line {\n width: 2px;\n height: 180px;\n background-color: lightgrey;\n }\n\n @media screen and (max-width: 970px) {\n .vertical-line {\n height: 135px !important;\n }\n\n .vertical-line-sm {\n height: 70px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n @media screen and (max-width: 570px) {\n .vertical-line {\n height: 180px !important;\n }\n\n .vertical-line-sm {\n height: 75px !important;\n }\n\n .gap-6 {\n gap: 0.5rem !important;\n }\n }\n\n .vertical-line-sm {\n width: 2px;\n height: 70px;\n background-color: lightgrey;\n }\n\n .form-check-input:disabled ~ .form-check-label,\n .form-check-input[disabled] ~ .form-check-label {\n opacity: 1;\n }\n\n .form-check-input {\n border-color: black !important;\n }\n\n .grey-btn {\n background-color: #687076;\n border: none;\n color: white;\n }\n\n .form-check-input:checked {\n background-color: #04a46e !important;\n border-color: #04a46e !important;\n }\n\n .dropdown-toggle:after {\n position: absolute;\n top: 46%;\n right: 5%;\n }\n\n .drop-btn {\n max-width: none !important;\n }\n\n .dropdown-menu {\n width: 100%;\n border-radius: 0.375rem !important;\n }\n\n .green-btn {\n background-color: #04a46e !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n .gap-6 {\n gap: 2.5rem;\n }\n\n .border-vertical {\n border-top: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n border-bottom: var(--bs-border-width) var(--bs-border-style)\n var(--bs-border-color) !important;\n }\n\n button.px-0 {\n padding-inline: 0px !important;\n }\n\n red-icon i {\n color: red;\n }\n\n input[type=\"radio\"] {\n min-width: 13px;\n }\n`;\n\nconst ProposalContainer = styled.div`\n border: 1px solid lightgrey;\n overflow: auto;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\nconst Text = styled.p`\n display: block;\n margin: 0;\n font-size: 14px;\n line-height: 20px;\n font-weight: 400;\n color: #687076;\n white-space: nowrap;\n`;\n\nconst Actions = styled.div`\n display: flex;\n align-items: center;\n gap: 12px;\n margin: -6px -6px 6px;\n`;\n\nconst Avatar = styled.div`\n width: 40px;\n height: 40px;\n pointer-events: none;\n\n img {\n object-fit: cover;\n border-radius: 40px;\n width: 100%;\n height: 100%;\n }\n`;\n\nconst LinkProfile = ({ account, children }) => {\n return (\n <Link href={`/near/widget/ProfilePage?accountId=${account}`}>\n {children}\n </Link>\n );\n};\n\nconst stepsArray = [1, 2, 3, 4, 5];\n\nconst { id, timestamp } = props;\nconst proposal = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: parseInt(id),\n});\n\nif (!proposal) {\n return (\n <div\n style={{ height: \"50vh\" }}\n className=\"d-flex justify-content-center align-items-center w-100\"\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Spinner\"}\n />\n </div>\n );\n}\nif (timestamp && proposal) {\n proposal.snapshot =\n proposal.snapshot_history.find((item) => item.timestamp === timestamp) ??\n proposal.snapshot;\n}\n\nconst { snapshot } = proposal;\n\nconst authorId = proposal.author_id;\nconst blockHeight = parseInt(proposal.social_db_post_block_height);\nconst item = {\n type: \"social\",\n path: `devhub.near/post/main`,\n blockHeight,\n};\nconst comments = Social.index(\"comment\", item, { subscribe: true }) ?? [];\n\nconst commentAuthors = [\n ...new Set(comments.map((comment) => comment.accountId)),\n];\n\nconst proposalURL = `https://devhub.near.page/proposal/${proposal.id}`;\n\nconst KycVerificationStatus = () => {\n const isVerified = true;\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <img\n src={\n isVerified\n ? \"https://ipfs.near.social/ipfs/bafkreidqveupkcc7e3rko2e67lztsqrfnjzw3ceoajyglqeomvv7xznusm\"\n : \"https://ipfs.near.social/ipfs/bafkreieq4222tf3hkbccfnbw5kpgedm3bf2zcfgzbnmismxav2phqdwd7q\"\n }\n height={40}\n />\n <div className=\"d-flex flex-column\">\n <div className=\"h6 mb-0\">KYC Verified</div>\n <div className=\"text-sm\">Expires on Aug 24, 2024</div>\n </div>\n </div>\n );\n};\n\nconst SidePanelItem = ({ title, children, hideBorder, ishidden }) => {\n return (\n <div\n style={{ gap: \"8px\" }}\n className={\n ishidden\n ? \"d-none\"\n : \"d-flex flex-column pb-3 \" + (!hideBorder && \" border-bottom\")\n }\n >\n <div className=\"h6 mb-0\">{title} </div>\n <div className=\"text-muted\">{children}</div>\n </div>\n );\n};\n\nconst proposalStatusOptions = [\n {\n label: \"Draft\",\n value: { status: TIMELINE_STATUS.DRAFT },\n },\n {\n label: \"Review\",\n value: {\n status: TIMELINE_STATUS.REVIEW,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n kyc_verified: false,\n },\n },\n {\n label: \"Approved\",\n value: {\n status: TIMELINE_STATUS.APPROVED,\n kyc_verified: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Approved-Conditionally\",\n value: {\n status: TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n kyc_verified: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Rejected\",\n value: {\n status: TIMELINE_STATUS.REJECTED,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n kyc_verified: true,\n },\n },\n {\n label: \"Canceled\",\n value: {\n status: TIMELINE_STATUS.CANCELED,\n sponsor_requested_review: false,\n reviewer_completed_attestation: false,\n kyc_verified: true,\n },\n },\n {\n label: \"Payment-processing\",\n value: {\n status: TIMELINE_STATUS.PAYMENT_PROCESSING,\n kyc_verified: true,\n test_transaction_sent: false,\n request_for_trustees_created: false,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n {\n label: \"Funded\",\n value: {\n status: TIMELINE_STATUS.FUNDED,\n trustees_released_payment: true,\n kyc_verified: true,\n test_transaction_sent: true,\n request_for_trustees_created: true,\n sponsor_requested_review: true,\n reviewer_completed_attestation: false,\n },\n },\n];\n\nconst LinkedProposals = () => {\n const linkedProposalsData = [];\n snapshot.linked_proposals.map((item) => {\n const data = Near.view(\"devhub.near\", \"get_proposal\", {\n proposal_id: item,\n });\n if (data !== null) {\n linkedProposalsData.push(data);\n }\n });\n\n return (\n <div className=\"d-flex flex-column gap-3\">\n {linkedProposalsData.map((item) => {\n return (\n <a\n href={`?page=proposal&id=${item.id}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <div className=\"d-flex gap-2\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: item.snapshot.editor_id,\n }}\n />\n <div className=\"d-flex flex-column\" style={{ maxWidth: 250 }}>\n <div className=\"text-truncate\">\n <LinkProfile account={item.snapshot.name}>\n <b>{item.snapshot.name}</b>\n </LinkProfile>\n </div>\n <div className=\"text-sm text-muted\">\n created on {readableDate(item.snapshot.timestamp / 1000000)}\n </div>\n </div>\n </div>\n </a>\n );\n })}\n </div>\n );\n};\n\nconst CheckBox = ({ value, isChecked, label, disabled, onClick }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n data-testid={label}\n class=\"form-check-input\"\n type=\"checkbox\"\n value={value}\n checked={isChecked}\n disabled={!isModerator || !showTimelineSetting || disabled}\n onChange={(e) => onClick(e.target.checked)}\n />\n <label style={{ width: \"90%\" }} class=\"form-check-label text-black\">\n {label}\n </label>\n </div>\n );\n};\n\nconst RadioButton = ({ value, isChecked, label }) => {\n return (\n <div className=\"d-flex gap-2 align-items-center\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n value={value}\n checked={isChecked}\n disabled={true}\n />\n <label class=\"form-check-label text-black\">{label}</label>\n </div>\n );\n};\n\nconst isAllowedToEditProposal = Near.view(\n \"devhub.near\",\n \"is_allowed_to_edit_proposal\",\n {\n proposal_id: proposal.id,\n editor: accountId,\n }\n);\n\nconst isModerator = Near.view(\"devhub.near\", \"has_moderator\", {\n account_id: accountId,\n});\n\nconst editProposal = ({ timeline }) => {\n const body = {\n proposal_body_version: \"V0\",\n name: snapshot.name,\n description: snapshot.description,\n category: snapshot.category,\n summary: snapshot.summary,\n linked_proposals: snapshot.linked_proposals,\n requested_sponsorship_usd_amount: snapshot.requested_sponsorship_usd_amount,\n requested_sponsorship_paid_in_currency:\n snapshot.requested_sponsorship_paid_in_currency,\n receiver_account: snapshot.receiver_account,\n supervisor: supervisor || null,\n requested_sponsor: snapshot.requested_sponsor,\n timeline: timeline,\n };\n const args = {\n labels: [],\n body: body,\n id: proposal.id,\n };\n\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal\",\n args: args,\n gas: 270000000000000,\n },\n ]);\n};\n\nconst editProposalStatus = ({ timeline }) => {\n Near.call([\n {\n contractName: \"devhub.near\",\n methodName: \"edit_proposal_versioned_timeline\",\n args: {\n id: proposal.id,\n timeline: { timeline_version: \"V1\", ...timeline },\n },\n gas: 270000000000000,\n },\n ]);\n setEditProposalTimelineCalled(true);\n};\n\nconst [isReviewModalOpen, setReviewModal] = useState(false);\nconst [isCancelModalOpen, setCancelModal] = useState(false);\nconst [isEditProposalTimelineCalled, setEditProposalTimelineCalled] =\n useState(false);\nconst [showTimeLineStatusSubmittedToast, setShowTimeLineStatusSubmittedToast] =\n useState(false);\nconst [showTimelineSetting, setShowTimelineSetting] = useState(false);\nconst proposalStatus = useCallback(\n () =>\n proposalStatusOptions.find(\n (i) => i.value.status === snapshot.timeline.status\n ),\n [snapshot]\n);\nconst [updatedProposalStatus, setUpdatedProposalStatus] = useState({});\n\nuseEffect(() => {\n if (isEditProposalTimelineCalled) {\n setShowTimeLineStatusSubmittedToast(true);\n setEditProposalTimelineCalled(false);\n }\n setUpdatedProposalStatus({\n ...proposalStatus(),\n value: { ...proposalStatus().value, ...snapshot.timeline },\n });\n}, [proposal]);\n\nconst [paymentHashes, setPaymentHashes] = useState([\"\"]);\nconst [supervisor, setSupervisor] = useState(snapshot.supervisor);\n\nconst selectedStatusIndex = useMemo(\n () =>\n proposalStatusOptions.findIndex((i) => {\n return updatedProposalStatus.value.status === i.value.status;\n }),\n [updatedProposalStatus]\n);\n\nconst TimelineItems = ({ title, children, value, values }) => {\n const indexOfCurrentItem = proposalStatusOptions.findIndex((i) =>\n Array.isArray(values)\n ? values.includes(i.value.status)\n : value === i.value.status\n );\n let color = \"transparent\";\n let statusIndex = selectedStatusIndex;\n\n // index 2,3,4,5 is of decision\n if (selectedStatusIndex === 3 || selectedStatusIndex === 2) {\n statusIndex = 2;\n }\n if (statusIndex === indexOfCurrentItem) {\n color = \"#FEF6EE\";\n }\n if (\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status === TIMELINE_STATUS.FUNDED\n ) {\n color = \"#EEFEF0\";\n }\n // reject\n if (statusIndex === 4 && indexOfCurrentItem === 2) {\n color = \"#FF7F7F\";\n }\n // cancelled\n if (statusIndex === 5 && indexOfCurrentItem === 2) {\n color = \"#F4F4F4\";\n }\n\n return (\n <div\n className=\"p-2 rounded-3\"\n style={{\n backgroundColor: color,\n }}\n >\n <div className=\"h6 text-black\"> {title}</div>\n <div className=\"text-sm\">{children}</div>\n </div>\n );\n};\n\nconst link = href({\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"create-proposal\",\n id: proposal.id,\n timestamp: timestamp,\n },\n});\n\nconst createdDate =\n proposal.snapshot_history?.[proposal.snapshot_history.length - 1]\n ?.timestamp ?? snapshot.timestamp;\n\nreturn (\n <Container className=\"d-flex flex-column gap-2 w-100 mt-4\">\n <Widget\n src=\"near/widget/DIG.Toast\"\n props={{\n title: \"Timeline status submitted successfully\",\n type: \"success\",\n open: showTimeLineStatusSubmittedToast,\n onOpenChange: (v) => setShowTimeLineStatusSubmittedToast(v),\n trigger: <></>,\n providerProps: { duration: 3000 },\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.ConfirmReviewModal\"}\n props={{\n isOpen: isReviewModalOpen,\n onCancelClick: () => setReviewModal(false),\n onReviewClick: () => {\n setReviewModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[1].value });\n },\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.ConfirmCancelModal\"}\n props={{\n isOpen: isCancelModalOpen,\n onCancelClick: () => setCancelModal(false),\n onConfirmClick: () => {\n setCancelModal(false);\n editProposalStatus({ timeline: proposalStatusOptions[5].value });\n },\n }}\n />\n <div className=\"d-flex px-3 px-lg-0 justify-content-between\">\n <div className=\"d-flex gap-2 align-items-center h3\">\n <div>{snapshot.name}</div>\n <div className=\"text-muted\">#{proposal.id}</div>\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.ShareLinkButton\"\n props={{\n postType: \"proposal\",\n url: proposalURL,\n }}\n />\n {((isAllowedToEditProposal &&\n snapshot.timeline.status === TIMELINE_STATUS.DRAFT) ||\n isModerator) && (\n <Link to={link} style={{ textDecoration: \"none\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: \"Edit\",\n classNames: { root: \"grey-btn btn-sm\" },\n }}\n />\n </Link>\n )}\n </div>\n </div>\n <div className=\"d-flex flex-wrap flex-md-nowrap px-3 px-lg-0 gap-2 align-items-center text-sm pb-3 w-100\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: snapshot.timeline.status,\n size: \"sm\",\n }}\n />\n <div className=\"w-100 d-flex flex-wrap flex-md-nowrap gap-1 align-items-center\">\n <div className=\"fw-bold text-truncate\">\n <LinkProfile account={authorId}>{authorId}</LinkProfile>\n </div>\n <div>created on {readableDate(createdDate / 1000000)}</div>\n </div>\n </div>\n <div className=\"card no-border rounded-0 full-width-div px-3 px-lg-0\">\n <div className=\"container-xl py-4\">\n {snapshot.timeline.status === TIMELINE_STATUS.DRAFT &&\n isAllowedToEditProposal && (\n <div className=\"draft-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in draft mode and open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n The author can still refine the proposal and build consensus\n before sharing it with sponsors. Click “Ready for review” when\n you want to start the official review process. This will lock\n the editing function, but comments are still open.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Ready for review\",\n classNames: { root: \"grey-btn btn-sm\" },\n onClick: () => setReviewModal(true),\n }}\n />\n </div>\n </div>\n )}\n {snapshot.timeline.status === TIMELINE_STATUS.REVIEW &&\n isAllowedToEditProposal && (\n <div className=\"review-info-container p-3 p-sm-4 d-flex flex-wrap flex-sm-nowrap justify-content-between align-items-center gap-2 rounded-2\">\n <div style={{ minWidth: \"300px\" }}>\n <b>\n This proposal is in review mode and still open for community\n comments.\n </b>\n <p className=\"text-sm text-muted mt-2\">\n You can’t edit the proposal, but comments are open. Only\n moderators can make changes. Click “Cancel Proposal” to cancel\n your proposal. This changes the status to Canceled, signaling\n to sponsors that it’s no longer active or relevant.\n </p>\n </div>\n <div style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: (\n <div className=\"d-flex align-items-center gap-1\">\n <i class=\"bi bi-trash3\"></i> Cancel Proposal\n </div>\n ),\n classNames: { root: \"btn-outline-danger btn-sm\" },\n onClick: () => setCancelModal(true),\n }}\n />\n </div>\n </div>\n )}\n <div className=\"my-4\">\n <div className=\"d-flex flex-wrap gap-6\">\n <div\n style={{ minWidth: \"350px\" }}\n className=\"flex-3 order-2 order-md-1\"\n >\n <div\n className=\"d-flex gap-2 flex-1\"\n style={{\n zIndex: 99,\n background: \"white\",\n position: \"relative\",\n }}\n >\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: authorId,\n }}\n />\n </div>\n <ProposalContainer className=\"rounded-2 flex-1\">\n <Header className=\"d-flex gap-1 align-items-center p-2 px-3 \">\n <div\n className=\"fw-bold text-truncate\"\n style={{ maxWidth: \"60%\" }}\n >\n <LinkProfile account={authorId}>{authorId}</LinkProfile>\n </div>\n <div\n className=\"text-muted\"\n style={{ minWidth: \"fit-content\" }}\n >\n ・{\" \"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: createdDate,\n }}\n />\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: authorId,\n blockHeight: blockHeight,\n }}\n />\n </div>\n )}\n </div>\n </Header>\n <div className=\"d-flex flex-column gap-1 p-2 px-3 description-box\">\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n PROPOSAL CATEGORY\n </div>\n <div>\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CategoryDropdown\"\n }\n props={{\n selectedValue: snapshot.category,\n disabled: true,\n }}\n />\n </div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n SUMMARY\n </div>\n <div>{snapshot.summary}</div>\n <div className=\"text-muted h6 border-bottom pb-1 mt-3\">\n DESCRIPTION\n </div>\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: snapshot.description,\n embeddCSS: `\n body {\n font-size: 14px;\n }\n `,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-3\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: authorId,\n }}\n />\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CommentIcon\"\n }\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: proposalURL,\n }}\n />\n </div>\n </div>\n </ProposalContainer>\n </div>\n <div className=\"border-bottom pb-4 mt-4\">\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CommentsAndLogs\"\n }\n props={{\n ...props,\n id: proposal.id,\n item: item,\n snapshotHistory: [...proposal.snapshot_history, snapshot],\n }}\n />\n </div>\n <div\n style={{\n position: \"relative\",\n zIndex: 99,\n backgroundColor: \"white\",\n }}\n className=\"pt-4\"\n >\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.ComposeComment\"\n }\n props={{\n ...props,\n item: item,\n notifyAccountId: authorId,\n id: proposal.id,\n sortedRelevantUsers: [\n authorId,\n snapshot.supervisor,\n snapshot.requested_sponsor,\n ...commentAuthors,\n ].filter((user) => user !== accountId),\n }}\n />\n </div>\n </div>\n <div\n style={{ minWidth: \"350px\" }}\n className=\"d-flex flex-column gap-4 flex-1 order-1 order-md-2\"\n >\n <SidePanelItem title=\"Author\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: authorId,\n noOverlay: true,\n }}\n />\n </SidePanelItem>\n <SidePanelItem\n title={\n \"Linked Proposals \" + `(${snapshot.linked_proposals.length})`\n }\n ishidden={!snapshot.linked_proposals.length}\n >\n <LinkedProposals />\n </SidePanelItem>\n <SidePanelItem title=\"Funding Ask\">\n <div className=\"h4 text-black\">\n {snapshot.requested_sponsorship_usd_amount && (\n <div className=\"d-flex flex-column gap-1\">\n <div>\n {parseInt(\n snapshot.requested_sponsorship_usd_amount\n ).toLocaleString()}{\" \"}\n USD\n </div>\n <div className=\"text-sm text-muted\">\n Requested in{\" \"}\n {snapshot.requested_sponsorship_paid_in_currency}\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Wallet Address\">\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.receiver_account,\n noOverlay: true,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Recipient Verification Status\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.VerificationStatus\"\n props={{\n receiverAccount: snapshot.receiver_account,\n showGetVerifiedBtn:\n accountId === snapshot.receiver_account ||\n accountId === authorId,\n }}\n />\n </SidePanelItem>\n <SidePanelItem title=\"Requested Sponsor\">\n {snapshot.requested_sponsor && (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.requested_sponsor,\n noOverlay: true,\n }}\n />\n )}\n </SidePanelItem>\n <SidePanelItem title=\"Supervisor\">\n {snapshot.supervisor ? (\n <Widget\n src=\"near/widget/AccountProfile\"\n props={{\n accountId: snapshot.supervisor,\n noOverlay: true,\n }}\n />\n ) : (\n \"No Supervisor\"\n )}\n </SidePanelItem>\n <SidePanelItem\n hideBorder={true}\n title={\n <div>\n <div className=\"d-flex justify-content-between align-content-center\">\n Timeline\n {isModerator && (\n <div onClick={() => setShowTimelineSetting(true)}>\n <i class=\"bi bi-gear\"></i>\n </div>\n )}\n </div>\n {showTimelineSetting && (\n <div className=\"mt-2 d-flex flex-column gap-2\">\n <h6 className=\"mb-0\">Proposal Status</h6>\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.DropDown\"\n props={{\n options: proposalStatusOptions,\n selectedValue: updatedProposalStatus,\n onUpdate: (v) => {\n setUpdatedProposalStatus({\n ...v,\n value: {\n ...v.value,\n ...updatedProposalStatus.value,\n status: v.value.status,\n },\n });\n },\n }}\n />\n </div>\n )}\n </div>\n }\n >\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"d-flex gap-3 mt-2\">\n <div className=\"d-flex flex-column\">\n {stepsArray.map((_, index) => {\n const indexOfCurrentItem = index;\n let color = \"\";\n let statusIndex = selectedStatusIndex;\n // index 2,3,4 is of decision\n if (\n selectedStatusIndex === 3 ||\n selectedStatusIndex === 2 ||\n selectedStatusIndex === 4 ||\n selectedStatusIndex === 5\n ) {\n statusIndex = 2;\n }\n if (selectedStatusIndex === 6) {\n statusIndex = 3;\n }\n const current = statusIndex === indexOfCurrentItem;\n const completed =\n statusIndex > indexOfCurrentItem ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED;\n return (\n <div className=\"d-flex flex-column align-items-center gap-1\">\n <div\n className={\n \"circle \" +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n >\n {completed && (\n <div\n className=\"d-flex justify-content-center align-items-center\"\n style={{ height: \"110%\" }}\n >\n <i class=\"bi bi-check\"></i>\n </div>\n )}\n </div>\n\n {index !== stepsArray.length - 1 && (\n <div\n className={\n \"vertical-line\" +\n (index === stepsArray.length - 2\n ? \"-sm \"\n : \" \") +\n (completed && \" green-fill \") +\n (current && \" yellow-fill \")\n }\n ></div>\n )}\n </div>\n );\n })}\n </div>\n <div className=\"d-flex flex-column gap-3\">\n <TimelineItems\n title=\"1) Draft\"\n value={TIMELINE_STATUS.DRAFT}\n >\n <div>\n Once an author submits a proposal, it is in draft mode\n and open for community comments. The author can still\n make changes to the proposal during this stage and\n submit it for official review when ready.\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"2) Review\"\n value={TIMELINE_STATUS.REVIEW}\n >\n <div className=\"d-flex flex-column gap-2\">\n Sponsors who agree to consider the proposal may\n request attestations from work groups.\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n sponsor_requested_review: value,\n },\n }))\n }\n label=\"Sponsor provides feedback or requests reviews\"\n isChecked={\n updatedProposalStatus.value\n .sponsor_requested_review\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 1}\n label=\"Reviewer completes attestations (Optional)\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n reviewer_completed_attestation: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .reviewer_completed_attestation\n }\n />\n <CheckBox\n value={updatedProposalStatus.value.kyc_verified}\n label=\"Sponsor verifies KYC/KYB\"\n disabled={selectedStatusIndex !== 1}\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n kyc_verified: value,\n },\n }))\n }\n isChecked={updatedProposalStatus.value.kyc_verified}\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"3) Decision\"\n values={[\n TIMELINE_STATUS.APPROVED,\n TIMELINE_STATUS.APPROVED_CONDITIONALLY,\n TIMELINE_STATUS.REJECTED,\n ]}\n >\n <div className=\"d-flex flex-column gap-2\">\n <div>Sponsor makes a final decision:</div>\n <RadioButton\n value=\"\"\n label={\n <>\n <div className=\"fw-bold\">Approved</div>\n <span>\n Recipient will receive invoice instructions\n within 1 business day at the email used for\n KYC/KYB verification.\n </span>\n </>\n }\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.PAYMENT_PROCESSING ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n }\n />\n <RadioButton\n value=\"\"\n label={\n <>\n <div className=\"fw-bold\">\n Approved - Conditional{\" \"}\n </div>\n <span>\n Requires follow up from recipient. Moderators\n will provide further details.\n </span>\n </>\n }\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED_CONDITIONALLY\n }\n />\n <RadioButton\n value=\"Reject\"\n label={<div className=\"fw-bold\">Rejected</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.REJECTED\n }\n />\n <RadioButton\n value=\"Canceled\"\n label={<div className=\"fw-bold\">Canceled</div>}\n isChecked={\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.CANCELED\n }\n />\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"4) Payment Processing\"\n value={TIMELINE_STATUS.PAYMENT_PROCESSING}\n >\n <div className=\"d-flex flex-column gap-2\">\n <CheckBox\n value={\n updatedProposalStatus.value.test_transaction_sent\n }\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor confirmed sponsorship and shared funding steps with recipient\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value,\n },\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n {/* Not needed for Alpha testing */}\n {/* <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor sends test transaction\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n test_transaction_sent: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value.test_transaction_sent\n }\n />\n <CheckBox\n value=\"\"\n disabled={selectedStatusIndex !== 6}\n label=\"Sponsor creates funding request from Trustees\"\n onClick={(value) =>\n setUpdatedProposalStatus((prevState) => ({\n ...prevState,\n value: {\n ...prevState.value,\n request_for_trustees_created: value\n }\n }))\n }\n isChecked={\n updatedProposalStatus.value\n .request_for_trustees_created\n }\n /> */}\n </div>\n </TimelineItems>\n <TimelineItems\n title=\"5) Funded\"\n value={TIMELINE_STATUS.FUNDED}\n >\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes?.length > 1 ? (\n paymentHashes.slice(0, -1).map((link, index) => (\n <a\n key={index}\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i className=\"bi bi-arrow-up-right\"></i>\n </a>\n ))\n ) : updatedProposalStatus.value.payouts.length > 0 ? (\n <div>\n {updatedProposalStatus.value.payouts.map(\n (link) => {\n return (\n <a\n href={link}\n className=\"text-decoration-underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n Payment Link\n <i class=\"bi bi-arrow-up-right\"></i>\n </a>\n );\n }\n )}\n </div>\n ) : (\n \"No Payouts yet\"\n )}\n </div>\n </TimelineItems>\n </div>\n </div>\n {showTimelineSetting && (\n <div className=\"d-flex flex-column gap-2\">\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Supervisor</label>\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.AccountInput\"\n props={{\n value: supervisor,\n placeholder: \"Enter Supervisor\",\n onUpdate: setSupervisor,\n }}\n />\n </div>\n {updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED && (\n <div className=\"border-vertical py-3 my-2\">\n <label className=\"text-black h6\">Payment Link</label>\n <div className=\"d-flex flex-column gap-2\">\n {paymentHashes.map((item, index) => (\n <div className=\"d-flex gap-2 justify-content-between align-items-center\">\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.Input\"\n props={{\n className: \"flex-grow-1\",\n value: item,\n onChange: (e) => {\n const updatedHashes = [...paymentHashes];\n updatedHashes[index] = e.target.value;\n setPaymentHashes(updatedHashes);\n },\n skipPaddingGap: true,\n placeholder: \"Enter URL\",\n }}\n />\n <div style={{ minWidth: 20 }}>\n {index !== paymentHashes.length - 1 ? (\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"btn-outline-danger shadow-none w-100\",\n },\n label: <i class=\"bi bi-trash3 h6\"></i>,\n onClick: () => {\n const updatedHashes = [\n ...paymentHashes,\n ];\n updatedHashes.splice(index, 1);\n setPaymentHashes(updatedHashes);\n },\n }}\n />\n ) : (\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n classNames: {\n root: \"green-btn shadow-none border-0 w-100\",\n },\n label: <i class=\"bi bi-plus-lg\"></i>,\n onClick: () =>\n setPaymentHashes([\n ...paymentHashes,\n \"\",\n ]),\n }}\n />\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n <div className=\"d-flex gap-2 align-items-center justify-content-end text-sm\">\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Cancel\",\n classNames: {\n root: \"btn-outline-danger border-0 shadow-none btn-sm\",\n },\n onClick: () => {\n setShowTimelineSetting(false);\n setUpdatedProposalStatus(proposalStatus);\n },\n }}\n />\n\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.Button\"\n }\n props={{\n label: \"Save\",\n disabled:\n ((updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.APPROVED_CONDITIONALLY ||\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.PAYMENT_PROCESSING) &&\n !updatedProposalStatus.value.kyc_verified) ||\n (!supervisor &&\n DecisionStage.includes(\n updatedProposalStatus.value.status\n )),\n classNames: { root: \"green-btn btn-sm\" },\n onClick: () => {\n if (snapshot.supervisor !== supervisor) {\n editProposal({\n timeline: updatedProposalStatus.value,\n });\n } else if (\n updatedProposalStatus.value.status ===\n TIMELINE_STATUS.FUNDED\n ) {\n editProposalStatus({\n timeline: {\n ...updatedProposalStatus.value,\n payouts: !paymentHashes[0]\n ? []\n : paymentHashes.filter(\n (item) => item !== \"\"\n ),\n },\n });\n } else {\n editProposalStatus({\n timeline: updatedProposalStatus.value,\n });\n }\n setShowTimelineSetting(false);\n },\n }}\n />\n </div>\n </div>\n )}\n </div>\n </SidePanelItem>\n </div>\n </div>\n </div>\n </div>\n </div>\n </Container>\n);\n" }, "devhub.entity.post.Post": { "": "// Ideally, this would be a page\n\nconst { href } = VM.require(\"devhub.near/widget/core.lib.url\");\nconst { getDepositAmountForWriteAccess } = VM.require(\n \"devhub.near/widget/core.lib.common\"\n);\n\ngetDepositAmountForWriteAccess || (getDepositAmountForWriteAccess = () => {});\nconst { draftState, onDraftStateChange } = VM.require(\n \"devhub.near/widget/devhub.entity.post.draft\"\n);\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst ButtonWithHover = styled.button`\n background-color: #fff;\n transition: all 300ms;\n border-radius: 0.5rem;\n\n &:hover {\n background-color: #e9ecef;\n color: #000;\n }\n\n &:disabled {\n background-color: #fff;\n color: #b7b7b7;\n }\n`;\n\nconst LikeLoadingSpinner = (\n <span\n className=\"like-loading-indicator spinner-border spinner-border-sm\"\n role=\"status\"\n aria-hidden=\"true\"\n />\n);\n\nconst postId = props.post.id ?? (props.id ? parseInt(props.id) : 0);\n\nconst [isLikeClicked, setIsLikeClicked] = useState(false);\nconst [numLikes, setNumLikes] = useState(null);\n\nconst post =\n props.post ??\n Near.view(\"devgovgigs.near\", \"get_post\", { post_id: postId });\n\nif (!post) {\n return <div>Loading ...</div>;\n}\n\nif (isLikeClicked && numLikes !== post.likes.length) {\n setIsLikeClicked(false);\n}\n\nsetNumLikes(post.likes.length);\n\nconst referral = props.referral;\nconst currentTimestamp = props.timestamp ?? post.snapshot.timestamp;\nconst compareTimestamp = props.compareTimestamp ?? \"\";\nconst swapTimestamps = currentTimestamp < compareTimestamp;\n\nconst snapshotHistory = post.snapshot_history;\n\nconst snapshot =\n currentTimestamp === post.snapshot.timestamp\n ? post.snapshot\n : (snapshotHistory &&\n snapshotHistory.find((s) => s.timestamp === currentTimestamp)) ??\n null;\n\nconst compareSnapshot =\n compareTimestamp === post.snapshot.timestamp\n ? post.snapshot\n : (snapshotHistory &&\n snapshotHistory.find((s) => s.timestamp === compareTimestamp)) ??\n null;\n\n// If this post is displayed under another post. Used to limit the size.\nconst isUnderPost = props.isUnderPost ? true : false;\n\nconst parentId = Near.view(\"devgovgigs.near\", \"get_parent_id\", {\n post_id: postId,\n});\n\nconst childPostIdsUnordered =\n Near.view(\"devgovgigs.near\", \"get_children_ids\", {\n post_id: postId,\n }) ?? [];\n\nconst childPostIds = props.isPreview ? [] : childPostIdsUnordered.reverse();\nconst expandable = props.isPreview ? false : props.expandable ?? false;\nconst defaultExpanded = expandable ? props.defaultExpanded : true;\n\nfunction readableDate(timestamp) {\n var a = new Date(timestamp);\n return a.toDateString() + \" \" + a.toLocaleTimeString();\n}\n\nconst timestamp = readableDate(\n snapshot.timestamp ? snapshot.timestamp / 1000000 : Date.now()\n);\n\nconst postSearchKeywords = props.searchKeywords ? (\n <div style={{ \"font-family\": \"monospace\" }} key=\"post-search-keywords\">\n <span>Found keywords: </span>\n\n {props.searchKeywords.map((tag) => (\n <Widget\n src={\"devhub.near/widget/devhub.components.atom.Tag\"}\n props={{ linkTo: \"Feed\", tag }}\n />\n ))}\n </div>\n) : (\n <div key=\"post-search-keywords\"></div>\n);\n\nconst searchKeywords = props.searchKeywords ? (\n <div class=\"mb-4\" key=\"search-keywords\">\n <small class=\"text-muted\">{postSearchKeywords}</small>\n </div>\n) : (\n <div key=\"search-keywords\"></div>\n);\n\nconst allowedToEdit =\n !props.isPreview &&\n Near.view(\"devgovgigs.near\", \"is_allowed_to_edit\", {\n post_id: postId,\n editor: context.accountId,\n });\n\nconst btnEditorWidget = (postType, name) => {\n return (\n <li>\n <a\n class=\"dropdown-item\"\n role=\"button\"\n onClick={() =>\n State.update({ postType, editorType: \"EDIT\", showEditor: true })\n }\n >\n {name}\n </a>\n </li>\n );\n};\n\nconst editControl = allowedToEdit ? (\n <div class=\"btn-group\" role=\"group\">\n <a\n class=\"card-link px-2\"\n role=\"button\"\n title=\"Edit post\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n type=\"button\"\n >\n <div class=\"bi bi-pencil-square\"></div>\n </a>\n\n <ul class=\"dropdown-menu\">\n {btnEditorWidget(\"Idea\", \"Edit as an idea\")}\n {btnEditorWidget(\"Solution\", \"Edit as a solution\")}\n {btnEditorWidget(\"Attestation\", \"Edit as an attestation\")}\n {btnEditorWidget(\"Sponsorship\", \"Edit as a sponsorship\")}\n {btnEditorWidget(\"Comment\", \"Edit as a comment\")}\n </ul>\n </div>\n) : (\n <div></div>\n);\n\nconst shareButton = props.isPreview ? (\n <div></div>\n) : (\n <Link\n class=\"card-link text-dark\"\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"post\", id: postId },\n })}\n role=\"button\"\n target=\"_blank\"\n title=\"Open in new tab\"\n >\n <div class=\"bi bi-share\"></div>\n </Link>\n);\n\nconst ProfileCardContainer = styled.div`\n @media screen and (max-width: 960px) {\n width: 100%;\n }\n`;\n\n// card-header\nconst header = (\n <div key=\"header\">\n <small class=\"text-muted\">\n <div class=\"row justify-content-between\">\n <div class=\"d-flex align-items-center flex-wrap\">\n <ProfileCardContainer>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.ProfileCard\"\n }\n props={{\n accountId: post.author_id,\n }}\n />\n </ProfileCardContainer>\n\n <div class=\"d-flex ms-auto\">\n {editControl}\n {timestamp}\n\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.History\"}\n props={{\n post,\n timestamp: currentTimestamp,\n }}\n />\n {shareButton}\n </div>\n </div>\n </div>\n </small>\n </div>\n);\n\n// const emptyIcons = {\n// Idea: \"bi-lightbulb\",\n// Comment: \"bi-chat\",\n// Solution: \"bi-rocket\",\n// Attestation: \"bi-check-circle\",\n// Sponsorship: \"bi-cash-coin\",\n// Github: \"bi-github\",\n// Like: \"bi-heart\",\n// Reply: \"bi-reply\",\n// };\n\nconst emptyIcons = {\n Idea: \"💡\",\n Comment: \"bi-chat\",\n Solution: \"🚀\",\n Attestation: \"✅\",\n Sponsorship: \"🪙\",\n Github: \"bi-github\",\n Like: \"bi-heart\",\n Reply: \"bi-reply\",\n};\n\nconst fillIcons = {\n Idea: \"💡\",\n Comment: \"bi-chat-fill\",\n Solution: \"🚀\",\n Attestation: \"✅\",\n Sponsorship: \"🪙\",\n Github: \"bi-github\",\n Like: \"bi-heart-fill\",\n Reply: \"bi-reply-fill\",\n};\n\n// Trigger saving this widget.\n\nconst borders = {\n Idea: \"border-light\",\n Comment: \"border-light\",\n Solution: \"border-light\",\n Attestation: \"border-light\",\n Sponsorship: \"border-light\",\n Github: \"border-light\",\n};\n\nconst containsLike = props.isPreview\n ? false\n : post.likes.find((l) => l.author_id == context.accountId);\nconst likeBtnClass = containsLike ? fillIcons.Like : emptyIcons.Like;\n// This must be outside onLike, because Near.view returns null at first, and when the view call finished, it returns true/false.\n// If checking this inside onLike, it will give `null` and we cannot tell the result is true or false.\nlet grantNotify = Near.view(\n \"social.near\",\n \"is_write_permission_granted\",\n {\n predecessor_id: \"devgovgigs.near\",\n key: context.accountId + \"/index/notify\",\n }\n);\n\nconst userStorageDeposit = Near.view(\n \"social.near\",\n \"storage_balance_of\",\n {\n account_id: context.accountId,\n }\n);\n\nif (grantNotify === null || userStorageDeposit === null) {\n return;\n}\n\nconst onLike = () => {\n if (!context.accountId) {\n return;\n }\n\n let likeTxn = [\n {\n contractName: \"devgovgigs.near\",\n methodName: \"add_like\",\n args: {\n post_id: postId,\n },\n gas: Big(10).pow(14),\n },\n ];\n\n if (grantNotify === false) {\n likeTxn.unshift({\n contractName: \"social.near\",\n methodName: \"grant_write_permission\",\n args: {\n predecessor_id: \"devgovgigs.near\",\n keys: [context.accountId + \"/index/notify\"],\n },\n gas: Big(10).pow(14),\n deposit: getDepositAmountForWriteAccess(userStorageDeposit),\n });\n }\n\n setIsLikeClicked(true);\n Near.call(likeTxn);\n};\n\nconst btnCreatorWidget = (postType, icon, name, desc) => {\n return (\n <li class=\"py-1\">\n <a\n class=\"dropdown-item text-decoration-none d-flex align-items-center lh-sm\"\n style={{ color: \"rgb(55,109,137)\" }}\n role=\"button\"\n onClick={() =>\n State.update({ postType, editorType: \"CREATE\", showEditor: true })\n }\n >\n <i class={`bi ${icon}`} style={{ fontSize: \"1.5rem\" }}>\n {\" \"}\n </i>\n\n <div class=\"ps-2 text-wrap\" style={{ width: \"18rem\" }}>\n <div>{name}</div>\n <small class=\"fw-light text-secondary\">{desc}</small>\n </div>\n </a>\n </li>\n );\n};\n\nconst FooterButtonsContianer = styled.div`\n width: 66.66666667%;\n\n @media screen and (max-width: 960px) {\n width: 100%;\n }\n`;\n\nconst buttonsFooter = props.isPreview ? null : (\n <div class=\"row\" key=\"buttons-footer\">\n <FooterButtonsContianer>\n <div class=\"btn-group\" role=\"group\" aria-label=\"Basic outlined example\">\n <ButtonWithHover\n type=\"button\"\n class=\"btn d-flex align-items-center\"\n style={{ border: \"0px\" }}\n onClick={onLike}\n disabled={isLikeClicked}\n >\n <i class={`bi ${likeBtnClass}`}> </i>\n {isLikeClicked ? LikeLoadingSpinner : <></>}\n {post.likes.length == 0 ? (\n \"Like\"\n ) : (\n <Widget\n src=\"devhub.near/widget/devhub.components.layout.LikeButton.Faces\"\n props={{\n likesByUsers: Object.fromEntries(\n post.likes.map(({ author_id }) => [author_id, \"\"])\n ),\n }}\n />\n )}\n </ButtonWithHover>\n\n <div class=\"btn-group\" role=\"group\">\n <ButtonWithHover\n type=\"button\"\n class=\"btn\"\n style={{ border: \"0px\" }}\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n >\n ↪ Reply\n </ButtonWithHover>\n <ul class=\"dropdown-menu\">\n {btnCreatorWidget(\n \"Idea\",\n emptyIcons.Idea,\n \"Idea\",\n \"Get feedback from the community about a problem, opportunity, or need.\"\n )}\n {btnCreatorWidget(\n \"Solution\",\n emptyIcons.Solution,\n \"Solution\",\n \"Provide a specific proposal or implementation to an idea, optionally requesting funding.\"\n )}\n {btnCreatorWidget(\n \"Attestation\",\n emptyIcons.Attestation,\n \"Attestation\",\n \"Formally review or validate a solution as a recognized expert.\"\n )}\n {btnCreatorWidget(\n \"Sponsorship\",\n emptyIcons.Sponsorship,\n \"Sponsorship\",\n \"Offer to fund projects, events, or proposals that match your needs.\"\n )}\n <li>\n <hr class=\"dropdown-divider\" />\n </li>\n {btnCreatorWidget(\n \"Comment\",\n emptyIcons.Comment,\n \"Comment\",\n \"Ask a question, provide information, or share a resource that is relevant to the thread.\"\n )}\n </ul>\n </div>\n {childPostIds.length > 0 && (\n <ButtonWithHover\n type=\"button\"\n class=\"btn\"\n style={{ border: \"0px\" }}\n data-bs-toggle=\"collapse\"\n href={`#collapseChildPosts${postId}`}\n aria-expanded={defaultExpanded}\n aria-controls={`collapseChildPosts${postId}`}\n onClick={() =>\n State.update({ expandReplies: !state.expandReplies })\n }\n >\n <i\n class={`bi bi-chevron-${state.expandReplies ? \"up\" : \"down\"}`}\n ></i>{\" \"}\n {`${state.expandReplies ? \"Collapse\" : \"Expand\"} Replies (${\n childPostIds.length\n })`}\n </ButtonWithHover>\n )}\n\n {isUnderPost || !parentId ? (\n <div key=\"link-to-parent\"></div>\n ) : (\n <Link\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"post\", id: parentId },\n })}\n >\n <ButtonWithHover\n type=\"button\"\n style={{ border: \"0px\" }}\n className=\"btn\"\n key=\"link-to-parent\"\n >\n <i class=\"bi bi-arrow-90deg-up\"></i>Go to parent\n </ButtonWithHover>\n </Link>\n )}\n </div>\n </FooterButtonsContianer>\n </div>\n);\n\nconst tokenMapping = {\n NEAR: \"NEAR\",\n USDT: {\n NEP141: {\n address: \"usdt.tether-token.near\",\n },\n },\n USDC: {\n NEP141: {\n address:\n \"17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1\",\n },\n },\n // Add more tokens here as needed\n};\n\nconst reverseTokenMapping = Object.keys(tokenMapping).reduce(\n (reverseMap, key) => {\n const value = tokenMapping[key];\n if (typeof value === \"object\") {\n reverseMap[JSON.stringify(value)] = key;\n }\n return reverseMap;\n },\n {}\n);\n\nfunction tokenResolver(token) {\n if (typeof token === \"string\") {\n return token;\n } else if (typeof token === \"object\") {\n const tokenString = reverseTokenMapping[JSON.stringify(token)];\n return tokenString || null;\n } else {\n return null; // Invalid input\n }\n}\n\nconst isDraft =\n (draftState?.parent_post_id === postId &&\n draftState?.postType === state.postType) ||\n (draftState?.edit_post_id === postId &&\n draftState?.postType === state.postType);\n\nconst setExpandReplies = (value) => {\n State.update({ expandReplies: value });\n};\n\nconst setEditorState = (value) => {\n if (draftState && !value) {\n // clear the draft state since user initiated cancel\n onDraftStateChange(null);\n }\n State.update({ showEditor: value });\n};\n\nlet amount = null;\nlet token = null;\nlet supervisor = null;\n\nif (state.postType === \"Solution\") {\n const amountMatch = post.snapshot.description.match(\n /Requested amount: (\\d+(\\.\\d+)?) (\\w+)/\n );\n amount = amountMatch ? parseFloat(amountMatch[1]) : null;\n token = amountMatch ? amountMatch[3] : null;\n\n const sponsorMatch = post.snapshot.description.match(\n /Requested sponsor: @([^\\s]+)/\n );\n supervisor = sponsorMatch ? sponsorMatch[1] : null;\n}\n\nconst seekingFunding = amount !== null || token !== null || supervisor !== null;\n\nfunction Editor() {\n return (\n <div class=\"row mt-2\" id={`accordion${postId}`} key=\"editors-footer\">\n <div\n key={`${state.postType}${state.editorType}${postId}`}\n className={\"w-100\"}\n >\n {state.editorType === \"CREATE\" ? (\n <>\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.PostEditor\"}\n props={{\n postType: state.postType,\n onDraftStateChange,\n draftState:\n draftState?.parent_post_id == postId ? draftState : undefined,\n parentId: postId,\n mode: \"Create\",\n transactionHashes: props.transactionHashes,\n setExpandReplies,\n setEditorState: setEditorState,\n }}\n />\n </>\n ) : (\n <>\n <Widget\n src={\"devhub.near/widget/devhub.entity.post.PostEditor\"}\n props={{\n postType: state.postType,\n postId,\n mode: \"Edit\",\n author_id: post.author_id,\n labels: post.snapshot.labels,\n name: post.snapshot.name,\n description: post.snapshot.description,\n amount: post.snapshot.amount || amount,\n token: tokenResolver(post.snapshot.sponsorship_token || token),\n supervisor:\n post.snapshot.post.snapshot.supervisor || supervisor,\n seekingFunding: seekingFunding,\n githubLink: post.snapshot.github_link,\n onDraftStateChange,\n draftState:\n draftState?.edit_post_id == postId ? draftState : undefined,\n setEditorState: setEditorState,\n transactionHashes: props.transactionHashes,\n setExpandReplies,\n }}\n />\n </>\n )}\n </div>\n </div>\n );\n}\n\nconst renamedPostType =\n snapshot.post_type == \"Submission\" ? \"Solution\" : snapshot.post_type;\n\nconst tags = post.snapshot.labels ? (\n <div\n class=\"card-title d-flex flex-wrap align-items-center\"\n style={{ margin: \"20px 0\" }}\n key=\"post-labels\"\n >\n {post.snapshot.labels.map((tag, idx) => (\n <div className=\"d-flex align-items-center my-3 me-3\">\n <Link\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"feed\", tag: tag },\n })}\n >\n <div\n onClick={() => {\n if (typeof props.updateTagInParent === \"function\") {\n props.updateTagInParent(tag);\n }\n }}\n className=\"d-flex gap-3 align-items-center\"\n style={{ cursor: \"pointer\", textDecoration: \"none\" }}\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.atom.Tag\"}\n props={{\n tag,\n black: true,\n }}\n />\n </div>\n </Link>\n {idx !== post.snapshot.labels.length - 1 && (\n <span className=\"ms-3\">•</span>\n )}\n </div>\n ))}\n </div>\n) : (\n <div key=\"post-labels\"></div>\n);\n\nconst Title = styled.h5`\n margin: 1rem 0;\n\n color: #151515;\n font-size: 1.15rem;\n font-style: normal;\n font-weight: 700;\n line-height: 1.625rem; /* 55.556% */\n`;\n\nconst postTitle =\n snapshot.post_type == \"Comment\" ? (\n <div key=\"post-title\"></div>\n ) : (\n <Title key=\"post-title\">\n {emptyIcons[snapshot.post_type]} {renamedPostType}: {snapshot.name}\n </Title>\n );\n\nconst postExtra =\n snapshot.post_type == \"Sponsorship\" ? (\n <div key=\"post-extra\">\n <h6 class=\"card-subtitle mb-2 text-muted\">\n Maximum amount: {snapshot.amount}{\" \"}\n {tokenResolver(snapshot.sponsorship_token)}\n </h6>\n <h6 class=\"card-subtitle mb-2 text-muted\">\n Supervisor:{\" \"}\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.ProfileLine\"}\n props={{ accountId: snapshot.supervisor }}\n />\n </h6>\n </div>\n ) : (\n <div></div>\n );\n\nconst childPostHasDraft = childPostIds.find(\n (childId) =>\n childId == draftState?.edit_post_id || childId == draftState?.parent_post_id\n);\nif (\n (childPostHasDraft || state.childrenOfChildPostsHasDraft) &&\n props.expandParent\n) {\n props.expandParent();\n}\n\nconst postsList =\n props.isPreview || childPostIds.length == 0 ? (\n <div key=\"posts-list\"></div>\n ) : (\n <div class=\"row\" key=\"posts-list\">\n <div\n class={`collapse mt-3 ${\n defaultExpanded ||\n childPostHasDraft ||\n state.childrenOfChildPostsHasDraft ||\n state.expandReplies\n ? \"show\"\n : \"\"\n }`}\n id={`collapseChildPosts${postId}`}\n >\n {childPostIds.map((childId) => (\n <div key={childId} style={{ marginBottom: \"0.5rem\" }}>\n <Widget\n src=\"devhub.near/widget/devhub.entity.post.Post\"\n props={{\n id: childId,\n isUnderPost: true,\n onDraftStateChange,\n draftState,\n expandParent: () =>\n State.update({ childrenOfChildPostsHasDraft: true }),\n referral: `subpost${childId}of${postId}`,\n }}\n />\n </div>\n ))}\n </div>\n </div>\n );\n\nconst LimitedMarkdown = styled.div`\n max-height: 20em;\n`;\n\n// Determine if located in the post page.\nconst isInList = props.isInList;\nconst contentArray = snapshot.description.split(\"\\n\");\nconst needClamp = isInList && contentArray.length > 5;\n\ninitState({\n clamp: needClamp,\n expandReplies: defaultExpanded,\n});\n\nconst clampedContent = needClamp\n ? contentArray.slice(0, 3).join(\"\\n\")\n : snapshot.description;\n\nconst SeeMore = styled.a`\n cursor: pointer;\n color: #00b774 !important;\n font-weight: bold;\n`;\n\n// Should make sure the posts under the currently top viewed post are limited in size.\nconst descriptionArea = isUnderPost ? (\n <LimitedMarkdown className=\"overflow-auto\" key=\"description-area\">\n {/* {widget(\"components.molecule.markdown-viewer\", {\n text: snapshot.description,\n })} */}\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{\n content: snapshot.description,\n }}\n />\n </LimitedMarkdown>\n) : (\n <div className=\"w-100 overflow-auto\">\n <div class={state.clamp ? \"clamp\" : \"\"}>\n {/* {widget(\"components.molecule.markdown-viewer\", {\n text: state.clamp ? clampedContent : snapshot.description,\n })} */}\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{\n content: state.clamp ? clampedContent : snapshot.description,\n }}\n />\n </div>\n {state.clamp ? (\n <div class=\"d-flex justify-content-start\">\n <SeeMore onClick={() => State.update({ clamp: false })}>\n See more\n </SeeMore>\n </div>\n ) : (\n <></>\n )}\n </div>\n);\n\nconst timestampElement = (_snapshot) => {\n return (\n <Link\n class=\"text-muted\"\n href={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"post\",\n id: postId,\n timestamp: _snapshot.timestamp,\n compareTimestamp: null,\n referral,\n },\n })}\n >\n {readableDate(_snapshot.timestamp / 1000000).substring(4)}\n\n <Widget\n src=\"mob.near/widget/ProfileImage\"\n props={{\n accountId: _snapshot.editor_id,\n style: {\n width: \"1.25em\",\n height: \"1.25em\",\n },\n imageStyle: {\n transform: \"translateY(-12.5%)\",\n },\n }}\n />\n {_snapshot.editor_id.substring(0, 8)}\n </Link>\n );\n};\n\nfunction combineText(_snapshot) {\n return (\n \"## \" +\n _snapshot.post_type +\n \": \" +\n _snapshot.name +\n \"\\n\" +\n _snapshot.description\n );\n}\n\nconst CardContainer = styled.div`\n padding: 1.5rem 3rem !important;\n border-radius: 16px !important;\n border: 1px solid rgba(129, 129, 129, 0.3) !important;\n background: #fffefe !important;\n\n @media screen and (max-width: 960px) {\n padding: 1rem !important;\n }\n`;\n\nreturn (\n <CardContainer className={`card ${borders[snapshot.post_type]} attractable`}>\n {header}\n <div className=\"card-body\" style={{ padding: 0 }}>\n {searchKeywords}\n {compareSnapshot ? (\n <div\n class=\"border rounded\"\n style={{ marginTop: \"16px\", marginBottom: \"16px\" }}\n >\n <div class=\"d-flex justify-content-end\" style={{ fontSize: \"12px\" }}>\n <div class=\"d-flex w-50 justify-content-end mt-1 me-2\">\n {timestampElement(snapshot)}\n {snapshot !== compareSnapshot && (\n <>\n <div class=\"mx-1 align-self-center\">\n <i class=\"bi bi-file-earmark-diff\" />\n </div>\n {timestampElement(compareSnapshot)}\n </>\n )}\n </div>\n </div>\n <div className=\"w-100 overflow-auto\">\n <Widget\n src=\"markeljan.near/widget/MarkdownDiff\"\n props={{\n post: post,\n currentCode: combineText(\n swapTimestamps ? compareSnapshot : snapshot\n ),\n prevCode: combineText(\n swapTimestamps ? snapshot : compareSnapshot\n ),\n showLineNumber: true,\n }}\n />\n </div>\n </div>\n ) : (\n <>\n {postTitle}\n {postExtra}\n {descriptionArea}\n </>\n )}\n {tags}\n {buttonsFooter}\n {!props.isPreview && (isDraft || state.showEditor) && <Editor />}\n {postsList}\n </div>\n </CardContainer>\n);\n" }, "devhub.entity.addon.blogv2.Page": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\") || {\n href: () => {},\n};\nconst httpbin = fetch(`https://httpbin.org/headers`);\n\nconst { getCommunity } = VM.require(\n \"devhub.near/widget/core.adapter.devhub-contract\"\n) || {\n getCommunity: () => {},\n};\nconst imagelink =\n \"https://ipfs.near.social/ipfs/bafkreiajzvmy7574k7mp3if6u53mdukfr3hoc2kjkhjadt6x56vqhd5swy\";\n\nconst { data, onEdit, community: handle, isAllowedToEdit } = props;\n\nconst {\n category,\n title,\n subtitle,\n publishedAt: date,\n content,\n author,\n communityAddonId,\n} = data;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n margin-bottom: 2rem;\n position: relative;\n\n span.news {\n color: #f40303;\n }\n\n span.reference {\n color: #ff7a00;\n }\n\n span.guide {\n color: #004be1;\n }\n\n span.category {\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 20px; /* 125% */\n text-transform: uppercase;\n }\n\n div.date {\n color: #818181;\n font-size: 1rem;\n font-style: normal;\n font-weight: 400;\n line-height: 20px; /* 125% */\n margin: 1.5rem 0;\n width: 100%;\n }\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1.5rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n .edit-icon {\n position: absolute;\n top: 20px;\n right: 20px;\n cursor: pointer;\n }\n\n .share-icon {\n position: absolute;\n top: 25px;\n right: 55px;\n cursor: pointer;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n`;\n\nconst BackgroundImage = styled.img`\n width: 100%;\n height: auto;\n object-fit: cover;\n margin-bottom: 1rem;\n`;\n\nif (!handle) {\n return <div>Community handle not found</div>;\n}\n\nconst community = getCommunity({ handle: handle });\n\nlet communityConfig = (community.addons || []).find(\n (addon) => addon.id === communityAddonId\n);\n\nif (communityConfig === undefined) {\n return <div>Community addon not found</div>;\n}\nconst parameters = JSON.parse(communityConfig.parameters || {}) || {};\n\n// Make sure only the categories that are configured in the settings are displayed.\nlet categoryIsOptionInSettings = true;\nlet categoriesInSettings = (parameters?.categories || []).map((c) => c.value);\nif (\n categoriesInSettings.length > 0 &&\n !categoriesInSettings.includes(category) &&\n category !== \"\" &&\n category !== null\n) {\n categoryIsOptionInSettings = false;\n}\n\nconst options = { year: \"numeric\", month: \"short\", day: \"numeric\" };\nconst formattedDate = new Date(date).toLocaleString(\"en-US\", options);\n\nreturn (\n <>\n <BackgroundImage src={imagelink} />\n <Container>\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.ShareButton\"\n props={{\n className: \"share-icon\",\n size: \"35px\",\n postType: \"blog\",\n externalLink: href({\n gateway: httpbin?.body?.headers?.Origin.slice(8) ?? \"near.social\",\n widgetSrc: \"devhub.near/widget/app\",\n params: {\n page: \"blogv2\",\n community: community.handle,\n id: data.id,\n },\n }),\n }}\n />\n {isAllowedToEdit && (\n <div className=\"edit-icon\" onClick={onEdit}>\n <div class=\"bi bi-pencil-square\" style={{ fontSize: \"30px\" }}></div>\n </div>\n )}\n {category &&\n parameters.categoriesEnabled === \"enabled\" &&\n categoryIsOptionInSettings && (\n <span\n className={`category ${category && category.toLowerCase()}`}\n data-testid=\"blog-category\"\n >\n {category}\n </span>\n )}\n <h1 data-testid=\"blog-title\">{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <div className=\"d-flex flex-row justify-content-between date\">\n {author && parameters.authorEnabled !== \"disabled\" && (\n <div data-testid=\"blog-author\">{author}</div>\n )}\n <div data-testid=\"blog-date\">{formattedDate}</div>\n </div>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{ content: content }}\n />\n </Container>\n </>\n);\n" }, "devhub.entity.proposal.Feed": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\") || {\n href: () => {},\n};\n\nconst instance = props.instance ?? \"\";\n\nconst {\n contract,\n rfpFeedIndexerQueryName,\n proposalFeedAnnouncement,\n availableCategoryOptions,\n proposalFeedIndexerQueryName,\n indexerHasuraRole,\n isDevhub,\n isInfra,\n isEvents,\n} = VM.require(`${instance}/widget/config.data`);\n\nconst loader = (\n <div className=\"d-flex justify-content-center align-items-center w-100\">\n <Widget src={\"devhub.near/widget/devhub.components.molecule.Spinner\"} />\n </div>\n);\n\nif (!contract) {\n return loader;\n}\n\nfunction isNumber(v) {\n return typeof v === \"number\";\n}\n\nconst Container = styled.div`\n .full-width-div {\n width: 100vw;\n position: relative;\n left: 50%;\n right: 50%;\n margin-left: -50vw;\n margin-right: -50vw;\n }\n\n .card.no-border {\n border-left: none !important;\n border-right: none !important;\n margin-bottom: -3.5rem;\n }\n\n @media screen and (max-width: 768px) {\n font-size: 13px;\n }\n\n .text-sm {\n font-size: 13px;\n }\n\n .bg-grey {\n background-color: #f4f4f4;\n }\n\n .border-bottom {\n border-bottom: 1px solid grey;\n }\n\n .cursor-pointer {\n cursor: pointer;\n }\n\n .proposal-card {\n border-left: none !important;\n border-right: none !important;\n border-bottom: none !important;\n &:hover {\n background-color: #f4f4f4;\n }\n }\n\n @media screen and (max-width: 768px) {\n .theme-btn {\n padding: 0.5rem 0.8rem !important;\n min-height: 32px;\n }\n }\n\n a.no-space {\n display: inline-block;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n\n .text-center {\n text-align: center;\n }\n\n .btn-grey-outline {\n background-color: #fafafa;\n border: 1px solid #e6e8eb;\n color: #11181c;\n\n &:hover {\n background-color: #e6e8eb;\n }\n\n &:active {\n border: 2px solid #e6e8eb;\n }\n }\n`;\n\nconst Heading = styled.div`\n font-size: 24px;\n font-weight: 700;\n width: 100%;\n\n .text-normal {\n font-weight: normal !important;\n }\n\n @media screen and (max-width: 768px) {\n font-size: 18px;\n }\n`;\n\nconst FeedItem = ({ proposal, index }) => {\n const accountId = proposal.author_id;\n const profile = Social.get(`${accountId}/profile/**`, \"final\");\n // We will have to get the proposal from the contract to get the block height.\n const blockHeight = parseInt(proposal.social_db_post_block_height);\n const item = {\n type: \"social\",\n path: `${contract}/post/main`,\n blockHeight: blockHeight,\n };\n\n const isLinked = isNumber(proposal.linked_rfp);\n const rfpData = proposal.rfpData;\n\n return (\n <a\n href={href({\n widgetSrc: `${instance}/widget/app`,\n params: {\n page: \"proposal\",\n id: proposal.proposal_id,\n },\n })}\n onClick={(e) => e.stopPropagation()}\n style={{ textDecoration: \"none\" }}\n >\n <div\n className={\n \"proposal-card d-flex justify-content-between gap-2 text-muted cursor-pointer p-3 w-100 flex-wrap flex-sm-nowrap \" +\n (index !== 0 && \" border\")\n }\n >\n <div className=\"d-flex gap-4 w-100\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId,\n }}\n />\n <div className=\"d-flex flex-column gap-2 w-100 text-wrap\">\n <div className=\"d-flex gap-2 align-items-center flex-wrap w-100\">\n <div className=\"h6 mb-0 text-black\">{proposal.name}</div>\n {(isInfra || isEvents) && (\n <Widget\n src={`devhub.near/widget/devhub.entity.proposal.MultiSelectLabelsDropdown`}\n props={{\n selected: proposal.labels,\n disabled: true,\n hideDropdown: true,\n onChange: () => {},\n availableOptions: availableCategoryOptions,\n }}\n />\n )}\n {isDevhub && (\n <Widget\n src={\n \"devhub.near/widget/devhub.entity.proposal.CategoryTag\"\n }\n props={{\n category: proposal.category,\n }}\n />\n )}\n </div>\n {isLinked && rfpData && (\n <div className=\"text-sm text-muted d-flex gap-1 align-items-center\">\n <i class=\"bi bi-link-45deg\"></i>\n In response to RFP :\n <a\n className=\"text-decoration-underline flex-1\"\n href={href({\n widgetSrc: `${instance}/widget/app`,\n params: {\n page: \"rfp\",\n id: rfpData.rfp_id,\n },\n })}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {rfpData.name}\n </a>\n </div>\n )}\n <div className=\"d-flex gap-2 align-items-center flex-wrap flex-sm-nowrap text-sm w-100\">\n <div>#{proposal.proposal_id} ・ </div>\n <div className=\"text-truncate\">\n By {profile.name ?? accountId} ・{\" \"}\n </div>\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight,\n blockTimestamp: proposal.timestamp,\n }}\n />\n </div>\n <div className=\"d-flex gap-2 align-items-center\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item,\n proposalId: proposal.id,\n notifyAccountId: accountId,\n instance,\n }}\n />\n\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.CommentIcon\"}\n props={{\n item,\n showOverlay: false,\n onClick: () => {},\n }}\n />\n </div>\n </div>\n </div>\n <div className=\"align-self-center\" style={{ minWidth: \"fit-content\" }}>\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: proposal.timeline.status,\n }}\n />\n </div>\n </div>\n </a>\n );\n};\n\nconst getProposal = (proposal_id) => {\n return Near.asyncView(contract, \"get_proposal\", {\n proposal_id,\n });\n};\n\nconst FeedPage = () => {\n const QUERYAPI_ENDPOINT = `https://near-queryapi.api.pagoda.co/v1/graphql`;\n\n State.init({\n data: [],\n author: \"\",\n stage: \"\",\n sort: \"\",\n category: \"\",\n input: \"\",\n loading: false,\n searchLoader: false,\n makeMoreLoader: false,\n aggregatedCount: null,\n currentlyDisplaying: 0,\n });\n\n const query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${proposalFeedIndexerQueryName}_bool_exp = {}) {\n ${proposalFeedIndexerQueryName}(\n offset: $offset\n limit: $limit\n order_by: {proposal_id: desc}\n where: $where\n ) {\n author_id\n block_height\n name\n category\n summary\n editor_id\n proposal_id\n ts\n timeline\n views\n labels\n linked_rfp\n }\n ${proposalFeedIndexerQueryName}_aggregate(\n order_by: {proposal_id: desc}\n where: $where\n ) {\n aggregate {\n count\n }\n }\n }`;\n\n function fetchGraphQL(operationsDoc, operationName, variables) {\n return asyncFetch(QUERYAPI_ENDPOINT, {\n method: \"POST\",\n headers: { \"x-hasura-role\": indexerHasuraRole },\n body: JSON.stringify({\n query: operationsDoc,\n variables: variables,\n operationName: operationName,\n }),\n });\n }\n\n function separateNumberAndText(str) {\n const numberRegex = /\\d+/;\n\n if (numberRegex.test(str)) {\n const number = str.match(numberRegex)[0];\n const text = str.replace(numberRegex, \"\").trim();\n return { number: parseInt(number), text };\n } else {\n return { number: null, text: str.trim() };\n }\n }\n\n const buildWhereClause = () => {\n let where = {};\n if (state.author) {\n where = { author_id: { _eq: state.author }, ...where };\n }\n\n if (state.category) {\n if (isInfra || isEvents) {\n where = { labels: { _contains: state.category }, ...where };\n } else {\n where = { category: { _eq: state.category }, ...where };\n }\n }\n\n if (state.stage) {\n // timeline is stored as jsonb\n where = {\n timeline: { _cast: { String: { _regex: `${state.stage}` } } },\n ...where,\n };\n }\n if (state.input) {\n const { number, text } = separateNumberAndText(state.input);\n if (number) {\n where = { proposal_id: { _eq: number }, ...where };\n }\n\n if (text) {\n where = {\n _or: [\n { name: { _iregex: `${text}` } },\n { summary: { _iregex: `${text}` } },\n { description: { _iregex: `${text}` } },\n ],\n ...where,\n };\n }\n }\n\n return where;\n };\n\n const makeMoreItems = () => {\n State.update({ makeMoreLoader: true });\n fetchProposals(state.data.length);\n };\n\n const fetchProposals = (offset) => {\n if (!offset) {\n offset = 0;\n }\n if (state.loading) return;\n State.update({ loading: true });\n const FETCH_LIMIT = 10;\n const variables = {\n limit: FETCH_LIMIT,\n offset,\n where: buildWhereClause(),\n };\n fetchGraphQL(query, \"GetLatestSnapshot\", variables).then(async (result) => {\n if (result.status === 200) {\n if (result.body.data) {\n const data = result.body.data[proposalFeedIndexerQueryName];\n const totalResult =\n result.body.data[`${proposalFeedIndexerQueryName}_aggregate`];\n const promises = data.map((item) => {\n if (isNumber(item.linked_rfp)) {\n return fetchGraphQL(rfpQuery, \"GetLatestSnapshot\", {}).then(\n (result) => {\n const rfpData = result.body.data?.[rfpQueryName];\n return { ...item, rfpData: rfpData[0] };\n }\n );\n } else {\n return Promise.resolve(item);\n }\n });\n Promise.all(promises).then((res) => {\n State.update({ aggregatedCount: totalResult.aggregate.count });\n fetchBlockHeights(res, offset);\n });\n }\n }\n });\n };\n\n useEffect(() => {\n State.update({ searchLoader: true });\n fetchProposals();\n }, [state.author, state.sort, state.category, state.stage]);\n\n const mergeItems = (newItems) => {\n const items = [\n ...new Set([...newItems, ...state.data].map((i) => JSON.stringify(i))),\n ].map((i) => JSON.parse(i));\n // Sorting in the front end\n if (state.sort === \"proposal_id\" || state.sort === \"\") {\n items.sort((a, b) => b.proposal_id - a.proposal_id);\n } else if (state.sort === \"views\") {\n items.sort((a, b) => b.views - a.views);\n }\n\n return items;\n };\n\n const fetchBlockHeights = (data, offset) => {\n let promises = data.map((item) => getProposal(item.proposal_id));\n Promise.all(promises).then((blockHeights) => {\n data = data.map((item, index) => ({\n ...item,\n timeline: JSON.parse(item.timeline),\n social_db_post_block_height:\n blockHeights[index].social_db_post_block_height,\n }));\n if (offset) {\n let newData = mergeItems(data);\n State.update({\n data: newData,\n currentlyDisplaying: newData.length,\n loading: false,\n searchLoader: false,\n makeMoreLoader: false,\n });\n } else {\n State.update({\n data,\n currentlyDisplaying: data.length,\n loading: false,\n searchLoader: false,\n makeMoreLoader: false,\n });\n }\n });\n };\n\n useEffect(() => {\n const handler = setTimeout(() => {\n fetchProposals();\n }, 1000);\n\n return () => {\n clearTimeout(handler);\n };\n }, [state.input]);\n\n return (\n <Container className=\"w-100 py-4 px-2 d-flex flex-column gap-3\">\n <div className=\"d-flex justify-content-between flex-wrap gap-2 align-items-center\">\n <Heading>\n Proposals\n <span className=\"text-muted text-normal\">\n ({state.aggregatedCount ?? state.data.length}){\" \"}\n </span>\n </Heading>\n <div className=\"d-flex flex-wrap gap-4 align-items-center\">\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-input\"\n }\n props={{\n search: state.input,\n className: \"w-xs-100\",\n onSearch: (input) => {\n State.update({ input });\n },\n onEnter: () => {\n fetchProposals();\n },\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.feature.proposal-search.by-sort\"}\n props={{\n onStateChange: (select) => {\n State.update({ sort: select.value });\n },\n }}\n />\n <div className=\"d-flex gap-4 align-items-center\">\n {!isInfra && (\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-category\"\n }\n props={{\n categoryOptions: availableCategoryOptions,\n onStateChange: (select) => {\n State.update({ category: select.value });\n },\n }}\n />\n )}\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-stage\"\n }\n props={{\n onStateChange: (select) => {\n State.update({ stage: select.value });\n },\n }}\n />\n <Widget\n src={\n \"devhub.near/widget/devhub.feature.proposal-search.by-author\"\n }\n props={{\n contract,\n onAuthorChange: (select) => {\n State.update({ author: select.value });\n },\n }}\n />\n </div>\n </div>\n <div className=\"mt-2 mt-xs-0\">\n <Link\n to={href({\n widgetSrc: `${instance}/widget/app`,\n params: { page: \"create-proposal\" },\n })}\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n label: (\n <div className=\"d-flex gap-2 align-items-center\">\n <div>\n <i class=\"bi bi-plus-circle-fill\"></i>\n </div>\n Submit Proposal\n </div>\n ),\n classNames: { root: \"theme-btn\" },\n }}\n />\n </Link>\n </div>\n </div>\n <div style={{ minHeight: \"50vh\" }}>\n {!Array.isArray(state.data) ? (\n loader\n ) : (\n <div className=\"card no-border rounded-0 mt-4 py-3 full-width-div\">\n <div className=\"container-xl\">\n {proposalFeedAnnouncement}\n <div className=\"mt-4 border rounded-2\">\n {state.aggregatedCount === 0 ? (\n <div class=\"alert alert-danger m-2\" role=\"alert\">\n No proposals found for selected filter.{\" \"}\n </div>\n ) : state.searchLoader ? (\n loader\n ) : state.aggregatedCount > 0 ? (\n state.data.map((item, index) => {\n return (\n <div\n key={item.proposal_id}\n className={\n (index !== state.data.length - 1 &&\n \"border-bottom \") +\n index ===\n 0 && \" rounded-top-2\"\n }\n >\n <FeedItem proposal={item} index={index} />\n </div>\n );\n })\n ) : (\n loader\n )}\n </div>\n {state.aggregatedCount > 0 &&\n state.aggregatedCount > state.data.length && (\n <div className=\"my-3 container-xl\">\n {state.makeMoreLoader ? (\n loader\n ) : (\n <div>\n {!state.loading && (\n <div className=\"w-100\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.Button`}\n props={{\n classNames: {\n root: \"btn-grey-outline w-100 \",\n label: \"text-center w-100\",\n },\n label: \"Load More\",\n onClick: () => makeMoreItems(),\n }}\n />\n </div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n </Container>\n );\n};\n\nreturn FeedPage(props);\n" }, "devhub.entity.community.Sidebar": { "": "const { href } = VM.require(\"devhub.near/widget/core.lib.url\");\n\nif (!href) {\n return <p>Loading modules...</p>;\n}\n\nconst { community } = props;\n\nconst CommunitySummary = () => {\n return (\n <>\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"}\n props={{\n content: community.bio_markdown,\n }}\n />\n <small class=\"text-muted mb-3\">\n <Link\n to={href({\n widgetSrc: \"devhub.near/widget/app\",\n params: { page: \"feed\", tag: community.tag },\n })}\n >\n <Widget\n src={\"devhub.near/widget/devhub.components.atom.Tag\"}\n props={{ tag: community.tag }}\n />\n </Link>\n </small>\n </>\n );\n};\n\nreturn community === null ? (\n <div>Loading...</div>\n) : (\n <div class=\"d-flex flex-column align-items-end\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.community.Tile\"}\n props={{\n fullWidth: true,\n minHeight: 0,\n noBorder: true,\n children: <CommunitySummary />,\n style: { marginTop: \"0.5rem\" },\n }}\n />\n\n <Widget\n src={\"devhub.near/widget/devhub.entity.community.Tile\"}\n props={{\n heading: \"Admins\",\n\n children: (community?.admins ?? []).map((accountId) => (\n <div key={accountId} className=\"d-flex\" style={{ fontWeight: 500 }}>\n <Widget\n src=\"devhub.near/widget/devhub.components.molecule.ProfileCard\"\n props={{ accountId }}\n />\n </div>\n )),\n\n fullWidth: true,\n minHeight: 0,\n noBorder: true,\n }}\n />\n </div>\n);\n" }, "devhub.components.molecule.SimpleMDE": { "": "/**\n * iframe embedding a SimpleMDE component\n * https://github.com/sparksuite/simplemde-markdown-editor\n */\nconst { getLinkUsingCurrentGateway } = VM.require(\n \"devhub.near/widget/core.lib.url\"\n) || { getLinkUsingCurrentGateway: () => {} };\nconst data = props.data;\nconst onChange = props.onChange ?? (() => {});\nconst onChangeKeyup = props.onChangeKeyup ?? (() => {}); // in case where we want immediate action\nconst height = props.height ?? \"390\";\nconst className = props.className ?? \"w-100\";\nconst embeddCSS = props.embeddCSS;\nconst sortedRelevantUsers = props.sortedRelevantUsers || [];\n\nState.init({\n iframeHeight: height,\n message: props.data,\n});\n\nconst profilesData = Social.get(\"*/profile/name\", \"final\") ?? {};\nconst followingData =\n Social.get(`${context.accountId}/graph/follow/**`, \"final\") ?? {};\n\n// SIMPLEMDE CONFIG //\nconst fontFamily = props.fontFamily ?? \"sans-serif\";\nconst alignToolItems = props.alignToolItems ?? \"right\";\nconst placeholder = props.placeholder ?? \"\";\nconst showAccountAutoComplete = props.showAutoComplete ?? false;\nconst showProposalIdAutoComplete = props.showProposalIdAutoComplete ?? false;\nconst autoFocus = props.autoFocus ?? false;\n\nconst queryName =\n \"polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot\";\nconst query = `query GetLatestSnapshot($offset: Int = 0, $limit: Int = 10, $where: ${queryName}_bool_exp = {}) {\n${queryName}(\n offset: $offset\n limit: $limit\n order_by: {proposal_id: desc}\n where: $where\n) {\n name\n proposal_id\n}\n}`;\n\nconst proposalLink = getLinkUsingCurrentGateway(\n `devhub.near/widget/app?page=proposal&id=`\n);\n\nconst code = `\n<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n <style>\n body { \n margin: auto;\n font-family: ${fontFamily};\n overflow: visible;\n font-size:14px !important;\n }\n\n @media screen and (max-width: 768px) {\n body {\n font-size: 12px;\n }\n }\n \n .cursor-pointer {\n cursor: pointer;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n\n .dropdown-item:hover,\n .dropdown-item:focus {\n background-color:rgb(0, 236, 151) !important;\n color:white !important;\n outline: none !important;\n }\n\n .editor-toolbar {\n text-align: ${alignToolItems};\n }\n \n .CodeMirror {\n min-height:200px !important; // for autocomplete to be visble \n }\n\n .CodeMirror-scroll {\n min-height:200px !important; // for autocomplete to be visble \n }\n\n ${embeddCSS}\n\n </style>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css\">\n <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65\" crossorigin=\"anonymous\">\n</head>\n<body>\n<div class=\"dropdown\">\n <button style=\"display: none\" type=\"button\" data-bs-toggle=\"dropdown\">\n Dropdown button\n </button>\n\n <ul class=\"dropdown-menu\" id=\"mentiondropdown\" style=\"position: absolute;\">\n</div>\n<div class=\"dropdown\">\n <button style=\"display: none\" type=\"button\" data-bs-toggle=\"dropdown\">\n Dropdown button\n </button>\n <ul class=\"dropdown-menu\" id=\"referencedropdown\" style=\"position: absolute;\">\n</div>\n</ul>\n\n<textarea></textarea>\n\n<script src=\"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js\" integrity=\"sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3\" crossorigin=\"anonymous\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js\" integrity=\"sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V\" crossorigin=\"anonymous\"></script>\n<script>\nlet codeMirrorInstance;\nlet isEditorInitialized = false;\nlet followingData = {};\nlet profilesData = {};\nlet proposalLink = '';\nlet query = '';\nlet showAccountAutoComplete = ${showAccountAutoComplete};\nlet showProposalIdAutoComplete = ${showProposalIdAutoComplete};\n\nfunction getSuggestedAccounts(term) {\n let results = [];\n\n term = (term || \"\").replace(/\\W/g, \"\").toLowerCase();\n let limit = 5;\n if (term.length < 2) {\n results = [${sortedRelevantUsers\n .map((u) => \"{accountId:'\" + u + \"', score: 60}\")\n .join(\",\")}];\n limit = ${5 + sortedRelevantUsers.length};\n }\n\n const profiles = Object.entries(profilesData);\n\n for (let i = 0; i < profiles.length; i++) {\n let score = 0;\n const accountId = profiles[i][0];\n const accountIdSearch = profiles[i][0].replace(/\\W/g, \"\").toLowerCase();\n const nameSearch = (profiles[i][1]?.profile?.name || \"\")\n .replace(/\\W/g, \"\")\n .toLowerCase();\n const accountIdSearchIndex = accountIdSearch.indexOf(term);\n const nameSearchIndex = nameSearch.indexOf(term);\n\n if (accountIdSearchIndex > -1 || nameSearchIndex > -1) {\n score += 10;\n\n if (accountIdSearchIndex === 0) {\n score += 10;\n }\n if (nameSearchIndex === 0) {\n score += 10;\n }\n if (followingData[accountId] === \"\") {\n score += 30;\n }\n\n results.push({\n accountId,\n score,\n });\n }\n }\n\n results.sort((a, b) => b.score - a.score);\n results = results.slice(0, limit);\n\n return results;\n}\n\nasync function asyncFetch(endpoint, { method, headers, body }) {\n try {\n const response = await fetch(endpoint, {\n method: method,\n headers: headers,\n body: body\n });\n\n if (!response.ok) {\n throw new Error(\"HTTP error!\");\n }\n\n return await response.json();\n } catch (error) {\n console.error('Error fetching data:', error);\n throw error;\n }\n}\n\nfunction extractNumbers(str) {\n let numbers = \"\";\n for (let i = 0; i < str.length; i++) {\n if (!isNaN(str[i])) {\n numbers += str[i];\n }\n }\n return numbers;\n};\n\nasync function getSuggestedProposals(id) {\n let results = [];\n const variables = {\n limit: 5,\n offset: 0,\n where: {},\n };\n if (id) {\n const proposalId = extractNumbers(id);\n if (proposalId) {\n variables[\"where\"] = { proposal_id: { _eq: id } };\n } else {\n variables[\"where\"] = {\n _or: [\n { name: { _iregex: id } },\n { summary: { _iregex: id } },\n { description: { _iregex: id } },\n ],\n };\n }\n }\n await asyncFetch(\"https://near-queryapi.api.pagoda.co/v1/graphql\", {\n method: \"POST\",\n headers: { \"x-hasura-role\": \"polyprogrammist_near\" },\n body: JSON.stringify({\n query: query,\n variables: variables,\n operationName: \"GetLatestSnapshot\",\n }),\n })\n .then((res) => {\n const proposals =\n res?.data?.[\n \"polyprogrammist_near_devhub_prod_v1_proposals_with_latest_snapshot\"\n ];\n results = proposals;\n })\n .catch((error) => {\n console.error(error);\n });\n return results;\n};\n\n// Initializes SimpleMDE element and attaches to text-area\nconst simplemde = new SimpleMDE({\n forceSync: true,\n toolbar: [\n \"heading\",\n \"bold\",\n \"italic\",\n \"|\", // adding | creates a divider in the toolbar\n \"quote\",\n \"code\",\n \"link\",\n {\n name: \"image\",\n action: function customFunction(editor) {\n const loadingIndicator = document.createElement('div');\n loadingIndicator.textContent = 'Uploading...';\n loadingIndicator.style.position = 'absolute';\n loadingIndicator.style.top = '10px'; // Adjust position as needed\n loadingIndicator.style.right = '10px';\n loadingIndicator.style.display = 'none'; // Initially hidden\n loadingIndicator.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';\n loadingIndicator.style.border = '1px solid #ccc';\n loadingIndicator.style.padding = '5px';\n loadingIndicator.style.borderRadius = '5px';\n document.body.appendChild(loadingIndicator); // Append to the body or desired container\n\n\n const fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.accept = 'image/*';\n\n fileInput.addEventListener('change', async function(event) {\n const file = event.target.files[0];\n if (file) {\n loadingIndicator.style.display = 'block';\n try {\n const response = await fetch(\"https://ipfs.near.social/add\", {\n method: \"POST\",\n headers: {\n Accept: \"application/json\"\n },\n body: file\n });\n const data = await response.json();\n if (data && data.cid) {\n const imgSrc = 'https://ipfs.near.social/ipfs/' + data.cid\n const imgMarkdown = \"![\" + imgSrc + \"](\" + imgSrc + \")\";\n editor.codemirror.replaceRange(imgMarkdown, editor.codemirror.getCursor());\n editor.codemirror.focus();\n } else {\n console.error('Image upload failed:', data);\n }\n } catch (error) {\n console.error('Error uploading image:', error);\n }\n finally {\n // Hide the loading indicator when done\n loadingIndicator.style.display = 'none';\n }\n }\n });\n \n fileInput.click();\n },\n className: \"fa fa-picture-o\",\n title: \"Upload Image\",\n },\n ],\n placeholder: \\`${placeholder}\\`,\n initialValue: \"\",\n insertTexts: {\n link: [\"[\", \"]()\"],\n },\n spellChecker: false,\n renderingConfig: {\n\t\tsingleLineBreaks: false,\n\t\tcodeSyntaxHighlighting: true,\n\t},\n autofocus:${autoFocus}\n});\n\ncodeMirrorInstance = simplemde.codemirror;\n\n/**\n * Sends message to Widget to update content\n */\nconst updateContent = () => {\n const content = simplemde.value();\n window.parent.postMessage({ handler: \"update\", content }, \"*\");\n};\n\n/**\n * Sends message to Widget to update iframe height\n */\nconst updateIframeHeight = () => {\n const iframeHeight = document.body.scrollHeight;\n window.parent.postMessage({ handler: \"resize\", height: iframeHeight }, \"*\");\n};\n\n// On Change\nsimplemde.codemirror.on('blur', () => {\n updateContent();\n});\n\nsimplemde.codemirror.on('keyup', () => {\n updateIframeHeight();\n const content = simplemde.value();\n window.parent.postMessage({ handler: \"updateOnKeyup\", content }, \"*\");\n});\n\n\nif (showAccountAutoComplete) {\n let mentionToken;\n let mentionCursorStart;\n const dropdown = document.getElementById(\"mentiondropdown\");\n\n simplemde.codemirror.on(\"keydown\", () => {\n if (mentionToken && event.key === 'ArrowDown') {\n dropdown.querySelector('button').focus();\n event.preventDefault();\n return false;\n }\n });\n\n simplemde.codemirror.on(\"keyup\", (cm, event) => {\n const cursor = cm.getCursor();\n const token = cm.getTokenAt(cursor);\n\n const createMentionDropDownOptions = () => {\n const mentionInput = cm.getRange(mentionCursorStart, cursor);\n dropdown.innerHTML = getSuggestedAccounts(mentionInput)\n .map(\n (item) =>\n '<li><button class=\"dropdown-item cursor-pointer w-100 text-wrap\">' + item?.accountId + '</button></li>'\n )\n .join(\"\");\n\n dropdown.querySelectorAll(\"li\").forEach((li) => {\n li.addEventListener(\"click\", () => {\n const selectedText = li.textContent.trim();\n simplemde.codemirror.replaceRange(selectedText, mentionCursorStart, cursor);\n mentionToken = null;\n dropdown.classList.remove(\"show\");\n cm.focus();\n });\n });\n }\n // show dropwdown only when @ is at first place or when there is a space before @\n if (!mentionToken && (token.string === \"@\" && cursor.ch === 1 || token.string === \"@\" && cm.getTokenAt({line:cursor.line, ch: cursor.ch - 1}).string == ' ')) {\n mentionToken = token;\n mentionCursorStart = cursor;\n // Calculate cursor position relative to the iframe's viewport\n const rect = cm.charCoords(cursor);\n const x = rect.left;\n const y = rect.bottom;\n\n // Create dropdown with options\n dropdown.style.top = y + \"px\";\n dropdown.style.left = x + \"px\";\n\n createMentionDropDownOptions();\n\n dropdown.classList.add(\"show\");\n\n // Close dropdown on outside click\n document.addEventListener(\"click\", function(event) {\n if (!dropdown.contains(event.target)) {\n mentionToken = null;\n dropdown.classList.remove(\"show\");\n }\n });\n } else if (mentionToken && token.string.match(/[^@a-z0-9.]/)) {\n mentionToken = null;\n dropdown.classList.remove(\"show\");\n } else if (mentionToken) {\n createMentionDropDownOptions();\n }\n});\n}\n\nif (showProposalIdAutoComplete) {\n let proposalId;\n let referenceCursorStart;\n const dropdown = document.getElementById(\"referencedropdown\");\n const loader = document.createElement('div');\n loader.className = 'loader';\n loader.textContent = 'Loading...';\n\n simplemde.codemirror.on(\"keydown\", () => {\n if (proposalId && event.key === 'ArrowDown') {\n dropdown.querySelector('button').focus();\n event.preventDefault();\n return false;\n }\n });\n\n simplemde.codemirror.on(\"keyup\", (cm, event) => {\n const cursor = cm.getCursor();\n const token = cm.getTokenAt(cursor);\n\n const createReferenceDropDownOptions = async () => {\n try {\n const proposalIdInput = cm.getRange(referenceCursorStart, cursor);\n dropdown.innerHTML = ''; // Clear previous content\n dropdown.appendChild(loader); // Show loader\n\n const suggestedProposals = await getSuggestedProposals(proposalIdInput);\n dropdown.innerHTML = suggestedProposals\n .map(\n (item) =>\n '<li><button class=\"dropdown-item cursor-pointer w-100 text-wrap\">' + \"#\" + item?.proposal_id + \" \" + item.name + '</button></li>'\n )\n .join(\"\");\n\n dropdown.querySelectorAll(\"li\").forEach((li) => {\n li.addEventListener(\"click\", () => {\n const selectedText = li.textContent.trim();\n const startIndex = selectedText.indexOf('#') + 1; \n const endIndex = selectedText.indexOf(' ', startIndex);\n const id = endIndex !== -1 ? selectedText.substring(startIndex, endIndex) : selectedText.substring(startIndex);\n const link = proposalLink + id;\n const adjustedStart = {\n line: referenceCursorStart.line,\n ch: referenceCursorStart.ch - 1\n };\n simplemde.codemirror.replaceRange(\"[\" + selectedText + \"]\" + \"(\" + link + \")\", adjustedStart, cursor);\n proposalId = null;\n dropdown.classList.remove(\"show\");\n cm.focus();\n });\n });\n } catch (error) {\n console.error('Error fetching data:', error);\n // Handle error: Remove loader\n dropdown.innerHTML = ''; // Clear previous content\n } finally {\n // Remove loader\n dropdown.removeChild(loader);\n }\n }\n\n // show dropwdown only when there is space before # or it's first char\n if (!proposalId && (token.string === \"#\" && cursor.ch === 1 || token.string === \"#\" && cm.getTokenAt({line:cursor.line, ch: cursor.ch - 1}).string == ' ')) {\n proposalId = token;\n referenceCursorStart = cursor;\n // Calculate cursor position relative to the iframe's viewport\n const rect = cm.charCoords(cursor);\n const x = rect.left;\n const y = rect.bottom;\n\n // Create dropdown with options\n dropdown.style.top = y + \"px\";\n dropdown.style.left = x + \"px\";\n\n createReferenceDropDownOptions();\n\n dropdown.classList.add(\"show\");\n\n // Close dropdown on outside click\n document.addEventListener(\"click\", function(event) {\n if (!dropdown.contains(event.target)) {\n proposalId = null;\n dropdown.classList.remove(\"show\");\n }\n });\n } else if (proposalId && (token.string.match(/[^#a-z0-9.]/) || !token.string)) {\n proposalId = null;\n dropdown.classList.remove(\"show\");\n } else if (proposalId) {\n createReferenceDropDownOptions();\n }\n});\n\n}\n\nwindow.addEventListener(\"message\", (event) => {\n if (!isEditorInitialized && event.data !== \"\") {\n simplemde.value(event.data.content);\n isEditorInitialized = true;\n } else {\n if (event.data.handler === 'refreshEditor' || event.data.handler === 'committed') {\n codeMirrorInstance.getDoc().setValue(event.data.content);\n }\n }\n if (event.data.followingData) {\n followingData = event.data.followingData;\n }\n if (event.data.profilesData) {\n profilesData = JSON.parse(event.data.profilesData);\n }\n if (event.data.query) {\n query = event.data.query;\n }\n if (event.data.proposalLink) {\n proposalLink = event.data.proposalLink;\n }\n});\n</script>\n</body>\n</html>\n`;\n\nreturn (\n <iframe\n className={className}\n style={{\n height: `${state.iframeHeight}px`,\n maxHeight: \"410px\",\n minHeight: \"250px\",\n }}\n srcDoc={code}\n message={{\n content: props.data?.content ?? \"\",\n followingData,\n profilesData: JSON.stringify(profilesData),\n query: query,\n handler: props.data.handler,\n proposalLink: proposalLink,\n }}\n onMessage={(e) => {\n switch (e.handler) {\n case \"update\":\n {\n onChange(e.content);\n }\n break;\n case \"resize\":\n {\n const offset = 10;\n State.update({ iframeHeight: e.height + offset });\n }\n break;\n case \"updateOnKeyup\":\n {\n onChangeKeyup(e.content);\n }\n break;\n }\n }}\n />\n);\n" }, "devhub.components.molecule.Compose": { "": "const EmbeddCSS = `\n .CodeMirror {\n margin-inline:10px;\n border-radius:5px;\n }\n\n .editor-toolbar {\n border: none !important;\n }\n`;\n\nconst Wrapper = styled.div`\n .nav-link {\n color: inherit !important;\n }\n\n .card-header {\n padding-bottom: 0px !important;\n }\n`;\n\nconst Compose = ({\n data,\n onChange,\n autocompleteEnabled,\n placeholder,\n height,\n embeddCSS,\n showProposalIdAutoComplete,\n onChangeKeyup,\n handler,\n sortedRelevantUsers,\n isTailwind,\n}) => {\n State.init({\n data: data,\n selectedTab: \"editor\",\n autoFocus: false,\n });\n\n useEffect(() => {\n if (typeof onChange === \"function\") {\n onChange(state.data);\n }\n }, [state.data]);\n\n useEffect(() => {\n // for clearing editor after txn approval/ showing draft state\n if (data !== state.data || handler !== state.handler) {\n State.update({ data: data, handler: handler });\n }\n }, [data, handler]);\n\n // classes are different for tailwind and bootstrap\n if (isTailwind) {\n return (\n <div>\n <div className=\"bg-white shadow-sm rounded-lg\">\n <ul\n className=\"p-2 flex flex-wrap text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:border-gray-700 dark:text-gray-400\"\n style={{ position: \"relative\" }}\n >\n <li className=\"me-2\">\n <button\n className={\n \"inline-block p-3 rounded-t-lg \" +\n (state.selectedTab === \"editor\" &&\n \" active text-blue-600 bg-gray-100\")\n }\n onClick={() =>\n State.update({ selectedTab: \"editor\", autoFocus: true })\n }\n >\n Write\n </button>\n </li>\n <li className=\"me-2\">\n <button\n className={\n \"inline-block p-3 rounded-t-lg \" +\n (state.selectedTab === \"preview\" &&\n \" active text-blue-600 bg-gray-100\")\n }\n onClick={() => State.update({ selectedTab: \"preview\" })}\n >\n Preview\n </button>\n </li>\n </ul>\n\n {state.selectedTab === \"editor\" ? (\n <>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDE\"\n }\n props={{\n data: { handler: state.handler, content: state.data },\n onChange: (content) => {\n State.update({ data: content, handler: \"update\" });\n },\n placeholder: placeholder,\n height,\n embeddCSS: embeddCSS || EmbeddCSS,\n showAutoComplete: autocompleteEnabled,\n showProposalIdAutoComplete: showProposalIdAutoComplete,\n autoFocus: state.autoFocus,\n onChangeKeyup: onChangeKeyup,\n sortedRelevantUsers,\n }}\n />\n </>\n ) : (\n <div className=\"p-3\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: state.data,\n embeddCSS: `\n body{\n font-size:14px;\n }\n `,\n }}\n />\n </div>\n )}\n </div>\n </div>\n );\n } else\n return (\n <Wrapper>\n <div className=\"card\">\n <div className=\"card-header\" style={{ position: \"relative\" }}>\n <div>\n <ul class=\"nav nav-tabs\">\n <li class=\"nav-item\">\n <button\n class={`nav-link ${\n state.selectedTab === \"editor\" ? \"active\" : \"\"\n }`}\n onClick={() =>\n State.update({ selectedTab: \"editor\", autoFocus: true })\n }\n >\n Write\n </button>\n </li>\n <li class=\"nav-item\">\n <button\n class={`nav-link ${\n state.selectedTab === \"preview\" ? \"active\" : \"\"\n }`}\n onClick={() => State.update({ selectedTab: \"preview\" })}\n >\n Preview\n </button>\n </li>\n </ul>\n </div>\n </div>\n\n {state.selectedTab === \"editor\" ? (\n <>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDE\"\n }\n props={{\n data: { handler: state.handler, content: state.data },\n onChange: (content) => {\n State.update({ data: content, handler: \"update\" });\n },\n placeholder: placeholder,\n height,\n embeddCSS: embeddCSS || EmbeddCSS,\n showAutoComplete: autocompleteEnabled,\n showProposalIdAutoComplete: showProposalIdAutoComplete,\n autoFocus: state.autoFocus,\n onChangeKeyup: onChangeKeyup,\n sortedRelevantUsers,\n }}\n />\n </>\n ) : (\n <div className=\"card-body\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: state.data,\n embeddCSS: `\n body {\n font-size: 14px;\n }\n `,\n }}\n />\n </div>\n )}\n </div>\n </Wrapper>\n );\n};\n\nreturn Compose(props);\n" }, "devhub.components.organism.Configurator": { "": "const Struct = VM.require(\"devhub.near/widget/core.lib.struct\");\n\nif (!Struct) {\n return <p>Loading modules...</p>;\n}\n\nconst useForm = ({ initialValues, onUpdate, stateKey }) => {\n const initialFormState = {\n hasUnsubmittedChanges: false,\n values: initialValues ?? {},\n };\n\n const formState = state[stateKey] ?? null;\n\n const formReset = () =>\n State.update((lastKnownComponentState) => ({\n ...lastKnownComponentState,\n [stateKey]: initialFormState,\n hasUnsubmittedChanges: false,\n }));\n\n const formUpdate =\n ({ path, via: customFieldUpdate, ...params }) =>\n (fieldInput) => {\n const transformFn = (node) => {\n if (typeof customFieldUpdate === \"function\") {\n return customFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n } else {\n return Struct.defaultFieldUpdate({\n input: fieldInput?.target?.value ?? fieldInput,\n lastKnownValue: node,\n params,\n });\n }\n };\n const updatedValues = Struct.deepFieldUpdate(\n formState?.values ?? {},\n path,\n (node) => transformFn(node)\n );\n State.update((lastKnownComponentState) => ({\n ...lastKnownComponentState,\n [stateKey]: {\n hasUnsubmittedChanges: !Struct.isEqual(\n updatedValues,\n initialFormState.values\n ),\n values: updatedValues,\n },\n }));\n\n if (typeof onUpdate === \"function\") {\n onUpdate(updatedValues);\n }\n };\n\n return {\n hasUnsubmittedChanges: formState?.hasUnsubmittedChanges ?? false,\n values: {\n ...(initialValues ?? {}),\n ...(formState?.values ?? {}),\n },\n reset: formReset,\n stateKey,\n update: formUpdate,\n };\n};\n\nconst ValueView = styled.div`\n & > p {\n margin: 0;\n }\n`;\n\nconst fieldParamsByType = {\n array: {\n name: \"components.molecule.Input\",\n inputProps: { type: \"text\" },\n },\n\n boolean: {\n name: \"components.atom.Toggle\",\n },\n\n string: {\n name: \"components.molecule.Input\",\n inputProps: { type: \"text\" },\n },\n};\n\nconst defaultFieldsRender = ({ schema, form, isEditable }) => (\n <>\n {Object.entries(schema).map(\n (\n [key, { format, inputProps, noop, label, order, style, ...fieldProps }],\n idx\n ) => {\n const fieldKey = `${idx}-${key}`,\n fieldValue = form.values[key];\n\n const fieldType = Array.isArray(fieldValue)\n ? \"array\"\n : typeof (fieldValue ?? \"\");\n\n const isDisabled = noop ?? inputProps.disabled ?? false;\n\n const viewClassName = [\n (fieldValue?.length ?? 0) > 0 ? \"\" : \"text-muted\",\n \"m-0\",\n ].join(\" \");\n\n return (\n <>\n <div\n className={[\n \"d-flex gap-3\",\n isEditable || noop ? \"d-none\" : \"\",\n ].join(\" \")}\n key={fieldKey}\n style={{ order }}\n >\n <label className=\"fw-bold w-25\">{label}</label>\n\n <ValueView className={[viewClassName, \"w-75\"].join(\" \")}>\n {format !== \"markdown\" ? (\n <span>\n {(fieldType === \"array\" && format === \"comma-separated\"\n ? fieldValue\n .filter((string) => string.length > 0)\n .join(\", \")\n : fieldValue\n )?.toString?.() || \"none\"}\n </span>\n ) : (fieldValue?.length ?? 0) > 0 ? (\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"\n }\n props={{\n content: fieldValue,\n }}\n />\n ) : (\n <span>none</span>\n )}\n </ValueView>\n </div>\n <Widget\n src={`devhub.near/widget/devhub.${\n (fieldParamsByType[fieldType] ?? fieldParamsByType[\"string\"])\n .name\n }`}\n props={{\n ...fieldProps,\n className: [\n \"w-100\",\n fieldProps.className ?? \"\",\n isEditable && !noop ? \"\" : \"d-none\",\n ].join(\" \"),\n\n disabled: isDisabled,\n format,\n key: `${fieldKey}--editable`,\n label,\n onChange: form.update({ path: [key] }),\n style: { ...style, order },\n\n value:\n fieldType === \"array\" && format === \"comma-separated\"\n ? fieldValue.join(\", \")\n : fieldValue,\n\n inputProps: {\n ...(inputProps ?? {}),\n disabled: isDisabled,\n\n title:\n noop ?? false\n ? \"Temporarily disabled due to technical reasons.\"\n : inputProps.title,\n\n ...(fieldParamsByType[fieldType].inputProps ?? {}),\n tabIndex: order,\n },\n }}\n />\n </>\n );\n }\n )}\n </>\n);\n\nconst Configurator = ({\n actionsAdditional,\n cancelLabel,\n classNames,\n externalState,\n fieldsRender: customFieldsRender,\n formatter: toFormatted,\n isValid,\n isActive,\n onCancel,\n onChange,\n onSubmit,\n schema,\n submitIcon,\n submitLabel,\n hideSubmitBtn,\n}) => {\n const fieldsRender = customFieldsRender || defaultFieldsRender;\n\n const initialValues = Struct.typeMatch(schema)\n ? Struct.pick(externalState ?? {}, Object.keys(schema))\n : {};\n\n const form = useForm({ initialValues, onUpdate: onChange, stateKey: \"form\" });\n\n const formFormattedValues = toFormatted\n ? toFormatted(form.values)\n : form.values;\n\n const internalValidation = () =>\n Object.keys(schema).every((key) => {\n const fieldDefinition = schema[key];\n const value = form.values[key];\n if (!value || value.length === 0) {\n return !fieldDefinition.inputProps.required;\n } else if (\n fieldDefinition.inputProps.min &&\n fieldDefinition.inputProps.min > value?.length\n ) {\n return false;\n } else if (\n fieldDefinition.inputProps.max &&\n fieldDefinition.inputProps.max < value?.length\n ) {\n return false;\n } else if (\n fieldDefinition.inputProps.allowCommaAndSpace === false &&\n /^[^,\\s]*$/.test(value) === false\n ) {\n return false;\n } else if (\n fieldDefinition.inputProps.validUrl === true &&\n /^(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/.test(\n value\n ) === false\n ) {\n return false;\n }\n return true;\n });\n\n const isFormValid = () => {\n return internalValidation() && (!isValid || isValid(formFormattedValues));\n };\n\n const onCancelClick = () => {\n form.reset();\n if (onCancel) onCancel();\n };\n\n const onSubmitClick = () => {\n if (onSubmit && isFormValid()) {\n onSubmit(formFormattedValues);\n }\n };\n\n return (\n <div className=\"flex-grow-1 d-flex flex-column gap-4\">\n <div className={`d-flex flex-column gap-${isActive ? 1 : 4}`}>\n {fieldsRender({\n form,\n isEditable: isActive,\n schema,\n })}\n </div>\n {isActive && !hideSubmitBtn && (\n <div className=\"d-flex align-items-center justify-content-end gap-3 mt-auto\">\n {actionsAdditional ? (\n <div className=\"me-auto\">{actionsAdditional}</div>\n ) : null}\n\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: \"btn-outline-danger shadow-none border-0\" },\n label: cancelLabel || \"Cancel\",\n onClick: onCancelClick,\n }}\n />\n <Widget\n src={\"devhub.near/widget/devhub.components.molecule.Button\"}\n props={{\n classNames: { root: classNames.submit || \"btn-success\" },\n disabled: !form.hasUnsubmittedChanges || !isFormValid(),\n icon: submitIcon || {\n type: \"bootstrap_icon\",\n variant: \"bi-check-circle-fill\",\n },\n label: submitLabel || \"Submit\",\n onClick: onSubmitClick,\n }}\n />\n </div>\n )}\n </div>\n );\n};\n\nreturn Configurator(props);\n" }, "config.css": { "": "const CssContainer = styled.div`\n .theme-btn {\n background-color: var(--theme-color) !important;\n border: none;\n color: white;\n\n &:active {\n color: white;\n }\n }\n\n a {\n color: inherit;\n text-decoration: inherit;\n }\n\n .attractable {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n transition: box-shadow 0.6s;\n\n &:hover {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n }\n }\n`;\n\nreturn { CssContainer };\n" }, "devhub.entity.proposal.CommentsAndLogs": { "": "const { getLinkUsingCurrentGateway } = VM.require(\n \"devhub.near/widget/core.lib.url\"\n) || { getLinkUsingCurrentGateway: () => {} };\nconst snapshotHistory = props.snapshotHistory;\nconst proposalId = props.id;\n\nconst Wrapper = styled.div`\n position: relative;\n .log-line {\n position: absolute;\n left: 7%;\n top: -30px;\n bottom: 0;\n z-index: 1;\n width: 1px;\n background-color: var(--bs-border-color);\n z-index: 1;\n }\n\n .text-wrap {\n overflow: hidden;\n white-space: normal;\n }\n\n .fw-bold {\n font-weight: 600 !important;\n }\n\n .inline-flex {\n display: -webkit-inline-box !important;\n align-items: center !important;\n gap: 0.25rem !important;\n margin-right: 2px;\n flex-wrap: wrap;\n }\n`;\n\nconst CommentContainer = styled.div`\n border: 1px solid lightgrey;\n overflow: auto;\n`;\n\nconst Header = styled.div`\n position: relative;\n background-color: #f4f4f4;\n height: 50px;\n\n .menu {\n position: absolute;\n right: 10px;\n top: 4px;\n font-size: 30px;\n }\n`;\n\n// check snapshot history all keys and values for differences\nfunction getDifferentKeysWithValues(obj1, obj2) {\n return Object.keys(obj1)\n .filter((key) => {\n if (key !== \"editor_id\" && obj2.hasOwnProperty(key)) {\n const value1 = obj1[key];\n const value2 = obj2[key];\n\n if (typeof value1 === \"object\" && typeof value2 === \"object\") {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else if (Array.isArray(value1) && Array.isArray(value2)) {\n return JSON.stringify(value1) !== JSON.stringify(value2);\n } else {\n return value1 !== value2;\n }\n }\n return false;\n })\n .map((key) => ({\n key,\n originalValue: obj1[key],\n modifiedValue: obj2[key],\n }));\n}\n\nState.init({\n data: null,\n socialComments: null,\n changedKeysListWithValues: null,\n snapshotHistoryLength: 0,\n});\n\nfunction sortTimelineAndComments() {\n const comments = Social.index(\"comment\", props.item, { subscribe: true });\n\n if (snapshotHistory.length > state.snapshotHistoryLength) {\n const changedKeysListWithValues = snapshotHistory\n .slice(1)\n .map((item, index) => {\n const startingPoint = snapshotHistory[index]; // Set comparison to the previous item\n // we don't show timeline_version in logs\n delete startingPoint.timeline.timeline_version;\n delete item.timeline.timeline_version;\n if (\n startingPoint.timeline.kyc_verified === undefined &&\n item.timeline.kyc_verified === false\n ) {\n startingPoint.timeline.kyc_verified = false;\n }\n\n return {\n editorId: item.editor_id,\n ...getDifferentKeysWithValues(startingPoint, item),\n };\n });\n\n // add log for accepting terms and condition\n changedKeysListWithValues.unshift({\n 0: {\n key: \"timestamp\",\n originalValue: \"0\",\n modifiedValue: snapshotHistory[0].timestamp,\n },\n 1: {\n key: \"terms_and_condition\",\n originalValue: \"\",\n modifiedValue: \"accepted\",\n },\n editorId: snapshotHistory[0].editor_id,\n });\n\n State.update({\n changedKeysListWithValues,\n snapshotHistoryLength: snapshotHistory.length,\n });\n }\n\n // sort comments and timeline logs by time\n const snapShotTimeStamp = Array.isArray(snapshotHistory)\n ? snapshotHistory.map((i) => {\n return { blockHeight: null, timestamp: parseFloat(i.timestamp / 1e6) };\n })\n : [];\n\n const commentsTimeStampPromise = Array.isArray(comments)\n ? Promise.all(\n comments.map((item) => {\n return asyncFetch(\n `https://api.near.social/time?blockHeight=${item.blockHeight}`\n ).then((res) => {\n const timeMs = parseFloat(res.body);\n return {\n blockHeight: item.blockHeight,\n timestamp: timeMs,\n };\n });\n })\n ).then((res) => res)\n : Promise.resolve([]);\n\n commentsTimeStampPromise.then((commentsTimeStamp) => {\n const combinedArray = [...snapShotTimeStamp, ...commentsTimeStamp];\n combinedArray.sort((a, b) => a.timestamp - b.timestamp);\n State.update({ data: combinedArray, socialComments: comments });\n });\n}\n\nsortTimelineAndComments();\nconst Comment = ({ commentItem }) => {\n const { accountId, blockHeight } = commentItem;\n const item = {\n type: \"social\",\n path: `${accountId}/post/comment`,\n blockHeight,\n };\n const content = JSON.parse(Social.get(item.path, blockHeight) ?? \"null\");\n const link = `https://devhub.near.page/proposal/${proposalId}?accountId=${accountId}&blockHeight=${blockHeight}`;\n const hightlightComment =\n parseInt(props.blockHeight ?? \"\") === blockHeight &&\n props.accountId === accountId;\n\n return (\n <div style={{ zIndex: 99, background: \"white\" }}>\n <div className=\"d-flex gap-2 flex-1\">\n <div className=\"d-none d-sm-flex\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n }}\n />\n </div>\n <CommentContainer\n id={`${accountId.replace(/[^a-z0-9]/g, \"\")}${blockHeight}`}\n style={{ border: hightlightComment ? \"2px solid black\" : \"\" }}\n className=\"rounded-2 flex-1\"\n >\n <Header className=\"d-flex gap-3 align-items-center p-2 px-3\">\n <div className=\"text-muted\">\n <Link href={`/near/widget/ProfilePage?accountId=${accountId}`}>\n <span className=\"fw-bold text-black\">{accountId}</span>\n </Link>\n commented ・{\" \"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockHeight: blockHeight,\n }}\n />\n </div>\n {context.accountId && (\n <div className=\"menu\">\n <Widget\n src=\"near/widget/Posts.Menu\"\n props={{\n accountId: accountId,\n blockHeight: blockHeight,\n contentPath: `/post/comment`,\n contentType: \"comment\",\n }}\n />\n </div>\n )}\n </Header>\n <div className=\"p-2 px-3\">\n <Widget\n src={`devhub.near/widget/devhub.components.molecule.SimpleMDEViewer`}\n props={{\n content: content.text,\n embeddCSS: `\n body {\n font-size:14px;\n }\n `,\n }}\n />\n\n <div className=\"d-flex gap-2 align-items-center mt-3\">\n <Widget\n src=\"devhub.near/widget/devhub.entity.proposal.LikeButton\"\n props={{\n item: item,\n notifyAccountId: accountId,\n }}\n />\n <Widget\n src=\"near/widget/CopyUrlButton\"\n props={{\n url: link,\n }}\n />\n </div>\n </div>\n </CommentContainer>\n </div>\n </div>\n );\n};\n\nfunction capitalizeFirstLetter(string) {\n const updated = string.replace(\"_\", \" \");\n return updated.charAt(0).toUpperCase() + updated.slice(1).toLowerCase();\n}\n\nfunction parseTimelineKeyAndValue(timeline, originalValue, modifiedValue) {\n const oldValue = originalValue[timeline];\n const newValue = modifiedValue[timeline];\n switch (timeline) {\n case \"status\":\n return (\n oldValue !== newValue && (\n <span className=\"inline-flex\">\n moved proposal from{\" \"}\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: oldValue,\n }}\n />\n to{\" \"}\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.StatusTag\"}\n props={{\n timelineStatus: newValue,\n }}\n />\n stage\n </span>\n )\n );\n case \"sponsor_requested_review\": {\n if (!oldValue && newValue) {\n return <span>completed review</span>;\n } else if (oldValue && !newValue) return <span>unmarked review</span>;\n return null;\n }\n case \"reviewer_completed_attestation\":\n return !oldValue && newValue && <span>completed attestation</span>;\n case \"kyc_verified\":\n return !oldValue && newValue && <span>verified KYC/KYB</span>;\n case \"test_transaction_sent\":\n return (\n !oldValue &&\n newValue && (\n <span>\n confirmed sponsorship and shared funding steps with recipient\n </span>\n )\n );\n case \"payouts\":\n return <span>updated the funding payment links.</span>;\n // we don't have this step for now\n // case \"request_for_trustees_created\":\n // return !oldValue && newValue && <span>successfully created request for trustees</span>;\n default:\n return null;\n }\n}\n\nconst AccountProfile = ({ accountId }) => {\n return (\n <span className=\"inline-flex fw-bold text-black\">\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.Profile\"}\n props={{\n accountId: accountId,\n size: \"sm\",\n showAccountId: true,\n }}\n />\n </span>\n );\n};\n\nconst parseProposalKeyAndValue = (key, modifiedValue, originalValue) => {\n switch (key) {\n case \"terms_and_condition\": {\n return (\n <span>\n accepted\n <Widget\n src={\"devhub.near/widget/devhub.entity.proposal.AcceptedTerms\"}\n props={{ proposalId: proposalId }}\n />\n </span>\n );\n }\n case \"name\":\n return <span>changed title</span>;\n case \"summary\":\n case \"description\":\n return <span>changed {key}</span>;\n case \"category\":\n return (\n <span>\n changed category from {originalValue} to {modifiedValue}\n </span>\n );\n case \"linked_proposals\":\n return <span>updated linked proposals</span>;\n case \"requested_sponsorship_usd_amount\":\n return (\n <span>\n changed sponsorship amount from {originalValue} to {modifiedValue}\n </span>\n );\n case \"requested_sponsorship_paid_in_currency\":\n return (\n <span>\n changed sponsorship currency from {originalValue} to {modifiedValue}\n </span>\n );\n case \"receiver_account\":\n return (\n <span className=\"inline-flex\">\n changed receiver account from{\" \"}\n <AccountProfile accountId={originalValue} />\n to <AccountProfile accountId={modifiedValue} />\n </span>\n );\n case \"supervisor\":\n return !originalValue && modifiedValue ? (\n <span className=\"inline-flex\">\n added\n <AccountProfile accountId={modifiedValue} />\n as supervisor\n </span>\n ) : (\n <span className=\"inline-flex\">\n changed receiver account from{\" \"}\n <AccountProfile accountId={originalValue} />\n to <AccountProfile accountId={modifiedValue} />\n </span>\n );\n case \"requested_sponsor\":\n return (\n <span className=\"inline-flex\">\n changed sponsor from <AccountProfile accountId={originalValue} />\n to <AccountProfile accountId={modifiedValue} />\n </span>\n );\n case \"timeline\": {\n const modifiedKeys = Object.keys(modifiedValue);\n const originalKeys = Object.keys(originalValue);\n return modifiedKeys.map((i, index) => {\n const text = parseTimelineKeyAndValue(i, originalValue, modifiedValue);\n return (\n text && (\n <span key={index} className=\"inline-flex\">\n {text}\n {text && \"・\"}\n </span>\n )\n );\n });\n }\n default:\n return null;\n }\n};\n\nconst LogIconContainer = styled.div`\n margin-left: 50px;\n z-index: 99;\n\n @media screen and (max-width: 768px) {\n margin-left: 10px;\n }\n`;\n\nconst Log = ({ timestamp }) => {\n const updatedData = useMemo(\n () =>\n state.changedKeysListWithValues.find((obj) =>\n Object.values(obj).some(\n (value) =>\n value && parseFloat(value.modifiedValue / 1e6) === timestamp\n )\n ),\n [state.changedKeysListWithValues, timestamp]\n );\n\n const editorId = updatedData.editorId;\n const valuesArray = Object.values(updatedData ?? {});\n // if valuesArray length is 2 that means it only has timestamp and editorId\n if (!updatedData || valuesArray.length === 2) {\n return <></>;\n }\n\n return valuesArray.map((i, index) => {\n if (i.key && i.key !== \"timestamp\" && i.key !== \"proposal_body_version\") {\n return (\n <LogIconContainer\n className=\"d-flex gap-3 align-items-center\"\n key={index}\n >\n <img\n src=\"https://ipfs.near.social/ipfs/bafkreiffqrxdi4xqu7erf46gdlwuodt6dm6rji2jtixs3iionjvga6rhdi\"\n height={30}\n />\n <div\n className={\n \"flex-1 gap-1 w-100 text-wrap text-muted align-items-center \" +\n (i.key === \"timeline\" &&\n Object.keys(i.originalValue ?? {}).length > 1\n ? \"\"\n : \"inline-flex\")\n }\n >\n <span\n className=\"inline-flex fw-bold text-black\"\n style={{ marginRight: 0 }}\n >\n <AccountProfile accountId={editorId} showAccountId={true} />\n </span>\n {parseProposalKeyAndValue(i.key, i.modifiedValue, i.originalValue)}\n {i.key !== \"timeline\" && \"・\"}\n <Widget\n src=\"near/widget/TimeAgo\"\n props={{\n blockTimestamp: timestamp * 1000000,\n }}\n />\n </div>\n </LogIconContainer>\n );\n }\n });\n};\n\nif (Array.isArray(state.data)) {\n return (\n <Wrapper>\n <div\n className=\"log-line\"\n style={{ height: state.data.length > 2 ? \"110%\" : \"150%\" }}\n ></div>\n <div className=\"d-flex flex-column gap-4\">\n {state.data.map((i, index) => {\n if (i.blockHeight) {\n const item = state.socialComments.find(\n (t) => t.blockHeight === i.blockHeight\n );\n return <Comment commentItem={item} />;\n } else {\n return <Log timestamp={i.timestamp} key={index} />;\n }\n })}\n </div>\n </Wrapper>\n );\n}\n" }, "devhub.entity.addon.wiki.Viewer": { "": "const { content, title, subtitle, textAlign } = props;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n width: 100%;\n\n padding: 0 3rem;\n\n margin: 0 auto;\n text-align: ${(p) => p.textAlign ?? \"left\"};\n\n h1 {\n color: #151515;\n font-size: 3.5rem;\n font-style: normal;\n font-weight: 700;\n line-height: 100%; /* 88px */\n margin: 1rem 0;\n }\n\n p.subtitle {\n color: #555;\n font-size: 1.5rem;\n font-style: normal;\n font-weight: 400;\n line-height: 110%; /* 35.2px */\n margin: 0;\n }\n\n @media screen and (max-width: 768px) {\n padding: 0 1rem;\n\n span.category {\n font-size: 0.75rem;\n }\n\n h1 {\n font-size: 2rem;\n }\n\n p.subtitle {\n font-size: 1rem;\n }\n }\n\n a {\n color: #0000ee;\n }\n`;\n\nconst Content = styled.div`\n margin: 20px 0;\n text-align: left;\n`;\n\nconst Title = styled.h1`\n margin-bottom: 10px;\n`;\n\nconst Subtitle = styled.p`\n margin-bottom: 20px;\n`;\n\nconst CenteredMessage = styled.div`\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: ${(p) => p.height ?? \"100%\"};\n`;\n\nif (!content) {\n return (\n <CenteredMessage height={\"384px\"}>\n <h2>No Wiki Configured</h2>\n </CenteredMessage>\n );\n} else {\n return (\n <Container textAlign={textAlign}>\n <h1>{title}</h1>\n <p className=\"subtitle\">{subtitle}</p>\n <Content>\n <Widget\n src={\n \"devhub.near/widget/devhub.components.molecule.SimpleMDEViewer\"\n }\n props={{ content: content }}\n />\n </Content>\n </Container>\n );\n}\n" }, "devhub.components.molecule.SimpleMDEViewer": { "": "const content = props.content ?? \"\";\nconst height = props.height;\nconst embeddCSS = props.embeddCSS;\nconst [iframeHeight, setIframeHeight] = useState(\"100px\");\n\nconst code = `\n <!doctype html>\n <html>\n <head>\n <meta charset=\"utf-8\" />\n <style>\n body { \n margin: 0;\n overflow: ${height ? \"auto\" : \"hidden\"};\n font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", \"Noto Sans\", \"Liberation Sans\", Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n line-height:1.5;\n }\n\n blockquote {\n margin: 1em 0;\n padding-left: 1.5em;\n border-left: 4px solid #ccc;\n color: #666;\n font-style: italic;\n font-size: inherit;\n }\n\n .no-margin {\n margin: 0px !important;\n }\n\n h1, h2, h3, h4, h5, h6 {\n font-weight:500;\n margin-block:5px;\n }\n\n p {\n white-space: pre-wrap;\n }\n\n img {\n height: 200px;\n object-fit:contain;\n width: -webkit-fill-available;\n }\n \n ${embeddCSS}\n </style>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/highlight.js/latest/styles/github.min.css\">\n <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65\" crossorigin=\"anonymous\">\n </head> \n <body>\n <div id=\"content\"></div>\n <script src=\"https://cdn.jsdelivr.net/npm/marked/marked.min.js\"></script>\n <script>\n \n \n\n function updateContent(content) {\n document.getElementById('content').innerHTML = marked.parse(content)\n \n const newHeight = Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight,\n document.body.offsetHeight,\n document.documentElement.offsetHeight\n );\n \n window.parent.postMessage({\n handler: 'IFRAME_HEIGHT',\n height: newHeight\n }, '*');\n }\n\n window.addEventListener('message', (event) => {\n if (event.data.content) {\n updateContent(event.data.content);\n }\n });\n\n window.addEventListener('load', () => {\n window.parent.postMessage({\n handler: 'IFRAME_HEIGHT',\n height: document.body.scrollHeight\n }, '*');\n });\n </script>\n </body>\n </html>\n `;\n\nreturn (\n <iframe\n srcDoc={code}\n message={{\n content: content,\n }}\n style={{ height: height ?? iframeHeight }}\n className=\"w-100\"\n onMessage={(e) => {\n switch (e.handler) {\n case \"IFRAME_HEIGHT\": {\n setIframeHeight(`${e.height}px`);\n break;\n }\n }\n }}\n />\n);\n" } } } } }
Result:
{ "block_height": "128740686" }
No logs
Receipt:
Predecessor ID:
Receiver ID:
Gas Burned:
223 Ggas
Tokens Burned:
0 
Transferred 0.18397  to devhub.near
Empty result
No logs